From 7265d18d48b6a917c0bb4fa818996015480b5a3c Mon Sep 17 00:00:00 2001 From: ChickenLover Date: Tue, 23 Apr 2024 09:26:40 +0700 Subject: [PATCH] ICICLE V2 Release (#492) This PR introduces major updates for ICICLE Core, Rust and Golang bindings --------- Co-authored-by: Yuval Shekel Co-authored-by: DmytroTym Co-authored-by: Otsar <122266060+Otsar-Raikou@users.noreply.github.com> Co-authored-by: VitaliiH Co-authored-by: release-bot Co-authored-by: Stas Co-authored-by: Jeremy Felder Co-authored-by: ImmanuelSegol <3ditds@gmail.com> Co-authored-by: JimmyHongjichuan <45908291+JimmyHongjichuan@users.noreply.github.com> Co-authored-by: pierre Co-authored-by: Leon Hibnik <107353745+LeonHibnik@users.noreply.github.com> Co-authored-by: nonam3e Co-authored-by: Vlad <88586482+vladfdp@users.noreply.github.com> Co-authored-by: LeonHibnik Co-authored-by: nonam3e <71525212+nonam3e@users.noreply.github.com> Co-authored-by: vladfdp --- .codespellignore | 1 + .github/workflows/codespell.yml | 2 +- .github/workflows/cpp_cuda.yml | 40 +- .github/workflows/examples.yml | 4 +- .github/workflows/golang.yml | 63 +- .github/workflows/rust.yml | 16 +- .gitignore | 2 +- docs/docs/icicle/core.md | 181 + docs/docs/icicle/golang-bindings.md | 29 +- docs/docs/icicle/golang-bindings/ecntt.md | 97 + .../golang-bindings/msm-pre-computation.md | 58 +- docs/docs/icicle/golang-bindings/msm.md | 169 +- docs/docs/icicle/golang-bindings/multi-gpu.md | 63 +- docs/docs/icicle/golang-bindings/ntt.md | 117 +- docs/docs/icicle/golang-bindings/vec-ops.md | 164 +- docs/docs/icicle/integrations.md | 2 +- docs/docs/icicle/introduction.md | 128 +- docs/docs/icicle/polynomials/ffi.uml | 27 + docs/docs/icicle/polynomials/hw_backends.uml | 86 + docs/docs/icicle/polynomials/overview.md | 373 + docs/docs/icicle/rust-bindings/ecntt.md | 35 + docs/docs/icicle/rust-bindings/multi-gpu.md | 10 +- docs/docs/icicle/rust-bindings/ntt.md | 37 +- docs/docs/icicle/rust-bindings/polynomials.md | 261 + docs/docs/icicle/rust-bindings/vec-ops.md | 64 +- .../icicle/supporting-additional-curves.md | 117 - docs/package-lock.json | 9761 ++++++++++++++++- docs/sidebars.js | 38 +- examples/c++/msm/CMakeLists.txt | 10 +- examples/c++/msm/compile.sh | 14 +- examples/c++/msm/example.cu | 76 +- examples/c++/msm/run.sh | 2 +- .../c++/multi-gpu-poseidon/CMakeLists.txt | 4 +- examples/c++/multi-gpu-poseidon/compile.sh | 14 +- examples/c++/multi-gpu-poseidon/example.cu | 15 +- examples/c++/multi-gpu-poseidon/run.sh | 2 +- examples/c++/multiply/CMakeLists.txt | 6 +- examples/c++/multiply/compile.sh | 14 +- examples/c++/multiply/example.cu | 17 +- examples/c++/multiply/run.sh | 2 +- examples/c++/ntt/CMakeLists.txt | 13 +- examples/c++/ntt/compile.sh | 12 +- examples/c++/ntt/example.cu | 19 +- examples/c++/ntt/run.sh | 2 +- .../c++/pedersen-commitment/CMakeLists.txt | 9 +- examples/c++/pedersen-commitment/compile.sh | 14 +- examples/c++/pedersen-commitment/example.cu | 12 +- examples/c++/pedersen-commitment/run.sh | 2 +- .../polynomial_multiplication/CMakeLists.txt | 9 +- .../c++/polynomial_multiplication/compile.sh | 12 +- .../c++/polynomial_multiplication/example.cu | 35 +- examples/c++/polynomial_multiplication/run.sh | 4 +- examples/c++/poseidon/CMakeLists.txt | 8 +- examples/c++/poseidon/compile.sh | 14 +- examples/c++/poseidon/example.cu | 25 +- examples/c++/poseidon/run.sh | 2 +- examples/rust/msm/Cargo.toml | 7 +- examples/rust/msm/src/main.rs | 42 +- examples/rust/ntt/src/main.rs | 43 +- examples/rust/polynomials/Cargo.toml | 14 + examples/rust/polynomials/src/main.rs | 101 + examples/rust/poseidon/src/main.rs | 33 +- icicle/CMakeLists.txt | 197 +- icicle/appUtils/msm/Makefile | 4 - icicle/appUtils/tree/Makefile | 3 - icicle/benchmarks/CMakeLists.txt | 5 + icicle/benchmarks/README.md | 25 + icicle/benchmarks/benches.cu | 6 + icicle/benchmarks/curve_benchmarks.cu | 79 + icicle/benchmarks/field_benchmarks.cu | 108 + icicle/cmake/Common.cmake | 72 + icicle/cmake/CurvesCommon.cmake | 17 + icicle/cmake/FieldsCommon.cmake | 17 + icicle/curves/curve_config.cuh | 89 - icicle/include/api/babybear.h | 73 + icicle/include/api/bls12_377.h | 132 + icicle/include/api/bls12_381.h | 132 + icicle/include/api/bn254.h | 132 + icicle/include/api/bw6_761.h | 132 + icicle/include/api/grumpkin.h | 94 + icicle/include/api/hash.h | 16 + icicle/include/api/templates/curves/curve.h | 13 + .../include/api/templates/curves/curve_g2.h | 13 + icicle/include/api/templates/curves/ecntt.h | 2 + icicle/include/api/templates/curves/msm.h | 11 + icicle/include/api/templates/curves/msm_g2.h | 11 + icicle/include/api/templates/fields/field.h | 4 + .../include/api/templates/fields/field_ext.h | 4 + icicle/include/api/templates/fields/ntt.h | 7 + icicle/include/api/templates/fields/ntt_ext.h | 2 + .../include/api/templates/fields/poseidon.h | 26 + icicle/include/api/templates/fields/vec_ops.h | 17 + .../api/templates/fields/vec_ops_ext.h | 17 + .../{primitives => include/curves}/affine.cuh | 23 +- icicle/include/curves/curve_config.cuh | 34 + icicle/include/curves/macro.h | 42 + icicle/include/curves/params/bls12_377.cuh | 48 + icicle/include/curves/params/bls12_381.cuh | 48 + icicle/include/curves/params/bn254.cuh | 39 + icicle/include/curves/params/bw6_761.cuh | 58 + .../curves/params/grumpkin.cuh} | 12 +- .../curves}/projective.cuh | 32 +- .../{primitives => include/fields}/field.cuh | 218 +- icicle/include/fields/field_config.cuh | 35 + .../{utils => include/fields}/host_math.cuh | 2 +- icicle/include/fields/id.h | 13 + icicle/{utils => include/fields}/ptx.cuh | 0 .../fields/quadratic_extension.cuh} | 79 +- icicle/include/fields/quartic_extension.cuh | 257 + .../fields/snark_fields/bls12_377_base.cuh} | 232 +- .../fields/snark_fields/bls12_377_scalar.cuh | 202 + .../fields/snark_fields/bls12_381_base.cuh | 61 + .../fields/snark_fields/bls12_381_scalar.cuh} | 93 +- .../fields/snark_fields/bn254_base.cuh | 49 + .../fields/snark_fields/bn254_scalar.cuh} | 72 +- .../fields/snark_fields/bw6_761_base.cuh} | 38 +- .../fields/snark_fields/bw6_761_scalar.cuh | 19 + .../fields/snark_fields/grumpkin_base.cuh | 12 + .../fields/snark_fields/grumpkin_scalar.cuh | 18 + .../include/fields/stark_fields/babybear.cuh | 62 + icicle/{utils => include/fields}/storage.cuh | 0 .../gpu-utils}/device_context.cuh | 0 .../gpu-utils}/error_handler.cuh | 0 .../gpu-utils/modifiers.cuh} | 0 .../gpu-utils}/sharedmem.cuh | 24 +- .../hash}/keccak/keccak.cuh | 4 +- icicle/{appUtils => include}/msm/msm.cuh | 44 +- icicle/{appUtils => include}/ntt/ntt.cuh | 39 +- icicle/{appUtils => include}/ntt/ntt_impl.cuh | 2 +- .../cuda_backend/polynomial_cuda_backend.cuh | 23 + .../polynomials/polynomial_abstract_factory.h | 49 + .../include/polynomials/polynomial_backend.h | 71 + .../include/polynomials/polynomial_context.h | 93 + icicle/include/polynomials/polynomials.h | 133 + .../poseidon/constants/bls12_377_poseidon.h | 0 .../poseidon/constants/bls12_381_poseidon.h | 0 .../poseidon/constants/bn254_poseidon.h | 0 .../poseidon/constants/bw6_761_poseidon.h | 0 .../poseidon/constants/constants_template.h | 0 .../poseidon/constants/generate_parameters.py | 0 .../poseidon/constants/grumpkin_poseidon.h | 0 .../poseidon/poseidon.cuh | 10 +- .../poseidon}/tree/merkle.cuh | 11 +- icicle/include/utils/integrity_pointer.h | 102 + icicle/{ => include}/utils/mont.cuh | 6 +- icicle/include/utils/test_functions.cuh | 142 + icicle/{ => include}/utils/utils.h | 2 +- icicle/{ => include}/utils/utils_kernels.cuh | 0 icicle/{utils => include/vec_ops}/vec_ops.cuh | 20 +- icicle/primitives/field.cu | 10 - icicle/primitives/projective.cu | 60 - icicle/primitives/test_kernels.cuh | 113 - icicle/src/curves/CMakeLists.txt | 29 + icicle/src/curves/extern.cu | 51 + icicle/src/curves/extern_g2.cu | 51 + icicle/src/fields/CMakeLists.txt | 40 + icicle/src/fields/extern.cu | 22 + icicle/src/fields/extern_extension.cu | 22 + icicle/src/hash/CMakeLists.txt | 5 + icicle/{appUtils => src/hash}/keccak/Makefile | 0 .../{appUtils => src/hash}/keccak/keccak.cu | 6 +- icicle/{appUtils => src/hash}/keccak/test.cu | 6 +- icicle/src/hash/keccak/test_keccak | Bin 0 -> 1052896 bytes icicle/src/msm/Makefile | 4 + icicle/src/msm/extern.cu | 43 + icicle/src/msm/extern_g2.cu | 43 + icicle/{appUtils => src}/msm/msm.cu | 130 +- .../{appUtils => src}/msm/tests/msm_test.cu | 36 +- icicle/{appUtils => src}/ntt/Makefile | 4 +- icicle/src/ntt/extern.cu | 58 + icicle/src/ntt/extern_ecntt.cu | 25 + icicle/src/ntt/extern_extension.cu | 23 + icicle/{appUtils => src}/ntt/kernel_ntt.cu | 78 +- icicle/{appUtils => src}/ntt/ntt.cu | 136 +- .../ntt/tests/verification.cu | 48 +- icicle/{appUtils => src}/ntt/thread_ntt.cu | 2 +- .../src/polynomials/cuda_backend/kernels.cuh | 104 + .../cuda_backend/polynomial_cuda_backend.cu | 886 ++ icicle/src/polynomials/polynomials.cu | 204 + icicle/src/polynomials/polynomials_c_api.cu | 283 + icicle/{appUtils => src}/poseidon/.gitignore | 0 icicle/{appUtils => src}/poseidon/Makefile | 2 +- .../{appUtils => src}/poseidon/constants.cu | 37 +- icicle/{appUtils => src}/poseidon/kernels.cu | 3 +- icicle/{appUtils => src}/poseidon/poseidon.cu | 22 +- icicle/{appUtils => src}/poseidon/test.cu | 8 +- .../poseidon}/tree/.gitignore | 0 icicle/src/poseidon/tree/Makefile | 3 + .../{appUtils => src/poseidon}/tree/merkle.cu | 24 +- .../{appUtils => src/poseidon}/tree/test.cu | 5 +- icicle/src/vec_ops/extern.cu | 62 + icicle/src/vec_ops/extern_extension.cu | 59 + icicle/src/vec_ops/vec_ops.cu | 167 + icicle/tests/CMakeLists.txt | 32 + icicle/tests/curve_test.cu | 194 + icicle/tests/device_error_test.cu | 2 +- icicle/tests/error_handler_test.cu | 2 +- icicle/tests/field_test.cu | 145 + icicle/tests/polynomial_test.cu | 982 ++ icicle/tests/primitives_test.cu | 425 - icicle/tests/runner.cu | 5 +- icicle/utils/device_context.cu | 7 - icicle/utils/mont.cu | 60 - icicle/utils/vec_ops.cu | 216 - scripts/gen_c_api.py | 147 + wrappers/golang/README.md | 31 +- wrappers/golang/build.sh | 110 +- wrappers/golang/core/internal/curve_test.go | 101 - wrappers/golang/core/internal/field_test.go | 82 - .../core/internal/{curve.go => mock_curve.go} | 8 +- wrappers/golang/core/internal/mock_field.go | 84 + wrappers/golang/core/msm.go | 26 +- wrappers/golang/core/msm_test.go | 28 +- wrappers/golang/core/ntt.go | 16 +- wrappers/golang/core/ntt_test.go | 39 +- wrappers/golang/core/slice.go | 21 +- wrappers/golang/core/slice_test.go | 24 +- wrappers/golang/core/vec_ops.go | 44 +- wrappers/golang/core/vec_ops_test.go | 4 +- wrappers/golang/curves/bls12377/base_field.go | 7 +- wrappers/golang/curves/bls12377/bls12_377.go | 4 - wrappers/golang/curves/bls12377/curve.go | 16 +- .../golang/curves/bls12377/ecntt/ecntt.go | 24 + .../curves/bls12377/ecntt/include/ecntt.h | 19 + .../g2_curve.go => bls12377/g2/curve.go} | 22 +- .../g2/g2base_field.go} | 17 +- .../golang/curves/bls12377/g2/include/curve.h | 26 + .../golang/curves/bls12377/g2/include/msm.h | 24 + .../curves/bls12377/g2/include/scalar_field.h | 21 + wrappers/golang/curves/bls12377/g2/msm.go | 45 + wrappers/golang/curves/bls12377/g2_msm.go | 82 - .../golang/curves/bls12377/helpers_test.go | 31 - .../golang/curves/bls12377/include/curve.h | 17 +- .../golang/curves/bls12377/include/g2_curve.h | 23 - .../golang/curves/bls12377/include/g2_msm.h | 19 - wrappers/golang/curves/bls12377/include/msm.h | 19 - wrappers/golang/curves/bls12377/include/ntt.h | 21 - .../curves/bls12377/include/scalar_field.h | 8 +- wrappers/golang/curves/bls12377/main.go | 4 + wrappers/golang/curves/bls12377/msm.go | 80 - .../golang/curves/bls12377/msm/include/msm.h | 24 + wrappers/golang/curves/bls12377/msm/msm.go | 45 + wrappers/golang/curves/bls12377/ntt.go | 97 - .../golang/curves/bls12377/ntt/include/ntt.h | 23 + wrappers/golang/curves/bls12377/ntt/ntt.go | 56 + .../bls12377/polynomial/include/polynomial.h | 51 + .../curves/bls12377/polynomial/polynomial.go | 176 + .../golang/curves/bls12377/scalar_field.go | 13 +- .../tests}/base_field_test.go | 46 +- .../{bn254 => bls12377/tests}/curve_test.go | 64 +- .../curves/bls12377/tests/ecntt_test.go | 30 + .../tests}/g2_curve_test.go | 66 +- .../tests/g2_g2base_field_test.go} | 50 +- .../bls12377/{ => tests}/g2_msm_test.go | 150 +- .../golang/curves/bls12377/tests/main_test.go | 48 + .../curves/bls12377/{ => tests}/msm_test.go | 135 +- .../curves/bls12377/{ => tests}/ntt_test.go | 133 +- .../curves/bls12377/tests/polynomial_test.go | 229 + .../bls12377/tests/scalar_field_test.go | 120 + .../curves/bls12377/tests/vec_ops_test.go | 69 + .../bls12377/{ => vecOps}/include/vec_ops.h | 22 +- .../golang/curves/bls12377/vecOps/vec_ops.go | 48 + wrappers/golang/curves/bls12377/vec_ops.go | 55 - .../golang/curves/bls12377/vec_ops_test.go | 33 - wrappers/golang/curves/bls12381/base_field.go | 7 +- wrappers/golang/curves/bls12381/bls12_381.go | 4 - wrappers/golang/curves/bls12381/curve.go | 16 +- .../golang/curves/bls12381/ecntt/ecntt.go | 24 + .../curves/bls12381/ecntt/include/ecntt.h | 19 + .../g2_curve.go => bls12381/g2/curve.go} | 22 +- .../g2/g2base_field.go} | 17 +- .../golang/curves/bls12381/g2/include/curve.h | 26 + .../golang/curves/bls12381/g2/include/msm.h | 24 + .../curves/bls12381/g2/include/scalar_field.h | 21 + wrappers/golang/curves/bls12381/g2/msm.go | 45 + wrappers/golang/curves/bls12381/g2_msm.go | 82 - .../golang/curves/bls12381/helpers_test.go | 31 - .../golang/curves/bls12381/include/curve.h | 17 +- .../golang/curves/bls12381/include/g2_curve.h | 23 - .../golang/curves/bls12381/include/g2_msm.h | 19 - wrappers/golang/curves/bls12381/include/msm.h | 19 - wrappers/golang/curves/bls12381/include/ntt.h | 21 - .../curves/bls12381/include/scalar_field.h | 8 +- wrappers/golang/curves/bls12381/main.go | 4 + wrappers/golang/curves/bls12381/msm.go | 80 - .../golang/curves/bls12381/msm/include/msm.h | 24 + wrappers/golang/curves/bls12381/msm/msm.go | 45 + wrappers/golang/curves/bls12381/ntt.go | 97 - .../golang/curves/bls12381/ntt/include/ntt.h | 23 + wrappers/golang/curves/bls12381/ntt/ntt.go | 56 + .../bls12381/polynomial/include/polynomial.h | 51 + .../curves/bls12381/polynomial/polynomial.go | 176 + .../golang/curves/bls12381/scalar_field.go | 13 +- .../curves/bls12381/tests/base_field_test.go | 88 + .../curves/bls12381/tests/curve_test.go | 103 + .../curves/bls12381/tests/ecntt_test.go | 30 + .../tests}/g2_curve_test.go | 66 +- .../tests/g2_g2base_field_test.go} | 50 +- .../bls12381/{ => tests}/g2_msm_test.go | 150 +- .../golang/curves/bls12381/tests/main_test.go | 48 + .../curves/bls12381/{ => tests}/msm_test.go | 135 +- .../curves/bls12381/{ => tests}/ntt_test.go | 133 +- .../curves/bls12381/tests/polynomial_test.go | 229 + .../bls12381/tests/scalar_field_test.go | 120 + .../curves/bls12381/tests/vec_ops_test.go | 69 + .../bls12381/{ => vecOps}/include/vec_ops.h | 22 +- .../golang/curves/bls12381/vecOps/vec_ops.go | 48 + wrappers/golang/curves/bls12381/vec_ops.go | 55 - .../golang/curves/bls12381/vec_ops_test.go | 33 - wrappers/golang/curves/bn254/base_field.go | 7 +- wrappers/golang/curves/bn254/bn254.go | 4 - wrappers/golang/curves/bn254/curve.go | 16 +- wrappers/golang/curves/bn254/ecntt/ecntt.go | 24 + .../golang/curves/bn254/ecntt/include/ecntt.h | 19 + .../g2_curve.go => bn254/g2/curve.go} | 22 +- .../g2/g2base_field.go} | 17 +- .../golang/curves/bn254/g2/include/curve.h | 26 + wrappers/golang/curves/bn254/g2/include/msm.h | 24 + .../curves/bn254/g2/include/scalar_field.h | 21 + wrappers/golang/curves/bn254/g2/msm.go | 45 + wrappers/golang/curves/bn254/g2_msm.go | 82 - wrappers/golang/curves/bn254/helpers_test.go | 31 - wrappers/golang/curves/bn254/include/curve.h | 17 +- .../golang/curves/bn254/include/g2_curve.h | 23 - wrappers/golang/curves/bn254/include/g2_msm.h | 19 - wrappers/golang/curves/bn254/include/msm.h | 19 - wrappers/golang/curves/bn254/include/ntt.h | 21 - .../curves/bn254/include/scalar_field.h | 8 +- wrappers/golang/curves/bn254/main.go | 4 + wrappers/golang/curves/bn254/msm.go | 80 - .../golang/curves/bn254/msm/include/msm.h | 24 + wrappers/golang/curves/bn254/msm/msm.go | 45 + wrappers/golang/curves/bn254/ntt.go | 97 - .../golang/curves/bn254/ntt/include/ntt.h | 23 + wrappers/golang/curves/bn254/ntt/ntt.go | 56 + .../bn254/polynomial/include/polynomial.h | 51 + .../curves/bn254/polynomial/polynomial.go | 176 + wrappers/golang/curves/bn254/scalar_field.go | 13 +- .../tests}/base_field_test.go | 46 +- .../{bls12377 => bn254/tests}/curve_test.go | 64 +- .../golang/curves/bn254/tests/ecntt_test.go | 30 + .../tests}/g2_curve_test.go | 66 +- .../tests/g2_g2base_field_test.go} | 50 +- .../curves/bn254/{ => tests}/g2_msm_test.go | 150 +- .../golang/curves/bn254/tests/main_test.go | 48 + .../curves/bn254/{ => tests}/msm_test.go | 135 +- .../curves/bn254/{ => tests}/ntt_test.go | 133 +- .../curves/bn254/tests/polynomial_test.go | 229 + .../bn254/{ => tests}/scalar_field_test.go | 62 +- .../golang/curves/bn254/tests/vec_ops_test.go | 69 + .../bn254/{ => vecOps}/include/vec_ops.h | 22 +- .../golang/curves/bn254/vecOps/vec_ops.go | 48 + wrappers/golang/curves/bn254/vec_ops.go | 55 - wrappers/golang/curves/bn254/vec_ops_test.go | 33 - wrappers/golang/curves/bw6761/base_field.go | 7 +- wrappers/golang/curves/bw6761/bw6_761.go | 4 - wrappers/golang/curves/bw6761/curve.go | 16 +- wrappers/golang/curves/bw6761/ecntt/ecntt.go | 24 + .../curves/bw6761/ecntt/include/ecntt.h | 19 + .../bw6761/{g2_curve.go => g2/curve.go} | 22 +- .../g2/g2base_field.go} | 17 +- .../golang/curves/bw6761/g2/include/curve.h | 26 + .../golang/curves/bw6761/g2/include/msm.h | 24 + .../curves/bw6761/g2/include/scalar_field.h | 21 + wrappers/golang/curves/bw6761/g2/msm.go | 45 + wrappers/golang/curves/bw6761/g2_msm.go | 82 - wrappers/golang/curves/bw6761/helpers_test.go | 31 - wrappers/golang/curves/bw6761/include/curve.h | 17 +- .../golang/curves/bw6761/include/g2_curve.h | 23 - .../golang/curves/bw6761/include/g2_msm.h | 19 - wrappers/golang/curves/bw6761/include/msm.h | 19 - wrappers/golang/curves/bw6761/include/ntt.h | 21 - .../curves/bw6761/include/scalar_field.h | 8 +- wrappers/golang/curves/bw6761/main.go | 4 + wrappers/golang/curves/bw6761/msm.go | 80 - .../golang/curves/bw6761/msm/include/msm.h | 24 + wrappers/golang/curves/bw6761/msm/msm.go | 45 + wrappers/golang/curves/bw6761/ntt.go | 97 - .../golang/curves/bw6761/ntt/include/ntt.h | 23 + wrappers/golang/curves/bw6761/ntt/ntt.go | 56 + .../bw6761/polynomial/include/polynomial.h | 51 + .../curves/bw6761/polynomial/polynomial.go | 176 + wrappers/golang/curves/bw6761/scalar_field.go | 13 +- .../bw6761/{ => tests}/base_field_test.go | 46 +- .../{bls12381 => bw6761/tests}/curve_test.go | 64 +- .../golang/curves/bw6761/tests/ecntt_test.go | 30 + .../{bn254 => bw6761/tests}/g2_curve_test.go | 66 +- .../tests/g2_g2base_field_test.go} | 50 +- .../curves/bw6761/{ => tests}/g2_msm_test.go | 137 +- .../golang/curves/bw6761/tests/main_test.go | 48 + .../curves/bw6761/{ => tests}/msm_test.go | 135 +- .../curves/bw6761/{ => tests}/ntt_test.go | 133 +- .../curves/bw6761/tests/polynomial_test.go | 229 + .../bw6761/{ => tests}/scalar_field_test.go | 62 +- .../curves/bw6761/tests/vec_ops_test.go | 69 + .../bw6761/{ => vecOps}/include/vec_ops.h | 22 +- .../golang/curves/bw6761/vecOps/vec_ops.go | 48 + wrappers/golang/curves/bw6761/vec_ops.go | 55 - wrappers/golang/curves/bw6761/vec_ops_test.go | 33 - .../grumpkin/base_field.go} | 29 +- wrappers/golang/curves/grumpkin/curve.go | 177 + .../golang/curves/grumpkin/include/curve.h | 26 + .../curves/grumpkin/include/scalar_field.h | 21 + wrappers/golang/curves/grumpkin/main.go | 4 + .../golang/curves/grumpkin/msm/include/msm.h | 24 + wrappers/golang/curves/grumpkin/msm/msm.go | 45 + .../golang/curves/grumpkin/scalar_field.go | 121 + .../tests}/base_field_test.go | 46 +- .../{bw6761 => grumpkin/tests}/curve_test.go | 64 +- .../golang/curves/grumpkin/tests/main_test.go | 17 + .../golang/curves/grumpkin/tests/msm_test.go | 168 + .../tests}/scalar_field_test.go | 62 +- .../curves/grumpkin/tests/vec_ops_test.go | 69 + .../curves/grumpkin/vecOps/include/vec_ops.h | 53 + .../golang/curves/grumpkin/vecOps/vec_ops.go | 48 + wrappers/golang/curves/include/types.h | 27 - .../babybear/extension/extension_field.go | 121 + .../babybear/extension/include/scalar_field.h | 21 + .../golang/fields/babybear/extension/main.go | 4 + .../babybear/extension/ntt/include/ntt.h | 22 + .../fields/babybear/extension/ntt/ntt.go | 24 + .../extension/vecOps/include/vec_ops.h | 53 + .../babybear/extension/vecOps/vec_ops.go | 48 + .../fields/babybear/include/scalar_field.h | 21 + wrappers/golang/fields/babybear/main.go | 4 + .../golang/fields/babybear/ntt/include/ntt.h | 23 + wrappers/golang/fields/babybear/ntt/ntt.go | 56 + .../babybear/polynomial/include/polynomial.h | 51 + .../fields/babybear/polynomial/polynomial.go | 176 + .../golang/fields/babybear/scalar_field.go | 121 + .../babybear/tests/extension_field_test.go | 120 + .../babybear/tests/extension_vec_ops_test.go | 69 + .../golang/fields/babybear/tests/main_test.go | 42 + .../babybear/tests/ntt_no_domain_test.go | 81 + .../golang/fields/babybear/tests/ntt_test.go | 170 + .../fields/babybear/tests/polynomial_test.go | 229 + .../babybear/tests}/scalar_field_test.go | 62 +- .../fields/babybear/tests/vec_ops_test.go | 69 + .../fields/babybear/vecOps/include/vec_ops.h | 53 + .../golang/fields/babybear/vecOps/vec_ops.go | 48 + .../internal/generator/config/babybear.go | 17 + .../internal/generator/config/bls12377.go | 19 + .../internal/generator/config/bls12381.go | 19 + .../golang/internal/generator/config/bn254.go | 19 + .../internal/generator/config/bw6761.go | 19 + .../internal/generator/config/config.go | 50 + .../internal/generator/config/grumpkin.go | 19 + .../internal/generator/curves/generate.go | 42 + .../generator/curves/templates/curve.go.tmpl | 178 + .../generator/curves/templates/curve.h.tmpl | 26 + .../curves/templates/curve_test.go.tmpl | 103 + .../generator/curves/templates/main.go.tmpl | 4 + .../internal/generator/ecntt/generate.go | 29 + .../generator/ecntt/templates/ecntt.go.tmpl | 24 + .../generator/ecntt/templates/ecntt.h.tmpl | 19 + .../ecntt/templates/ecntt_test.go.tmpl | 30 + .../internal/generator/fields/generate.go | 46 + .../generator/fields/templates/field.go.tmpl | 124 + .../fields/templates/field_test.go.tmpl | 121 + .../generator/fields/templates/main.go.tmpl | 4 + .../fields/templates/scalar_field.h.tmpl | 21 + .../generator/generator_utils/generate.go | 107 + .../internal/generator/lib_linker/generate.go | 32 + wrappers/golang/internal/generator/main.go | 297 +- .../internal/generator/mock/generate.go | 37 + .../golang/internal/generator/msm/generate.go | 39 + .../generator/msm/templates/msm.go.tmpl | 45 + .../generator/msm/templates/msm.h.tmpl | 24 + .../generator/msm/templates/msm_test.go.tmpl | 365 + .../golang/internal/generator/ntt/generate.go | 54 + .../generator/ntt/templates/ntt.go.tmpl | 58 + .../generator/ntt/templates/ntt.h.tmpl | 24 + .../ntt/templates/ntt_no_domain_test.go.tmpl | 84 + .../{ => ntt}/templates/ntt_test.go.tmpl | 151 +- .../internal/generator/polynomial/generate.go | 31 + .../polynomial/templates/polynomial.go.tmpl | 176 + .../polynomial/templates/polynomial.h.tmpl | 51 + .../templates/polynomial_test.go.tmpl | 229 + .../generator/templates/curve.go.tmpl | 182 - .../generator/templates/curve_test.go.tmpl | 105 - .../generator/templates/field.go.tmpl | 90 - .../generator/templates/field_test.go.tmpl | 91 - .../generator/templates/helpers_test.go.tmpl | 31 - .../generator/templates/include/curve.h.tmpl | 23 - .../templates/include/g2_curve.h.tmpl | 23 - .../generator/templates/include/g2_msm.h.tmpl | 19 - .../generator/templates/include/msm.h.tmpl | 19 - .../generator/templates/include/ntt.h.tmpl | 21 - .../templates/include/scalar_field.h.tmpl | 19 - .../templates/include/vec_ops.h.tmpl | 39 - .../internal/generator/templates/main.go.tmpl | 4 - .../internal/generator/templates/msm.go.tmpl | 84 - .../generator/templates/msm_test.go.tmpl | 276 - .../internal/generator/templates/ntt.go.tmpl | 97 - .../generator/templates/scalar_field.go.tmpl | 43 - .../templates/scalar_field_test.go.tmpl | 34 - .../generator/templates/vec_ops.go.tmpl | 55 - .../generator/templates/vec_ops_test.go.tmpl | 33 - .../internal/generator/tests/generate.go | 29 + .../tests/templates/main_test.go.tmpl | 58 + .../internal/generator/vecOps/generate.go | 39 + .../vecOps/templates/vec_ops.go.tmpl | 48 + .../generator/vecOps/templates/vec_ops.h.tmpl | 53 + .../vecOps/templates/vec_ops_test.go.tmpl | 69 + .../helpers.go} | 8 +- wrappers/rust/Cargo.toml | 2 + wrappers/rust/icicle-core/Cargo.toml | 7 + wrappers/rust/icicle-core/src/curve.rs | 118 +- wrappers/rust/icicle-core/src/ecntt/mod.rs | 271 + wrappers/rust/icicle-core/src/ecntt/tests.rs | 92 + wrappers/rust/icicle-core/src/field.rs | 111 +- wrappers/rust/icicle-core/src/lib.rs | 5 +- wrappers/rust/icicle-core/src/msm/mod.rs | 59 +- wrappers/rust/icicle-core/src/msm/tests.rs | 54 +- wrappers/rust/icicle-core/src/ntt/mod.rs | 218 +- wrappers/rust/icicle-core/src/ntt/tests.rs | 171 +- .../rust/icicle-core/src/polynomials/mod.rs | 814 ++ wrappers/rust/icicle-core/src/poseidon/mod.rs | 46 +- .../rust/icicle-core/src/poseidon/tests.rs | 18 +- wrappers/rust/icicle-core/src/tests.rs | 48 +- wrappers/rust/icicle-core/src/traits.rs | 9 +- wrappers/rust/icicle-core/src/tree/mod.rs | 19 +- wrappers/rust/icicle-core/src/tree/tests.rs | 10 +- wrappers/rust/icicle-core/src/vec_ops/mod.rs | 124 +- .../rust/icicle-core/src/vec_ops/tests.rs | 31 +- wrappers/rust/icicle-cuda-runtime/build.rs | 5 + .../rust/icicle-cuda-runtime/src/device.rs | 22 +- .../icicle-cuda-runtime/src/device_context.rs | 9 - wrappers/rust/icicle-cuda-runtime/src/lib.rs | 2 +- .../rust/icicle-cuda-runtime/src/memory.rs | 515 +- .../icicle-curves/icicle-bls12-377/Cargo.toml | 7 + .../icicle-bls12-377/benches/ecntt.rs | 10 + .../icicle-curves/icicle-bls12-377/build.rs | 25 +- .../icicle-bls12-377/src/curve.rs | 11 +- .../icicle-bls12-377/src/ecntt/mod.rs | 23 + .../icicle-curves/icicle-bls12-377/src/lib.rs | 2 + .../icicle-bls12-377/src/msm/mod.rs | 8 +- .../icicle-bls12-377/src/ntt/mod.rs | 8 +- .../icicle-bls12-377/src/polynomials/mod.rs | 10 + .../icicle-curves/icicle-bls12-381/Cargo.toml | 7 + .../icicle-bls12-381/benches/ecntt.rs | 10 + .../icicle-curves/icicle-bls12-381/build.rs | 13 +- .../icicle-bls12-381/src/curve.rs | 11 +- .../icicle-bls12-381/src/ecntt/mod.rs | 23 + .../icicle-curves/icicle-bls12-381/src/lib.rs | 2 + .../icicle-bls12-381/src/msm/mod.rs | 8 +- .../icicle-bls12-381/src/ntt/mod.rs | 8 +- .../icicle-bls12-381/src/polynomials/mod.rs | 10 + .../icicle-curves/icicle-bn254/Cargo.toml | 7 + .../icicle-bn254/benches/ecntt.rs | 10 + .../rust/icicle-curves/icicle-bn254/build.rs | 14 +- .../icicle-curves/icicle-bn254/src/curve.rs | 11 +- .../icicle-bn254/src/ecntt/mod.rs | 23 + .../icicle-curves/icicle-bn254/src/lib.rs | 2 + .../icicle-curves/icicle-bn254/src/msm/mod.rs | 8 +- .../icicle-curves/icicle-bn254/src/ntt/mod.rs | 8 +- .../icicle-bn254/src/polynomials/mod.rs | 10 + .../icicle-curves/icicle-bw6-761/Cargo.toml | 5 +- .../icicle-curves/icicle-bw6-761/src/curve.rs | 5 +- .../icicle-bw6-761/src/msm/mod.rs | 8 +- .../icicle-bw6-761/src/ntt/mod.rs | 1 + .../icicle-curve-template/Cargo.toml | 30 - .../icicle-curve-template/build.rs | 28 - .../icicle-curve-template/src/curve.rs | 61 - .../icicle-curve-template/src/lib.rs | 8 - .../icicle-curve-template/src/msm/mod.rs | 31 - .../icicle-curve-template/src/ntt/mod.rs | 22 - .../icicle-curve-template/src/poseidon/mod.rs | 22 - .../icicle-curve-template/src/tree/mod.rs | 21 - .../icicle-curves/icicle-grumpkin/build.rs | 10 +- .../icicle-grumpkin/src/curve.rs | 7 +- .../icicle-grumpkin/src/msm/mod.rs | 6 +- .../icicle-fields/icicle-babybear/Cargo.toml | 28 + .../icicle-fields/icicle-babybear/build.rs | 24 + .../icicle-babybear/src/field.rs | 34 + .../icicle-fields/icicle-babybear/src/lib.rs | 4 + .../icicle-babybear/src/ntt/mod.rs | 174 + .../icicle-babybear/src/polynomials/mod.rs | 10 + .../icicle-babybear}/src/vec_ops/mod.rs | 12 +- wrappers/rust/icicle-hash/Cargo.toml | 18 + wrappers/rust/icicle-hash/build.rs | 21 + wrappers/rust/icicle-hash/src/keccak/mod.rs | 102 + wrappers/rust/icicle-hash/src/keccak/tests.rs | 1 + wrappers/rust/icicle-hash/src/lib.rs | 1 + 584 files changed, 33460 insertions(+), 8856 deletions(-) create mode 100644 docs/docs/icicle/core.md create mode 100644 docs/docs/icicle/golang-bindings/ecntt.md create mode 100644 docs/docs/icicle/polynomials/ffi.uml create mode 100644 docs/docs/icicle/polynomials/hw_backends.uml create mode 100644 docs/docs/icicle/polynomials/overview.md create mode 100644 docs/docs/icicle/rust-bindings/ecntt.md create mode 100644 docs/docs/icicle/rust-bindings/polynomials.md delete mode 100644 docs/docs/icicle/supporting-additional-curves.md create mode 100644 examples/rust/polynomials/Cargo.toml create mode 100644 examples/rust/polynomials/src/main.rs delete mode 100644 icicle/appUtils/msm/Makefile delete mode 100644 icicle/appUtils/tree/Makefile create mode 100644 icicle/benchmarks/CMakeLists.txt create mode 100644 icicle/benchmarks/README.md create mode 100644 icicle/benchmarks/benches.cu create mode 100644 icicle/benchmarks/curve_benchmarks.cu create mode 100644 icicle/benchmarks/field_benchmarks.cu create mode 100644 icicle/cmake/Common.cmake create mode 100644 icicle/cmake/CurvesCommon.cmake create mode 100644 icicle/cmake/FieldsCommon.cmake delete mode 100644 icicle/curves/curve_config.cuh create mode 100644 icicle/include/api/babybear.h create mode 100644 icicle/include/api/bls12_377.h create mode 100644 icicle/include/api/bls12_381.h create mode 100644 icicle/include/api/bn254.h create mode 100644 icicle/include/api/bw6_761.h create mode 100644 icicle/include/api/grumpkin.h create mode 100644 icicle/include/api/hash.h create mode 100644 icicle/include/api/templates/curves/curve.h create mode 100644 icicle/include/api/templates/curves/curve_g2.h create mode 100644 icicle/include/api/templates/curves/ecntt.h create mode 100644 icicle/include/api/templates/curves/msm.h create mode 100644 icicle/include/api/templates/curves/msm_g2.h create mode 100644 icicle/include/api/templates/fields/field.h create mode 100644 icicle/include/api/templates/fields/field_ext.h create mode 100644 icicle/include/api/templates/fields/ntt.h create mode 100644 icicle/include/api/templates/fields/ntt_ext.h create mode 100644 icicle/include/api/templates/fields/poseidon.h create mode 100644 icicle/include/api/templates/fields/vec_ops.h create mode 100644 icicle/include/api/templates/fields/vec_ops_ext.h rename icicle/{primitives => include/curves}/affine.cuh (51%) create mode 100644 icicle/include/curves/curve_config.cuh create mode 100644 icicle/include/curves/macro.h create mode 100644 icicle/include/curves/params/bls12_377.cuh create mode 100644 icicle/include/curves/params/bls12_381.cuh create mode 100644 icicle/include/curves/params/bn254.cuh create mode 100644 icicle/include/curves/params/bw6_761.cuh rename icicle/{curves/grumpkin_params.cuh => include/curves/params/grumpkin.cuh} (80%) rename icicle/{primitives => include/curves}/projective.cuh (92%) rename icicle/{primitives => include/fields}/field.cuh (83%) create mode 100644 icicle/include/fields/field_config.cuh rename icicle/{utils => include/fields}/host_math.cuh (98%) create mode 100644 icicle/include/fields/id.h rename icicle/{utils => include/fields}/ptx.cuh (100%) rename icicle/{primitives/extension_field.cuh => include/fields/quadratic_extension.cuh} (64%) create mode 100644 icicle/include/fields/quartic_extension.cuh rename icicle/{curves/bls12_377_params.cuh => include/fields/snark_fields/bls12_377_base.cuh} (55%) create mode 100644 icicle/include/fields/snark_fields/bls12_377_scalar.cuh create mode 100644 icicle/include/fields/snark_fields/bls12_381_base.cuh rename icicle/{curves/bls12_381_params.cuh => include/fields/snark_fields/bls12_381_scalar.cuh} (64%) create mode 100644 icicle/include/fields/snark_fields/bn254_base.cuh rename icicle/{curves/bn254_params.cuh => include/fields/snark_fields/bn254_scalar.cuh} (69%) rename icicle/{curves/bw6_761_params.cuh => include/fields/snark_fields/bw6_761_base.cuh} (72%) create mode 100644 icicle/include/fields/snark_fields/bw6_761_scalar.cuh create mode 100644 icicle/include/fields/snark_fields/grumpkin_base.cuh create mode 100644 icicle/include/fields/snark_fields/grumpkin_scalar.cuh create mode 100644 icicle/include/fields/stark_fields/babybear.cuh rename icicle/{utils => include/fields}/storage.cuh (100%) rename icicle/{utils => include/gpu-utils}/device_context.cuh (100%) rename icicle/{utils => include/gpu-utils}/error_handler.cuh (100%) rename icicle/{common.cuh => include/gpu-utils/modifiers.cuh} (100%) rename icicle/{utils => include/gpu-utils}/sharedmem.cuh (89%) rename icicle/{appUtils => include/hash}/keccak/keccak.cuh (96%) rename icicle/{appUtils => include}/msm/msm.cuh (87%) rename icicle/{appUtils => include}/ntt/ntt.cuh (85%) rename icicle/{appUtils => include}/ntt/ntt_impl.cuh (95%) create mode 100644 icicle/include/polynomials/cuda_backend/polynomial_cuda_backend.cuh create mode 100644 icicle/include/polynomials/polynomial_abstract_factory.h create mode 100644 icicle/include/polynomials/polynomial_backend.h create mode 100644 icicle/include/polynomials/polynomial_context.h create mode 100644 icicle/include/polynomials/polynomials.h rename icicle/{appUtils => include}/poseidon/constants/bls12_377_poseidon.h (100%) rename icicle/{appUtils => include}/poseidon/constants/bls12_381_poseidon.h (100%) rename icicle/{appUtils => include}/poseidon/constants/bn254_poseidon.h (100%) rename icicle/{appUtils => include}/poseidon/constants/bw6_761_poseidon.h (100%) rename icicle/{appUtils => include}/poseidon/constants/constants_template.h (100%) rename icicle/{appUtils => include}/poseidon/constants/generate_parameters.py (100%) rename icicle/{appUtils => include}/poseidon/constants/grumpkin_poseidon.h (100%) rename icicle/{appUtils => include}/poseidon/poseidon.cuh (95%) rename icicle/{appUtils => include/poseidon}/tree/merkle.cuh (90%) create mode 100644 icicle/include/utils/integrity_pointer.h rename icicle/{ => include}/utils/mont.cuh (77%) create mode 100644 icicle/include/utils/test_functions.cuh rename icicle/{ => include}/utils/utils.h (82%) rename icicle/{ => include}/utils/utils_kernels.cuh (100%) rename icicle/{utils => include/vec_ops}/vec_ops.cuh (86%) delete mode 100644 icicle/primitives/field.cu delete mode 100644 icicle/primitives/projective.cu delete mode 100644 icicle/primitives/test_kernels.cuh create mode 100644 icicle/src/curves/CMakeLists.txt create mode 100644 icicle/src/curves/extern.cu create mode 100644 icicle/src/curves/extern_g2.cu create mode 100644 icicle/src/fields/CMakeLists.txt create mode 100644 icicle/src/fields/extern.cu create mode 100644 icicle/src/fields/extern_extension.cu create mode 100644 icicle/src/hash/CMakeLists.txt rename icicle/{appUtils => src/hash}/keccak/Makefile (100%) rename icicle/{appUtils => src/hash}/keccak/keccak.cu (98%) rename icicle/{appUtils => src/hash}/keccak/test.cu (91%) create mode 100755 icicle/src/hash/keccak/test_keccak create mode 100644 icicle/src/msm/Makefile create mode 100644 icicle/src/msm/extern.cu create mode 100644 icicle/src/msm/extern_g2.cu rename icicle/{appUtils => src}/msm/msm.cu (90%) rename icicle/{appUtils => src}/msm/tests/msm_test.cu (91%) rename icicle/{appUtils => src}/ntt/Makefile (52%) create mode 100644 icicle/src/ntt/extern.cu create mode 100644 icicle/src/ntt/extern_ecntt.cu create mode 100644 icicle/src/ntt/extern_extension.cu rename icicle/{appUtils => src}/ntt/kernel_ntt.cu (96%) rename icicle/{appUtils => src}/ntt/ntt.cu (89%) rename icicle/{appUtils => src}/ntt/tests/verification.cu (90%) rename icicle/{appUtils => src}/ntt/thread_ntt.cu (99%) create mode 100644 icicle/src/polynomials/cuda_backend/kernels.cuh create mode 100644 icicle/src/polynomials/cuda_backend/polynomial_cuda_backend.cu create mode 100644 icicle/src/polynomials/polynomials.cu create mode 100644 icicle/src/polynomials/polynomials_c_api.cu rename icicle/{appUtils => src}/poseidon/.gitignore (100%) rename icicle/{appUtils => src}/poseidon/Makefile (51%) rename icicle/{appUtils => src}/poseidon/constants.cu (78%) rename icicle/{appUtils => src}/poseidon/kernels.cu (98%) rename icicle/{appUtils => src}/poseidon/poseidon.cu (85%) rename icicle/{appUtils => src}/poseidon/test.cu (99%) rename icicle/{appUtils => src/poseidon}/tree/.gitignore (100%) create mode 100644 icicle/src/poseidon/tree/Makefile rename icicle/{appUtils => src/poseidon}/tree/merkle.cu (93%) rename icicle/{appUtils => src/poseidon}/tree/test.cu (99%) create mode 100644 icicle/src/vec_ops/extern.cu create mode 100644 icicle/src/vec_ops/extern_extension.cu create mode 100644 icicle/src/vec_ops/vec_ops.cu create mode 100644 icicle/tests/CMakeLists.txt create mode 100644 icicle/tests/curve_test.cu create mode 100644 icicle/tests/field_test.cu create mode 100644 icicle/tests/polynomial_test.cu delete mode 100644 icicle/tests/primitives_test.cu delete mode 100644 icicle/utils/device_context.cu delete mode 100644 icicle/utils/mont.cu delete mode 100644 icicle/utils/vec_ops.cu create mode 100755 scripts/gen_c_api.py delete mode 100644 wrappers/golang/core/internal/curve_test.go delete mode 100644 wrappers/golang/core/internal/field_test.go rename wrappers/golang/core/internal/{curve.go => mock_curve.go} (92%) create mode 100644 wrappers/golang/core/internal/mock_field.go delete mode 100644 wrappers/golang/curves/bls12377/bls12_377.go create mode 100644 wrappers/golang/curves/bls12377/ecntt/ecntt.go create mode 100644 wrappers/golang/curves/bls12377/ecntt/include/ecntt.h rename wrappers/golang/curves/{bls12381/g2_curve.go => bls12377/g2/curve.go} (88%) rename wrappers/golang/curves/{bn254/g2_base_field.go => bls12377/g2/g2base_field.go} (86%) create mode 100644 wrappers/golang/curves/bls12377/g2/include/curve.h create mode 100644 wrappers/golang/curves/bls12377/g2/include/msm.h create mode 100644 wrappers/golang/curves/bls12377/g2/include/scalar_field.h create mode 100644 wrappers/golang/curves/bls12377/g2/msm.go delete mode 100644 wrappers/golang/curves/bls12377/g2_msm.go delete mode 100644 wrappers/golang/curves/bls12377/helpers_test.go delete mode 100644 wrappers/golang/curves/bls12377/include/g2_curve.h delete mode 100644 wrappers/golang/curves/bls12377/include/g2_msm.h delete mode 100644 wrappers/golang/curves/bls12377/include/msm.h delete mode 100644 wrappers/golang/curves/bls12377/include/ntt.h create mode 100644 wrappers/golang/curves/bls12377/main.go delete mode 100644 wrappers/golang/curves/bls12377/msm.go create mode 100644 wrappers/golang/curves/bls12377/msm/include/msm.h create mode 100644 wrappers/golang/curves/bls12377/msm/msm.go delete mode 100644 wrappers/golang/curves/bls12377/ntt.go create mode 100644 wrappers/golang/curves/bls12377/ntt/include/ntt.h create mode 100644 wrappers/golang/curves/bls12377/ntt/ntt.go create mode 100644 wrappers/golang/curves/bls12377/polynomial/include/polynomial.h create mode 100644 wrappers/golang/curves/bls12377/polynomial/polynomial.go rename wrappers/golang/curves/{bls12381 => bls12377/tests}/base_field_test.go (58%) rename wrappers/golang/curves/{bn254 => bls12377/tests}/curve_test.go (50%) create mode 100644 wrappers/golang/curves/bls12377/tests/ecntt_test.go rename wrappers/golang/curves/{bw6761 => bls12377/tests}/g2_curve_test.go (51%) rename wrappers/golang/curves/{bn254/g2_base_field_test.go => bls12377/tests/g2_g2base_field_test.go} (57%) rename wrappers/golang/curves/bls12377/{ => tests}/g2_msm_test.go (60%) create mode 100644 wrappers/golang/curves/bls12377/tests/main_test.go rename wrappers/golang/curves/bls12377/{ => tests}/msm_test.go (58%) rename wrappers/golang/curves/bls12377/{ => tests}/ntt_test.go (71%) create mode 100644 wrappers/golang/curves/bls12377/tests/polynomial_test.go create mode 100644 wrappers/golang/curves/bls12377/tests/scalar_field_test.go create mode 100644 wrappers/golang/curves/bls12377/tests/vec_ops_test.go rename wrappers/golang/curves/bls12377/{ => vecOps}/include/vec_ops.h (51%) create mode 100644 wrappers/golang/curves/bls12377/vecOps/vec_ops.go delete mode 100644 wrappers/golang/curves/bls12377/vec_ops.go delete mode 100644 wrappers/golang/curves/bls12377/vec_ops_test.go delete mode 100644 wrappers/golang/curves/bls12381/bls12_381.go create mode 100644 wrappers/golang/curves/bls12381/ecntt/ecntt.go create mode 100644 wrappers/golang/curves/bls12381/ecntt/include/ecntt.h rename wrappers/golang/curves/{bn254/g2_curve.go => bls12381/g2/curve.go} (87%) rename wrappers/golang/curves/{bw6761/g2_base_field.go => bls12381/g2/g2base_field.go} (86%) create mode 100644 wrappers/golang/curves/bls12381/g2/include/curve.h create mode 100644 wrappers/golang/curves/bls12381/g2/include/msm.h create mode 100644 wrappers/golang/curves/bls12381/g2/include/scalar_field.h create mode 100644 wrappers/golang/curves/bls12381/g2/msm.go delete mode 100644 wrappers/golang/curves/bls12381/g2_msm.go delete mode 100644 wrappers/golang/curves/bls12381/helpers_test.go delete mode 100644 wrappers/golang/curves/bls12381/include/g2_curve.h delete mode 100644 wrappers/golang/curves/bls12381/include/g2_msm.h delete mode 100644 wrappers/golang/curves/bls12381/include/msm.h delete mode 100644 wrappers/golang/curves/bls12381/include/ntt.h create mode 100644 wrappers/golang/curves/bls12381/main.go delete mode 100644 wrappers/golang/curves/bls12381/msm.go create mode 100644 wrappers/golang/curves/bls12381/msm/include/msm.h create mode 100644 wrappers/golang/curves/bls12381/msm/msm.go delete mode 100644 wrappers/golang/curves/bls12381/ntt.go create mode 100644 wrappers/golang/curves/bls12381/ntt/include/ntt.h create mode 100644 wrappers/golang/curves/bls12381/ntt/ntt.go create mode 100644 wrappers/golang/curves/bls12381/polynomial/include/polynomial.h create mode 100644 wrappers/golang/curves/bls12381/polynomial/polynomial.go create mode 100644 wrappers/golang/curves/bls12381/tests/base_field_test.go create mode 100644 wrappers/golang/curves/bls12381/tests/curve_test.go create mode 100644 wrappers/golang/curves/bls12381/tests/ecntt_test.go rename wrappers/golang/curves/{bls12377 => bls12381/tests}/g2_curve_test.go (51%) rename wrappers/golang/curves/{bw6761/g2_base_field_test.go => bls12381/tests/g2_g2base_field_test.go} (57%) rename wrappers/golang/curves/bls12381/{ => tests}/g2_msm_test.go (60%) create mode 100644 wrappers/golang/curves/bls12381/tests/main_test.go rename wrappers/golang/curves/bls12381/{ => tests}/msm_test.go (58%) rename wrappers/golang/curves/bls12381/{ => tests}/ntt_test.go (71%) create mode 100644 wrappers/golang/curves/bls12381/tests/polynomial_test.go create mode 100644 wrappers/golang/curves/bls12381/tests/scalar_field_test.go create mode 100644 wrappers/golang/curves/bls12381/tests/vec_ops_test.go rename wrappers/golang/curves/bls12381/{ => vecOps}/include/vec_ops.h (51%) create mode 100644 wrappers/golang/curves/bls12381/vecOps/vec_ops.go delete mode 100644 wrappers/golang/curves/bls12381/vec_ops.go delete mode 100644 wrappers/golang/curves/bls12381/vec_ops_test.go delete mode 100644 wrappers/golang/curves/bn254/bn254.go create mode 100644 wrappers/golang/curves/bn254/ecntt/ecntt.go create mode 100644 wrappers/golang/curves/bn254/ecntt/include/ecntt.h rename wrappers/golang/curves/{bls12377/g2_curve.go => bn254/g2/curve.go} (88%) rename wrappers/golang/curves/{bls12377/g2_base_field.go => bn254/g2/g2base_field.go} (86%) create mode 100644 wrappers/golang/curves/bn254/g2/include/curve.h create mode 100644 wrappers/golang/curves/bn254/g2/include/msm.h create mode 100644 wrappers/golang/curves/bn254/g2/include/scalar_field.h create mode 100644 wrappers/golang/curves/bn254/g2/msm.go delete mode 100644 wrappers/golang/curves/bn254/g2_msm.go delete mode 100644 wrappers/golang/curves/bn254/helpers_test.go delete mode 100644 wrappers/golang/curves/bn254/include/g2_curve.h delete mode 100644 wrappers/golang/curves/bn254/include/g2_msm.h delete mode 100644 wrappers/golang/curves/bn254/include/msm.h delete mode 100644 wrappers/golang/curves/bn254/include/ntt.h create mode 100644 wrappers/golang/curves/bn254/main.go delete mode 100644 wrappers/golang/curves/bn254/msm.go create mode 100644 wrappers/golang/curves/bn254/msm/include/msm.h create mode 100644 wrappers/golang/curves/bn254/msm/msm.go delete mode 100644 wrappers/golang/curves/bn254/ntt.go create mode 100644 wrappers/golang/curves/bn254/ntt/include/ntt.h create mode 100644 wrappers/golang/curves/bn254/ntt/ntt.go create mode 100644 wrappers/golang/curves/bn254/polynomial/include/polynomial.h create mode 100644 wrappers/golang/curves/bn254/polynomial/polynomial.go rename wrappers/golang/curves/{bls12377 => bn254/tests}/base_field_test.go (59%) rename wrappers/golang/curves/{bls12377 => bn254/tests}/curve_test.go (51%) create mode 100644 wrappers/golang/curves/bn254/tests/ecntt_test.go rename wrappers/golang/curves/{bls12381 => bn254/tests}/g2_curve_test.go (51%) rename wrappers/golang/curves/{bls12381/g2_base_field_test.go => bn254/tests/g2_g2base_field_test.go} (57%) rename wrappers/golang/curves/bn254/{ => tests}/g2_msm_test.go (60%) create mode 100644 wrappers/golang/curves/bn254/tests/main_test.go rename wrappers/golang/curves/bn254/{ => tests}/msm_test.go (59%) rename wrappers/golang/curves/bn254/{ => tests}/ntt_test.go (71%) create mode 100644 wrappers/golang/curves/bn254/tests/polynomial_test.go rename wrappers/golang/curves/bn254/{ => tests}/scalar_field_test.go (59%) create mode 100644 wrappers/golang/curves/bn254/tests/vec_ops_test.go rename wrappers/golang/curves/bn254/{ => vecOps}/include/vec_ops.h (51%) create mode 100644 wrappers/golang/curves/bn254/vecOps/vec_ops.go delete mode 100644 wrappers/golang/curves/bn254/vec_ops.go delete mode 100644 wrappers/golang/curves/bn254/vec_ops_test.go delete mode 100644 wrappers/golang/curves/bw6761/bw6_761.go create mode 100644 wrappers/golang/curves/bw6761/ecntt/ecntt.go create mode 100644 wrappers/golang/curves/bw6761/ecntt/include/ecntt.h rename wrappers/golang/curves/bw6761/{g2_curve.go => g2/curve.go} (88%) rename wrappers/golang/curves/{bls12381/g2_base_field.go => bw6761/g2/g2base_field.go} (86%) create mode 100644 wrappers/golang/curves/bw6761/g2/include/curve.h create mode 100644 wrappers/golang/curves/bw6761/g2/include/msm.h create mode 100644 wrappers/golang/curves/bw6761/g2/include/scalar_field.h create mode 100644 wrappers/golang/curves/bw6761/g2/msm.go delete mode 100644 wrappers/golang/curves/bw6761/g2_msm.go delete mode 100644 wrappers/golang/curves/bw6761/helpers_test.go delete mode 100644 wrappers/golang/curves/bw6761/include/g2_curve.h delete mode 100644 wrappers/golang/curves/bw6761/include/g2_msm.h delete mode 100644 wrappers/golang/curves/bw6761/include/msm.h delete mode 100644 wrappers/golang/curves/bw6761/include/ntt.h create mode 100644 wrappers/golang/curves/bw6761/main.go delete mode 100644 wrappers/golang/curves/bw6761/msm.go create mode 100644 wrappers/golang/curves/bw6761/msm/include/msm.h create mode 100644 wrappers/golang/curves/bw6761/msm/msm.go delete mode 100644 wrappers/golang/curves/bw6761/ntt.go create mode 100644 wrappers/golang/curves/bw6761/ntt/include/ntt.h create mode 100644 wrappers/golang/curves/bw6761/ntt/ntt.go create mode 100644 wrappers/golang/curves/bw6761/polynomial/include/polynomial.h create mode 100644 wrappers/golang/curves/bw6761/polynomial/polynomial.go rename wrappers/golang/curves/bw6761/{ => tests}/base_field_test.go (59%) rename wrappers/golang/curves/{bls12381 => bw6761/tests}/curve_test.go (50%) create mode 100644 wrappers/golang/curves/bw6761/tests/ecntt_test.go rename wrappers/golang/curves/{bn254 => bw6761/tests}/g2_curve_test.go (51%) rename wrappers/golang/curves/{bls12377/g2_base_field_test.go => bw6761/tests/g2_g2base_field_test.go} (57%) rename wrappers/golang/curves/bw6761/{ => tests}/g2_msm_test.go (60%) create mode 100644 wrappers/golang/curves/bw6761/tests/main_test.go rename wrappers/golang/curves/bw6761/{ => tests}/msm_test.go (59%) rename wrappers/golang/curves/bw6761/{ => tests}/ntt_test.go (71%) create mode 100644 wrappers/golang/curves/bw6761/tests/polynomial_test.go rename wrappers/golang/curves/bw6761/{ => tests}/scalar_field_test.go (59%) create mode 100644 wrappers/golang/curves/bw6761/tests/vec_ops_test.go rename wrappers/golang/curves/bw6761/{ => vecOps}/include/vec_ops.h (51%) create mode 100644 wrappers/golang/curves/bw6761/vecOps/vec_ops.go delete mode 100644 wrappers/golang/curves/bw6761/vec_ops.go delete mode 100644 wrappers/golang/curves/bw6761/vec_ops_test.go rename wrappers/golang/{core/internal/field.go => curves/grumpkin/base_field.go} (62%) create mode 100644 wrappers/golang/curves/grumpkin/curve.go create mode 100644 wrappers/golang/curves/grumpkin/include/curve.h create mode 100644 wrappers/golang/curves/grumpkin/include/scalar_field.h create mode 100644 wrappers/golang/curves/grumpkin/main.go create mode 100644 wrappers/golang/curves/grumpkin/msm/include/msm.h create mode 100644 wrappers/golang/curves/grumpkin/msm/msm.go create mode 100644 wrappers/golang/curves/grumpkin/scalar_field.go rename wrappers/golang/curves/{bn254 => grumpkin/tests}/base_field_test.go (59%) rename wrappers/golang/curves/{bw6761 => grumpkin/tests}/curve_test.go (50%) create mode 100644 wrappers/golang/curves/grumpkin/tests/main_test.go create mode 100644 wrappers/golang/curves/grumpkin/tests/msm_test.go rename wrappers/golang/curves/{bls12377 => grumpkin/tests}/scalar_field_test.go (58%) create mode 100644 wrappers/golang/curves/grumpkin/tests/vec_ops_test.go create mode 100644 wrappers/golang/curves/grumpkin/vecOps/include/vec_ops.h create mode 100644 wrappers/golang/curves/grumpkin/vecOps/vec_ops.go delete mode 100644 wrappers/golang/curves/include/types.h create mode 100644 wrappers/golang/fields/babybear/extension/extension_field.go create mode 100644 wrappers/golang/fields/babybear/extension/include/scalar_field.h create mode 100644 wrappers/golang/fields/babybear/extension/main.go create mode 100644 wrappers/golang/fields/babybear/extension/ntt/include/ntt.h create mode 100644 wrappers/golang/fields/babybear/extension/ntt/ntt.go create mode 100644 wrappers/golang/fields/babybear/extension/vecOps/include/vec_ops.h create mode 100644 wrappers/golang/fields/babybear/extension/vecOps/vec_ops.go create mode 100644 wrappers/golang/fields/babybear/include/scalar_field.h create mode 100644 wrappers/golang/fields/babybear/main.go create mode 100644 wrappers/golang/fields/babybear/ntt/include/ntt.h create mode 100644 wrappers/golang/fields/babybear/ntt/ntt.go create mode 100644 wrappers/golang/fields/babybear/polynomial/include/polynomial.h create mode 100644 wrappers/golang/fields/babybear/polynomial/polynomial.go create mode 100644 wrappers/golang/fields/babybear/scalar_field.go create mode 100644 wrappers/golang/fields/babybear/tests/extension_field_test.go create mode 100644 wrappers/golang/fields/babybear/tests/extension_vec_ops_test.go create mode 100644 wrappers/golang/fields/babybear/tests/main_test.go create mode 100644 wrappers/golang/fields/babybear/tests/ntt_no_domain_test.go create mode 100644 wrappers/golang/fields/babybear/tests/ntt_test.go create mode 100644 wrappers/golang/fields/babybear/tests/polynomial_test.go rename wrappers/golang/{curves/bls12381 => fields/babybear/tests}/scalar_field_test.go (58%) create mode 100644 wrappers/golang/fields/babybear/tests/vec_ops_test.go create mode 100644 wrappers/golang/fields/babybear/vecOps/include/vec_ops.h create mode 100644 wrappers/golang/fields/babybear/vecOps/vec_ops.go create mode 100644 wrappers/golang/internal/generator/config/babybear.go create mode 100644 wrappers/golang/internal/generator/config/bls12377.go create mode 100644 wrappers/golang/internal/generator/config/bls12381.go create mode 100644 wrappers/golang/internal/generator/config/bn254.go create mode 100644 wrappers/golang/internal/generator/config/bw6761.go create mode 100644 wrappers/golang/internal/generator/config/config.go create mode 100644 wrappers/golang/internal/generator/config/grumpkin.go create mode 100644 wrappers/golang/internal/generator/curves/generate.go create mode 100644 wrappers/golang/internal/generator/curves/templates/curve.go.tmpl create mode 100644 wrappers/golang/internal/generator/curves/templates/curve.h.tmpl create mode 100644 wrappers/golang/internal/generator/curves/templates/curve_test.go.tmpl create mode 100644 wrappers/golang/internal/generator/curves/templates/main.go.tmpl create mode 100644 wrappers/golang/internal/generator/ecntt/generate.go create mode 100644 wrappers/golang/internal/generator/ecntt/templates/ecntt.go.tmpl create mode 100644 wrappers/golang/internal/generator/ecntt/templates/ecntt.h.tmpl create mode 100644 wrappers/golang/internal/generator/ecntt/templates/ecntt_test.go.tmpl create mode 100644 wrappers/golang/internal/generator/fields/generate.go create mode 100644 wrappers/golang/internal/generator/fields/templates/field.go.tmpl create mode 100644 wrappers/golang/internal/generator/fields/templates/field_test.go.tmpl create mode 100644 wrappers/golang/internal/generator/fields/templates/main.go.tmpl create mode 100644 wrappers/golang/internal/generator/fields/templates/scalar_field.h.tmpl create mode 100644 wrappers/golang/internal/generator/generator_utils/generate.go create mode 100644 wrappers/golang/internal/generator/lib_linker/generate.go create mode 100644 wrappers/golang/internal/generator/mock/generate.go create mode 100644 wrappers/golang/internal/generator/msm/generate.go create mode 100644 wrappers/golang/internal/generator/msm/templates/msm.go.tmpl create mode 100644 wrappers/golang/internal/generator/msm/templates/msm.h.tmpl create mode 100644 wrappers/golang/internal/generator/msm/templates/msm_test.go.tmpl create mode 100644 wrappers/golang/internal/generator/ntt/generate.go create mode 100644 wrappers/golang/internal/generator/ntt/templates/ntt.go.tmpl create mode 100644 wrappers/golang/internal/generator/ntt/templates/ntt.h.tmpl create mode 100644 wrappers/golang/internal/generator/ntt/templates/ntt_no_domain_test.go.tmpl rename wrappers/golang/internal/generator/{ => ntt}/templates/ntt_test.go.tmpl (66%) create mode 100644 wrappers/golang/internal/generator/polynomial/generate.go create mode 100644 wrappers/golang/internal/generator/polynomial/templates/polynomial.go.tmpl create mode 100644 wrappers/golang/internal/generator/polynomial/templates/polynomial.h.tmpl create mode 100644 wrappers/golang/internal/generator/polynomial/templates/polynomial_test.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/curve.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/curve_test.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/field.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/field_test.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/helpers_test.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/include/curve.h.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/include/g2_curve.h.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/include/g2_msm.h.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/include/msm.h.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/include/ntt.h.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/include/scalar_field.h.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/include/vec_ops.h.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/main.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/msm.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/msm_test.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/ntt.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/scalar_field.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/scalar_field_test.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/vec_ops.go.tmpl delete mode 100644 wrappers/golang/internal/generator/templates/vec_ops_test.go.tmpl create mode 100644 wrappers/golang/internal/generator/tests/generate.go create mode 100644 wrappers/golang/internal/generator/tests/templates/main_test.go.tmpl create mode 100644 wrappers/golang/internal/generator/vecOps/generate.go create mode 100644 wrappers/golang/internal/generator/vecOps/templates/vec_ops.go.tmpl create mode 100644 wrappers/golang/internal/generator/vecOps/templates/vec_ops.h.tmpl create mode 100644 wrappers/golang/internal/generator/vecOps/templates/vec_ops_test.go.tmpl rename wrappers/golang/{core/internal/helpers_test.go => test_helpers/helpers.go} (70%) create mode 100644 wrappers/rust/icicle-core/src/ecntt/mod.rs create mode 100644 wrappers/rust/icicle-core/src/ecntt/tests.rs create mode 100644 wrappers/rust/icicle-core/src/polynomials/mod.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bls12-377/benches/ecntt.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bls12-377/src/ecntt/mod.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bls12-377/src/polynomials/mod.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bls12-381/benches/ecntt.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bls12-381/src/ecntt/mod.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bls12-381/src/polynomials/mod.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bn254/benches/ecntt.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bn254/src/ecntt/mod.rs create mode 100644 wrappers/rust/icicle-curves/icicle-bn254/src/polynomials/mod.rs delete mode 100644 wrappers/rust/icicle-curves/icicle-curve-template/Cargo.toml delete mode 100644 wrappers/rust/icicle-curves/icicle-curve-template/build.rs delete mode 100644 wrappers/rust/icicle-curves/icicle-curve-template/src/curve.rs delete mode 100644 wrappers/rust/icicle-curves/icicle-curve-template/src/lib.rs delete mode 100644 wrappers/rust/icicle-curves/icicle-curve-template/src/msm/mod.rs delete mode 100644 wrappers/rust/icicle-curves/icicle-curve-template/src/ntt/mod.rs delete mode 100644 wrappers/rust/icicle-curves/icicle-curve-template/src/poseidon/mod.rs delete mode 100644 wrappers/rust/icicle-curves/icicle-curve-template/src/tree/mod.rs create mode 100644 wrappers/rust/icicle-fields/icicle-babybear/Cargo.toml create mode 100644 wrappers/rust/icicle-fields/icicle-babybear/build.rs create mode 100644 wrappers/rust/icicle-fields/icicle-babybear/src/field.rs create mode 100644 wrappers/rust/icicle-fields/icicle-babybear/src/lib.rs create mode 100644 wrappers/rust/icicle-fields/icicle-babybear/src/ntt/mod.rs create mode 100644 wrappers/rust/icicle-fields/icicle-babybear/src/polynomials/mod.rs rename wrappers/rust/{icicle-curves/icicle-curve-template => icicle-fields/icicle-babybear}/src/vec_ops/mod.rs (55%) create mode 100644 wrappers/rust/icicle-hash/Cargo.toml create mode 100644 wrappers/rust/icicle-hash/build.rs create mode 100644 wrappers/rust/icicle-hash/src/keccak/mod.rs create mode 100644 wrappers/rust/icicle-hash/src/keccak/tests.rs create mode 100644 wrappers/rust/icicle-hash/src/lib.rs diff --git a/.codespellignore b/.codespellignore index acadd68e..5ae69ef4 100644 --- a/.codespellignore +++ b/.codespellignore @@ -3,3 +3,4 @@ crate lmit mut uint +dout \ No newline at end of file diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index e85cc922..ff9738e1 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -4,7 +4,7 @@ on: pull_request: branches: - main - - dev + - V2 jobs: spelling-checker: diff --git a/.github/workflows/cpp_cuda.yml b/.github/workflows/cpp_cuda.yml index 73a85206..de99ff74 100644 --- a/.github/workflows/cpp_cuda.yml +++ b/.github/workflows/cpp_cuda.yml @@ -4,11 +4,11 @@ on: pull_request: branches: - main - - dev + - V2 push: branches: - main - - dev + - V2 concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -29,7 +29,7 @@ jobs: if: needs.check-changed-files.outputs.cpp_cuda == 'true' run: if [[ $(find ./ \( -path ./icicle/build -prune -o -path ./**/target -prune -o -path ./examples -prune \) -iname *.h -or -iname *.cuh -or -iname *.cu -or -iname *.c -or -iname *.cpp | xargs clang-format --dry-run -ferror-limit=1 -style=file 2>&1) ]]; then echo "Please run clang-format"; exit 1; fi - test-linux: + test-linux-curve: name: Test on Linux runs-on: [self-hosted, Linux, X64, icicle] needs: [check-changed-files, check-format] @@ -39,14 +39,36 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@v4 - - name: Build + - name: Build curve working-directory: ./icicle if: needs.check-changed-files.outputs.cpp_cuda == 'true' run: | - mkdir -p build - cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release -DCURVE=${{ matrix.curve }} -DG2_DEFINED=ON -S . -B build - cmake --build build - - name: Run C++ Tests - working-directory: ./icicle/build + mkdir -p build && rm -rf build/* + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DCURVE=${{ matrix.curve }} -DG2=ON -S . -B build + cmake --build build -j + - name: Run C++ curve Tests + working-directory: ./icicle/build/tests if: needs.check-changed-files.outputs.cpp_cuda == 'true' run: ctest + + test-linux-field: + name: Test on Linux + runs-on: [self-hosted, Linux, X64, icicle] + needs: [check-changed-files, check-format] + strategy: + matrix: + field: [babybear] + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + - name: Build field + working-directory: ./icicle + if: needs.check-changed-files.outputs.cpp_cuda == 'true' + run: | + mkdir -p build && rm -rf build/* + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DFIELD=${{ matrix.field }} -DEXT_FIELD=ON -S . -B build + cmake --build build -j + - name: Run C++ field Tests + working-directory: ./icicle/build/tests + if: needs.check-changed-files.outputs.cpp_cuda == 'true' + run: ctest \ No newline at end of file diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index c3776096..594b2ad3 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -11,11 +11,11 @@ on: pull_request: branches: - main - - dev + - V2 push: branches: - main - - dev + - V2 concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/golang.yml b/.github/workflows/golang.yml index 68bd5deb..1a89d12d 100644 --- a/.github/workflows/golang.yml +++ b/.github/workflows/golang.yml @@ -4,11 +4,11 @@ on: pull_request: branches: - main - - dev + - V2 push: branches: - main - - dev + - V2 concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -33,13 +33,23 @@ jobs: if: needs.check-changed-files.outputs.golang == 'true' run: if [[ $(go list ./... | xargs go fmt) ]]; then echo "Please run go fmt"; exit 1; fi - build-linux: - name: Build on Linux + build-curves-linux: + name: Build curves on Linux runs-on: [self-hosted, Linux, X64, icicle] needs: [check-changed-files, check-format] strategy: matrix: - curve: [bn254, bls12_381, bls12_377, bw6_761] + curve: + - name: bn254 + build_args: -g2 -ecntt + - name: bls12_381 + build_args: -g2 -ecntt + - name: bls12_377 + build_args: -g2 -ecntt + - name: bw6_761 + build_args: -g2 -ecntt + - name: grumpkin + build_args: steps: - name: Checkout Repo uses: actions/checkout@v4 @@ -50,19 +60,50 @@ jobs: - name: Build working-directory: ./wrappers/golang if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' - run: ./build.sh ${{ matrix.curve }} ON ON # builds a single curve with G2 and ECNTT enabled + run: ./build.sh -curve=${{ matrix.curve.name }} ${{ matrix.curve.build_args }} # builds a single curve with G2 and ECNTT enabled - name: Upload ICICLE lib artifacts uses: actions/upload-artifact@v4 if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' with: - name: icicle-builds-${{ matrix.curve }}-${{ github.workflow }}-${{ github.sha }} - path: icicle/build/libingo_${{ matrix.curve }}.a + name: icicle-builds-${{ matrix.curve.name }}-${{ github.workflow }}-${{ github.sha }} + path: | + icicle/build/lib/libingo_curve_${{ matrix.curve.name }}.a + icicle/build/lib/libingo_field_${{ matrix.curve.name }}.a + retention-days: 1 + + build-fields-linux: + name: Build fields on Linux + runs-on: [self-hosted, Linux, X64, icicle] + needs: [check-changed-files, check-format] + strategy: + matrix: + field: + - name: babybear + build_args: -field-ext + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.20.0' + - name: Build + working-directory: ./wrappers/golang + if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + run: ./build.sh -field=${{ matrix.field.name }} ${{ matrix.field.build_args }} # builds a single field with field-ext enabled + - name: Upload ICICLE lib artifacts + uses: actions/upload-artifact@v4 + if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + with: + name: icicle-builds-${{ matrix.field.name }}-${{ github.workflow }}-${{ github.sha }} + path: | + icicle/build/lib/libingo_field_${{ matrix.field.name }}.a retention-days: 1 test-linux: name: Test on Linux runs-on: [self-hosted, Linux, X64, icicle] - needs: [check-changed-files, build-linux] + needs: [check-changed-files, build-curves-linux, build-fields-linux] steps: - name: Checkout Repo uses: actions/checkout@v4 @@ -74,7 +115,7 @@ jobs: uses: actions/download-artifact@v4 if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' with: - path: ./icicle/build/ + path: ./icicle/build/lib merge-multiple: true - name: Run Tests working-directory: ./wrappers/golang @@ -83,7 +124,7 @@ jobs: # -p controls the number of programs that can be run in parallel run: | export CPATH=$CPATH:/usr/local/cuda/include - go test --tags=g2 ./... -count=1 -failfast -p 2 -timeout 60m + go test ./... -count=1 -failfast -p 2 -timeout 60m # TODO: bw6 on windows requires more memory than the standard runner has # Add a large runner and then enable this job diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fc54e407..0d842a74 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -4,11 +4,11 @@ on: pull_request: branches: - main - - dev + - V2 push: branches: - main - - dev + - V2 concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -60,7 +60,17 @@ jobs: if: needs.check-changed-files.outputs.rust == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' # Running tests from the root workspace will run all workspace members' tests by default # We need to limit the number of threads to avoid running out of memory on weaker machines - run: cargo test --release --verbose --features=g2 -- --test-threads=2 + # ignored tests are polynomial tests. Since they conflict with NTT tests, they are executed sperately + run: | + cargo test --workspace --exclude icicle-babybear --release --verbose --features=g2 -- --test-threads=2 --ignored + cargo test --workspace --exclude icicle-babybear --release --verbose --features=g2 -- --test-threads=2 + + - name: Run baby bear tests + working-directory: ./wrappers/rust/icicle-fields/icicle-babybear + if: needs.check-changed-files.outputs.rust == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + run: | + cargo test --release --verbose -- --ignored + cargo test --release --verbose build-windows: name: Build on Windows diff --git a/.gitignore b/.gitignore index 561bd946..29fb6b74 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,6 @@ **/Cargo.lock **/icicle/build/ **/wrappers/rust/icicle-cuda-runtime/src/bindings.rs -**/build +**/build* **/icicle/appUtils/large_ntt/work icicle/appUtils/large_ntt/work/test_ntt diff --git a/docs/docs/icicle/core.md b/docs/docs/icicle/core.md new file mode 100644 index 00000000..4965f311 --- /dev/null +++ b/docs/docs/icicle/core.md @@ -0,0 +1,181 @@ +# ICICLE Core + +ICICLE Core is a library written in C++/CUDA. All the ICICLE primitives are implemented within ICICLE Core. + +The Core is split into logical modules that can be compiled into static libraries using different [strategies](#compilation-strategies). You can then [link](#linking) these libraries with your C++ project or write your own [bindings](#writing-new-bindings-for-icicle) for other programming languages. If you want to use ICICLE with existing bindings please refer to [Rust](/icicle/rust-bindings) / [Golang](/icicle/golang-bindings). + +## Compilation strategies + +Most of the codebase is curve/field agnostic, which means it can be compiled for different curves and fields. When you build ICICLE Core you choose a single curve or field. If you need multiple curves or fields - you just compile ICICLE into multiple static libraries. It's that simple. Currently, the following choices are supported: + + - [Field mode](#compiling-for-a-field) - used for STARK fields like BabyBear / Mersenne / Goldilocks. Includes field arithmetic, NTT, Poseidon, Extension fields and other primitives. + - [Curve mode](#compiling-for-a-curve) - used for SNARK curves like BN254/ BLS curves / Grumpkin / etc. Curve mode is built upon field mode, so it includes everything that field does. It also includes curve operations / MSM / ECNTT / G2 and other curve-related primitives. + +:::info + +If you only want to use curve's scalar/base field, you still need to go with a curve mode. You can disable MSM with [options](#compilation-options) + +::: + +### Compiling for a field + +ICICLE supports the following STARK fields: + - [BabyBear](https://eprint.iacr.org/2023/824.pdf) + +Field mode includes: + - [Field arithmetic](https://github.com/ingonyama-zk/icicle/blob/main/icicle/include/fields/field.cuh) - field multiplication, addition, subtraction + - [NTT](icicle/primitives/ntt) - FFT / iFFT + - [Poseidon Hash](icicle/primitives/poseidon) + - [Vector operations](https://github.com/ingonyama-zk/icicle/blob/main/icicle/include/vec_ops/vec_ops.cuh) + - [Polynomial](#) - structs and methods to work with polynomials + +You can compile ICICLE for a STARK field using this command: + +```sh +cd icicle +mkdir -p build +cmake -DFIELD= -S . -B build +cmake --build build -j +``` + +Icicle Supports the following `` FIELDS: +- `babybear` + +This command will output `libingo_field_.a` into `build/lib`. + +### Compiling for a curve + +ICICLE supports the following SNARK curves: + - [BN254](https://neuromancer.sk/std/bn/bn254) + - [BLS12-377](https://neuromancer.sk/std/bls/BLS12-377) + - [BLS12-381](https://neuromancer.sk/std/bls/BLS12-381) + - [BW6-761](https://eprint.iacr.org/2020/351) + - Grumpkin + +Curve mode includes everything you can find in field mode with addition of: + - [MSM](icicle/primitives/msm) - MSM / Batched MSM + - [ECNTT](#) + +:::note + +Field related primitives will be compiled for the scalar field of the curve + +::: + +You can compile ICICLE for a SNARK curve using this command: + +```sh +cd icicle +mkdir -p build +cmake -DCURVE= -S . -B build +cmake --build build -j +``` + +Where `` can be one of `bn254`/`bls12_377`/`bls12_381`/`bw6_761`/`grumpkin`. + +This command will output both `libingo_curve_.a` and `libingo_field_.a` into `build/lib`. + +### Compilation options + +There exist multiple options that allow you to customize your build or enable additional functionality. + +#### EXT_FIELD + +Used only in a [field mode](#compiling-for-a-field) to add Extension field into a build. Adds NTT for the extension field. + +Default: `OFF` + +Usage: `-DEXT_FIELD=ON` + +#### G2 + +Used only in a [curve mode](#compiling-for-a-curve) to add G2 definitions into a build. Also adds G2 MSM. + +Default: `OFF` + +Usage: `-DG2=ON` + +#### ECNTT + +Used only in a [curve mode](#compiling-for-a-curve) to add ECNTT function into a build. + +Default: `OFF` + +Usage: `-DECNTT=ON` + +#### MSM + +Used only in a [curve mode](#compiling-for-a-curve) to add MSM function into a build. As MSM takes a lot of time to build, you can disable it with this option to reduce compilation time. + +Default: `ON` + +Usage: `-DMSM=OFF` + +#### BUILD_HASH + +Can be used in any mode to build a hash library. Currently it only includes Keccak hash function, but more are coming. + +Default: `OFF` + +Usage: `-DBUILD_HASH=ON` + +#### BUILD_TESTS + +Can be used in any mode to include tests runner binary. + +Default: `OFF` + +USAGE: `-DBUILD_TESTS=ON` + +#### BUILD_BENCHMARKS + +Can be used in any mode to include benchmarks runner binary. + +Default: `OFF` + +USAGE: `-DBUILD_BENCHMARKS=ON` + +#### DEVMODE + +Can be used in any mode to include debug symbols in the build. + +Default: `OFF` + +USAGE: `-DEVMODE=ON` + +## Linking + +To link ICICLE with your project you first need to compile ICICLE with options of your choice. After that you can use CMake `target_link_libraries` to link with the generated static libraries and `target_include_directories` to include ICICLE headers (located in `icicle/include`). + +Refer to our [c++ examples](https://github.com/ingonyama-zk/icicle/tree/main/examples/c%2B%2B) for more info. Take a look at this [CMakeLists.txt](https://github.com/ingonyama-zk/icicle/blob/main/examples/c%2B%2B/msm/CMakeLists.txt#L22) + + +## Writing new bindings for ICICLE + +Since ICICLE Core is written in CUDA / C++ its really simple to generate static libraries. These static libraries can be installed on any system and called by higher level languages such as Golang. + +Static libraries can be loaded into memory once and used by multiple programs, reducing memory usage and potentially improving performance. They also allow you to separate functionality into distinct modules so your static library may need to compile only specific features that you want to use. + +Let's review the [Golang bindings](golang-bindings.md) since its a pretty verbose example (compared to rust which hides it pretty well) of using static libraries. Golang has a library named `CGO` which can be used to link static libraries. Here's a basic example on how you can use cgo to link these libraries: + +```go +/* +#cgo LDFLAGS: -L/path/to/shared/libs -lbn254 -lbls12_381 -lbls12_377 -lbw6_671 +#include "icicle.h" // make sure you use the correct header file(s) +*/ +import "C" + +func main() { + // Now you can call the C functions from the ICICLE libraries. + // Note that C function calls are prefixed with 'C.' in Go code. + + out := (*C.BN254_projective_t)(unsafe.Pointer(p)) + in := (*C.BN254_affine_t)(unsafe.Pointer(affine)) + + C.projective_from_affine_bn254(out, in) +} +``` + +The comments on the first line tell `CGO` which libraries to import as well as which header files to include. You can then call methods which are part of the static library and defined in the header file, `C.projective_from_affine_bn254` is an example. + +If you wish to create your own bindings for a language of your choice we suggest you start by investigating how you can call static libraries. \ No newline at end of file diff --git a/docs/docs/icicle/golang-bindings.md b/docs/docs/icicle/golang-bindings.md index 6b832cdd..b688ac42 100644 --- a/docs/docs/icicle/golang-bindings.md +++ b/docs/docs/icicle/golang-bindings.md @@ -33,28 +33,31 @@ go get github.com/ingonyama-zk/icicle@ To build the shared libraries you can run this script: +```bash +./build.sh [-curve= | -field=] [-cuda_version=] [-g2] [-ecntt] [-devmode] ``` -./build [G2_enabled] +- **`curve`** - The name of the curve to build or "all" to build all curves +- **`field`** - The name of the field to build or "all" to build all fields +- **`g2`** - Optional - build with G2 enabled +- **`ecntt`** - Optional - build with ECNTT enabled +- **`devmode`** - Optional - build in devmode +- Usage can be displayed with the flag `-help` -curve - The name of the curve to build or "all" to build all curves -G2_enabled - Optional - To build with G2 enabled -``` - -For example if you want to build all curves with G2 enabled you would run: +To build ICICLE libraries for all supported curves with G2 and ECNTT enabled. ```bash -./build.sh all ON +./build.sh all -g2 -ecntt ``` -If you are interested in building a specific curve you would run: +If you wish to build for a specific curve, for example bn254, without G2 or ECNTT enabled. -```bash -./build.sh bls12_381 ON +``` bash +./build.sh -curve=bn254 ``` Now you can import ICICLE into your project -```golang +```go import ( "github.com/stretchr/testify/assert" "testing" @@ -85,13 +88,13 @@ go test -count=1 The libraries produced from the CUDA code compilation are used to bind Golang to ICICLE's CUDA code. -1. These libraries (named `libingo_.a`) can be imported in your Go project to leverage the GPU accelerated functionalities provided by ICICLE. +1. These libraries (named `libingo_curve_.a` and `libingo_field_.a`) can be imported in your Go project to leverage the GPU accelerated functionalities provided by ICICLE. 2. In your Go project, you can use `cgo` to link these libraries. Here's a basic example on how you can use `cgo` to link these libraries: ```go /* -#cgo LDFLAGS: -L/path/to/shared/libs -lingo_bn254 +#cgo LDFLAGS: -L/path/to/shared/libs -lingo_curve_bn254 -L$/path/to/shared/libs -lingo_field_bn254 -lstdc++ -lm #include "icicle.h" // make sure you use the correct header file(s) */ import "C" diff --git a/docs/docs/icicle/golang-bindings/ecntt.md b/docs/docs/icicle/golang-bindings/ecntt.md new file mode 100644 index 00000000..78c10f32 --- /dev/null +++ b/docs/docs/icicle/golang-bindings/ecntt.md @@ -0,0 +1,97 @@ +# ECNTT + +### Supported curves + +`bls12-377`, `bls12-381`, `bn254` + +## ECNTT Method + +The `ECNtt[T any]()` function performs the Elliptic Curve Number Theoretic Transform (EC-NTT) on the input points slice, using the provided dir (direction), cfg (configuration), and stores the results in the results slice. + +```go +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError +``` + +### Parameters: + +- **`points`**: A slice of elliptic curve points (in projective coordinates) that will be transformed. The slice can be stored on the host or the device, as indicated by the `core.HostOrDeviceSlice` type. +- **`dir`**: The direction of the EC-NTT transform, either `core.KForward` or `core.KInverse`. +- **`cfg`**: A pointer to an `NTTConfig` object, containing configuration options for the NTT operation. +- **`results`**: A slice that will store the transformed elliptic curve points (in projective coordinates). The slice can be stored on the host or the device, as indicated by the `core.HostOrDeviceSlice` type. + + +### Return Value + +- **`CudaError`**: A `core.IcicleError` value, which will be `core.IcicleErrorCode(0)` if the EC-NTT operation was successful, or an error if something went wrong. + +## NTT Configuration (NTTConfig) + +The `NTTConfig` structure holds configuration parameters for the NTT operation, allowing customization of its behavior to optimize performance based on the specifics of your protocol. + +```go +type NTTConfig[T any] struct { + Ctx cr.DeviceContext + CosetGen T + BatchSize int32 + ColumnsBatch bool + Ordering Ordering + areInputsOnDevice bool + areOutputsOnDevice bool + IsAsync bool + NttAlgorithm NttAlgorithm +} +``` + +### Fields + +- **`Ctx`**: Device context containing details like device ID and stream ID. +- **`CosetGen`**: Coset generator used for coset (i)NTTs, defaulting to no coset being used. +- **`BatchSize`**: The number of NTTs to compute in one operation, defaulting to 1. +- **`ColumnsBatch`**: If true the function will compute the NTTs over the columns of the input matrix and not over the rows. Defaults to `false`. +- **`Ordering`**: Ordering of inputs and outputs (`KNN`, `KNR`, `KRN`, `KRR`), affecting how data is arranged. +- **`areInputsOnDevice`**: Indicates if input scalars are located on the device. +- **`areOutputsOnDevice`**: Indicates if results are stored on the device. +- **`IsAsync`**: Controls whether the NTT operation runs asynchronously. +- **`NttAlgorithm`**: Explicitly select the NTT algorithm. ECNTT supports running on `Radix2` algoruithm. + +### Default Configuration + +Use `GetDefaultNTTConfig` to obtain a default configuration, customizable as needed. + +```go +func GetDefaultNTTConfig[T any](cosetGen T) NTTConfig[T] +``` + +## ECNTT Example + +```go +package main + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +func Main() { + // Obtain the default NTT configuration with a predefined coset generator. + cfg := GetDefaultNttConfig() + + // Define the size of the input scalars. + size := 1 << 18 + + // Generate Points for the ECNTT operation. + points := GenerateProjectivePoints(size) + + // Set the direction of the NTT (forward or inverse). + dir := core.KForward + + // Allocate memory for the results of the NTT operation. + results := make(core.HostSlice[Projective], size) + + // Perform the NTT operation. + err := ECNtt(points, dir, &cfg, results) + if err != cr.CudaSuccess { + panic("ECNTT operation failed") + } +} +``` \ No newline at end of file diff --git a/docs/docs/icicle/golang-bindings/msm-pre-computation.md b/docs/docs/icicle/golang-bindings/msm-pre-computation.md index e5f34296..1fcb6e1c 100644 --- a/docs/docs/icicle/golang-bindings/msm-pre-computation.md +++ b/docs/docs/icicle/golang-bindings/msm-pre-computation.md @@ -4,7 +4,7 @@ To understand the theory behind MSM pre computation technique refer to Niall Emm ### Supported curves -`bls12-377`, `bls12-381`, `bn254`, `bw6-761` +`bls12-377`, `bls12-381`, `bn254`, `bw6-761`, `grumpkin` ## Core package @@ -37,15 +37,27 @@ func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c in ##### Example ```go -cfg := GetDefaultMSMConfig() -points := GenerateAffinePoints(1024) -precomputeFactor := 8 -var precomputeOut core.DeviceSlice -_, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) +package main -err := PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) -if err != cr.CudaSuccess { - log.Fatalf("PrecomputeBases failed: %v", err) +import ( + "log" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" +) + +func main() { + cfg := bn254.GetDefaultMSMConfig() + points := bn254.GenerateAffinePoints(1024) + var precomputeFactor int32 = 8 + var precomputeOut core.DeviceSlice + precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + + err := bn254.PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + if err != cr.CudaSuccess { + log.Fatalf("PrecomputeBases failed: %v", err) + } } ``` @@ -68,15 +80,27 @@ func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c ##### Example ```go -cfg := G2GetDefaultMSMConfig() -points := G2GenerateAffinePoints(1024) -precomputeFactor := 8 -var precomputeOut core.DeviceSlice -_, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) +package main -err := G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) -if err != cr.CudaSuccess { - log.Fatalf("G2PrecomputeBases failed: %v", err) +import ( + "log" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + g2 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/g2" +) + +func main() { + cfg := g2.G2GetDefaultMSMConfig() + points := g2.G2GenerateAffinePoints(1024) + var precomputeFactor int32 = 8 + var precomputeOut core.DeviceSlice + precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + + err := g2.G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + if err != cr.CudaSuccess { + log.Fatalf("PrecomputeBases failed: %v", err) + } } ``` diff --git a/docs/docs/icicle/golang-bindings/msm.md b/docs/docs/icicle/golang-bindings/msm.md index 1f4bd5c6..b9e45678 100644 --- a/docs/docs/icicle/golang-bindings/msm.md +++ b/docs/docs/icicle/golang-bindings/msm.md @@ -3,7 +3,7 @@ ### Supported curves -`bls12-377`, `bls12-381`, `bn254`, `bw6-761` +`bls12-377`, `bls12-381`, `bn254`, `bw6-761`, `grumpkin` ## MSM Example @@ -11,52 +11,54 @@ package main import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" ) -func Main() { - // Obtain the default MSM configuration. - cfg := GetDefaultMSMConfig() - - // Define the size of the problem, here 2^18. - size := 1 << 18 +func main() { + // Obtain the default MSM configuration. + cfg := bn254.GetDefaultMSMConfig() - // Generate scalars and points for the MSM operation. - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + // Define the size of the problem, here 2^18. + size := 1 << 18 - // Create a CUDA stream for asynchronous operations. - stream, _ := cr.CreateStream() - var p Projective - - // Allocate memory on the device for the result of the MSM operation. - var out core.DeviceSlice - _, e := out.MallocAsync(p.Size(), p.Size(), stream) + // Generate scalars and points for the MSM operation. + scalars := bn254.GenerateScalars(size) + points := bn254.GenerateAffinePoints(size) - if e != cr.CudaSuccess { - panic(e) - } - - // Set the CUDA stream in the MSM configuration. - cfg.Ctx.Stream = &stream - cfg.IsAsync = true - - // Perform the MSM operation. - e = Msm(scalars, points, &cfg, out) - - if e != cr.CudaSuccess { - panic(e) - } - - // Allocate host memory for the results and copy the results from the device. - outHost := make(core.HostSlice[Projective], 1) - cr.SynchronizeStream(&stream) - outHost.CopyFromDevice(&out) - - // Free the device memory allocated for the results. - out.Free() + // Create a CUDA stream for asynchronous operations. + stream, _ := cr.CreateStream() + var p bn254.Projective + + // Allocate memory on the device for the result of the MSM operation. + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + + if e != cr.CudaSuccess { + panic(e) + } + + // Set the CUDA stream in the MSM configuration. + cfg.Ctx.Stream = &stream + cfg.IsAsync = true + + // Perform the MSM operation. + e = bn254.Msm(scalars, points, &cfg, out) + + if e != cr.CudaSuccess { + panic(e) + } + + // Allocate host memory for the results and copy the results from the device. + outHost := make(core.HostSlice[bn254.Projective], 1) + cr.SynchronizeStream(&stream) + outHost.CopyFromDevice(&out) + + // Free the device memory allocated for the results. + out.Free() } + ``` ## MSM Method @@ -67,14 +69,14 @@ func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *cor ### Parameters -- **scalars**: A slice containing the scalars for multiplication. It can reside either in host memory or device memory. -- **points**: A slice containing the points to be multiplied with scalars. Like scalars, these can also be in host or device memory. -- **cfg**: A pointer to an `MSMConfig` object, which contains various configuration options for the MSM operation. -- **results**: A slice where the results of the MSM operation will be stored. This slice can be in host or device memory. +- **`scalars`**: A slice containing the scalars for multiplication. It can reside either in host memory or device memory. +- **`points`**: A slice containing the points to be multiplied with scalars. Like scalars, these can also be in host or device memory. +- **`cfg`**: A pointer to an `MSMConfig` object, which contains various configuration options for the MSM operation. +- **`results`**: A slice where the results of the MSM operation will be stored. This slice can be in host or device memory. ### Return Value -- **CudaError**: Returns a CUDA error code indicating the success or failure of the MSM operation. +- **`CudaError`**: Returns a CUDA error code indicating the success or failure of the MSM operation. ## MSMConfig @@ -100,19 +102,19 @@ type MSMConfig struct { ### Fields -- **Ctx**: Device context containing details like device id and stream. -- **PrecomputeFactor**: Controls the number of extra points to pre-compute. -- **C**: Window bitsize, a key parameter in the "bucket method" for MSM. -- **Bitsize**: Number of bits of the largest scalar. -- **LargeBucketFactor**: Sensitivity to frequently occurring buckets. -- **batchSize**: Number of results to compute in one batch. -- **areScalarsOnDevice**: Indicates if scalars are located on the device. -- **AreScalarsMontgomeryForm**: True if scalars are in Montgomery form. -- **arePointsOnDevice**: Indicates if points are located on the device. -- **ArePointsMontgomeryForm**: True if point coordinates are in Montgomery form. -- **areResultsOnDevice**: Indicates if results are stored on the device. -- **IsBigTriangle**: If `true` MSM will run in Large triangle accumulation if `false` Bucket accumulation will be chosen. Default value: false. -- **IsAsync**: If true, runs MSM asynchronously. +- **`Ctx`**: Device context containing details like device id and stream. +- **`PrecomputeFactor`**: Controls the number of extra points to pre-compute. +- **`C`**: Window bitsize, a key parameter in the "bucket method" for MSM. +- **`Bitsize`**: Number of bits of the largest scalar. +- **`LargeBucketFactor`**: Sensitivity to frequently occurring buckets. +- **`batchSize`**: Number of results to compute in one batch. +- **`areScalarsOnDevice`**: Indicates if scalars are located on the device. +- **`AreScalarsMontgomeryForm`**: True if scalars are in Montgomery form. +- **`arePointsOnDevice`**: Indicates if points are located on the device. +- **`ArePointsMontgomeryForm`**: True if point coordinates are in Montgomery form. +- **`areResultsOnDevice`**: Indicates if results are stored on the device. +- **`IsBigTriangle`**: If `true` MSM will run in Large triangle accumulation if `false` Bucket accumulation will be chosen. Default value: false. +- **`IsAsync`**: If true, runs MSM asynchronously. ### Default Configuration @@ -157,44 +159,43 @@ out.Malloc(batchSize*p.Size(), p.Size()) ## Support for G2 group -To activate G2 support first you must make sure you are building the static libraries with G2 feature enabled. +To activate G2 support first you must make sure you are building the static libraries with G2 feature enabled as described in the [Golang building instructions](../golang-bindings.md#using-icicle-golang-bindings-in-your-project). -```bash -./build.sh bls12_381 ON -``` -Now when importing `icicle`, you should have access to G2 features. + +Now you may import `g2` package of the specified curve. ```go import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls254/g2" ) ``` -These features include `G2Projective` and `G2Affine` points as well as a `G2Msm` method. +This package include `G2Projective` and `G2Affine` points as well as a `G2Msm` method. ```go -... +package main -cfg := GetDefaultMSMConfig() -size := 1 << 12 -batchSize := 3 -totalSize := size * batchSize -scalars := GenerateScalars(totalSize) -points := G2GenerateAffinePoints(totalSize) +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + g2 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/g2" +) -var p G2Projective -var out core.DeviceSlice -out.Malloc(batchSize*p.Size(), p.Size()) -G2Msm(scalars, points, &cfg, out) +func main() { + cfg := bn254.GetDefaultMSMConfig() + size := 1 << 12 + batchSize := 3 + totalSize := size * batchSize + scalars := bn254.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) + + var p g2.G2Projective + var out core.DeviceSlice + out.Malloc(batchSize*p.Size(), p.Size()) + g2.G2Msm(scalars, points, &cfg, out) +} -... ``` `G2Msm` works the same way as normal MSM, the difference is that it uses G2 Points. - -Additionally when you are building your application make sure to use the g2 feature flag - -```bash -go build -tags=g2 -``` diff --git a/docs/docs/icicle/golang-bindings/multi-gpu.md b/docs/docs/icicle/golang-bindings/multi-gpu.md index ec00561f..02943c37 100644 --- a/docs/docs/icicle/golang-bindings/multi-gpu.md +++ b/docs/docs/icicle/golang-bindings/multi-gpu.md @@ -15,41 +15,52 @@ In this example we will display how you can ```go +package main + +import ( + "fmt" + "sync" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" +) + func main() { - numDevices, _ := cuda_runtime.GetDeviceCount() + numDevices, _ := cr.GetDeviceCount() fmt.Println("There are ", numDevices, " devices available") wg := sync.WaitGroup{} for i := 0; i < numDevices; i++ { wg.Add(1) - // RunOnDevice makes sure each MSM runs on a single thread - cuda_runtime.RunOnDevice(i, func(args ...any) { + // RunOnDevice makes sure each MSM runs on a single thread + cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := bn254.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{10, 18} { size := 1 << power // 2^pwr - // generate random scalars - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + // generate random scalars + scalars := bn254.GenerateScalars(size) + points := bn254.GenerateAffinePoints(size) - // create a stream and allocate result pointer - stream, _ := cuda_runtime.CreateStream() - var p Projective + // create a stream and allocate result pointer + stream, _ := cr.CreateStream() + var p bn254.Projective var out core.DeviceSlice - _, e := out.MallocAsync(p.Size(), p.Size(), stream) - // assign stream to device context + out.MallocAsync(p.Size(), p.Size(), stream) + // assign stream to device context cfg.Ctx.Stream = &stream - // execute MSM - e = Msm(scalars, points, &cfg, out) - // read result from device - outHost := make(core.HostSlice[Projective], 1) + // execute MSM + bn254.Msm(scalars, points, &cfg, out) + // read result from device + outHost := make(core.HostSlice[bn254.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) - // sync the stream + // sync the stream cr.SynchronizeStream(&stream) } }) @@ -78,9 +89,9 @@ While the goroutine is locked to the host thread, the Go runtime will not assign **Parameters:** -- `deviceId int`: The ID of the device on which to run the provided function. Device IDs start from 0. -- `funcToRun func(args ...any)`: The function to be executed on the specified device. -- `args ...any`: Arguments to be passed to `funcToRun`. +- **`deviceId int`**: The ID of the device on which to run the provided function. Device IDs start from 0. +- **`funcToRun func(args ...any)`**: The function to be executed on the specified device. +- **`args ...any`**: Arguments to be passed to `funcToRun`. **Behavior:** @@ -102,11 +113,11 @@ Sets the active device for the current host thread. All subsequent CUDA calls ma **Parameters:** -- `device int`: The ID of the device to set as the current device. +- **`device int`**: The ID of the device to set as the current device. **Returns:** -- `CudaError`: Error code indicating the success or failure of the operation. +- **`CudaError`**: Error code indicating the success or failure of the operation. ### `GetDeviceCount` @@ -114,7 +125,7 @@ Retrieves the number of CUDA-capable devices available on the host. **Returns:** -- `(int, CudaError)`: The number of devices and an error code indicating the success or failure of the operation. +- **`(int, CudaError)`**: The number of devices and an error code indicating the success or failure of the operation. ### `GetDevice` @@ -122,7 +133,7 @@ Gets the ID of the currently active device for the calling host thread. **Returns:** -- `(int, CudaError)`: The ID of the current device and an error code indicating the success or failure of the operation. +- **`(int, CudaError)`**: The ID of the current device and an error code indicating the success or failure of the operation. ### `GetDeviceFromPointer` @@ -130,10 +141,10 @@ Retrieves the device associated with a given pointer. **Parameters:** -- `ptr unsafe.Pointer`: Pointer to query. +- **`ptr unsafe.Pointer`**: Pointer to query. **Returns:** -- `int`: The device ID associated with the memory pointed to by `ptr`. +- **`int`**: The device ID associated with the memory pointed to by `ptr`. This documentation should provide a clear understanding of how to effectively manage multiple GPUs in Go applications using CUDA, with a particular emphasis on the `RunOnDevice` function for executing tasks on specific GPUs. \ No newline at end of file diff --git a/docs/docs/icicle/golang-bindings/ntt.md b/docs/docs/icicle/golang-bindings/ntt.md index 135fc047..ace88b83 100644 --- a/docs/docs/icicle/golang-bindings/ntt.md +++ b/docs/docs/icicle/golang-bindings/ntt.md @@ -10,31 +10,49 @@ package main import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" ) -func Main() { - // Obtain the default NTT configuration with a predefined coset generator. - cfg := GetDefaultNttConfig() - - // Define the size of the input scalars. - size := 1 << 18 +func init() { + cfg := bn254.GetDefaultNttConfig() + initDomain(18, cfg) +} - // Generate scalars for the NTT operation. - scalars := GenerateScalars(size) +func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { + rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) + rou := rouMont.Bits() + rouIcicle := bn254.ScalarField{} - // Set the direction of the NTT (forward or inverse). - dir := core.KForward + rouIcicle.FromLimbs(rou[:]) + e := bn254.InitDomain(rouIcicle, cfg.Ctx, false) + return e +} - // Allocate memory for the results of the NTT operation. - results := make(core.HostSlice[ScalarField], size) +func main() { + // Obtain the default NTT configuration with a predefined coset generator. + cfg := bn254.GetDefaultNttConfig() - // Perform the NTT operation. - err := Ntt(scalars, dir, &cfg, results) - if err != cr.CudaSuccess { - panic("NTT operation failed") - } + // Define the size of the input scalars. + size := 1 << 18 + + // Generate scalars for the NTT operation. + scalars := bn254.GenerateScalars(size) + + // Set the direction of the NTT (forward or inverse). + dir := core.KForward + + // Allocate memory for the results of the NTT operation. + results := make(core.HostSlice[bn254.ScalarField], size) + + // Perform the NTT operation. + err := bn254.Ntt(scalars, dir, &cfg, results) + if err.CudaErrorCode != cr.CudaSuccess { + panic("NTT operation failed") + } } ``` @@ -46,14 +64,14 @@ func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTCo ### Parameters -- **scalars**: A slice containing the input scalars for the transform. It can reside either in host memory or device memory. -- **dir**: The direction of the NTT operation (`KForward` or `KInverse`). -- **cfg**: A pointer to an `NTTConfig` object, containing configuration options for the NTT operation. -- **results**: A slice where the results of the NTT operation will be stored. This slice can be in host or device memory. +- **`scalars`**: A slice containing the input scalars for the transform. It can reside either in host memory or device memory. +- **`dir`**: The direction of the NTT operation (`KForward` or `KInverse`). +- **`cfg`**: A pointer to an `NTTConfig` object, containing configuration options for the NTT operation. +- **`results`**: A slice where the results of the NTT operation will be stored. This slice can be in host or device memory. ### Return Value -- **CudaError**: Returns a CUDA error code indicating the success or failure of the NTT operation. +- **`CudaError`**: Returns a CUDA error code indicating the success or failure of the NTT operation. ## NTT Configuration (NTTConfig) @@ -75,15 +93,15 @@ type NTTConfig[T any] struct { ### Fields -- **Ctx**: Device context containing details like device ID and stream ID. -- **CosetGen**: Coset generator used for coset (i)NTTs, defaulting to no coset being used. -- **BatchSize**: The number of NTTs to compute in one operation, defaulting to 1. -- **ColumnsBatch**: If true the function will compute the NTTs over the columns of the input matrix and not over the rows. Defaults to `false`. -- **Ordering**: Ordering of inputs and outputs (`KNN`, `KNR`, `KRN`, `KRR`, `KMN`, `KNM`), affecting how data is arranged. -- **areInputsOnDevice**: Indicates if input scalars are located on the device. -- **areOutputsOnDevice**: Indicates if results are stored on the device. -- **IsAsync**: Controls whether the NTT operation runs asynchronously. -- **NttAlgorithm**: Explicitly select the NTT algorithm. Default value: Auto (the implementation selects radix-2 or mixed-radix algorithm based on heuristics). +- **`Ctx`**: Device context containing details like device ID and stream ID. +- **`CosetGen`**: Coset generator used for coset (i)NTTs, defaulting to no coset being used. +- **`BatchSize`**: The number of NTTs to compute in one operation, defaulting to 1. +- **`ColumnsBatch`**: If true the function will compute the NTTs over the columns of the input matrix and not over the rows. Defaults to `false`. +- **`Ordering`**: Ordering of inputs and outputs (`KNN`, `KNR`, `KRN`, `KRR`, `KMN`, `KNM`), affecting how data is arranged. +- **`areInputsOnDevice`**: Indicates if input scalars are located on the device. +- **`areOutputsOnDevice`**: Indicates if results are stored on the device. +- **`IsAsync`**: Controls whether the NTT operation runs asynchronously. +- **`NttAlgorithm`**: Explicitly select the NTT algorithm. Default value: Auto (the implementation selects radix-2 or mixed-radix algorithm based on heuristics). ### Default Configuration @@ -102,3 +120,36 @@ func InitDomain(primitiveRoot ScalarField, ctx cr.DeviceContext, fastTwiddles bo ``` This function initializes the domain with a given primitive root, optionally using fast twiddle factors to optimize the computation. + +### Releasing the domain + +The `ReleaseDomain` function is responsible for releasing the resources associated with a specific domain in the CUDA device context. + +```go +func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError +``` + +### Parameters + +- **`ctx`**: a reference to the `DeviceContext` object, which represents the CUDA device context. + +### Return Value + +The function returns a `core.IcicleError`, which represents the result of the operation. If the operation is successful, the function returns `core.IcicleErrorCode(0)`. + +### Example + +```go +import ( + "github.com/icicle-crypto/icicle-core/cr" + "github.com/icicle-crypto/icicle-core/core" +) + +func example() { + cfg := GetDefaultNttConfig() + err := ReleaseDomain(cfg.Ctx) + if err != nil { + // Handle the error + } +} +``` diff --git a/docs/docs/icicle/golang-bindings/vec-ops.md b/docs/docs/icicle/golang-bindings/vec-ops.md index 69dbe5b9..4feaff2e 100644 --- a/docs/docs/icicle/golang-bindings/vec-ops.md +++ b/docs/docs/icicle/golang-bindings/vec-ops.md @@ -1,105 +1,111 @@ # Vector Operations ## Overview +Icicle is exposing a number of vector operations which a user can control: +* The VecOps API provides efficient vector operations such as addition, subtraction, and multiplication. +* MatrixTranspose API allows a user to perform a transpose on a vector representation of a matrix -The VecOps API provides efficient vector operations such as addition, subtraction, and multiplication. -## Example +## VecOps API Documentation +### Example -### Vector addition +#### Vector addition ```go package main import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" ) func main() { - testSize := 1 << 12 - a := GenerateScalars(testSize) - b := GenerateScalars(testSize) - out := make(core.HostSlice[ScalarField], testSize) - cfg := core.DefaultVecOpsConfig() + testSize := 1 << 12 + a := bn254.GenerateScalars(testSize) + b := bn254.GenerateScalars(testSize) + out := make(core.HostSlice[bn254.ScalarField], testSize) + cfg := core.DefaultVecOpsConfig() - // Perform vector addition - err := VecOp(a, b, out, cfg, core.Add) - if err != cr.CudaSuccess { - panic("Vector addition failed") - } + // Perform vector multiplication + err := bn254.VecOp(a, b, out, cfg, core.Add) + if err != cr.CudaSuccess { + panic("Vector addition failed") + } } ``` -### Vector Subtraction +#### Vector Subtraction ```go package main import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" ) func main() { - testSize := 1 << 12 - a := GenerateScalars(testSize) - b := GenerateScalars(testSize) - out := make(core.HostSlice[ScalarField], testSize) - cfg := core.DefaultVecOpsConfig() + testSize := 1 << 12 + a := bn254.GenerateScalars(testSize) + b := bn254.GenerateScalars(testSize) + out := make(core.HostSlice[bn254.ScalarField], testSize) + cfg := core.DefaultVecOpsConfig() - // Perform vector subtraction - err := VecOp(a, b, out, cfg, core.Sub) - if err != cr.CudaSuccess { - panic("Vector subtraction failed") - } + // Perform vector multiplication + err := bn254.VecOp(a, b, out, cfg, core.Sub) + if err != cr.CudaSuccess { + panic("Vector subtraction failed") + } } ``` -### Vector Multiplication +#### Vector Multiplication ```go package main import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" ) func main() { - testSize := 1 << 12 - a := GenerateScalars(testSize) - b := GenerateScalars(testSize) - out := make(core.HostSlice[ScalarField], testSize) - cfg := core.DefaultVecOpsConfig() + testSize := 1 << 12 + a := bn254.GenerateScalars(testSize) + b := bn254.GenerateScalars(testSize) + out := make(core.HostSlice[bn254.ScalarField], testSize) + cfg := core.DefaultVecOpsConfig() - // Perform vector multiplication - err := VecOp(a, b, out, cfg, core.Mul) - if err != cr.CudaSuccess { - panic("Vector multiplication failed") - } + // Perform vector multiplication + err := bn254.VecOp(a, b, out, cfg, core.Mul) + if err != cr.CudaSuccess { + panic("Vector multiplication failed") + } } ``` -## VecOps Method +### VecOps Method ```go func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) ``` -### Parameters +#### Parameters -- **a**: The first input vector. -- **b**: The second input vector. -- **out**: The output vector where the result of the operation will be stored. -- **config**: A `VecOpsConfig` object containing various configuration options for the vector operations. -- **op**: The operation to perform, specified as one of the constants (`Sub`, `Add`, `Mul`) from the `VecOps` type. +- **`a`**: The first input vector. +- **`b`**: The second input vector. +- **`out`**: The output vector where the result of the operation will be stored. +- **`config`**: A `VecOpsConfig` object containing various configuration options for the vector operations. +- **`op`**: The operation to perform, specified as one of the constants (`Sub`, `Add`, `Mul`) from the `VecOps` type. -### Return Value +#### Return Value -- **CudaError**: Returns a CUDA error code indicating the success or failure of the vector operation. +- **`CudaError`**: Returns a CUDA error code indicating the success or failure of the vector operation. -## VecOpsConfig +### VecOpsConfig The `VecOpsConfig` structure holds configuration parameters for the vector operations, allowing customization of its behavior. @@ -109,24 +115,72 @@ type VecOpsConfig struct { isAOnDevice bool isBOnDevice bool isResultOnDevice bool - IsResultMontgomeryForm bool IsAsync bool } ``` -### Fields +#### Fields - **Ctx**: Device context containing details like device ID and stream ID. - **isAOnDevice**: Indicates if vector `a` is located on the device. - **isBOnDevice**: Indicates if vector `b` is located on the device. - **isResultOnDevice**: Specifies where the result vector should be stored (device or host memory). -- **IsResultMontgomeryForm**: Determines if the result vector should be in Montgomery form. - **IsAsync**: Controls whether the vector operation runs asynchronously. -### Default Configuration +#### Default Configuration Use `DefaultVecOpsConfig` to obtain a default configuration, customizable as needed. ```go func DefaultVecOpsConfig() VecOpsConfig ``` + +## MatrixTranspose API Documentation + +This section describes the functionality of the `TransposeMatrix` function used for matrix transposition. + +The function takes a matrix represented as a 1D slice and transposes it, storing the result in another 1D slice. + +### Function + +```go +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError) +``` + +## Parameters + +- **`in`**: The input matrix is a `core.HostOrDeviceSlice`, stored as a 1D slice. +- **`out`**: The output matrix is a `core.HostOrDeviceSlice`, which will be the transpose of the input matrix, stored as a 1D slice. +- **`columnSize`**: The number of columns in the input matrix. +- **`rowSize`**: The number of rows in the input matrix. +- **`ctx`**: The device context `cr.DeviceContext` to be used for the matrix transpose operation. +- **`onDevice`**: Indicates whether the input and output slices are stored on the device (GPU) or the host (CPU). +- **`isAsync`**: Indicates whether the matrix transpose operation should be executed asynchronously. + +## Return Value + +The function returns a `core.IcicleError` value, which represents the result of the matrix transpose operation. If the operation is successful, the returned value will be `0`. + +## Example Usage + +```go +var input = make(core.HostSlice[ScalarField], 20) +var output = make(core.HostSlice[ScalarField], 20) + +// Populate the input matrix +// ... + +// Get device context +ctx, _ := cr.GetDefaultDeviceContext() + +// Transpose the matrix +err := TransposeMatrix(input, output, 5, 4, ctx, false, false) +if err.IcicleErrorCode != core.IcicleErrorCode(0) { + // Handle the error +} + +// Use the transposed matrix +// ... +``` + +In this example, the `TransposeMatrix` function is used to transpose a 5x4 matrix stored in a 1D slice. The input and output slices are stored on the host (CPU), and the operation is executed synchronously. \ No newline at end of file diff --git a/docs/docs/icicle/integrations.md b/docs/docs/icicle/integrations.md index 9a89aaff..e628894b 100644 --- a/docs/docs/icicle/integrations.md +++ b/docs/docs/icicle/integrations.md @@ -1,6 +1,6 @@ # ICICLE integrated provers -ICICLE has been used by companies and projects such as [Celer Network](https://github.com/celer-network), [Consensys Gnark](https://github.com/Consensys/gnark), [EZKL](https://blog.ezkl.xyz/post/acceleration/) and others to accelerate their ZK proving pipeline. +ICICLE has been used by companies and projects such as [Celer Network](https://github.com/celer-network), [Consensys Gnark](https://github.com/Consensys/gnark), [EZKL](https://blog.ezkl.xyz/post/acceleration/), [ZKWASM](https://twitter.com/DelphinusLab/status/1762604988797513915) and others to accelerate their ZK proving pipeline. Many of these integrations have been a collaboration between Ingonyama and the integrating company. We have learned a lot about designing GPU based ZK provers. diff --git a/docs/docs/icicle/introduction.md b/docs/docs/icicle/introduction.md index b3f13b01..41a26b2c 100644 --- a/docs/docs/icicle/introduction.md +++ b/docs/docs/icicle/introduction.md @@ -8,24 +8,24 @@ This guide is oriented towards developers who want to start writing code with th The diagram above displays the general architecture of ICICLE and the API layers that exist. The CUDA API, which we also call ICICLE Core, is the lowest level and is comprised of CUDA kernels which implement all primitives such as MSM as well as C++ wrappers which expose these methods for different curves. -ICICLE Core compiles into a static library. This library can be used with our official Golang and Rust wrappers or you can implement a wrapper for it in any language. +ICICLE Core compiles into a static library. This library can be used with our official Golang and Rust wrappers or linked with your C++ project. You can also implement a wrapper for it in any other language. -Based on this dependency architecture, the ICICLE repository has three main sections, each of which is independent from the other. +Based on this dependency architecture, the ICICLE repository has three main sections: -- ICICLE core -- ICICLE Rust bindings -- ICICLE Golang bindings +- [ICICLE Core](#icicle-core) +- [ICICLE Rust bindings](#icicle-rust-and-golang-bindings) +- [ICICLE Golang bindings](#icicle-rust-and-golang-bindings) ### ICICLE Core -[ICICLE core](https://github.com/ingonyama-zk/icicle/tree/main/icicle) contains all the low level CUDA code implementing primitives such as [points](https://github.com/ingonyama-zk/icicle/tree/main/icicle/primitives) and [MSM](https://github.com/ingonyama-zk/icicle/tree/main/icicle/appUtils/msm). There also exists higher level C++ wrappers to expose the low level CUDA primitives ([example](https://github.com/ingonyama-zk/icicle/blob/c1a32a9879a7612916e05aa3098f76144de4109e/icicle/appUtils/msm/msm.cu#L1)). +[ICICLE Core](/icicle/core) is a library that directly works with GPU by defining CUDA kernels and algorithms that invoke them. It contains code for [fast field arithmetic](https://github.com/ingonyama-zk/icicle/tree/main/icicle/include/field/field.cuh), cryptographic primitives used in ZK such as [NTT](https://github.com/ingonyama-zk/icicle/tree/main/icicle/src/ntt/), [MSM](https://github.com/ingonyama-zk/icicle/tree/main/icicle/src/msm/), [Poseidon Hash](https://github.com/ingonyama-zk/icicle/tree/main/icicle/src/poseidon/), [Polynomials](https://github.com/ingonyama-zk/icicle/tree/main/icicle/src/polynomials/) and others. -ICICLE Core would typically be compiled into a static library and used in a third party language such as Rust or Golang. +ICICLE Core would typically be compiled into a static library and either used in a third party language such as Rust or Golang, or linked with your own C++ project. ### ICICLE Rust and Golang bindings -- [ICICLE Rust bindings](https://github.com/ingonyama-zk/icicle/tree/main/wrappers/rust) -- [ICICLE Golang bindings](https://github.com/ingonyama-zk/icicle/tree/main/goicicle) +- [ICICLE Rust bindings](/icicle/rust-bindings) +- [ICICLE Golang bindings](/icicle/golang-bindings) These bindings allow you to easily use ICICLE in a Rust or Golang project. Setting up Golang bindings requires a bit of extra steps compared to the Rust bindings which utilize the `cargo build` tool. @@ -33,6 +33,12 @@ These bindings allow you to easily use ICICLE in a Rust or Golang project. Setti This guide assumes that you have a Linux or Windows machine with an Nvidia GPU installed. If you don't have access to an Nvidia GPU you can access one for free on [Google Colab](https://colab.google/). +:::info note + +ICICLE can only run on Linux or Windows. **MacOS is not supported**. + +::: + ### Prerequisites - NVCC (version 12.0 or newer) @@ -50,9 +56,9 @@ If you don't wish to install these prerequisites you can follow this tutorial us ### Setting up ICICLE and running tests -The objective of this guide is to make sure you can run the ICICLE Core, Rust and Golang tests. Achieving this will ensure you know how to setup ICICLE and run a ICICLE program. For simplicity, we will be using the ICICLE docker container as our environment, however, you may install the prerequisites on your machine and follow the same commands in your terminal. +The objective of this guide is to make sure you can run the ICICLE Core, Rust and Golang tests. Achieving this will ensure you know how to setup ICICLE and run an ICICLE program. For simplicity, we will be using the ICICLE docker container as our environment, however, you may install the prerequisites on your machine and [skip](#icicle-core-1) the docker section. -#### Setting up our environment +#### Setting up environment with Docker Lets begin by cloning the ICICLE repository: @@ -105,29 +111,23 @@ ICICLE Core is found under [`/icicle`](https://github.com/ingonyam cd icicle ``` -We are going to compile ICICLE for a specific curve +For this example, we are going to compile ICICLE for a `bn254` curve. However other compilation strategies are supported. ```sh mkdir -p build cmake -S . -B build -DCURVE=bn254 -DBUILD_TESTS=ON -cmake --build build +cmake --build build -j ``` -`-DBUILD_TESTS=ON` compiles the tests, without this flag `ctest` won't work. -`-DCURVE=bn254` tells the compiler which curve to build. You can find a list of supported curves [here](https://github.com/ingonyama-zk/icicle/tree/main/icicle/curves). +`-DBUILD_TESTS` option compiles the tests, without this flag `ctest` won't work. +`-DCURVE` option tells the compiler which curve to build. You can find a list of supported curves [here](https://github.com/ingonyama-zk/icicle/tree/main/icicle/cmake/CurvesCommon.cmake#L2). The output in `build` folder should include the static libraries for the compiled curve. -:::info - -Make sure to only use `-DBUILD_TESTS=ON` for running tests as the archive output will only be available when `-DBUILD_TESTS=ON` is not supplied. - -::: - To run the test ```sh -cd build +cd build/tests ctest ``` @@ -169,8 +169,24 @@ Golang is WIP in v1, coming soon. Please checkout a previous [release v0.1.0](ht ### Running ICICLE examples -ICICLE examples can be found [here](https://github.com/ingonyama-zk/icicle-examples) these examples cover some simple use cases using C++, rust and golang. +ICICLE examples can be found [here](https://github.com/ingonyama-zk/icicle/tree/main/examples) these examples cover some simple use cases using C++, rust and golang. +Lets run one of our C++ examples, in this case the [MSM example](https://github.com/ingonyama-zk/icicle/blob/main/examples/c%2B%2B/msm/example.cu). + +```sh +cd examples/c++/msm +./compile.sh +./run.sh +``` + +:::tip + +Read through the compile.sh and CMakeLists.txt to understand how to link your own C++ project with ICICLE + +::: + + +#### Running with Docker In each example directory, ZK-container files are located in a subdirectory `.devcontainer`. ```sh @@ -180,21 +196,6 @@ msm/ └── Dockerfile ``` -Lets run one of our C++ examples, in this case the [MSM example](https://github.com/ingonyama-zk/icicle-examples/blob/main/c%2B%2B/msm/example.cu). - -Clone the repository - -```sh -git clone https://github.com/ingonyama-zk/icicle-examples.git -cd icicle-examples -``` - -Enter the test directory - -```sh -cd c++/msm -``` - Now lets build our docker file and run the test inside it. Make sure you have installed the [optional prerequisites](#optional-prerequisites). ```sh @@ -207,54 +208,11 @@ Lets start and enter the container docker run -it --rm --gpus all -v .:/icicle-example icicle-example-msm ``` -to run the example +Inside the container you can run the same commands: ```sh -rm -rf build -mkdir -p build -cmake -S . -B build -cmake --build build -./build/example +./compile.sh +./run.sh ``` -You can now experiment with our other examples, perhaps try to run a rust or golang example next. - -## Writing new bindings for ICICLE - -Since ICICLE Core is written in CUDA / C++ its really simple to generate static libraries. These static libraries can be installed on any system and called by higher level languages such as Golang. - -static libraries can be loaded into memory once and used by multiple programs, reducing memory usage and potentially improving performance. They also allow you to separate functionality into distinct modules so your static library may need to compile only specific features that you want to use. - -Lets review the Golang bindings since its a pretty verbose example (compared to rust which hides it pretty well) of using static libraries. Golang has a library named `CGO` which can be used to link static libraries. Here's a basic example on how you can use cgo to link these libraries: - -```go -/* -#cgo LDFLAGS: -L/path/to/shared/libs -lbn254 -lbls12_381 -lbls12_377 -lbw6_671 -#include "icicle.h" // make sure you use the correct header file(s) -*/ -import "C" - -func main() { - // Now you can call the C functions from the ICICLE libraries. - // Note that C function calls are prefixed with 'C.' in Go code. - - out := (*C.BN254_projective_t)(unsafe.Pointer(p)) - in := (*C.BN254_affine_t)(unsafe.Pointer(affine)) - - C.projective_from_affine_bn254(out, in) -} -``` - -The comments on the first line tell `CGO` which libraries to import as well as which header files to include. You can then call methods which are part of the static library and defined in the header file, `C.projective_from_affine_bn254` is an example. - -If you wish to create your own bindings for a language of your choice we suggest you start by investigating how you can call static libraries. - -### ICICLE Adapters - -One of the core ideas behind ICICLE is that developers can gradually accelerate their provers. Many protocols are written using other cryptographic libraries and completely replacing them may be complex and time consuming. - -Therefore we offer adapters for various popular libraries, these adapters allow us to convert points and scalars between different formats defined by various libraries. Here is a list: - -Golang adapters: - -- [Gnark crypto adapter](https://github.com/ingonyama-zk/iciclegnark) +You can now experiment with our other examples, perhaps try to run a rust or golang example next. \ No newline at end of file diff --git a/docs/docs/icicle/polynomials/ffi.uml b/docs/docs/icicle/polynomials/ffi.uml new file mode 100644 index 00000000..5d0a1e3c --- /dev/null +++ b/docs/docs/icicle/polynomials/ffi.uml @@ -0,0 +1,27 @@ +@startuml +skinparam componentStyle uml2 + +' Define Components +component "C++ Template\nComponent" as CppTemplate { + [Parameterizable Interface] +} +component "C API Wrapper\nComponent" as CApiWrapper { + [C API Interface] +} +component "Rust Code\nComponent" as RustCode { + [Macro Interface\n(Template Instantiation)] +} + +' Define Artifact +artifact "Static Library\n«artifact»" as StaticLib + +' Connections +CppTemplate -down-> CApiWrapper : Instantiates +CApiWrapper .down.> StaticLib : Compiles into +RustCode -left-> StaticLib : Links against\nand calls via FFI + +' Notes +note right of CppTemplate : Generic C++\ntemplate implementation +note right of CApiWrapper : Exposes C API for FFI\nto Rust/Go +note right of RustCode : Uses macros to\ninstantiate templates +@enduml diff --git a/docs/docs/icicle/polynomials/hw_backends.uml b/docs/docs/icicle/polynomials/hw_backends.uml new file mode 100644 index 00000000..bb96db09 --- /dev/null +++ b/docs/docs/icicle/polynomials/hw_backends.uml @@ -0,0 +1,86 @@ +@startuml + +' Define Interface for Polynomial Backend Operations +interface IPolynomialBackend { + +add() + +subtract() + +multiply() + +divide() + +evaluate() +} + +' Define Interface for Polynomial Context (State Management) +interface IPolynomialContext { + +initFromCoeffs() + +initFromEvals() + +getCoeffs() + +getEvals() +} + +' PolynomialAPI now uses two strategies: Backend and Context +class PolynomialAPI { + -backendStrategy: IPolynomialBackend + -contextStrategy: IPolynomialContext + -setBackendStrategy(IPolynomialBackend) + -setContextStrategy(IPolynomialContext) + +add() + +subtract() + +multiply() + +divide() + +evaluate() +} + +' Backend Implementations +class GPUPolynomialBackend implements IPolynomialBackend { + #gpuResources: Resource + +add() + +subtract() + +multiply() + +divide() + +evaluate() +} + +class ZPUPolynomialBackend implements IPolynomialBackend { + #zpuResources: Resource + +add() + +subtract() + +multiply() + +divide() + +evaluate() +} + +class TracerPolynomialBackend implements IPolynomialBackend { + #traceData: Data + +add() + +subtract() + +multiply() + +divide() + +evaluate() +} + +' Context Implementations (Placeholder for actual implementation) +class GPUContext implements IPolynomialContext { + +initFromCoeffs() + +initFromEvals() + +getCoeffs() + +getEvals() +} + +class ZPUContext implements IPolynomialContext { + +initFromCoeffs() + +initFromEvals() + +getCoeffs() + +getEvals() +} + +class TracerContext implements IPolynomialContext { + +initFromCoeffs() + +initFromEvals() + +getCoeffs() + +getEvals() +} + +' Relationships +PolynomialAPI o-- IPolynomialBackend : uses +PolynomialAPI o-- IPolynomialContext : uses +@enduml diff --git a/docs/docs/icicle/polynomials/overview.md b/docs/docs/icicle/polynomials/overview.md new file mode 100644 index 00000000..205b84f0 --- /dev/null +++ b/docs/docs/icicle/polynomials/overview.md @@ -0,0 +1,373 @@ +# Polynomial API Overview + +## Introduction + +The Polynomial API offers a robust framework for polynomial operations within a computational environment. It's designed for flexibility and efficiency, supporting a broad range of operations like arithmetic, evaluation, and manipulation, all while abstracting from the computation and storage specifics. This enables adaptability to various backend technologies, employing modern C++ practices. + +## Key Features + +### Backend Agnostic Architecture +Our API is structured to be independent of any specific computational backend. While a CUDA backend is currently implemented, the architecture facilitates easy integration of additional backends. This capability allows users to perform polynomial operations without the need to tailor their code to specific hardware, enhancing code portability and scalability. + +### Templating in the Polynomial API + +The Polynomial API is designed with a templated structure to accommodate different data types for coefficients, the domain, and images. This flexibility allows the API to be adapted for various computational needs and types of data. + +```cpp +template +class Polynomial { + // Polynomial class definition +} +``` + +In this template: + +- **`Coeff`**: Represents the type of the coefficients of the polynomial. +- **`Domain`**: Specifies the type for the input values over which the polynomial is evaluated. By default, it is the same as the type of the coefficients but can be specified separately to accommodate different computational contexts. +- **`Image`**: Defines the type of the output values of the polynomial. This is typically the same as the coefficients. + +#### Default instantiation +```cpp +extern template class Polynomial; +``` + +#### Extended use cases +The templated nature of the Polynomial API also supports more complex scenarios. For example, coefficients and images could be points on an elliptic curve (EC points), which are useful in cryptographic applications and advanced algebraic structures. This approach allows the API to be extended easily to support new algebraic constructions without modifying the core implementation. + +### Supported Operations +The Polynomial class encapsulates a polynomial, providing a variety of operations: +- **Construction**: Create polynomials from coefficients or evaluations on roots-of-unity domains. +- **Arithmetic Operations**: Perform addition, subtraction, multiplication, and division. +- **Evaluation**: Directly evaluate polynomials at specific points or across a domain. +- **Manipulation**: Features like slicing polynomials, adding or subtracting monomials inplace, and computing polynomial degrees. +- **Memory Access**: Access internal states or obtain device-memory views of polynomials. + +## Usage + +This section outlines how to use the Polynomial API in C++. Bindings for Rust and Go are detailed under the Bindings sections. + +### Backend Initialization +Initialization with an appropriate factory is required to configure the computational context and backend. + +```cpp +#include "polynomials/polynomials.h" +#include "polynomials/cuda_backend/polynomial_cuda_backend.cuh" + +// Initialize with a CUDA backend +Polynomial::initialize(std::make_shared()); +``` + +:::note Icicle is built to a library per field/curve. Initialization must be done per library. That is, applications linking to multiple curves/fields should do it per curve/field. +::: + +### Construction +Polynomials can be constructed from coefficients, from evaluations on roots-of-unity domains, or by cloning existing polynomials. + +```cpp +// Construction +static Polynomial from_coefficients(const Coeff* coefficients, uint64_t nof_coefficients); +static Polynomial from_rou_evaluations(const Image* evaluations, uint64_t nof_evaluations); +// Clone the polynomial +Polynomial clone() const; +``` + +Example: + +```cpp +auto p_from_coeffs = Polynomial_t::from_coefficients(coeff /* :scalar_t* */, nof_coeffs); +auto p_from_rou_evals = Polynomial_t::from_rou_evaluations(rou_evals /* :scalar_t* */, nof_evals); +auto p_cloned = p.clone(); // p_cloned and p do not share memory +``` + +:::note +The coefficients or evaluations may be allocated either on host or device memory. In both cases the memory is copied to backend device. +::: + +### Arithmetic +Constructed polynomials can be used for various arithmetic operations: + +```cpp +// Addition +Polynomial operator+(const Polynomial& rhs) const; +Polynomial& operator+=(const Polynomial& rhs); // inplace addition + +// Subtraction +Polynomial operator-(const Polynomial& rhs) const; + +// Multiplication +Polynomial operator*(const Polynomial& rhs) const; +Polynomial operator*(const Domain& scalar) const; // scalar multiplication + +// Division A(x) = B(x)Q(x) + R(x) +std::pair divide(const Polynomial& rhs) const; // returns (Q(x), R(x)) +Polynomial operator/(const Polynomial& rhs) const; // returns quotient Q(x) +Polynomial operator%(const Polynomial& rhs) const; // returns remainder R(x) +Polynomial divide_by_vanishing_polynomial(uint64_t degree) const; // sdivision by the vanishing polynomial V(x)=X^N-1 +``` + +#### Example: +Given polynomials A(x),B(x),C(x) and V(x) the vanishing polynomial. + +$$ +H(x)=\frac{A(x) \cdot B(x) - C(x)}{V(x)} \space where \space V(x) = X^{N}-1 +$$ + +```cpp +auto H = (A*B-C).divide_by_vanishing_polynomial(N); +``` + +### Evaluation +Evaluate polynomials at arbitrary domain points or across a domain. + +```cpp +Image operator()(const Domain& x) const; // evaluate f(x) +void evaluate(const Domain* x, Image* evals /*OUT*/) const; +void evaluate_on_domain(Domain* domain, uint64_t size, Image* evals /*OUT*/) const; // caller allocates memory +``` + +Example: + +```cpp +Coeff x = rand(); +Image f_x = f(x); // evaluate f at x + +// evaluate f(x) on a domain +uint64_t domain_size = ...; +auto domain = /*build domain*/; // host or device memory +auto evaluations = std::make_unique(domain_size); // can be device memory too +f.evaluate_on_domain(domain, domain_size, evaluations); +``` + +:::note For special domains such as roots of unity this method is not the most efficient for two reasons: +- Need to build the domain of size N. +- The implementation is not trying to identify this special domain. + +Therefore the computation is typically $O(n^2)$ rather than $O(nlogn)$. +See the 'device views' section for more details. +::: + + +### Manipulations +Beyond arithmetic, the API supports efficient polynomial manipulations: + +#### Monomials +```cpp +// Monomial operations +Polynomial& add_monomial_inplace(Coeff monomial_coeff, uint64_t monomial = 0); +Polynomial& sub_monomial_inplace(Coeff monomial_coeff, uint64_t monomial = 0); +``` + +The ability to add or subtract monomials directly and in-place is an efficient way to manipualte polynomials. + +Example: +```cpp +f.add_monomial_in_place(scalar_t::from(5)); // f(x) += 5 +f.sub_monomial_in_place(scalar_t::from(3), 8); // f(x) -= 3x^8 +``` + +#### Computing the degree of a Polynomial +```cpp +// Degree computation +int64_t degree(); +``` + +The degree of a polynomial is a fundamental characteristic that describes the highest power of the variable in the polynomial expression with a non-zero coefficient. +The `degree()` function in the API returns the degree of the polynomial, corresponding to the highest exponent with a non-zero coefficient. + +- For the polynomial $f(x) = x^5 + 2x^3 + 4$, the degree is 5 because the highest power of $x$ with a non-zero coefficient is 5. +- For a scalar value such as a constant term (e.g., $f(x) = 7$, the degree is considered 0, as it corresponds to $x^0$. +- The degree of the zero polynomial, $f(x) = 0$, where there are no non-zero coefficients, is defined as -1. This special case often represents an "empty" or undefined state in many mathematical contexts. + +Example: +```cpp +auto f = /*some expression*/; +auto degree_of_f = f.degree(); +``` + +#### Slicing +```cpp +// Slicing and selecting even or odd components. +Polynomial slice(uint64_t offset, uint64_t stride, uint64_t size = 0 /*0 means take all elements*/); +Polynomial even(); +Polynomial odd(); +``` + +The Polynomial API provides methods for slicing polynomials and selecting specific components, such as even or odd indexed terms. Slicing allows extracting specific sections of a polynomial based on an offset, stride, and size. + +The following examples demonstrate folding a polynomial's even and odd parts and arbitrary slicing; +```cpp +// folding a polynomials even and odd parts with randomness +auto x = rand(); +auto even = f.even(); +auto odd = f.odd(); +auto fold_poly = even + odd * x; + +// arbitrary slicing (first quarter) +auto first_quarter = f.slice(0 /*offset*/, 1 /*stride*/, f.degree()/4 /*size*/); +``` + +### Memory access (copy/view) +Access to the polynomial's internal state can be vital for operations like commitment schemes or when more efficient custom operations are necessary. This can be done in one of two ways: +- **Copy** the coefficients or evaluations to user allocated memory or +- **View** into the device memory without copying. + +#### Copy +Copy the polynomial coefficients to either host or device allocated memory. +:::note copying to host memory is backend agnostic while copying to device memory requires the memory to be allocated on the corresponding backend. +::: + +```cpp +Coeff get_coeff(uint64_t idx) const; // copy single coefficient to host +uint64_t copy_coeffs(Coeff* coeffs, uint64_t start_idx, uint64_t end_idx) const; +``` + +Example: +```cpp +auto coeffs_device = /*allocate CUDA or host memory*/ +f.copy_coeffs(coeffs_device, 0/*start*/, f.degree()); + +MSMConfig cfg = msm::defaultMSMConfig(); +cfg.are_points_on_device = true; // assuming copy to device memory +auto rv = msm::MSM(coeffs_device, points, msm_size, cfg, results); +``` + +#### Views +The Polynomial API supports efficient data handling through the use of memory views. These views provide direct access to the polynomial's internal state, such as coefficients or evaluations, without the need to copy data. This feature is particularly useful for operations that require direct access to device memory, enhancing both performance and memory efficiency. + +##### What is a Memory View? + +A memory view is essentially a pointer to data stored in device memory. By providing a direct access pathway to the data, it eliminates the need for data duplication, thus conserving both time and system resources. This is especially beneficial in high-performance computing environments where data size and operation speed are critical factors. + +##### Applications of Memory Views + +Memory views are extremely versatile and can be employed in various computational contexts such as: + +- **Commitments**: Views can be used to commit polynomial states in cryptographic schemes, such as Multi-Scalar Multiplications (MSM), or for constructing Merkle trees without duplicating the underlying data. +- **External Computations**: They allow external functions or algorithms to utilize the polynomial's data directly, facilitating operations outside the core polynomial API. This is useful for custom operations that are not covered by the API. + +##### Obtaining and Using Views + +To create and use views within the Polynomial API, functions are provided to obtain pointers to both coefficients and evaluation data. Here’s how they are generally structured: + +```cpp +// Obtain a view of the polynomial's coefficients +std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> get_coefficients_view(); +// obtain a view of the evaluations. Can specify the domain size and whether to compute reversed evaluations. +std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> +get_rou_evaluations_view(uint64_t nof_evaluations = 0, bool is_reversed = false); +``` + +Example usage: + +```cpp +auto [coeffs_view, size, device_id] = polynomial.get_coefficients_view(); + +// Use coeffs_view in a computational routine that requires direct access to polynomial coefficients +// Example: Passing the view to a GPU-accelerated function +gpu_accelerated_function(coeffs_view.get(),...); +``` + +##### Integrity-Pointer: Managing Memory Views +Within the Polynomial API, memory views are managed through a specialized tool called the Integrity-Pointer. This pointer type is designed to safeguard operations by monitoring the validity of the memory it points to. It can detect if the memory has been modified or released, thereby preventing unsafe access to stale or non-existent data. +The Integrity-Pointer not only acts as a regular pointer but also provides additional functionality to ensure the integrity of the data it references. Here are its key features: + +```cpp +// Checks whether the pointer is still considered valid +bool isValid() const; + +// Retrieves the raw pointer or nullptr if pointer is invalid +const T* get() const; + +// Dereferences the pointer. Throws exception if the pointer is invalid. +const T& operator*() const; + +//Provides access to the member of the pointed-to object. Throws exception if the pointer is invalid. +const T* operator->() const; +``` + +Consider the Following case: + +```cpp +auto [coeff_view, size, device] = f.get_coefficients_view(); + +// Use the coefficients view to perform external operations +commit_to_polynomial(coeff_view.get(), size); + +// Modification of the original polynomial +f += g; // Any operation that modifies 'f' potentially invalidates 'coeff_view' + +// Check if the view is still valid before using it further +if (coeff_view.isValid()) { + perform_additional_computation(coeff_view.get(), size); +} else { + handle_invalid_data(); +} +``` + +#### Evaluations View: Accessing Polynomial Evaluations Efficiently +The Polynomial API offers a specialized method, `get_rou_evaluations_view(...)`, which facilitates direct access to the evaluations of a polynomial. This method is particularly useful for scenarios where polynomial evaluations need to be accessed frequently or manipulated externally without the overhead of copying data. +This method provides a memory view into the device memory where polynomial evaluations are stored. It allows for efficient interpolation on larger domains, leveraging the raw evaluations directly from memory. +:::warning +Invalid request: requesting evaluations on a domain smaller than the degree of the polynomial is not supported and is considered invalid. +::: + +```cpp +// Assume a polynomial `p` of degree N +auto [evals_view, size, device_id] = p.get_rou_evaluations_view(4*N); // expanding the evaluation domain + +// Use the evaluations view to perform further computations or visualizations +process_polynomial_evaluations(evals_view.get(), size, device_id); +``` + +## Multi-GPU Support with CUDA Backend + +The Polynomial API includes comprehensive support for multi-GPU environments, a crucial feature for leveraging the full computational power of systems equipped with multiple NVIDIA GPUs. This capability is part of the API's CUDA backend, which is designed to efficiently manage polynomial computations across different GPUs. + +### Setting the CUDA Device + +Like other components of the icicle framework, the Polynomial API allows explicit setting of the current CUDA device: + +```cpp +cudaSetDevice(int deviceID); +``` + +This function sets the active CUDA device. All subsequent operations that allocate or deal with polynomial data will be performed on this device. + +### Allocation Consistency +Polynomials are always allocated on the current CUDA device at the time of their creation. It is crucial to ensure that the device context is correctly set before initiating any operation that involves memory allocation: +```cpp +// Set the device before creating polynomials +cudaSetDevice(0); +Polynomial p1 = Polynomial::from_coefficients(coeffs, size); + +cudaSetDevice(1); +Polynomial p2 = Polynomial::from_coefficients(coeffs, size); +``` + +### Matching Devices for Operations +When performing operations that result in the creation of new polynomials (such as addition or multiplication), it is imperative that both operands are on the same CUDA device. If the operands reside on different devices, an exception is thrown: + +```cpp +// Ensure both operands are on the same device +cudaSetDevice(0); +auto p3 = p1 + p2; // Throws an exception if p1 and p2 are not on the same device +``` + +### Device-Agnostic Operations +Operations that do not involve the creation of new polynomials, such as computing the degree of a polynomial or performing in-place modifications, can be executed regardless of the current device setting: +```cpp +// 'degree' and in-place operations do not require device matching +int deg = p1.degree(); +p1 += p2; // Valid if p1 and p2 are on the same device, throws otherwise +``` + +### Error Handling +The API is designed to throw exceptions if operations are attempted across polynomials that are not located on the same GPU. This ensures that all polynomial operations are performed consistently and without data integrity issues due to device mismatches. + +### Best Practices +To maximize the performance and avoid runtime errors in a multi-GPU setup, always ensure that: + +- The CUDA device is set correctly before polynomial allocation. +- Operations involving new polynomial creation are performed with operands on the same device. + +By adhering to these guidelines, developers can effectively harness the power of multiple GPUs to handle large-scale polynomial computations efficiently. diff --git a/docs/docs/icicle/rust-bindings/ecntt.md b/docs/docs/icicle/rust-bindings/ecntt.md new file mode 100644 index 00000000..2463107a --- /dev/null +++ b/docs/docs/icicle/rust-bindings/ecntt.md @@ -0,0 +1,35 @@ +# ECNTT + +### Supported curves + +`bls12-377`, `bls12-381`, `bn254` + +## ECNTT Method + +The `ecntt` function computes the Elliptic Curve Number Theoretic Transform (EC-NTT) or its inverse on a batch of points of a curve. + +```rust +pub fn ecntt( + input: &(impl HostOrDeviceSlice> + ?Sized), + dir: NTTDir, + cfg: &NTTConfig, + output: &mut (impl HostOrDeviceSlice> + ?Sized), +) -> IcicleResult<()> +where + C::ScalarField: FieldImpl, + ::Config: ECNTT, +{ + // ... function implementation ... +} +``` + +## Parameters + +- **`input`**: The input data as a slice of `Projective`. This represents points on a specific elliptic curve `C`. +- **`dir`**: The direction of the NTT. It can be `NTTDir::kForward` for forward NTT or `NTTDir::kInverse` for inverse NTT. +- **`cfg`**: The NTT configuration object of type `NTTConfig`. This object specifies parameters for the NTT computation, such as the batch size and algorithm to use. +- **`output`**: The output buffer to write the results into. This should be a slice of `Projective` with the same size as the input. + +## Return Value + +- **`IcicleResult<()>`**: This function returns an `IcicleResult` which is a wrapper type that indicates success or failure of the NTT computation. On success, it contains `Ok(())`. diff --git a/docs/docs/icicle/rust-bindings/multi-gpu.md b/docs/docs/icicle/rust-bindings/multi-gpu.md index 685d5cd4..8e36a539 100644 --- a/docs/docs/icicle/rust-bindings/multi-gpu.md +++ b/docs/docs/icicle/rust-bindings/multi-gpu.md @@ -62,11 +62,11 @@ Sets the current CUDA device by its ID, when calling `set_device` it will set th **Parameters:** -- `device_id: usize`: The ID of the device to set as the current device. Device IDs start from 0. +- **`device_id: usize`**: The ID of the device to set as the current device. Device IDs start from 0. **Returns:** -- `CudaResult<()>`: An empty result indicating success if the device is set successfully. In case of failure, returns a `CudaError`. +- **`CudaResult<()>`**: An empty result indicating success if the device is set successfully. In case of failure, returns a `CudaError`. **Errors:** @@ -88,7 +88,7 @@ Retrieves the number of CUDA devices available on the machine. **Returns:** -- `CudaResult`: The number of available CUDA devices. On success, contains the count of CUDA devices. On failure, returns a `CudaError`. +- **`CudaResult`**: The number of available CUDA devices. On success, contains the count of CUDA devices. On failure, returns a `CudaError`. **Errors:** @@ -109,7 +109,7 @@ Retrieves the ID of the current CUDA device. **Returns:** -- `CudaResult`: The ID of the current CUDA device. On success, contains the device ID. On failure, returns a `CudaError`. +- **`CudaResult`**: The ID of the current CUDA device. On success, contains the device ID. On failure, returns a `CudaError`. **Errors:** @@ -191,7 +191,7 @@ Validates that the specified `device_id` matches the ID of the currently active #### Behavior -- **Panics** if the `device_id` does not match the active device's ID, preventing cross-device operation errors. +- **`Panics`** if the `device_id` does not match the active device's ID, preventing cross-device operation errors. #### Example diff --git a/docs/docs/icicle/rust-bindings/ntt.md b/docs/docs/icicle/rust-bindings/ntt.md index 8bde4129..6c7de174 100644 --- a/docs/docs/icicle/rust-bindings/ntt.md +++ b/docs/docs/icicle/rust-bindings/ntt.md @@ -29,7 +29,7 @@ fn main() { // Create a CUDA stream let stream = CudaStream::create().expect("Failed to create CUDA stream"); let ctx = DeviceContext::default(); // Assuming default device context - ScalarCfg::initialize_domain(ScalarField::from_ark(icicle_omega), &ctx).unwrap(); + ScalarCfg::initialize_domain(ScalarField::from_ark(icicle_omega), &ctx, true).unwrap(); // Configure NTT let mut cfg = ntt::NTTConfig::default(); @@ -61,10 +61,10 @@ pub fn ntt( `ntt:ntt` expects: -`input` - buffer to read the inputs of the NTT from.
-`dir` - whether to compute forward or inverse NTT.
-`cfg` - config used to specify extra arguments of the NTT.
-`output` - buffer to write the NTT outputs into. Must be of the same size as input. +- **`input`** - buffer to read the inputs of the NTT from.
+- **`dir`** - whether to compute forward or inverse NTT.
+- **`cfg`** - config used to specify extra arguments of the NTT.
+- **`output`** - buffer to write the NTT outputs into. Must be of the same size as input. The `input` and `output` buffers can be on device or on host. Being on host means that they will be transferred to device during runtime. @@ -155,13 +155,13 @@ Deciding weather to use `batch NTT` vs `single NTT` is highly dependent on your Before performing NTT operations, its necessary to initialize the NTT domain, It only needs to be called once per GPU since the twiddles are cached. ```rust -ScalarCfg::initialize_domain(ScalarField::from_ark(icicle_omega), &ctx).unwrap(); +ScalarCfg::initialize_domain(ScalarField::from_ark(icicle_omega), &ctx, true).unwrap(); ``` ### `initialize_domain` ```rust -pub fn initialize_domain(primitive_root: F, ctx: &DeviceContext) -> IcicleResult<()> +pub fn initialize_domain(primitive_root: F, ctx: &DeviceContext, fast_twiddles: bool) -> IcicleResult<()> where F: FieldImpl, ::Config: NTT; @@ -177,23 +177,32 @@ where - **`IcicleResult<()>`**: Will return an error if the operation fails. -### `initialize_domain_fast_twiddles_mode` +#### Parameters -Similar to `initialize_domain`, `initialize_domain_fast_twiddles_mode` is a faster implementation and can be used for larger NTTs. +- **`primitive_root`**: The primitive root of unity, chosen based on the maximum NTT size required for the computations. It must be of an order that is a power of two. This root is used to generate twiddle factors that are essential for the NTT operations. + +- **`ctx`**: A reference to a `DeviceContext` specifying which device and stream the computation should be executed on. + +#### Returns + +- **`IcicleResult<()>`**: Will return an error if the operation fails. + +### Releaseing the domain + +The `release_domain` function is responsible for releasing the resources associated with a specific domain in the CUDA device context. ```rust -pub fn initialize_domain_fast_twiddles_mode(primitive_root: F, ctx: &DeviceContext) -> IcicleResult<()> +pub fn release_domain(ctx: &DeviceContext) -> IcicleResult<()> where F: FieldImpl, - ::Config: NTT; + ::Config: NTT ``` #### Parameters -- **`primitive_root`**: The primitive root of unity, chosen based on the maximum NTT size required for the computations. It must be of an order that is a power of two. This root is used to generate twiddle factors that are essential for the NTT operations. - - **`ctx`**: A reference to a `DeviceContext` specifying which device and stream the computation should be executed on. #### Returns -- **`IcicleResult<()>`**: Will return an error if the operation fails. +The function returns an `IcicleResult<()>`, which represents the result of the operation. If the operation is successful, the function returns `Ok(())`, otherwise it returns an error. + diff --git a/docs/docs/icicle/rust-bindings/polynomials.md b/docs/docs/icicle/rust-bindings/polynomials.md new file mode 100644 index 00000000..15e4d3e3 --- /dev/null +++ b/docs/docs/icicle/rust-bindings/polynomials.md @@ -0,0 +1,261 @@ +:::note Please refer to the Polynomials overview page for a deep overview. This section is a brief description of the Rust FFI bindings. +::: + +# Rust FFI Bindings for Univariate Polynomial +This documentation is designed to provide developers with a clear understanding of how to utilize the Rust bindings for polynomial operations efficiently and effectively, leveraging the robust capabilities of both Rust and C++ in their applications. + +## Introduction +The Rust FFI bindings for the Univariate Polynomial serve as a "shallow wrapper" around the underlying C++ implementation. These bindings provide a straightforward Rust interface that directly calls functions from a C++ library, effectively bridging Rust and C++ operations. The Rust layer handles simple interface translations without delving into complex logic or data structures, which are managed on the C++ side. This design ensures efficient data handling, memory management, and execution of polynomial operations directly via C++. +Currently, these bindings are tailored specifically for polynomials where the coefficients, domain, and images are represented as scalar fields. + + +## Initialization Requirements + +Before utilizing any functions from the polynomial API, it is mandatory to initialize the appropriate polynomial backend (e.g., CUDA). Additionally, the NTT (Number Theoretic Transform) domain must also be initialized, as the CUDA backend relies on this for certain operations. Failing to properly initialize these components can result in errors. + +:::note +**Field-Specific Initialization Requirement** + +The ICICLE library is structured such that each field or curve has its dedicated library implementation. As a result, initialization must be performed individually for each field or curve to ensure the correct setup and functionality of the library. +::: + + +## Core Trait: `UnivariatePolynomial` + +The `UnivariatePolynomial` trait encapsulates the essential functionalities required for managing univariate polynomials in the Rust ecosystem. This trait standardizes the operations that can be performed on polynomials, regardless of the underlying implementation details. It allows for a unified approach to polynomial manipulation, providing a suite of methods that are fundamental to polynomial arithmetic. + +### Trait Definition +```rust +pub trait UnivariatePolynomial +where + Self::Field: FieldImpl, + Self::FieldConfig: FieldConfig, +{ + type Field: FieldImpl; + type FieldConfig: FieldConfig; + + // Methods to create polynomials from coefficients or roots-of-unity evaluations. + fn from_coeffs + ?Sized>(coeffs: &S, size: usize) -> Self; + fn from_rou_evals + ?Sized>(evals: &S, size: usize) -> Self; + + // Method to divide this polynomial by another, returning quotient and remainder. + fn divide(&self, denominator: &Self) -> (Self, Self) where Self: Sized; + + // Method to divide this polynomial by the vanishing polynomial 'X^N-1'. + fn div_by_vanishing(&self, degree: u64) -> Self; + + // Methods to add or subtract a monomial in-place. + fn add_monomial_inplace(&mut self, monomial_coeff: &Self::Field, monomial: u64); + fn sub_monomial_inplace(&mut self, monomial_coeff: &Self::Field, monomial: u64); + + // Method to slice the polynomial, creating a sub-polynomial. + fn slice(&self, offset: u64, stride: u64, size: u64) -> Self; + + // Methods to return new polynomials containing only the even or odd terms. + fn even(&self) -> Self; + fn odd(&self) -> Self; + + // Method to evaluate the polynomial at a given domain point. + fn eval(&self, x: &Self::Field) -> Self::Field; + + // Method to evaluate the polynomial over a domain and store the results. + fn eval_on_domain + ?Sized, E: HostOrDeviceSlice + ?Sized>( + &self, + domain: &D, + evals: &mut E, + ); + + // Method to retrieve a coefficient at a specific index. + fn get_coeff(&self, idx: u64) -> Self::Field; + + // Method to copy coefficients into a provided slice. + fn copy_coeffs + ?Sized>(&self, start_idx: u64, coeffs: &mut S); + + // Method to get the degree of the polynomial. + fn degree(&self) -> i64; +} +``` + +## `DensePolynomial` Struct +The DensePolynomial struct represents a dense univariate polynomial in Rust, leveraging a handle to manage its underlying memory within the CUDA device context. This struct acts as a high-level abstraction over complex C++ memory management practices, facilitating the integration of high-performance polynomial operations through Rust's Foreign Function Interface (FFI) bindings. + +```rust +pub struct DensePolynomial { + handle: PolynomialHandle, +} +``` + +### Traits implementation and methods + +#### `Drop` +Ensures proper resource management by releasing the CUDA memory when a DensePolynomial instance goes out of scope. This prevents memory leaks and ensures that resources are cleaned up correctly, adhering to Rust's RAII (Resource Acquisition Is Initialization) principles. + +#### `Clone` +Provides a way to create a new instance of a DensePolynomial with its own unique handle, thus duplicating the polynomial data in the CUDA context. Cloning is essential since the DensePolynomial manages external resources, which cannot be safely shared across instances without explicit duplication. + +#### Operator Overloading: `Add`, `Sub`, `Mul`, `Rem`, `Div` +These traits are implemented for references to DensePolynomial (i.e., &DensePolynomial), enabling natural mathematical operations such as addition (+), subtraction (-), multiplication (*), division (/), and remainder (%). This syntactic convenience allows users to compose complex polynomial expressions in a way that is both readable and expressive. + +#### Key Methods +In addition to the traits, the following methods are implemented: + +```rust +impl DensePolynomial { + pub fn init_cuda_backend() -> bool {...} + // Returns a mutable slice of the polynomial coefficients on the device + pub fn coeffs_mut_slice(&mut self) -> &mut DeviceSlice {...} +} +``` + +:::note Might be consolidated with `UnivariatePolynomial` trait +::: + +## Flexible Memory Handling With `HostOrDeviceSlice` +The DensePolynomial API is designed to accommodate a wide range of computational environments by supporting both host and device memory through the `HostOrDeviceSlice` trait. This approach ensures that polynomial operations can be seamlessly executed regardless of where the data resides, making the API highly adaptable and efficient for various hardware configurations. + +### Overview of `HostOrDeviceSlice` +The HostOrDeviceSlice is a Rust trait that abstracts over slices of memory that can either be on the host (CPU) or the device (GPU), as managed by CUDA. This abstraction is crucial for high-performance computing scenarios where data might need to be moved between different memory spaces depending on the operations being performed and the specific hardware capabilities available. + +### Usage in API Functions +Functions within the DensePolynomial API that deal with polynomial coefficients or evaluations use the HostOrDeviceSlice trait to accept inputs. This design allows the functions to be agnostic of the actual memory location of the data, whether it's in standard system RAM accessible by the CPU or in GPU memory accessible by CUDA cores. + +```rust +// Assume `coeffs` could either be in host memory or CUDA device memory +let coeffs: DeviceSlice = DeviceVec::::cuda_malloc(coeffs_len).unwrap(); +let p_from_coeffs = PolynomialBabyBear::from_coeffs(&coeffs, coeffs.len()); + +// Similarly for evaluations from roots of unity +let evals: HostSlice = HostSlice::from_slice(&host_memory_evals); +let p_from_evals = PolynomialBabyBear::from_rou_evals(&evals, evals.len()); + +// Same applies for any API that accepts HostOrDeviceSlice +``` + +## Usage +This section outlines practical examples demonstrating how to utilize the `DensePolynomial` Rust API. The API is flexible, supporting multiple scalar fields. Below are examples showing how to use polynomials defined over different fields and perform a variety of operations. + +### Initialization and Basic Operations +First, choose the appropriate field implementation for your polynomial operations, initializing the CUDA backend if necessary +```rust +use icicle_babybear::polynomials::DensePolynomial as PolynomialBabyBear; + +// Initialize the CUDA backend for polynomial operations +PolynomialBabyBear::init_cuda_backend(); +let f = PolynomialBabyBear::from_coeffs(...); + +// now use f by calling the implemented traits + +// For operations over another field, such as BN254 +use icicle_bn254::polynomials::DensePolynomial as PolynomialBn254; +// Use PolynomialBn254 similarly +``` + +### Creation +Polynomials can be created from coefficients or evaluations: + +```rust +// Assume F is the field type (e.g. icicle_bn254::curve::ScalarField or a type parameter) +let coeffs = ...; +let p_from_coeffs = PolynomialBabyBear::from_coeffs(HostSlice::from_slice(&coeffs), size); + +let evals = ...; +let p_from_evals = PolynomialBabyBear::from_rou_evals(HostSlice::from_slice(&evals), size); + +``` + +### Arithmetic Operations +Utilize overloaded operators for intuitive mathematical expressions: + +```rust +let add = &f + &g; // Addition +let sub = &f - &g; // Subtraction +let mul = &f * &g; // Multiplication +let mul_scalar = &f * &scalar; // Scalar multiplication +``` + +### Division and Remainder +Compute quotient and remainder or perform division by a vanishing polynomial: + +```rust +let (q, r) = f.divide(&g); // Compute both quotient and remainder +let q = &f / &g; // Quotient +let r = &f % &g; // Remainder + +let h = f.div_by_vanishing(N); // Division by V(x) = X^N - 1 + +``` + +### Monomial Operations +Add or subtract monomials in-place for efficient polynomial manipulation: + +```rust +f.add_monomial_inplace(&three, 1 /*monmoial*/); // Adds 3*x to f +f.sub_monomial_inplace(&one, 0 /*monmoial*/); // Subtracts 1 from f +``` + +### Slicing +Extract specific components: + +```rust +let even = f.even(); // Polynomial of even-indexed terms +let odd = f.odd(); // Polynomial of odd-indexed terms +let arbitrary_slice = f.slice(offset, stride, size); +``` + +### Evaluate +Evaluate the polynoomial: + +```rust +let x = rand(); // Random field element +let f_x = f.eval(&x); // Evaluate f at x + +// Evaluate on a predefined domain +let domain = [one, two, three]; +let mut host_evals = vec![ScalarField::zero(); domain.len()]; +f.eval_on_domain(HostSlice::from_slice(&domain), HostSlice::from_mut_slice(&mut host_evals)); +``` + +### Read coefficients +Read or copy polynomial coefficients for further processing: + +```rust +let x_squared_coeff = f.get_coeff(2); // Coefficient of x^2 + +// Copy coefficients to a device-specific memory space +let mut device_mem = DeviceVec::::cuda_malloc(coeffs.len()).unwrap(); +f.copy_coeffs(0, &mut device_mem[..]); +``` + +### Polynomial Degree +Determine the highest power of the variable with a non-zero coefficient: + +```rust +let deg = f.degree(); // Degree of the polynomial +``` + +### Memory Management: Views (rust slices) +Rust enforces correct usage of views at compile time, eliminating the need for runtime checks: + +```rust +let mut f = Poly::from_coeffs(HostSlice::from_slice(&coeffs), size); + +// Obtain a mutable slice of coefficients as a DeviceSlice +let coeffs_slice_dev = f.coeffs_mut_slice(); + +// Operations on f are restricted here due to mutable borrow of coeffs_slice_dev + +// Compute evaluations or perform other operations directly using the slice +// example: evaluate f on a coset of roots-of-unity. Computing from GPU to HOST/GPU +let mut config: NTTConfig<'_, F> = NTTConfig::default(); +config.coset_gen = /*some coset gen*/; +let mut coset_evals = vec![F::zero(); coeffs_slice_dev.len()]; +ntt( + coeffs_slice_dev, + NTTDir::kForward, + &config, + HostSlice::from_mut_slice(&mut coset_evals), +) +.unwrap(); + +// now can f can be borrowed once again +``` diff --git a/docs/docs/icicle/rust-bindings/vec-ops.md b/docs/docs/icicle/rust-bindings/vec-ops.md index 0f537edc..e4f7525b 100644 --- a/docs/docs/icicle/rust-bindings/vec-ops.md +++ b/docs/docs/icicle/rust-bindings/vec-ops.md @@ -74,7 +74,6 @@ pub struct VecOpsConfig<'a> { is_a_on_device: bool, is_b_on_device: bool, is_result_on_device: bool, - is_result_montgomery_form: bool, pub is_async: bool, } ``` @@ -85,7 +84,6 @@ pub struct VecOpsConfig<'a> { - **`is_a_on_device`**: Indicates if the first operand vector resides in device memory. - **`is_b_on_device`**: Indicates if the second operand vector resides in device memory. - **`is_result_on_device`**: Specifies if the result vector should be stored in device memory. -- **`is_result_montgomery_form`**: Determines if the result should be in Montgomery form. - **`is_async`**: Enables asynchronous operation. If `true`, operations are non-blocking; otherwise, they block the current thread. ### Default Configuration @@ -112,7 +110,6 @@ impl<'a> VecOpsConfig<'a> { is_a_on_device: false, is_b_on_device: false, is_result_on_device: false, - is_result_montgomery_form: false, is_async: false, } } @@ -157,3 +154,64 @@ All operations are element-wise operations, and the results placed into the `res - **`add`**: Computes the element-wise sum of two vectors. - **`sub`**: Computes the element-wise difference between two vectors. - **`mul`**: Performs element-wise multiplication of two vectors. + + +## MatrixTranspose API Documentation + +This section describes the functionality of the `TransposeMatrix` function used for matrix transposition. + +The function takes a matrix represented as a 1D slice and transposes it, storing the result in another 1D slice. + +### Function + +```rust +pub fn transpose_matrix( + input: &HostOrDeviceSlice, + row_size: u32, + column_size: u32, + output: &mut HostOrDeviceSlice, + ctx: &DeviceContext, + on_device: bool, + is_async: bool, +) -> IcicleResult<()> +where + F: FieldImpl, + ::Config: VecOps +``` + +### Parameters + +- **`input`**: A slice representing the input matrix. The slice can be stored on either the host or the device. +- **`row_size`**: The number of rows in the input matrix. +- **`column_size`**: The number of columns in the input matrix. +- **`output`**: A mutable slice to store the transposed matrix. The slice can be stored on either the host or the device. +- **`ctx`**: A reference to the `DeviceContext`, which provides information about the device where the operation will be performed. +- **`on_device`**: A boolean flag indicating whether the inputs and outputs are on the device. +- **`is_async`**: A boolean flag indicating whether the operation should be performed asynchronously. + +### Return Value + +`Ok(())` if the operation is successful, or an `IcicleResult` error otherwise. + +### Example + +```rust +use icicle::HostOrDeviceSlice; +use icicle::DeviceContext; +use icicle::FieldImpl; +use icicle::VecOps; + +let input: HostOrDeviceSlice = // ...; +let mut output: HostOrDeviceSlice = // ...; +let ctx: DeviceContext = // ...; + +transpose_matrix(&input, 5, 4, &mut output, &ctx, true, false) + .expect("Failed to transpose matrix"); +``` + + +The function takes a matrix represented as a 1D slice, transposes it, and stores the result in another 1D slice. The input and output slices can be stored on either the host or the device, and the operation can be performed synchronously or asynchronously. + +The function is generic and can work with any type `F` that implements the `FieldImpl` trait. The `::Config` type must also implement the `VecOps` trait, which provides the `transpose` method used to perform the actual transposition. + +The function returns an `IcicleResult<()>`, indicating whether the operation was successful or not. \ No newline at end of file diff --git a/docs/docs/icicle/supporting-additional-curves.md b/docs/docs/icicle/supporting-additional-curves.md deleted file mode 100644 index 97b7b556..00000000 --- a/docs/docs/icicle/supporting-additional-curves.md +++ /dev/null @@ -1,117 +0,0 @@ -# Supporting Additional Curves - -We understand the need for ZK developers to use different curves, some common some more exotic. For this reason we designed ICICLE to allow developers to add any curve they desire. - -## ICICLE Core - -ICICLE core is very generic by design so all algorithms and primitives are designed to work based of configuration files [selected during compile](https://github.com/ingonyama-zk/icicle/blob/main/icicle/curves/curve_config.cuh) time. This is why we compile ICICLE Core per curve. - -To add support for a new curve you must create a new file under [`icicle/curves`](https://github.com/ingonyama-zk/icicle/tree/main/icicle/curves). The file should be named `_params.cuh`. - -### Adding curve_name_params.cuh - -Start by copying `bn254_params.cuh` contents in your params file. Params should include: - - **fq_config** - parameters of the Base field. - - **limbs_count** - `ceil(field_byte_size / 4)`. - - **modulus_bit_count** - bit-size of the modulus. - - **num_of_reductions** - the number of times to reduce in reduce function. Use 2 if not sure. - - **modulus** - modulus of the field. - - **modulus_2** - modulus * 2. - - **modulus_4** - modulus * 4. - - **neg_modulus** - negated modulus. - - **modulus_wide** - modulus represented as a double-sized integer. - - **modulus_squared** - modulus**2 represented as a double-sized integer. - - **modulus_squared_2** - 2 * modulus**2 represented as a double-sized integer. - - **modulus_squared_4** - 4 * modulus**2 represented as a double-sized integer. - - **m** - value used in multiplication. Can be computed as `2**(2*modulus_bit_count) // modulus`. - - **one** - multiplicative identity. - - **zero** - additive identity. - - **montgomery_r** - `2 ** M % modulus` where M is a closest (larger than) bitsize multiple of 32. E.g. 384 or 768 for bls and bw curves respectively - - **montgomery_r_inv** - `2 ** (-M) % modulus` - - **fp_config** - parameters of the Scalar field. - Same as fq_config, but with additional arguments: - - **omegas_count** - [two-adicity](https://cryptologie.net/article/559/whats-two-adicity/) of the field. And thus the maximum size of NTT. - - **omegas** - an array of omegas for NTTs. An array of size `omegas_count`. The ith element is equal to `1.nth_root(2**(2**(omegas_count-i)))`. - - **inv** - an array of inverses of powers of two in a field. Ith element is equal to `(2 ** (i+1)) ** -1`. - - **G1 generators points** - affine coordinates of the generator point. - - **G2 generators points** - affine coordinates of the extension generator. Remove these if `G2` is not supported. - - **Weierstrass b value** - base field element equal to value of `b` in the curve equation. - - **Weierstrass b value G2** - base field element equal to value of `b` for the extension. Remove this if `G2` is not supported. - - :::note - - All the params are not in Montgomery form. - - ::: - - :::note - - To convert number values into `storage` type you can use the following python function - -```python -import struct - -def unpack(x, field_size): - return ', '.join(["0x" + format(x, '08x') for x in struct.unpack('I' * (field_size) // 4, int(x).to_bytes(field_size, 'little'))]) -``` - -::: - -We also require some changes to [`curve_config.cuh`](https://github.com/ingonyama-zk/icicle/blob/main/icicle/curves/curve_config.cuh#L16-L29), we need to add a new curve id. - -``` -... - -#define BN254 1 -#define BLS12_381 2 -#define BLS12_377 3 -#define BW6_761 4 -#define GRUMPKIN 5 -#define 6 - -... -``` - -Make sure to modify the [rest of the file](https://github.com/ingonyama-zk/icicle/blob/4beda3a900eda961f39af3a496f8184c52bf3b41/icicle/curves/curve_config.cuh#L16-L29) accordingly. - -Finally we must modify the [`make` file](https://github.com/ingonyama-zk/icicle/blob/main/icicle/CMakeLists.txt#L64) to make sure we can compile our new curve. - -``` -set(SUPPORTED_CURVES bn254;bls12_381;bls12_377;bw6_761;grumpkin;) -``` - -### Adding Poseidon support - -If you want your curve to implement a Poseidon hash function or a tree builder, you will need to pre-calculate its optimized parameters. -Copy [constants_template.h](https://github.com/ingonyama-zk/icicle/blob/main/icicle/appUtils/poseidon/constants/constants_template.h) into `icicle/appUtils/poseidon/constants/_poseidon.h`. Run the [constants generation script](https://dev.ingonyama.com/icicle/primitives/poseidon#constants). The script will print the number of partial rounds and generate a `constants.bin` file. Use `xxd -i constants.bin` to parse the file into C declarations. Copy the `unsigned char constants_bin[]` contents inside your new file. Repeat this process for arities 2, 4, 8 and 11. - -After you've generated the constants, add your curve in this [SUPPORTED_CURVES_WITH_POSEIDON](https://github.com/ingonyama-zk/icicle/blob/main/icicle/CMakeLists.txt#L72) in the `CMakeLists.txt`. - -## Bindings - -In order to support a new curve in the binding libraries you first must support it in ICICLE core. - -### Rust - -Go to [rust curves folder](https://github.com/ingonyama-zk/icicle/tree/main/wrappers/rust/icicle-curves) and copy `icicle-curve-template` to a new folder named `icicle-`. - -Find all the occurrences of `` placeholder inside the crate. (You can use `Ctrl+Shift+F` in VS Code or `grep -nr ""` in bash). You will then need to replace each occurrence with your new curve name. - -#### Limbs - -Go to your curve's `curve.rs` file and set `SCALAR_LIMBS`, `BASE_LIMBS` and `G2_BASE_LIMBS` (if G2 is needed) to a minimum number of `u64` required to store a single scalar field / base field element respectively. -e.g. for bn254, scalar field is 254 bit so `SCALAR_LIMBS` is set to 4. - -#### Primitives - -If your curve doesn't support some of the primitives (ntt/msm/poseidon/merkle tree/), or you simply don't want to include it, just remove a corresponding module from `src` and then from `lib.rs` - -#### G2 - -If your curve doesn't support G2 - remove all the code under `#[cfg(feature = "g2")]` and remove the feature from [Cargo.toml](https://github.com/ingonyama-zk/icicle/blob/main/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml#L29) and [build.rs](https://github.com/ingonyama-zk/icicle/blob/main/wrappers/rust/icicle-curves/icicle-bn254/build.rs#L15). - -After this is done, add your new crate in the [global Cargo.toml](https://github.com/ingonyama-zk/icicle/tree/main/wrappers/rust/Cargo.toml). - -### Golang - -Golang is WIP in v1, coming soon. Please checkout a previous [release v0.1.0](https://github.com/ingonyama-zk/icicle/releases/tag/v0.1.0) for golang bindings. diff --git a/docs/package-lock.json b/docs/package-lock.json index 45e5f48f..88c8705b 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,7 +1,7 @@ { "name": "docusaurus", "version": "0.0.0", - "lockfileVersion": 3, + "lockfileVersion": 2, "requires": true, "packages": { "": { @@ -3680,6 +3680,8 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "optional": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3694,7 +3696,9 @@ "node_modules/ajv-formats/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "optional": true, + "peer": true }, "node_modules/ajv-keywords": { "version": "3.5.2", @@ -13677,5 +13681,9758 @@ "url": "https://github.com/sponsors/wooorm" } } + }, + "dependencies": { + "@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "requires": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "requires": { + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "requires": { + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "requires": {} + }, + "@algolia/cache-browser-local-storage": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz", + "integrity": "sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==", + "requires": { + "@algolia/cache-common": "4.22.1" + } + }, + "@algolia/cache-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.22.1.tgz", + "integrity": "sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==" + }, + "@algolia/cache-in-memory": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz", + "integrity": "sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==", + "requires": { + "@algolia/cache-common": "4.22.1" + } + }, + "@algolia/client-account": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.22.1.tgz", + "integrity": "sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==", + "requires": { + "@algolia/client-common": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "@algolia/client-analytics": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.22.1.tgz", + "integrity": "sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==", + "requires": { + "@algolia/client-common": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "@algolia/client-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.22.1.tgz", + "integrity": "sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==", + "requires": { + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "@algolia/client-personalization": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.22.1.tgz", + "integrity": "sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==", + "requires": { + "@algolia/client-common": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "@algolia/client-search": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.22.1.tgz", + "integrity": "sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==", + "requires": { + "@algolia/client-common": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" + }, + "@algolia/logger-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.22.1.tgz", + "integrity": "sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==" + }, + "@algolia/logger-console": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.22.1.tgz", + "integrity": "sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==", + "requires": { + "@algolia/logger-common": "4.22.1" + } + }, + "@algolia/requester-browser-xhr": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz", + "integrity": "sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==", + "requires": { + "@algolia/requester-common": "4.22.1" + } + }, + "@algolia/requester-common": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.22.1.tgz", + "integrity": "sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==" + }, + "@algolia/requester-node-http": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz", + "integrity": "sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==", + "requires": { + "@algolia/requester-common": "4.22.1" + } + }, + "@algolia/transporter": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.22.1.tgz", + "integrity": "sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==", + "requires": { + "@algolia/cache-common": "4.22.1", + "@algolia/logger-common": "4.22.1", + "@algolia/requester-common": "4.22.1" + } + }, + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + } + }, + "@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" + }, + "@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "requires": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.23.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz", + "integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz", + "integrity": "sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==", + "requires": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "requires": { + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + } + }, + "@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + } + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==" + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + }, + "@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==" + }, + "@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "requires": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + } + }, + "@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "requires": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + } + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==" + }, + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + } + }, + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "requires": {} + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-async-generator-functions": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz", + "integrity": "sha512-8Q3veQEDGe14dTYuwagbRtwxQDnytyg1JFu4/HwEMETeofocrB0U0ejBJIXoeG/t2oXZ8kzCyI0ZZfbT80VFNQ==", + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "requires": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz", + "integrity": "sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "requires": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz", + "integrity": "sha512-KDlPRM6sLo4o1FkiSlXoAa8edLXFsKKIda779fbLrvmeuc3itnjCtaO6RrtoaANsIJANj+Vk1zqbZIMhkCAHVw==", + "requires": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "requires": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "requires": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + } + }, + "@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.23.3.tgz", + "integrity": "sha512-zP0QKq/p6O42OL94udMgSfKXyse4RyJ0JqbQ34zDAONWjyrEsghYEyTSK5FIpmXmCpB55SHokL1cRRKHv8L2Qw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", + "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "requires": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz", + "integrity": "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.9.tgz", + "integrity": "sha512-A7clW3a0aSjm3ONU9o2HAILSegJCYlEZmOhmBRReVtIpY/Z/p7yIZ+wR41Z+UipwdGuqwtID/V/dOdZXjwi9gQ==", + "requires": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", + "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/preset-env": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz", + "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==", + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.9", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.8", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.9", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.8", + "babel-plugin-polyfill-corejs3": "^0.9.0", + "babel-plugin-polyfill-regenerator": "^0.5.5", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/preset-react": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.23.3.tgz", + "integrity": "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-react-display-name": "^7.23.3", + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.23.3" + } + }, + "@babel/preset-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", + "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3" + } + }, + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, + "@babel/runtime": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", + "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@babel/runtime-corejs3": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.9.tgz", + "integrity": "sha512-oeOFTrYWdWXCvXGB5orvMTJ6gCZ9I6FBjR+M38iKNXCsPxr4xT0RTdg5uz1H7QP8pp74IzPtwritEr+JscqHXQ==", + "requires": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + } + }, + "@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + } + }, + "@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==" + }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true + }, + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==" + }, + "@docsearch/css": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", + "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==" + }, + "@docsearch/react": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", + "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", + "requires": { + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.5.2", + "algoliasearch": "^4.19.1" + } + }, + "@docusaurus/core": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-2.0.0-beta.18.tgz", + "integrity": "sha512-puV7l+0/BPSi07Xmr8tVktfs1BzhC8P5pm6Bs2CfvysCJ4nefNCD1CosPc1PGBWy901KqeeEJ1aoGwj9tU3AUA==", + "requires": { + "@babel/core": "^7.17.8", + "@babel/generator": "^7.17.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", + "@babel/preset-typescript": "^7.16.7", + "@babel/runtime": "^7.17.8", + "@babel/runtime-corejs3": "^7.17.8", + "@babel/traverse": "^7.17.3", + "@docusaurus/cssnano-preset": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/mdx-loader": "2.0.0-beta.18", + "@docusaurus/react-loadable": "5.5.2", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "@slorber/static-site-generator-webpack-plugin": "^4.0.4", + "@svgr/webpack": "^6.2.1", + "autoprefixer": "^10.4.4", + "babel-loader": "^8.2.4", + "babel-plugin-dynamic-import-node": "2.3.0", + "boxen": "^6.2.1", + "chokidar": "^3.5.3", + "clean-css": "^5.2.4", + "cli-table3": "^0.6.1", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "copy-webpack-plugin": "^10.2.4", + "core-js": "^3.21.1", + "css-loader": "^6.7.1", + "css-minimizer-webpack-plugin": "^3.4.1", + "cssnano": "^5.1.5", + "del": "^6.0.0", + "detect-port": "^1.3.0", + "escape-html": "^1.0.3", + "eta": "^1.12.3", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.1", + "html-minifier-terser": "^6.1.0", + "html-tags": "^3.1.0", + "html-webpack-plugin": "^5.5.0", + "import-fresh": "^3.3.0", + "is-root": "^2.1.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "mini-css-extract-plugin": "^2.6.0", + "nprogress": "^0.2.0", + "postcss": "^8.4.12", + "postcss-loader": "^6.2.1", + "prompts": "^2.4.2", + "react-dev-utils": "^12.0.0", + "react-helmet-async": "^1.2.3", + "react-loadable": "npm:@docusaurus/react-loadable@5.5.2", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.2.0", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.2.0", + "remark-admonitions": "^1.2.1", + "rtl-detect": "^1.0.4", + "semver": "^7.3.5", + "serve-handler": "^6.1.3", + "shelljs": "^0.8.5", + "terser-webpack-plugin": "^5.3.1", + "tslib": "^2.3.1", + "update-notifier": "^5.1.0", + "url-loader": "^4.1.1", + "wait-on": "^6.0.1", + "webpack": "^5.70.0", + "webpack-bundle-analyzer": "^4.5.0", + "webpack-dev-server": "^4.7.4", + "webpack-merge": "^5.8.0", + "webpackbar": "^5.0.2" + } + }, + "@docusaurus/cssnano-preset": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.18.tgz", + "integrity": "sha512-VxhYmpyx16Wv00W9TUfLVv0NgEK/BwP7pOdWoaiELEIAMV7SO1+6iB8gsFUhtfKZ31I4uPVLMKrCyWWakoFeFA==", + "requires": { + "cssnano-preset-advanced": "^5.3.1", + "postcss": "^8.4.12", + "postcss-sort-media-queries": "^4.2.1" + } + }, + "@docusaurus/logger": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-2.0.0-beta.18.tgz", + "integrity": "sha512-frNe5vhH3mbPmH980Lvzaz45+n1PQl3TkslzWYXQeJOkFX17zUd3e3U7F9kR1+DocmAqHkgAoWuXVcvEoN29fg==", + "requires": { + "chalk": "^4.1.2", + "tslib": "^2.3.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@docusaurus/mdx-loader": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.18.tgz", + "integrity": "sha512-pOmAQM4Y1jhuZTbEhjh4ilQa74Mh6Q0pMZn1xgIuyYDdqvIOrOlM/H0i34YBn3+WYuwsGim4/X0qynJMLDUA4A==", + "requires": { + "@babel/parser": "^7.17.8", + "@babel/traverse": "^7.17.3", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@mdx-js/mdx": "^1.6.22", + "escape-html": "^1.0.3", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.1", + "image-size": "^1.0.1", + "mdast-util-to-string": "^2.0.0", + "remark-emoji": "^2.1.0", + "stringify-object": "^3.3.0", + "tslib": "^2.3.1", + "unist-util-visit": "^2.0.2", + "url-loader": "^4.1.1", + "webpack": "^5.70.0" + } + }, + "@docusaurus/module-type-aliases": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.0-beta.18.tgz", + "integrity": "sha512-e6mples8FZRyT7QyqidGS6BgkROjM+gljJsdOqoctbtBp+SZ5YDjwRHOmoY7eqEfsQNOaFZvT2hK38ui87hCRA==", + "requires": { + "@docusaurus/types": "2.0.0-beta.18", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "*" + } + }, + "@docusaurus/plugin-content-blog": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.18.tgz", + "integrity": "sha512-qzK83DgB+mxklk3PQC2nuTGPQD/8ogw1nXSmaQpyXAyhzcz4CXAZ9Swl/Ee9A/bvPwQGnSHSP3xqIYl8OkFtfw==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/mdx-loader": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "cheerio": "^1.0.0-rc.10", + "feed": "^4.2.2", + "fs-extra": "^10.0.1", + "lodash": "^4.17.21", + "reading-time": "^1.5.0", + "remark-admonitions": "^1.2.1", + "tslib": "^2.3.1", + "utility-types": "^3.10.0", + "webpack": "^5.70.0" + } + }, + "@docusaurus/plugin-content-docs": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.18.tgz", + "integrity": "sha512-z4LFGBJuzn4XQiUA7OEA2SZTqlp+IYVjd3NrCk/ZUfNi1tsTJS36ATkk9Y6d0Nsp7K2kRXqaXPsz4adDgeIU+Q==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/mdx-loader": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "combine-promises": "^1.1.0", + "fs-extra": "^10.0.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "remark-admonitions": "^1.2.1", + "tslib": "^2.3.1", + "utility-types": "^3.10.0", + "webpack": "^5.70.0" + } + }, + "@docusaurus/plugin-content-pages": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.18.tgz", + "integrity": "sha512-CJ2Xeb9hQrMeF4DGywSDVX2TFKsQpc8ZA7czyeBAAbSFsoRyxXPYeSh8aWljqR4F1u/EKGSKy0Shk/D4wumaHw==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/mdx-loader": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "fs-extra": "^10.0.1", + "remark-admonitions": "^1.2.1", + "tslib": "^2.3.1", + "webpack": "^5.70.0" + } + }, + "@docusaurus/plugin-debug": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.18.tgz", + "integrity": "sha512-inLnLERgG7q0WlVmK6nYGHwVqREz13ivkynmNygEibJZToFRdgnIPW+OwD8QzgC5MpQTJw7+uYjcitpBumy1Gw==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "fs-extra": "^10.0.1", + "react-json-view": "^1.21.3", + "tslib": "^2.3.1" + } + }, + "@docusaurus/plugin-google-analytics": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.18.tgz", + "integrity": "sha512-s9dRBWDrZ1uu3wFXPCF7yVLo/+5LUFAeoxpXxzory8gn9GYDt8ZDj80h5DUyCLxiy72OG6bXWNOYS/Vc6cOPXQ==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "tslib": "^2.3.1" + } + }, + "@docusaurus/plugin-google-gtag": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.18.tgz", + "integrity": "sha512-h7vPuLVo/9pHmbFcvb4tCpjg4SxxX4k+nfVDyippR254FM++Z/nA5pRB0WvvIJ3ZTe0ioOb5Wlx2xdzJIBHUNg==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "tslib": "^2.3.1" + } + }, + "@docusaurus/plugin-sitemap": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.18.tgz", + "integrity": "sha512-Klonht0Ye3FivdBpS80hkVYNOH+8lL/1rbCPEV92rKhwYdwnIejqhdKct4tUTCl8TYwWiyeUFQqobC/5FNVZPQ==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "fs-extra": "^10.0.1", + "sitemap": "^7.1.1", + "tslib": "^2.3.1" + } + }, + "@docusaurus/preset-classic": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.18.tgz", + "integrity": "sha512-TfDulvFt/vLWr/Yy7O0yXgwHtJhdkZ739bTlFNwEkRMAy8ggi650e52I1I0T79s67llecb4JihgHPW+mwiVkCQ==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/plugin-content-blog": "2.0.0-beta.18", + "@docusaurus/plugin-content-docs": "2.0.0-beta.18", + "@docusaurus/plugin-content-pages": "2.0.0-beta.18", + "@docusaurus/plugin-debug": "2.0.0-beta.18", + "@docusaurus/plugin-google-analytics": "2.0.0-beta.18", + "@docusaurus/plugin-google-gtag": "2.0.0-beta.18", + "@docusaurus/plugin-sitemap": "2.0.0-beta.18", + "@docusaurus/theme-classic": "2.0.0-beta.18", + "@docusaurus/theme-common": "2.0.0-beta.18", + "@docusaurus/theme-search-algolia": "2.0.0-beta.18" + } + }, + "@docusaurus/react-loadable": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", + "integrity": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==", + "requires": { + "@types/react": "*", + "prop-types": "^15.6.2" + } + }, + "@docusaurus/theme-classic": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.18.tgz", + "integrity": "sha512-WJWofvSGKC4Luidk0lyUwkLnO3DDynBBHwmt4QrV+aAVWWSOHUjA2mPOF6GLGuzkZd3KfL9EvAfsU0aGE1Hh5g==", + "requires": { + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/plugin-content-blog": "2.0.0-beta.18", + "@docusaurus/plugin-content-docs": "2.0.0-beta.18", + "@docusaurus/plugin-content-pages": "2.0.0-beta.18", + "@docusaurus/theme-common": "2.0.0-beta.18", + "@docusaurus/theme-translations": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-common": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "@mdx-js/react": "^1.6.22", + "clsx": "^1.1.1", + "copy-text-to-clipboard": "^3.0.1", + "infima": "0.2.0-alpha.38", + "lodash": "^4.17.21", + "postcss": "^8.4.12", + "prism-react-renderer": "^1.3.1", + "prismjs": "^1.27.0", + "react-router-dom": "^5.2.0", + "rtlcss": "^3.5.0" + } + }, + "@docusaurus/theme-common": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-2.0.0-beta.18.tgz", + "integrity": "sha512-3pI2Q6ttScDVTDbuUKAx+TdC8wmwZ2hfWk8cyXxksvC9bBHcyzXhSgcK8LTsszn2aANyZ3e3QY2eNSOikTFyng==", + "requires": { + "@docusaurus/module-type-aliases": "2.0.0-beta.18", + "@docusaurus/plugin-content-blog": "2.0.0-beta.18", + "@docusaurus/plugin-content-docs": "2.0.0-beta.18", + "@docusaurus/plugin-content-pages": "2.0.0-beta.18", + "clsx": "^1.1.1", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^1.3.1", + "tslib": "^2.3.1", + "utility-types": "^3.10.0" + } + }, + "@docusaurus/theme-search-algolia": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.18.tgz", + "integrity": "sha512-2w97KO/gnjI49WVtYQqENpQ8iO1Sem0yaTxw7/qv/ndlmIAQD0syU4yx6GsA7bTQCOGwKOWWzZSetCgUmTnWgA==", + "requires": { + "@docsearch/react": "^3.0.0", + "@docusaurus/core": "2.0.0-beta.18", + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/plugin-content-docs": "2.0.0-beta.18", + "@docusaurus/theme-common": "2.0.0-beta.18", + "@docusaurus/theme-translations": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "@docusaurus/utils-validation": "2.0.0-beta.18", + "algoliasearch": "^4.13.0", + "algoliasearch-helper": "^3.7.4", + "clsx": "^1.1.1", + "eta": "^1.12.3", + "fs-extra": "^10.0.1", + "lodash": "^4.17.21", + "tslib": "^2.3.1", + "utility-types": "^3.10.0" + } + }, + "@docusaurus/theme-translations": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.18.tgz", + "integrity": "sha512-1uTEUXlKC9nco1Lx9H5eOwzB+LP4yXJG5wfv1PMLE++kJEdZ40IVorlUi3nJnaa9/lJNq5vFvvUDrmeNWsxy/Q==", + "requires": { + "fs-extra": "^10.0.1", + "tslib": "^2.3.1" + } + }, + "@docusaurus/types": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-2.0.0-beta.18.tgz", + "integrity": "sha512-zkuSmPQYP3+z4IjGHlW0nGzSSpY7Sit0Nciu/66zSb5m07TK72t6T1MlpCAn/XijcB9Cq6nenC3kJh66nGsKYg==", + "requires": { + "commander": "^5.1.0", + "joi": "^17.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.70.0", + "webpack-merge": "^5.8.0" + } + }, + "@docusaurus/utils": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-2.0.0-beta.18.tgz", + "integrity": "sha512-v2vBmH7xSbPwx3+GB90HgLSQdj+Rh5ELtZWy7M20w907k0ROzDmPQ/8Ke2DK3o5r4pZPGnCrsB3SaYI83AEmAA==", + "requires": { + "@docusaurus/logger": "2.0.0-beta.18", + "@svgr/webpack": "^6.2.1", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.1", + "github-slugger": "^1.4.0", + "globby": "^11.1.0", + "gray-matter": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", + "resolve-pathname": "^3.0.0", + "shelljs": "^0.8.5", + "tslib": "^2.3.1", + "url-loader": "^4.1.1", + "webpack": "^5.70.0" + } + }, + "@docusaurus/utils-common": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-2.0.0-beta.18.tgz", + "integrity": "sha512-pK83EcOIiKCLGhrTwukZMo5jqd1sqqqhQwOVyxyvg+x9SY/lsnNzScA96OEfm+qQLBwK1OABA7Xc1wfkgkUxvw==", + "requires": { + "tslib": "^2.3.1" + } + }, + "@docusaurus/utils-validation": { + "version": "2.0.0-beta.18", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.18.tgz", + "integrity": "sha512-3aDrXjJJ8Cw2MAYEk5JMNnr8UHPxmVNbPU/PIHFWmWK09nJvs3IQ8nc9+8I30aIjRdIyc/BIOCxgvAcJ4hsxTA==", + "requires": { + "@docusaurus/logger": "2.0.0-beta.18", + "@docusaurus/utils": "2.0.0-beta.18", + "joi": "^17.6.0", + "js-yaml": "^4.1.0", + "tslib": "^2.3.1" + } + }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + }, + "@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + }, + "@mdx-js/mdx": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz", + "integrity": "sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA==", + "requires": { + "@babel/core": "7.12.9", + "@babel/plugin-syntax-jsx": "7.12.1", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@mdx-js/util": "1.6.22", + "babel-plugin-apply-mdx-type-prop": "1.6.22", + "babel-plugin-extract-import-names": "1.6.22", + "camelcase-css": "2.0.1", + "detab": "2.0.4", + "hast-util-raw": "6.0.1", + "lodash.uniq": "4.5.0", + "mdast-util-to-hast": "10.0.1", + "remark-footnotes": "2.0.0", + "remark-mdx": "1.6.22", + "remark-parse": "8.0.3", + "remark-squeeze-paragraphs": "4.0.0", + "style-to-object": "0.3.0", + "unified": "9.2.0", + "unist-builder": "2.0.3", + "unist-util-visit": "2.0.3" + }, + "dependencies": { + "@babel/core": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "@mdx-js/react": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", + "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==", + "requires": {} + }, + "@mdx-js/util": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.22.tgz", + "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==" + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@polka/url": { + "version": "1.0.0-next.24", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", + "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==" + }, + "@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, + "@slorber/static-site-generator-webpack-plugin": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz", + "integrity": "sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA==", + "requires": { + "eval": "^0.1.8", + "p-map": "^4.0.0", + "webpack-sources": "^3.2.2" + } + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz", + "integrity": "sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==", + "requires": {} + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "requires": {} + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "requires": {} + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz", + "integrity": "sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==", + "requires": {} + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz", + "integrity": "sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==", + "requires": {} + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz", + "integrity": "sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==", + "requires": {} + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz", + "integrity": "sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==", + "requires": {} + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz", + "integrity": "sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==", + "requires": {} + }, + "@svgr/babel-preset": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.5.1.tgz", + "integrity": "sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==", + "requires": { + "@svgr/babel-plugin-add-jsx-attribute": "^6.5.1", + "@svgr/babel-plugin-remove-jsx-attribute": "*", + "@svgr/babel-plugin-remove-jsx-empty-expression": "*", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^6.5.1", + "@svgr/babel-plugin-svg-dynamic-title": "^6.5.1", + "@svgr/babel-plugin-svg-em-dimensions": "^6.5.1", + "@svgr/babel-plugin-transform-react-native-svg": "^6.5.1", + "@svgr/babel-plugin-transform-svg-component": "^6.5.1" + } + }, + "@svgr/core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz", + "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", + "requires": { + "@babel/core": "^7.19.6", + "@svgr/babel-preset": "^6.5.1", + "@svgr/plugin-jsx": "^6.5.1", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.1" + } + }, + "@svgr/hast-util-to-babel-ast": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz", + "integrity": "sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==", + "requires": { + "@babel/types": "^7.20.0", + "entities": "^4.4.0" + } + }, + "@svgr/plugin-jsx": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz", + "integrity": "sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==", + "requires": { + "@babel/core": "^7.19.6", + "@svgr/babel-preset": "^6.5.1", + "@svgr/hast-util-to-babel-ast": "^6.5.1", + "svg-parser": "^2.0.4" + } + }, + "@svgr/plugin-svgo": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz", + "integrity": "sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==", + "requires": { + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "svgo": "^2.8.0" + } + }, + "@svgr/webpack": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-6.5.1.tgz", + "integrity": "sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA==", + "requires": { + "@babel/core": "^7.19.6", + "@babel/plugin-transform-react-constant-elements": "^7.18.12", + "@babel/preset-env": "^7.19.4", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.18.6", + "@svgr/core": "^6.5.1", + "@svgr/plugin-jsx": "^6.5.1", + "@svgr/plugin-svgo": "^6.5.1" + } + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==" + }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "requires": { + "@types/node": "*" + } + }, + "@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "@types/eslint": { + "version": "8.56.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", + "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==", + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "requires": { + "@types/unist": "^2" + } + }, + "@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, + "@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "requires": { + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "@types/katex": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.11.1.tgz", + "integrity": "sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==" + }, + "@types/mdast": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", + "requires": { + "@types/unist": "^2" + } + }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "@types/node": { + "version": "20.11.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz", + "integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==", + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" + }, + "@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "@types/react": { + "version": "18.2.57", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.57.tgz", + "integrity": "sha512-ZvQsktJgSYrQiMirAN60y4O/LRevIV8hUzSOSNB6gfR3/o3wCBFQx3sPwIYtuDMeiVgsSS3UzCV26tEzgnfvQw==", + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "@types/react-router-config": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", + "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "^5.1.0" + } + }, + "@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "requires": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "requires": { + "@types/node": "*" + } + }, + "@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "requires": { + "@types/express": "*" + } + }, + "@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "requires": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "requires": { + "@types/node": "*" + } + }, + "@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "requires": { + "@types/node": "*" + } + }, + "@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "dependencies": { + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + } + } + }, + "acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" + }, + "acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "requires": {} + }, + "acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==" + }, + "address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==" + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": {}, + "dependencies": { + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "optional": true, + "peer": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "optional": true, + "peer": true + } + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} + }, + "algoliasearch": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.22.1.tgz", + "integrity": "sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==", + "requires": { + "@algolia/cache-browser-local-storage": "4.22.1", + "@algolia/cache-common": "4.22.1", + "@algolia/cache-in-memory": "4.22.1", + "@algolia/client-account": "4.22.1", + "@algolia/client-analytics": "4.22.1", + "@algolia/client-common": "4.22.1", + "@algolia/client-personalization": "4.22.1", + "@algolia/client-search": "4.22.1", + "@algolia/logger-common": "4.22.1", + "@algolia/logger-console": "4.22.1", + "@algolia/requester-browser-xhr": "4.22.1", + "@algolia/requester-common": "4.22.1", + "@algolia/requester-node-http": "4.22.1", + "@algolia/transporter": "4.22.1" + } + }, + "algoliasearch-helper": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.16.2.tgz", + "integrity": "sha512-Yl/Gu5Cq4Z5s/AJ0jR37OPI1H3+z7PHz657ibyaXgMOaWvPlZ3OACN13N+7HCLPUlB0BN+8BtmrG/CqTilowBA==", + "requires": { + "@algolia/events": "^4.0.1" + } + }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "requires": { + "string-width": "^4.1.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==" + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, + "autoprefixer": { + "version": "10.4.17", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", + "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", + "requires": { + "browserslist": "^4.22.2", + "caniuse-lite": "^1.0.30001578", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "axios": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz", + "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==", + "requires": { + "follow-redirects": "^1.14.7" + } + }, + "babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + } + }, + "babel-plugin-apply-mdx-type-prop": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz", + "integrity": "sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ==", + "requires": { + "@babel/helper-plugin-utils": "7.10.4", + "@mdx-js/util": "1.6.22" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", + "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-extract-import-names": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz", + "integrity": "sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ==", + "requires": { + "@babel/helper-plugin-utils": "7.10.4" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + } + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz", + "integrity": "sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==", + "requires": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.5.0", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz", + "integrity": "sha512-7nZPG1uzK2Ymhy/NbaOWTg3uibM2BmGASS4vHS4szRZAIR8R6GwA/xAujpdrXU5iyklrimWnLWU+BLF9suPTqg==", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.5.0", + "core-js-compat": "^3.34.0" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.5.tgz", + "integrity": "sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.5.0" + } + }, + "bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base16": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", + "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==" + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "requires": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "requires": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "requires": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" + } + } + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001588", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001588.tgz", + "integrity": "sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==" + }, + "ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==" + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==" + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" + }, + "cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" + }, + "clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "requires": { + "source-map": "~0.6.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + }, + "cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==" + }, + "cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + } + } + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + } + } + }, + "clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" + }, + "collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "combine-promises": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", + "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==" + }, + "comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==" + }, + "commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + } + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==" + }, + "consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==" + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "copy-text-to-clipboard": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", + "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==" + }, + "copy-webpack-plugin": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz", + "integrity": "sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==", + "requires": { + "fast-glob": "^3.2.7", + "glob-parent": "^6.0.1", + "globby": "^12.0.2", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==" + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "requires": { + "is-glob": "^4.0.3" + } + }, + "globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "requires": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==" + } + } + }, + "core-js": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz", + "integrity": "sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw==" + }, + "core-js-compat": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz", + "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==", + "requires": { + "browserslist": "^4.22.3" + } + }, + "core-js-pure": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz", + "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "requires": { + "layout-base": "^1.0.0" + } + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "requires": { + "node-fetch": "^2.6.12" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, + "css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "requires": {} + }, + "css-loader": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", + "requires": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.4", + "postcss-modules-scope": "^3.1.1", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + } + }, + "css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "requires": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } + } + }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "requires": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + } + }, + "cssnano-preset-advanced": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.10.tgz", + "integrity": "sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ==", + "requires": { + "autoprefixer": "^10.4.12", + "cssnano-preset-default": "^5.2.14", + "postcss-discard-unused": "^5.1.0", + "postcss-merge-idents": "^5.1.1", + "postcss-reduce-idents": "^5.2.0", + "postcss-zindex": "^5.1.0" + } + }, + "cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "requires": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + } + }, + "cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "requires": {} + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "requires": { + "css-tree": "^1.1.2" + } + }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "cytoscape": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.28.1.tgz", + "integrity": "sha512-xyItz4O/4zp9/239wCcH8ZcFuuZooEeF8KHRmzjDfGdXsj3OG9MFSMA0pJE0uX3uCN/ygof6hHf4L7lst+JaDg==", + "requires": { + "heap": "^0.2.6", + "lodash": "^4.17.21" + } + }, + "cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "requires": { + "cose-base": "^1.0.0" + } + }, + "cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "requires": { + "cose-base": "^2.2.0" + }, + "dependencies": { + "cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "requires": { + "layout-base": "^2.0.0" + } + }, + "layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" + } + } + }, + "d3": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", + "requires": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + } + }, + "d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + }, + "d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + } + }, + "d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "requires": { + "d3-path": "1 - 3" + } + }, + "d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" + }, + "d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "requires": { + "d3-array": "^3.2.0" + } + }, + "d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "requires": { + "delaunator": "5" + } + }, + "d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==" + }, + "d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + } + }, + "d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "requires": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + } + } + }, + "d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" + }, + "d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "requires": { + "d3-dsv": "1 - 3" + } + }, + "d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", + "requires": { + "d3-array": "2.5.0 - 3" + } + }, + "d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" + }, + "d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "requires": { + "d3-color": "1 - 3" + } + }, + "d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" + }, + "d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" + }, + "d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" + }, + "d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + } + }, + "d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==", + "requires": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + } + }, + "d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" + }, + "d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "requires": { + "d3-path": "^3.1.0" + } + }, + "d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + }, + "d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==" + }, + "d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "requires": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + } + }, + "dagre-d3-es": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz", + "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==", + "requires": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "requires": { + "execa": "^5.0.0" + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + } + }, + "delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "requires": { + "robust-predicates": "^3.0.2" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "detab": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detab/-/detab-2.0.4.tgz", + "integrity": "sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==", + "requires": { + "repeat-string": "^1.5.4" + } + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "requires": { + "address": "^1.0.1", + "debug": "4" + } + }, + "detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "requires": { + "path-type": "^4.0.0" + } + }, + "dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "requires": { + "@leichtgewicht/ip-codec": "^2.0.1" + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "requires": { + "utila": "~0.4" + } + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "requires": { + "domelementtype": "^2.3.0" + } + }, + "dompurify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", + "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==" + }, + "domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + } + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "requires": { + "is-obj": "^2.0.0" + }, + "dependencies": { + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + } + } + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "electron-to-chromium": { + "version": "1.4.678", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.678.tgz", + "integrity": "sha512-NbdGC2p0O5Q5iVhLEsNBSfytaw7wbEFJlIvaF71wi6QDtLAph5/rVogjyOpf/QggJIt8hNK3KdwNJnc2bzckbw==" + }, + "elkjs": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz", + "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==" + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "emoticon": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-3.2.0.tgz", + "integrity": "sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" + }, + "escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==" + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "eta": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/eta/-/eta-1.14.2.tgz", + "integrity": "sha512-wZmJAV7EFUG5W8XNXSazIdichnWEhGB1OWg4tnXWPj0CPNUcFdgorGNO6N9p6WBUgoUe4P0OziJYn1+6zxP2aQ==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "eval": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", + "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", + "requires": { + "@types/node": "*", + "require-like": ">= 0.1.1" + } + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + } + } + }, + "express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "requires": { + "punycode": "^1.3.2" + } + }, + "fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "requires": { + "reusify": "^1.0.4" + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fbemitter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz", + "integrity": "sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==", + "requires": { + "fbjs": "^3.0.0" + } + }, + "fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "requires": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==" + }, + "feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "requires": { + "xml-js": "^1.6.11" + } + }, + "file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" + }, + "flux": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/flux/-/flux-4.0.4.tgz", + "integrity": "sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==", + "requires": { + "fbemitter": "^3.0.0", + "fbjs": "^3.0.1" + } + }, + "follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + }, + "fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "requires": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "requires": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" + } + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-monkey": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "requires": { + "ini": "2.0.0" + }, + "dependencies": { + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==" + } + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "requires": { + "global-prefix": "^3.0.0" + } + }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "requires": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "requires": { + "duplexer": "^0.1.2" + } + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==" + }, + "hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "hast-to-hyperscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", + "integrity": "sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA==", + "requires": { + "@types/unist": "^2.0.3", + "comma-separated-tokens": "^1.0.0", + "property-information": "^5.3.0", + "space-separated-tokens": "^1.0.0", + "style-to-object": "^0.3.0", + "unist-util-is": "^4.0.0", + "web-namespaces": "^1.0.0" + } + }, + "hast-util-from-parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz", + "integrity": "sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA==", + "requires": { + "@types/parse5": "^5.0.0", + "hastscript": "^6.0.0", + "property-information": "^5.0.0", + "vfile": "^4.0.0", + "vfile-location": "^3.2.0", + "web-namespaces": "^1.0.0" + } + }, + "hast-util-is-element": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz", + "integrity": "sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==" + }, + "hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==" + }, + "hast-util-raw": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-6.0.1.tgz", + "integrity": "sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig==", + "requires": { + "@types/hast": "^2.0.0", + "hast-util-from-parse5": "^6.0.0", + "hast-util-to-parse5": "^6.0.0", + "html-void-elements": "^1.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^3.0.0", + "vfile": "^4.0.0", + "web-namespaces": "^1.0.0", + "xtend": "^4.0.0", + "zwitch": "^1.0.0" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + } + } + }, + "hast-util-to-parse5": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz", + "integrity": "sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ==", + "requires": { + "hast-to-hyperscript": "^9.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.0.0", + "xtend": "^4.0.0", + "zwitch": "^1.0.0" + } + }, + "hast-util-to-text": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-2.0.1.tgz", + "integrity": "sha512-8nsgCARfs6VkwH2jJU9b8LNTuR4700na+0h3PqCaEk4MAnMDeu5P0tP8mjk9LLNGxIeQRLbiDbZVw6rku+pYsQ==", + "requires": { + "hast-util-is-element": "^1.0.0", + "repeat-string": "^1.0.0", + "unist-util-find-after": "^3.0.0" + } + }, + "hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "requires": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==" + }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==" + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "requires": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + } + } + }, + "html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==" + }, + "html-void-elements": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", + "integrity": "sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w==" + }, + "html-webpack-plugin": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "requires": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + } + }, + "htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "requires": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "dependencies": { + "is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" + } + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "requires": {} + }, + "ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==" + }, + "image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "requires": { + "queue": "6.0.2" + } + }, + "immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==" + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, + "infima": { + "version": "0.2.0-alpha.38", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.38.tgz", + "integrity": "sha512-1WsmqSMI5IqzrUx3goq+miJznHBonbE3aoqZ1AR/i/oHhroxNeSV6Awv5VoVfXBhfTzLSnxkHaRI2qpAMYcCzw==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==" + }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==" + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "requires": { + "hasown": "^2.0.0" + } + }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==" + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==" + }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + } + }, + "is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==" + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==" + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==" + }, + "is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==" + }, + "is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==" + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "joi": { + "version": "17.12.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", + "integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==", + "requires": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "katex": { + "version": "0.13.24", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.13.24.tgz", + "integrity": "sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w==", + "requires": { + "commander": "^8.0.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + } + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "requires": { + "json-buffer": "3.0.0" + } + }, + "khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" + }, + "klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==" + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "requires": { + "package-json": "^6.3.0" + } + }, + "launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "requires": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + }, + "lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==" + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==" + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "lodash.curry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", + "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "lodash.flow": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", + "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "requires": { + "tslib": "^2.0.3" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==" + }, + "mdast-squeeze-paragraphs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", + "integrity": "sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==", + "requires": { + "unist-util-remove": "^2.0.0" + } + }, + "mdast-util-definitions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", + "integrity": "sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==", + "requires": { + "unist-util-visit": "^2.0.0" + } + }, + "mdast-util-to-hast": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz", + "integrity": "sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "mdast-util-definitions": "^4.0.0", + "mdurl": "^1.0.0", + "unist-builder": "^2.0.0", + "unist-util-generated": "^1.0.0", + "unist-util-position": "^3.0.0", + "unist-util-visit": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz", + "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==" + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, + "mdx-mermaid": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mdx-mermaid/-/mdx-mermaid-1.3.2.tgz", + "integrity": "sha512-8kw0tg3isKKBFzFwoe2DhIaEgKYtVeJXQtxZCCrdTPO0CTpXHnTHT0atDqsp7YkXi5iUCp/zAZPZu1cmr68T3w==", + "requires": {} + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "requires": { + "fs-monkey": "^1.0.4" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "mermaid": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.4.3.tgz", + "integrity": "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==", + "requires": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.4.0", + "dagre-d3-es": "7.0.9", + "dayjs": "^1.11.7", + "dompurify": "2.4.3", + "elkjs": "^0.8.2", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.2", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "~1.33.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "mini-css-extract-plugin": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz", + "integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==", + "requires": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "requires": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + } + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "requires": { + "lodash": "^4.17.21" + } + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==" + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==" + }, + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "requires": { + "path-key": "^3.0.0" + } + }, + "nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "requires": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==" + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "requires": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + }, + "pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" + } + } + }, + "postcss": { + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "requires": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "requires": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "requires": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "requires": {} + }, + "postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "requires": {} + }, + "postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "requires": {} + }, + "postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "requires": {} + }, + "postcss-discard-unused": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz", + "integrity": "sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==", + "requires": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "requires": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + } + }, + "postcss-merge-idents": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz", + "integrity": "sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==", + "requires": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "requires": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + } + }, + "postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "requires": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "requires": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "requires": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "requires": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "requires": {} + }, + "postcss-modules-local-by-default": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", + "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", + "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "requires": {} + }, + "postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "requires": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "requires": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "requires": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-reduce-idents": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz", + "integrity": "sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "requires": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "requires": { + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-sort-media-queries": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz", + "integrity": "sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw==", + "requires": { + "sort-css-media-queries": "2.1.0" + } + }, + "postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "requires": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + } + }, + "postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "requires": { + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "postcss-zindex": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-5.1.0.tgz", + "integrity": "sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==", + "requires": {} + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==" + }, + "prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true + }, + "pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "requires": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==" + }, + "prism-react-renderer": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz", + "integrity": "sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==", + "requires": {} + }, + "prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "requires": { + "xtend": "^4.0.0" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "requires": { + "escape-goat": "^2.0.0" + } + }, + "pure-color": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", + "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "requires": { + "inherits": "~2.0.3" + } + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + } + } + }, + "react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "react-base16-styling": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz", + "integrity": "sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==", + "requires": { + "base16": "^1.0.0", + "lodash.curry": "^4.0.1", + "lodash.flow": "^3.3.0", + "pure-color": "^1.2.0" + } + }, + "react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "requires": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==" + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + } + }, + "react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" + }, + "react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "react-helmet-async": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", + "integrity": "sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==", + "requires": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-json-view": { + "version": "1.21.3", + "resolved": "https://registry.npmjs.org/react-json-view/-/react-json-view-1.21.3.tgz", + "integrity": "sha512-13p8IREj9/x/Ye4WI/JpjhoIwuzEgUAtgJZNBJckfzJt1qyh24BdTm6UQNGnyTq9dapQdrqvquZTo3dz1X6Cjw==", + "requires": { + "flux": "^4.0.1", + "react-base16-styling": "^0.6.0", + "react-lifecycles-compat": "^3.0.4", + "react-textarea-autosize": "^8.3.2" + } + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-loadable": { + "version": "npm:@docusaurus/react-loadable@5.5.2", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", + "integrity": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==", + "requires": { + "@types/react": "*", + "prop-types": "^15.6.2" + } + }, + "react-loadable-ssr-addon-v5-slorber": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", + "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", + "requires": { + "@babel/runtime": "^7.10.3" + } + }, + "react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "requires": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-router-config": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", + "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "requires": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-textarea-autosize": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", + "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==", + "requires": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "requires": { + "picomatch": "^2.2.1" + } + }, + "reading-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "requires": { + "resolve": "^1.1.6" + } + }, + "recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "requires": { + "minimatch": "^3.0.5" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "requires": { + "regenerate": "^1.4.2" + } + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "requires": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + } + }, + "registry-auth-token": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", + "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", + "requires": { + "rc": "1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "requires": { + "rc": "^1.2.8" + } + }, + "regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" + } + } + }, + "rehype-katex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-5.0.0.tgz", + "integrity": "sha512-ksSuEKCql/IiIadOHiKRMjypva9BLhuwQNascMqaoGLDVd0k2NlE2wMvgZ3rpItzRKCd6vs8s7MFbb8pcR0AEg==", + "requires": { + "@types/katex": "^0.11.0", + "hast-util-to-text": "^2.0.0", + "katex": "^0.13.0", + "rehype-parse": "^7.0.0", + "unified": "^9.0.0", + "unist-util-visit": "^2.0.0" + } + }, + "rehype-parse": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-7.0.1.tgz", + "integrity": "sha512-fOiR9a9xH+Le19i4fGzIEowAbwG7idy2Jzs4mOrFWBSJ0sNUgy0ev871dwWnbOo371SjgjG4pwzrbgSVrKxecw==", + "requires": { + "hast-util-from-parse5": "^6.0.0", + "parse5": "^6.0.0" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + } + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" + }, + "remark-admonitions": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/remark-admonitions/-/remark-admonitions-1.2.1.tgz", + "integrity": "sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow==", + "requires": { + "rehype-parse": "^6.0.2", + "unified": "^8.4.2", + "unist-util-visit": "^2.0.1" + }, + "dependencies": { + "hast-util-from-parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", + "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", + "requires": { + "ccount": "^1.0.3", + "hastscript": "^5.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.1.2", + "xtend": "^4.0.1" + } + }, + "hastscript": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", + "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", + "requires": { + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + } + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "rehype-parse": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-6.0.2.tgz", + "integrity": "sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug==", + "requires": { + "hast-util-from-parse5": "^5.0.0", + "parse5": "^5.0.0", + "xtend": "^4.0.0" + } + }, + "unified": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-8.4.2.tgz", + "integrity": "sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA==", + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + } + } + } + }, + "remark-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-2.2.0.tgz", + "integrity": "sha512-P3cj9s5ggsUvWw5fS2uzCHJMGuXYRb0NnZqYlNecewXt8QBU9n5vW3DUUKOhepS8F9CwdMx9B8a3i7pqFWAI5w==", + "requires": { + "emoticon": "^3.2.0", + "node-emoji": "^1.10.0", + "unist-util-visit": "^2.0.3" + } + }, + "remark-footnotes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-2.0.0.tgz", + "integrity": "sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ==" + }, + "remark-math": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-3.0.1.tgz", + "integrity": "sha512-epT77R/HK0x7NqrWHdSV75uNLwn8g9qTyMqCRCDujL0vj/6T6+yhdrR7mjELWtkse+Fw02kijAaBuVcHBor1+Q==" + }, + "remark-mdx": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.22.tgz", + "integrity": "sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==", + "requires": { + "@babel/core": "7.12.9", + "@babel/helper-plugin-utils": "7.10.4", + "@babel/plugin-proposal-object-rest-spread": "7.12.1", + "@babel/plugin-syntax-jsx": "7.12.1", + "@mdx-js/util": "1.6.22", + "is-alphabetical": "1.0.4", + "remark-parse": "8.0.3", + "unified": "9.2.0" + }, + "dependencies": { + "@babel/core": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + }, + "@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "remark-parse": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", + "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", + "requires": { + "ccount": "^1.0.0", + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^2.0.0", + "vfile-location": "^3.0.0", + "xtend": "^4.0.1" + } + }, + "remark-squeeze-paragraphs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz", + "integrity": "sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw==", + "requires": { + "mdast-squeeze-paragraphs": "^4.0.0" + } + }, + "renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "requires": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + }, + "htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + } + } + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, + "rtl-detect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", + "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" + }, + "rtlcss": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-3.5.0.tgz", + "integrity": "sha512-wzgMaMFHQTnyi9YOwsx9LjOxYXJPzS8sYnFaKm6R5ysvTkwzHiB0vxnbHwchHQT65PTdBjDG21/kQBWI7q9O7A==", + "requires": { + "find-up": "^5.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.3.11", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + } + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "requires": { + "tslib": "^2.1.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, + "scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "search-insights": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", + "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", + "peer": true + }, + "section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "requires": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + }, + "selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "requires": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + } + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "requires": { + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + } + } + }, + "serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-handler": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "requires": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + }, + "dependencies": { + "path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + } + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "requires": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "requires": { + "kind-of": "^6.0.2" + } + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==" + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "side-channel": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "requires": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "requires": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "sitemap": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz", + "integrity": "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==", + "requires": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "dependencies": { + "@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + } + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } + } + }, + "sort-css-media-queries": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz", + "integrity": "sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==" + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==" + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "style-to-object": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", + "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==", + "requires": { + "inline-style-parser": "0.1.1" + } + }, + "stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "requires": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + } + }, + "stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + }, + "css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + } + }, + "dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + } + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "requires": { + "domelementtype": "^2.2.0" + } + }, + "domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + } + }, + "entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==" + } + } + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" + }, + "terser": { + "version": "5.27.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.2.tgz", + "integrity": "sha512-sHXmLSkImesJ4p5apTeT63DsV4Obe1s37qT8qvwHRmVxKTBH7Rv9Wr26VcAMmLbmk9UliiwK8z+657NyJHHy/w==", + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "requires": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==" + }, + "trim-trailing-lines": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", + "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==" + }, + "trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==" + }, + "ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==" + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "dependencies": { + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + } + } + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "peer": true + }, + "ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==" + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "requires": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" + }, + "unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" + }, + "unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" + }, + "unified": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", + "requires": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "unist-builder": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-2.0.3.tgz", + "integrity": "sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw==" + }, + "unist-util-find-after": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-3.0.0.tgz", + "integrity": "sha512-ojlBqfsBftYXExNu3+hHLfJQ/X1jYY/9vdm4yZWjIbf0VuWF6CRufci1ZyoD/wV2TYMKxXUoNuoqwy+CkgzAiQ==", + "requires": { + "unist-util-is": "^4.0.0" + } + }, + "unist-util-generated": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.6.tgz", + "integrity": "sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg==" + }, + "unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==" + }, + "unist-util-position": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.1.0.tgz", + "integrity": "sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==" + }, + "unist-util-remove": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unist-util-remove/-/unist-util-remove-2.1.0.tgz", + "integrity": "sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q==", + "requires": { + "unist-util-is": "^4.0.0" + } + }, + "unist-util-remove-position": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", + "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", + "requires": { + "unist-util-visit": "^2.0.0" + } + }, + "unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "requires": { + "@types/unist": "^2.0.2" + } + }, + "unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + } + }, + "unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + } + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "requires": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "requires": { + "string-width": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + } + } + }, + "url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "requires": { + "prepend-http": "^2.0.0" + } + }, + "use-composed-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", + "requires": {} + }, + "use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "requires": {} + }, + "use-latest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", + "requires": { + "use-isomorphic-layout-effect": "^1.1.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" + }, + "utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + } + }, + "vfile-location": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.2.0.tgz", + "integrity": "sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==" + }, + "vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + } + }, + "wait-on": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz", + "integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==", + "requires": { + "axios": "^0.25.0", + "joi": "^17.6.0", + "lodash": "^4.17.21", + "minimist": "^1.2.5", + "rxjs": "^7.5.4" + } + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "web-namespaces": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.4.tgz", + "integrity": "sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==" + }, + "web-worker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", + "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "webpack": { + "version": "5.90.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", + "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "dependencies": { + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "webpack-bundle-analyzer": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", + "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "requires": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "is-plain-object": "^5.0.0", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + } + } + }, + "webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "requires": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } + } + }, + "webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "requires": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + }, + "ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "requires": {} + } + } + }, + "webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "requires": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" + }, + "webpackbar": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", + "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "requires": { + "chalk": "^4.1.0", + "consola": "^2.15.3", + "pretty-time": "^1.1.0", + "std-env": "^3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "requires": { + "string-width": "^5.0.1" + } + }, + "wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "requires": {} + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, + "zwitch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz", + "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==" + } } } diff --git a/docs/sidebars.js b/docs/sidebars.js index 38968683..f8ca642e 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -21,8 +21,8 @@ module.exports = { }, { type: "doc", - label: "ICICLE Provers", - id: "icicle/integrations" + label: "ICICLE Core", + id: "icicle/core", }, { type: "category", @@ -54,15 +54,20 @@ module.exports = { label: "NTT", id: "icicle/golang-bindings/ntt", }, + { + type: "doc", + label: "EC-NTT", + id: "icicle/golang-bindings/ecntt", + }, { type: "doc", label: "Vector operations", id: "icicle/golang-bindings/vec-ops", }, { - type: "doc", - label: "Multi GPU Support", - id: "icicle/golang-bindings/multi-gpu", + type: "doc", + label: "Multi GPU Support", + id: "icicle/golang-bindings/multi-gpu", }, ] }, @@ -96,6 +101,11 @@ module.exports = { label: "NTT", id: "icicle/rust-bindings/ntt", }, + { + type: "doc", + label: "EC-NTT", + id: "icicle/rust-bindings/ecntt", + }, { type: "doc", label: "Vector operations", @@ -106,6 +116,11 @@ module.exports = { label: "Multi GPU Support", id: "icicle/rust-bindings/multi-gpu", }, + { + type: "doc", + label: "Polynomials", + id: "icicle/rust-bindings/polynomials", + }, ], }, { @@ -134,6 +149,11 @@ module.exports = { }, ], }, + { + type: "doc", + label: "Polynomials", + id: "icicle/polynomials/overview", + }, { type: "doc", label: "Multi GPU Support", @@ -141,13 +161,13 @@ module.exports = { }, { type: "doc", - label: "Supporting additional curves", - id: "icicle/supporting-additional-curves", + label: "Google Colab Instructions", + id: "icicle/colab-instructions", }, { type: "doc", - label: "Google Colab Instructions", - id: "icicle/colab-instructions", + label: "ICICLE Provers", + id: "icicle/integrations" }, ] }, diff --git a/examples/c++/msm/CMakeLists.txt b/examples/c++/msm/CMakeLists.txt index 6624a390..b29e07c7 100644 --- a/examples/c++/msm/CMakeLists.txt +++ b/examples/c++/msm/CMakeLists.txt @@ -8,18 +8,16 @@ if (${CMAKE_VERSION} VERSION_LESS "3.24.0") else() set(CMAKE_CUDA_ARCHITECTURES native) # on 3.24+, on earlier it is ignored, and the target is not passed endif () -project(icicle LANGUAGES CUDA CXX) +project(example LANGUAGES CUDA CXX) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS_RELEASE "") set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -g -G -O0") -# change the path to your Icicle location -include_directories("../../../icicle") + add_executable( example example.cu ) - -find_library(NVML_LIBRARY nvidia-ml PATHS /usr/local/cuda-12.0/targets/x86_64-linux/lib/stubs/ ) -target_link_libraries(example ${NVML_LIBRARY}) +target_include_directories(example PRIVATE "../../../icicle/include") +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_curve_bn254.a) set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) diff --git a/examples/c++/msm/compile.sh b/examples/c++/msm/compile.sh index 36c1ddac..7e8d781a 100755 --- a/examples/c++/msm/compile.sh +++ b/examples/c++/msm/compile.sh @@ -3,7 +3,13 @@ # Exit immediately on error set -e -rm -rf build -mkdir -p build -cmake -S . -B build -cmake --build build +mkdir -p build/example +mkdir -p build/icicle + +# Configure and build Icicle +cmake -S ../../../icicle/ -B build/icicle -DCMAKE_BUILD_TYPE=Release -DCURVE=bn254 -DG2=ON +cmake --build build/icicle + +# Configure and build the example application +cmake -S . -B build/example +cmake --build build/example \ No newline at end of file diff --git a/examples/c++/msm/example.cu b/examples/c++/msm/example.cu index 66f91355..ded07ee9 100644 --- a/examples/c++/msm/example.cu +++ b/examples/c++/msm/example.cu @@ -2,11 +2,8 @@ #include #include -#define G2_DEFINED -#define CURVE_ID 1 -// include MSM template -#include "appUtils/msm/msm.cu" -using namespace curve_config; +#include "api/bn254.h" +using namespace bn254; int main(int argc, char* argv[]) { @@ -24,11 +21,10 @@ int main(int argc, char* argv[]) scalar_t* scalars = new scalar_t[N]; affine_t* points = new affine_t[N]; projective_t result; - scalar_t::RandHostMany(scalars, N); - projective_t::RandHostManyAffine(points, N); + scalar_t::rand_host_many(scalars, N); + projective_t::rand_host_many_affine(points, N); std::cout << "Using default MSM configuration with on-host inputs" << std::endl; - // auto config = msm::DefaultMSMConfig(); device_context::DeviceContext ctx = device_context::get_default_device_context(); msm::MSMConfig config = { ctx, // ctx @@ -49,28 +45,9 @@ int main(int argc, char* argv[]) config.batch_size = batch_size; std::cout << "Running MSM kernel with on-host inputs" << std::endl; - // Create two events to time the MSM kernel cudaStream_t stream = config.ctx.stream; - cudaEvent_t start, stop; - float time; - cudaEventCreate(&start); - cudaEventCreate(&stop); - // Record the start event on the stream - cudaEventRecord(start, stream); // Execute the MSM kernel - msm::MSM(scalars, points, msm_size, config, &result); - // Record the stop event on the stream - cudaEventRecord(stop, stream); - // Wait for the stop event to complete - cudaEventSynchronize(stop); - // Calculate the elapsed time between the start and stop events - cudaEventElapsedTime(&time, start, stop); - // Destroy the events - cudaEventDestroy(start); - cudaEventDestroy(stop); - // Print the elapsed time - std::cout << "Kernel runtime: " << std::fixed << std::setprecision(3) << time * 1e-3 << " sec." << std::endl; - // Print the result + bn254_msm_cuda(scalars, points, msm_size, config, &result); std::cout << projective_t::to_affine(result) << std::endl; std::cout << "Copying inputs on-device" << std::endl; @@ -89,24 +66,9 @@ int main(int argc, char* argv[]) config.are_points_on_device = true; std::cout << "Running MSM kernel with on-device inputs" << std::endl; - // Create two events to time the MSM kernel - cudaEventCreate(&start); - cudaEventCreate(&stop); - // Record the start event on the stream - cudaEventRecord(start, stream); // Execute the MSM kernel - msm::MSM(scalars_d, points_d, msm_size, config, result_d); - // Record the stop event on the stream - cudaEventRecord(stop, stream); - // Wait for the stop event to complete - cudaEventSynchronize(stop); - // Calculate the elapsed time between the start and stop events - cudaEventElapsedTime(&time, start, stop); - // Destroy the events - cudaEventDestroy(start); - cudaEventDestroy(stop); - // Print the elapsed time - std::cout << "Kernel runtime: " << std::fixed << std::setprecision(3) << time * 1e-3 << " sec." << std::endl; + bn254_msm_cuda(scalars_d, points_d, msm_size, config, result_d); + // Copy the result back to the host cudaMemcpy(&result, result_d, sizeof(projective_t), cudaMemcpyDeviceToHost); // Print the result @@ -123,23 +85,14 @@ int main(int argc, char* argv[]) std::cout << "Generating random inputs on-host" << std::endl; // use the same scalars g2_affine_t* g2_points = new g2_affine_t[N]; - g2_projective_t::RandHostManyAffine(g2_points, N); + g2_projective_t::rand_host_many_affine(g2_points, N); std::cout << "Reconfiguring MSM to use on-host inputs" << std::endl; config.are_results_on_device = false; config.are_scalars_on_device = false; config.are_points_on_device = false; g2_projective_t g2_result; - cudaEventCreate(&start); - cudaEventCreate(&stop); - cudaEventRecord(start, stream); - msm::MSM(scalars, g2_points, msm_size, config, &g2_result); - cudaEventRecord(stop, stream); - cudaEventSynchronize(stop); - cudaEventElapsedTime(&time, start, stop); - cudaEventDestroy(start); - cudaEventDestroy(stop); - std::cout << "Kernel runtime: " << std::fixed << std::setprecision(3) << time * 1e-3 << " sec." << std::endl; + bn254_g2_msm_cuda(scalars, g2_points, msm_size, config, &g2_result); std::cout << g2_projective_t::to_affine(g2_result) << std::endl; std::cout << "Copying inputs on-device" << std::endl; @@ -157,16 +110,7 @@ int main(int argc, char* argv[]) config.are_points_on_device = true; std::cout << "Running MSM kernel with on-device inputs" << std::endl; - cudaEventCreate(&start); - cudaEventCreate(&stop); - cudaEventRecord(start, stream); - msm::MSM(scalars_d, g2_points_d, msm_size, config, g2_result_d); - cudaEventRecord(stop, stream); - cudaEventSynchronize(stop); - cudaEventElapsedTime(&time, start, stop); - cudaEventDestroy(start); - cudaEventDestroy(stop); - std::cout << "Kernel runtime: " << std::fixed << std::setprecision(3) << time * 1e-3 << " sec." << std::endl; + bn254_g2_msm_cuda(scalars_d, g2_points_d, msm_size, config, g2_result_d); cudaMemcpy(&g2_result, g2_result_d, sizeof(g2_projective_t), cudaMemcpyDeviceToHost); std::cout << g2_projective_t::to_affine(g2_result) << std::endl; diff --git a/examples/c++/msm/run.sh b/examples/c++/msm/run.sh index 6e3fc976..01eca66b 100755 --- a/examples/c++/msm/run.sh +++ b/examples/c++/msm/run.sh @@ -1,2 +1,2 @@ #!/bin/bash -./build/example +./build/example/example diff --git a/examples/c++/multi-gpu-poseidon/CMakeLists.txt b/examples/c++/multi-gpu-poseidon/CMakeLists.txt index 424b3e9b..24746ce7 100644 --- a/examples/c++/multi-gpu-poseidon/CMakeLists.txt +++ b/examples/c++/multi-gpu-poseidon/CMakeLists.txt @@ -14,11 +14,13 @@ set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS_RELEASE "") set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -g -G -O0") # change the path to your Icicle location -include_directories("../../../icicle") add_executable( example example.cu ) +target_include_directories(example PRIVATE "../../../icicle/include") +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_curve_bn254.a) +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_field_bn254.a) find_library(NVML_LIBRARY nvidia-ml PATHS /usr/local/cuda/targets/x86_64-linux/lib/stubs/ ) target_link_libraries(example ${NVML_LIBRARY}) set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) diff --git a/examples/c++/multi-gpu-poseidon/compile.sh b/examples/c++/multi-gpu-poseidon/compile.sh index 36c1ddac..ab4e191d 100755 --- a/examples/c++/multi-gpu-poseidon/compile.sh +++ b/examples/c++/multi-gpu-poseidon/compile.sh @@ -3,7 +3,13 @@ # Exit immediately on error set -e -rm -rf build -mkdir -p build -cmake -S . -B build -cmake --build build +mkdir -p build/example +mkdir -p build/icicle + +# Configure and build Icicle +cmake -S ../../../icicle/ -B build/icicle -DCMAKE_BUILD_TYPE=Release -DCURVE=bn254 +cmake --build build/icicle + +# Configure and build the example application +cmake -S . -B build/example +cmake --build build/example \ No newline at end of file diff --git a/examples/c++/multi-gpu-poseidon/example.cu b/examples/c++/multi-gpu-poseidon/example.cu index edb12f9b..cd772501 100644 --- a/examples/c++/multi-gpu-poseidon/example.cu +++ b/examples/c++/multi-gpu-poseidon/example.cu @@ -1,16 +1,13 @@ #include #include #include - #include -// select the curve -#define CURVE_ID 2 -#include "appUtils/poseidon/poseidon.cu" -#include "utils/error_handler.cuh" +#include "api/bn254.h" +#include "gpu-utils/error_handler.cuh" using namespace poseidon; -using namespace curve_config; +using namespace bn254; void checkCudaError(cudaError_t error) { if (error != cudaSuccess) { @@ -39,7 +36,7 @@ void threadPoseidon(device_context::DeviceContext ctx, unsigned size_partition, false, // loop_state false, // is_async }; - cudaError_t err = poseidon_hash(layers, column_hashes, (size_t) size_partition, *constants, column_config); + cudaError_t err = bn254_poseidon_hash_cuda(layers, column_hashes, (size_t) size_partition, size_col, *constants, column_config); checkCudaError(err); } @@ -109,13 +106,13 @@ int main() { CHECK_ALLOC(column_hash1); PoseidonConstants column_constants0, column_constants1; - init_optimized_poseidon_constants(size_col, ctx0, &column_constants0); + bn254_init_optimized_poseidon_constants_cuda(size_col, ctx0, &column_constants0); cudaError_t err_result = CHK_STICKY(cudaSetDevice(ctx1.device_id)); if (err_result != cudaSuccess) { std::cerr << "CUDA error: " << cudaGetErrorString(err_result) << std::endl; return; } - init_optimized_poseidon_constants(size_col, ctx1, &column_constants1); + bn254_init_optimized_poseidon_constants_cuda(size_col, ctx1, &column_constants1); std::cout << "Parallel execution of Poseidon threads" << std::endl; START_TIMER(parallel); diff --git a/examples/c++/multi-gpu-poseidon/run.sh b/examples/c++/multi-gpu-poseidon/run.sh index 6e3fc976..01eca66b 100755 --- a/examples/c++/multi-gpu-poseidon/run.sh +++ b/examples/c++/multi-gpu-poseidon/run.sh @@ -1,2 +1,2 @@ #!/bin/bash -./build/example +./build/example/example diff --git a/examples/c++/multiply/CMakeLists.txt b/examples/c++/multiply/CMakeLists.txt index 424b3e9b..f7048bb8 100644 --- a/examples/c++/multiply/CMakeLists.txt +++ b/examples/c++/multiply/CMakeLists.txt @@ -8,17 +8,17 @@ if (${CMAKE_VERSION} VERSION_LESS "3.24.0") else() set(CMAKE_CUDA_ARCHITECTURES native) # on 3.24+, on earlier it is ignored, and the target is not passed endif () -project(icicle LANGUAGES CUDA CXX) +project(example LANGUAGES CUDA CXX) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS_RELEASE "") set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -g -G -O0") -# change the path to your Icicle location -include_directories("../../../icicle") add_executable( example example.cu ) +target_include_directories(example PRIVATE "../../../icicle/include") +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_field_bn254.a) find_library(NVML_LIBRARY nvidia-ml PATHS /usr/local/cuda/targets/x86_64-linux/lib/stubs/ ) target_link_libraries(example ${NVML_LIBRARY}) set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) diff --git a/examples/c++/multiply/compile.sh b/examples/c++/multiply/compile.sh index 36c1ddac..de35c62d 100755 --- a/examples/c++/multiply/compile.sh +++ b/examples/c++/multiply/compile.sh @@ -3,7 +3,13 @@ # Exit immediately on error set -e -rm -rf build -mkdir -p build -cmake -S . -B build -cmake --build build +mkdir -p build/example +mkdir -p build/icicle + +# Configure and build Icicle +cmake -S ../../../icicle/ -B build/icicle -DMSM=OFF -DCMAKE_BUILD_TYPE=Release -DCURVE=bn254 +cmake --build build/icicle + +# Configure and build the example application +cmake -S . -B build/example +cmake --build build/example \ No newline at end of file diff --git a/examples/c++/multiply/example.cu b/examples/c++/multiply/example.cu index 64cd5297..0b378f24 100644 --- a/examples/c++/multiply/example.cu +++ b/examples/c++/multiply/example.cu @@ -3,22 +3,21 @@ #include #include -#define CURVE_ID 1 -#include "curves/curve_config.cuh" -#include "utils/device_context.cuh" -#include "utils/vec_ops.cu" +#include "api/bn254.h" +#include "vec_ops/vec_ops.cuh" -using namespace curve_config; +using namespace vec_ops; +using namespace bn254; typedef scalar_t T; int vector_mult(T* vec_b, T* vec_a, T* vec_result, size_t n_elments, device_context::DeviceContext ctx) { - vec_ops::VecOpsConfig config = vec_ops::DefaultVecOpsConfig(); + vec_ops::VecOpsConfig config = vec_ops::DefaultVecOpsConfig(); config.is_a_on_device = true; config.is_b_on_device = true; config.is_result_on_device = true; - cudaError_t err = vec_ops::Mul(vec_a, vec_b, n_elments, config, vec_result); + cudaError_t err = bn254_mul_cuda(vec_a, vec_b, n_elments, config, vec_result); if (err != cudaSuccess) { std::cerr << "Failed to multiply vectors - " << cudaGetErrorString(err) << std::endl; return 0; @@ -63,8 +62,8 @@ int main(int argc, char** argv) T* host_in1 = (T*)malloc(vector_size * sizeof(T)); T* host_in2 = (T*)malloc(vector_size * sizeof(T)); std::cout << "Initializing vectors with random data" << std::endl; - T::RandHostMany(host_in1, vector_size); - T::RandHostMany(host_in2, vector_size); + T::rand_host_many(host_in1, vector_size); + T::rand_host_many(host_in2, vector_size); // device data device_context::DeviceContext ctx = device_context::get_default_device_context(); T* device_in1; diff --git a/examples/c++/multiply/run.sh b/examples/c++/multiply/run.sh index 6e3fc976..01eca66b 100755 --- a/examples/c++/multiply/run.sh +++ b/examples/c++/multiply/run.sh @@ -1,2 +1,2 @@ #!/bin/bash -./build/example +./build/example/example diff --git a/examples/c++/ntt/CMakeLists.txt b/examples/c++/ntt/CMakeLists.txt index 03873910..de0a1a22 100644 --- a/examples/c++/ntt/CMakeLists.txt +++ b/examples/c++/ntt/CMakeLists.txt @@ -8,19 +8,16 @@ if (${CMAKE_VERSION} VERSION_LESS "3.24.0") else() set(CMAKE_CUDA_ARCHITECTURES native) # on 3.24+, on earlier it is ignored, and the target is not passed endif () -project(icicle LANGUAGES CUDA CXX) +project(example LANGUAGES CUDA CXX) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS_RELEASE "") set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -g -G -O0") -# change the path to your Icicle location -include_directories("../../../icicle") + add_executable( example example.cu ) - -find_library(NVML_LIBRARY nvidia-ml PATHS /usr/local/cuda-12.0/targets/x86_64-linux/lib/stubs/ ) -target_link_libraries(example ${NVML_LIBRARY}) -set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) - +target_include_directories(example PRIVATE "../../../icicle/include") +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_field_bn254.a) +set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) \ No newline at end of file diff --git a/examples/c++/ntt/compile.sh b/examples/c++/ntt/compile.sh index a7ba1621..de35c62d 100755 --- a/examples/c++/ntt/compile.sh +++ b/examples/c++/ntt/compile.sh @@ -3,9 +3,13 @@ # Exit immediately on error set -e -rm -rf build -mkdir -p build -cmake -S . -B build -cmake --build build +mkdir -p build/example +mkdir -p build/icicle +# Configure and build Icicle +cmake -S ../../../icicle/ -B build/icicle -DMSM=OFF -DCMAKE_BUILD_TYPE=Release -DCURVE=bn254 +cmake --build build/icicle +# Configure and build the example application +cmake -S . -B build/example +cmake --build build/example \ No newline at end of file diff --git a/examples/c++/ntt/example.cu b/examples/c++/ntt/example.cu index a01a54f9..5d98e475 100644 --- a/examples/c++/ntt/example.cu +++ b/examples/c++/ntt/example.cu @@ -1,12 +1,11 @@ #include #include -// select the curve -#define CURVE_ID 1 // include NTT template -#include "appUtils/ntt/ntt.cu" -#include "appUtils/ntt/kernel_ntt.cu" -using namespace curve_config; + +#include "curves/params/bn254.cuh" +#include "api/bn254.h" +using namespace bn254; using namespace ntt; // Operate on scalars @@ -86,14 +85,14 @@ int main(int argc, char* argv[]) std::cout << "Running NTT with on-host data" << std::endl; // Create a device context auto ctx = device_context::get_default_device_context(); - const S basic_root = S::omega(log_ntt_size /*NTT_LOG_SIZE*/); - InitDomain(basic_root, ctx); + S basic_root = S::omega(log_ntt_size /*NTT_LOG_SIZE*/); + bn254_initialize_domain(&basic_root, ctx, true); // Create an NTTConfig instance - NTTConfig config = DefaultNTTConfig(); + NTTConfig config = default_ntt_config(); config.ntt_algorithm = NttAlgorithm::MixedRadix; config.batch_size = nof_ntts; START_TIMER(MixedRadix); - cudaError_t err = NTT(input, ntt_size, NTTDir::kForward, config, output); + cudaError_t err = bn254_ntt_cuda(input, ntt_size, NTTDir::kForward, config, output); END_TIMER(MixedRadix, "MixedRadix NTT"); std::cout << "Validating output" << std::endl; @@ -101,7 +100,7 @@ int main(int argc, char* argv[]) config.ntt_algorithm = NttAlgorithm::Radix2; START_TIMER(Radix2); - err = NTT(input, ntt_size, NTTDir::kForward, config, output); + err = bn254_ntt_cuda(input, ntt_size, NTTDir::kForward, config, output); END_TIMER(Radix2, "Radix2 NTT"); std::cout << "Validating output" << std::endl; diff --git a/examples/c++/ntt/run.sh b/examples/c++/ntt/run.sh index 6e3fc976..ce22b116 100755 --- a/examples/c++/ntt/run.sh +++ b/examples/c++/ntt/run.sh @@ -1,2 +1,2 @@ #!/bin/bash -./build/example +./build/example/example \ No newline at end of file diff --git a/examples/c++/pedersen-commitment/CMakeLists.txt b/examples/c++/pedersen-commitment/CMakeLists.txt index 424b3e9b..b559716e 100644 --- a/examples/c++/pedersen-commitment/CMakeLists.txt +++ b/examples/c++/pedersen-commitment/CMakeLists.txt @@ -8,18 +8,19 @@ if (${CMAKE_VERSION} VERSION_LESS "3.24.0") else() set(CMAKE_CUDA_ARCHITECTURES native) # on 3.24+, on earlier it is ignored, and the target is not passed endif () -project(icicle LANGUAGES CUDA CXX) +project(example LANGUAGES CUDA CXX) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS_RELEASE "") set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -g -G -O0") -# change the path to your Icicle location -include_directories("../../../icicle") add_executable( example example.cu ) + +target_include_directories(example PRIVATE "../../../icicle/include") +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_curve_bn254.a) +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_field_bn254.a) find_library(NVML_LIBRARY nvidia-ml PATHS /usr/local/cuda/targets/x86_64-linux/lib/stubs/ ) target_link_libraries(example ${NVML_LIBRARY}) set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) - diff --git a/examples/c++/pedersen-commitment/compile.sh b/examples/c++/pedersen-commitment/compile.sh index 36c1ddac..ab4e191d 100755 --- a/examples/c++/pedersen-commitment/compile.sh +++ b/examples/c++/pedersen-commitment/compile.sh @@ -3,7 +3,13 @@ # Exit immediately on error set -e -rm -rf build -mkdir -p build -cmake -S . -B build -cmake --build build +mkdir -p build/example +mkdir -p build/icicle + +# Configure and build Icicle +cmake -S ../../../icicle/ -B build/icicle -DCMAKE_BUILD_TYPE=Release -DCURVE=bn254 +cmake --build build/icicle + +# Configure and build the example application +cmake -S . -B build/example +cmake --build build/example \ No newline at end of file diff --git a/examples/c++/pedersen-commitment/example.cu b/examples/c++/pedersen-commitment/example.cu index 567a9e05..93f47b2b 100644 --- a/examples/c++/pedersen-commitment/example.cu +++ b/examples/c++/pedersen-commitment/example.cu @@ -4,9 +4,9 @@ #include #include -#define CURVE_ID BN254 -#include "appUtils/msm/msm.cu" -using namespace curve_config; +#include "api/bn254.h" +#include "msm/msm.cuh" +using namespace bn254; typedef point_field_t T; @@ -138,15 +138,15 @@ int main(int argc, char** argv) std::cout << "Generating commitment vector" << std::endl; projective_t result; scalar_t* scalars = new scalar_t[N+1]; - scalar_t::RandHostMany(scalars, N); + scalar_t::rand_host_many(scalars, N); std::cout << "Generating salt" << std::endl; scalars[N] = scalar_t::rand_host(); std::cout << "Executing MSM" << std::endl; - auto config = msm::DefaultMSMConfig(); + auto config = msm::default_msm_config(); START_TIMER(msm); - msm::MSM(scalars, points, N+1, config, &result); + bn254_msm_cuda(scalars, points, N+1, config, &result); END_TIMER(msm, "Time to execute MSM"); std::cout << "Computed commitment: " << result << std::endl; diff --git a/examples/c++/pedersen-commitment/run.sh b/examples/c++/pedersen-commitment/run.sh index 6e3fc976..01eca66b 100755 --- a/examples/c++/pedersen-commitment/run.sh +++ b/examples/c++/pedersen-commitment/run.sh @@ -1,2 +1,2 @@ #!/bin/bash -./build/example +./build/example/example diff --git a/examples/c++/polynomial_multiplication/CMakeLists.txt b/examples/c++/polynomial_multiplication/CMakeLists.txt index 03873910..8879690e 100644 --- a/examples/c++/polynomial_multiplication/CMakeLists.txt +++ b/examples/c++/polynomial_multiplication/CMakeLists.txt @@ -8,7 +8,7 @@ if (${CMAKE_VERSION} VERSION_LESS "3.24.0") else() set(CMAKE_CUDA_ARCHITECTURES native) # on 3.24+, on earlier it is ignored, and the target is not passed endif () -project(icicle LANGUAGES CUDA CXX) +project(example LANGUAGES CUDA CXX) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS_RELEASE "") @@ -20,7 +20,8 @@ add_executable( example.cu ) -find_library(NVML_LIBRARY nvidia-ml PATHS /usr/local/cuda-12.0/targets/x86_64-linux/lib/stubs/ ) +target_include_directories(example PRIVATE "../../../icicle/include") +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_field_bn254.a) +find_library(NVML_LIBRARY nvidia-ml PATHS /usr/local/cuda/targets/x86_64-linux/lib/stubs/ ) target_link_libraries(example ${NVML_LIBRARY}) -set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) - +set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) \ No newline at end of file diff --git a/examples/c++/polynomial_multiplication/compile.sh b/examples/c++/polynomial_multiplication/compile.sh index a7ba1621..de35c62d 100755 --- a/examples/c++/polynomial_multiplication/compile.sh +++ b/examples/c++/polynomial_multiplication/compile.sh @@ -3,9 +3,13 @@ # Exit immediately on error set -e -rm -rf build -mkdir -p build -cmake -S . -B build -cmake --build build +mkdir -p build/example +mkdir -p build/icicle +# Configure and build Icicle +cmake -S ../../../icicle/ -B build/icicle -DMSM=OFF -DCMAKE_BUILD_TYPE=Release -DCURVE=bn254 +cmake --build build/icicle +# Configure and build the example application +cmake -S . -B build/example +cmake --build build/example \ No newline at end of file diff --git a/examples/c++/polynomial_multiplication/example.cu b/examples/c++/polynomial_multiplication/example.cu index dfaa5e87..7a6e4da1 100644 --- a/examples/c++/polynomial_multiplication/example.cu +++ b/examples/c++/polynomial_multiplication/example.cu @@ -1,18 +1,14 @@ -#define CURVE_ID BLS12_381 - #include #include #include - -#include "curves/curve_config.cuh" -#include "appUtils/ntt/ntt.cu" -#include "appUtils/ntt/kernel_ntt.cu" -#include "utils/vec_ops.cu" -#include "utils/error_handler.cuh" #include -typedef curve_config::scalar_t test_scalar; -typedef curve_config::scalar_t test_data; +#include "api/bn254.h" +#include "gpu-utils/error_handler.cuh" + +using namespace bn254; +typedef scalar_t test_scalar; +typedef scalar_t test_data; void random_samples(test_data* res, uint32_t count) { @@ -45,7 +41,7 @@ int main(int argc, char** argv) CHK_IF_RETURN(cudaFree(nullptr)); // init GPU context // init domain - auto ntt_config = ntt::DefaultNTTConfig(); + auto ntt_config = ntt::default_ntt_config(); const bool is_radix2_alg = (argc > 1) ? atoi(argv[1]) : false; ntt_config.ntt_algorithm = is_radix2_alg ? ntt::NttAlgorithm::Radix2 : ntt::NttAlgorithm::MixedRadix; @@ -55,8 +51,8 @@ int main(int argc, char** argv) CHK_IF_RETURN(cudaEventCreate(&start)); CHK_IF_RETURN(cudaEventCreate(&stop)); - const test_scalar basic_root = test_scalar::omega(NTT_LOG_SIZE); - ntt::InitDomain(basic_root, ntt_config.ctx, true /*=fast_twidddles_mode*/); + test_scalar basic_root = test_scalar::omega(NTT_LOG_SIZE); + bn254_initialize_domain(&basic_root, ntt_config.ctx, true /*=fast_twidddles_mode*/); // (1) cpu allocation auto CpuA = std::make_unique(NTT_SIZE); @@ -79,26 +75,25 @@ int main(int argc, char** argv) ntt_config.are_inputs_on_device = false; ntt_config.are_outputs_on_device = true; ntt_config.ordering = ntt::Ordering::kNM; - CHK_IF_RETURN(ntt::NTT(CpuA.get(), NTT_SIZE, ntt::NTTDir::kForward, ntt_config, GpuA)); - CHK_IF_RETURN(ntt::NTT(CpuB.get(), NTT_SIZE, ntt::NTTDir::kForward, ntt_config, GpuB)); + CHK_IF_RETURN(bn254_ntt_cuda(CpuA.get(), NTT_SIZE, ntt::NTTDir::kForward, ntt_config, GpuA)); + CHK_IF_RETURN(bn254_ntt_cuda(CpuB.get(), NTT_SIZE, ntt::NTTDir::kForward, ntt_config, GpuB)); // (4) multiply A,B CHK_IF_RETURN(cudaMallocAsync(&MulGpu, sizeof(test_data) * NTT_SIZE, ntt_config.ctx.stream)); - vec_ops::VecOpsConfig config{ + vec_ops::VecOpsConfig config{ ntt_config.ctx, true, // is_a_on_device true, // is_b_on_device true, // is_result_on_device - false, // is_montgomery false // is_async }; - CHK_IF_RETURN(vec_ops::Mul(GpuA, GpuB, NTT_SIZE, config, MulGpu)); + CHK_IF_RETURN(bn254_mul_cuda(GpuA, GpuB, NTT_SIZE, config, MulGpu)); // (5) INTT (in place) ntt_config.are_inputs_on_device = true; ntt_config.are_outputs_on_device = true; ntt_config.ordering = ntt::Ordering::kMN; - CHK_IF_RETURN(ntt::NTT(MulGpu, NTT_SIZE, ntt::NTTDir::kInverse, ntt_config, MulGpu)); + CHK_IF_RETURN(bn254_ntt_cuda(MulGpu, NTT_SIZE, ntt::NTTDir::kInverse, ntt_config, MulGpu)); CHK_IF_RETURN(cudaFreeAsync(GpuA, ntt_config.ctx.stream)); CHK_IF_RETURN(cudaFreeAsync(GpuB, ntt_config.ctx.stream)); @@ -117,7 +112,7 @@ int main(int argc, char** argv) benchmark(false); // warmup benchmark(true, 20); - ntt::ReleaseDomain(ntt_config.ctx); + bn254_release_domain(ntt_config.ctx); CHK_IF_RETURN(cudaStreamSynchronize(ntt_config.ctx.stream)); return 0; diff --git a/examples/c++/polynomial_multiplication/run.sh b/examples/c++/polynomial_multiplication/run.sh index b57c9a02..eac9b1b2 100755 --- a/examples/c++/polynomial_multiplication/run.sh +++ b/examples/c++/polynomial_multiplication/run.sh @@ -1,3 +1,3 @@ #!/bin/bash -./build/example 1 # radix2 -./build/example 0 # mixed-radix +./build/example/example 1 # radix2 +./build/example/example 0 # mixed-radix diff --git a/examples/c++/poseidon/CMakeLists.txt b/examples/c++/poseidon/CMakeLists.txt index c29c97e7..ac06091f 100644 --- a/examples/c++/poseidon/CMakeLists.txt +++ b/examples/c++/poseidon/CMakeLists.txt @@ -13,13 +13,11 @@ project(icicle LANGUAGES CUDA CXX) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") set(CMAKE_CUDA_FLAGS_RELEASE "") set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -g -G -O0") -# change the path to your Icicle location -include_directories("../../../icicle") + add_executable( example example.cu ) - -find_library(NVML_LIBRARY nvidia-ml PATHS /usr/local/cuda-12.0/targets/x86_64-linux/lib/stubs/ ) -target_link_libraries(example ${NVML_LIBRARY}) +target_include_directories(example PRIVATE "../../../icicle/include") +target_link_libraries(example ${CMAKE_SOURCE_DIR}/build/icicle/lib/libingo_field_bn254.a) set_target_properties(example PROPERTIES CUDA_SEPARABLE_COMPILATION ON) \ No newline at end of file diff --git a/examples/c++/poseidon/compile.sh b/examples/c++/poseidon/compile.sh index 36c1ddac..de35c62d 100755 --- a/examples/c++/poseidon/compile.sh +++ b/examples/c++/poseidon/compile.sh @@ -3,7 +3,13 @@ # Exit immediately on error set -e -rm -rf build -mkdir -p build -cmake -S . -B build -cmake --build build +mkdir -p build/example +mkdir -p build/icicle + +# Configure and build Icicle +cmake -S ../../../icicle/ -B build/icicle -DMSM=OFF -DCMAKE_BUILD_TYPE=Release -DCURVE=bn254 +cmake --build build/icicle + +# Configure and build the example application +cmake -S . -B build/example +cmake --build build/example \ No newline at end of file diff --git a/examples/c++/poseidon/example.cu b/examples/c++/poseidon/example.cu index b3bdbb74..3b2fe517 100644 --- a/examples/c++/poseidon/example.cu +++ b/examples/c++/poseidon/example.cu @@ -2,14 +2,12 @@ #include #include -// select the curve -#define CURVE_ID 2 -// include Poseidon template -#include "appUtils/poseidon/poseidon.cu" +#include "api/bn254.h" +#include "curves/params/bn254.cuh" using namespace poseidon; -using namespace curve_config; +using namespace bn254; -device_context::DeviceContext ctx= device_context::get_default_device_context(); +device_context::DeviceContext ctx = device_context::get_default_device_context(); // location of a tree node in the array for a given level and offset inline uint32_t tree_index(uint32_t level, uint32_t offset) { return (1 << level) - 1 + offset; } @@ -21,8 +19,7 @@ void build_tree( for (uint32_t level = tree_height - 1; level > 0; level--) { const uint32_t next_level = level - 1; const uint32_t next_level_width = 1 << next_level; - poseidon_hash( - &tree[tree_index(level, 0)], &tree[tree_index(next_level, 0)], next_level_width, *constants, config); + bn254_poseidon_hash_cuda(&tree[tree_index(level, 0)], &tree[tree_index(next_level, 0)], next_level_width, 2, *constants, config); } } @@ -85,7 +82,7 @@ uint32_t validate_proof( hashes_in[1] = level_hash; } // next level hash - poseidon_hash(hashes_in, hash_out, 1, *constants, config); + bn254_poseidon_hash_cuda(hashes_in, hash_out, 1, 2, *constants, config); level_hash = hash_out[0]; } return proof_hash[0] == level_hash; @@ -116,14 +113,14 @@ int main(int argc, char* argv[]) } std::cout << "Hashing blocks into tree leaves..." << std::endl; PoseidonConstants constants; - init_optimized_poseidon_constants(data_arity, ctx, &constants); - PoseidonConfig config = default_poseidon_config(data_arity+1); - poseidon_hash(data, &tree[tree_index(leaf_level, 0)], tree_width, constants, config); + bn254_init_optimized_poseidon_constants_cuda(data_arity, ctx, &constants); + PoseidonConfig config = default_poseidon_config(data_arity+1); + bn254_poseidon_hash_cuda(data, &tree[tree_index(leaf_level, 0)], tree_width, 4, constants, config); std::cout << "3. Building Merkle tree" << std::endl; PoseidonConstants tree_constants; - init_optimized_poseidon_constants(tree_arity, ctx, &tree_constants); - PoseidonConfig tree_config = default_poseidon_config(tree_arity+1); + bn254_init_optimized_poseidon_constants_cuda(tree_arity, ctx, &tree_constants); + PoseidonConfig tree_config = default_poseidon_config(tree_arity+1); build_tree(tree_height, tree, &tree_constants, tree_config); std::cout << "4. Generate membership proof" << std::endl; diff --git a/examples/c++/poseidon/run.sh b/examples/c++/poseidon/run.sh index 6e3fc976..ce22b116 100755 --- a/examples/c++/poseidon/run.sh +++ b/examples/c++/poseidon/run.sh @@ -1,2 +1,2 @@ #!/bin/bash -./build/example +./build/example/example \ No newline at end of file diff --git a/examples/rust/msm/Cargo.toml b/examples/rust/msm/Cargo.toml index 6ae66694..11e15108 100644 --- a/examples/rust/msm/Cargo.toml +++ b/examples/rust/msm/Cargo.toml @@ -8,12 +8,11 @@ icicle-cuda-runtime = { path = "../../../wrappers/rust/icicle-cuda-runtime" } icicle-core = { path = "../../../wrappers/rust/icicle-core" } icicle-bn254 = { path = "../../../wrappers/rust/icicle-curves/icicle-bn254", features = ["g2"] } icicle-bls12-377 = { path = "../../../wrappers/rust/icicle-curves/icicle-bls12-377" } -ark-bn254 = { version = "0.4.0", optional = true} -ark-bls12-377 = { version = "0.4.0", optional = true} -ark-ec = { version = "0.4.0", optional = true} +ark-bn254 = { version = "0.4.0", optional = true } +ark-bls12-377 = { version = "0.4.0", optional = true } +ark-ec = { version = "0.4.0", optional = true } clap = { version = "<=4.4.12", features = ["derive"] } [features] arkworks = ["ark-bn254", "ark-bls12-377", "ark-ec", "icicle-core/arkworks", "icicle-bn254/arkworks", "icicle-bls12-377/arkworks"] profile = [] -g2 = [] diff --git a/examples/rust/msm/src/main.rs b/examples/rust/msm/src/main.rs index 75544194..1e5118fa 100644 --- a/examples/rust/msm/src/main.rs +++ b/examples/rust/msm/src/main.rs @@ -4,7 +4,10 @@ use icicle_bls12_377::curve::{ CurveCfg as BLS12377CurveCfg, G1Projective as BLS12377G1Projective, ScalarCfg as BLS12377ScalarCfg, }; -use icicle_cuda_runtime::{memory::HostOrDeviceSlice, stream::CudaStream}; +use icicle_cuda_runtime::{ + memory::{DeviceVec, HostSlice}, + stream::CudaStream, +}; use icicle_core::{curve::Curve, msm, traits::GenerateRandom}; @@ -57,18 +60,18 @@ fn main() { log_size, size ); // Setting Bn254 points and scalars - let points = HostOrDeviceSlice::Host(upper_points[..size].to_vec()); - let g2_points = HostOrDeviceSlice::Host(g2_upper_points[..size].to_vec()); - let scalars = HostOrDeviceSlice::Host(upper_scalars[..size].to_vec()); + let points = HostSlice::from_slice(&upper_points[..size]); + let g2_points = HostSlice::from_slice(&g2_upper_points[..size]); + let scalars = HostSlice::from_slice(&upper_scalars[..size]); // Setting bls12377 points and scalars // let points_bls12377 = &upper_points_bls12377[..size]; - let points_bls12377 = HostOrDeviceSlice::Host(upper_points_bls12377[..size].to_vec()); // &upper_points_bls12377[..size]; - let scalars_bls12377 = HostOrDeviceSlice::Host(upper_scalars_bls12377[..size].to_vec()); + let points_bls12377 = HostSlice::from_slice(&upper_points_bls12377[..size]); // &upper_points_bls12377[..size]; + let scalars_bls12377 = HostSlice::from_slice(&upper_scalars_bls12377[..size]); println!("Configuring bn254 MSM..."); - let mut msm_results: HostOrDeviceSlice<'_, G1Projective> = HostOrDeviceSlice::cuda_malloc(1).unwrap(); - let mut g2_msm_results: HostOrDeviceSlice<'_, G2Projective> = HostOrDeviceSlice::cuda_malloc(1).unwrap(); + let mut msm_results = DeviceVec::::cuda_malloc(1).unwrap(); + let mut g2_msm_results = DeviceVec::::cuda_malloc(1).unwrap(); let stream = CudaStream::create().unwrap(); let g2_stream = CudaStream::create().unwrap(); let mut cfg = msm::MSMConfig::default(); @@ -82,8 +85,7 @@ fn main() { g2_cfg.is_async = true; println!("Configuring bls12377 MSM..."); - let mut msm_results_bls12377: HostOrDeviceSlice<'_, BLS12377G1Projective> = - HostOrDeviceSlice::cuda_malloc(1).unwrap(); + let mut msm_results_bls12377 = DeviceVec::::cuda_malloc(1).unwrap(); let stream_bls12377 = CudaStream::create().unwrap(); let mut cfg_bls12377 = msm::MSMConfig::default(); cfg_bls12377 @@ -94,7 +96,7 @@ fn main() { println!("Executing bn254 MSM on device..."); #[cfg(feature = "profile")] let start = Instant::now(); - msm::msm(&scalars, &points, &cfg, &mut msm_results).unwrap(); + msm::msm(scalars, points, &cfg, &mut msm_results[..]).unwrap(); #[cfg(feature = "profile")] println!( "ICICLE BN254 MSM on size 2^{log_size} took: {} ms", @@ -102,16 +104,16 @@ fn main() { .elapsed() .as_millis() ); - msm::msm(&scalars, &g2_points, &g2_cfg, &mut g2_msm_results).unwrap(); + msm::msm(scalars, g2_points, &g2_cfg, &mut g2_msm_results[..]).unwrap(); println!("Executing bls12377 MSM on device..."); #[cfg(feature = "profile")] let start = Instant::now(); msm::msm( - &scalars_bls12377, - &points_bls12377, + scalars_bls12377, + points_bls12377, &cfg_bls12377, - &mut msm_results_bls12377, + &mut msm_results_bls12377[..], ) .unwrap(); #[cfg(feature = "profile")] @@ -134,10 +136,10 @@ fn main() { .synchronize() .unwrap(); msm_results - .copy_to_host(&mut msm_host_result[..]) + .copy_to_host(HostSlice::from_mut_slice(&mut msm_host_result[..])) .unwrap(); g2_msm_results - .copy_to_host(&mut g2_msm_host_result[..]) + .copy_to_host(HostSlice::from_mut_slice(&mut g2_msm_host_result[..])) .unwrap(); println!("bn254 result: {:#?}", msm_host_result); println!("G2 bn254 result: {:#?}", g2_msm_host_result); @@ -146,7 +148,7 @@ fn main() { .synchronize() .unwrap(); msm_results_bls12377 - .copy_to_host(&mut msm_host_result_bls12377[..]) + .copy_to_host(HostSlice::from_mut_slice(&mut msm_host_result_bls12377[..])) .unwrap(); println!("bls12377 result: {:#?}", msm_host_result_bls12377); @@ -154,23 +156,19 @@ fn main() { { println!("Checking against arkworks..."); let ark_points: Vec = points - .as_slice() .iter() .map(|&point| point.to_ark()) .collect(); let ark_scalars: Vec = scalars - .as_slice() .iter() .map(|scalar| scalar.to_ark()) .collect(); let ark_points_bls12377: Vec = points_bls12377 - .as_slice() .iter() .map(|point| point.to_ark()) .collect(); let ark_scalars_bls12377: Vec = scalars_bls12377 - .as_slice() .iter() .map(|scalar| scalar.to_ark()) .collect(); diff --git a/examples/rust/ntt/src/main.rs b/examples/rust/ntt/src/main.rs index 1de1b195..9f560099 100644 --- a/examples/rust/ntt/src/main.rs +++ b/examples/rust/ntt/src/main.rs @@ -2,10 +2,14 @@ use icicle_bn254::curve::{ScalarCfg, ScalarField}; use icicle_bls12_377::curve::{ScalarCfg as BLS12377ScalarCfg, ScalarField as BLS12377ScalarField}; -use icicle_cuda_runtime::{device_context::DeviceContext, memory::HostOrDeviceSlice, stream::CudaStream}; +use icicle_cuda_runtime::{ + device_context::DeviceContext, + memory::{DeviceVec, HostSlice}, + stream::CudaStream, +}; use icicle_core::{ - ntt::{self, NTT}, + ntt::{self, initialize_domain}, traits::{FieldImpl, GenerateRandom}, }; @@ -41,14 +45,13 @@ fn main() { ); // Setting Bn254 points and scalars println!("Generating random inputs on host for bn254..."); - let scalars = HostOrDeviceSlice::Host(ScalarCfg::generate_random(size)); - let mut ntt_results: HostOrDeviceSlice<'_, ScalarField> = HostOrDeviceSlice::cuda_malloc(size).unwrap(); + let scalars = ScalarCfg::generate_random(size); + let mut ntt_results = DeviceVec::::cuda_malloc(size).unwrap(); // Setting bls12377 points and scalars println!("Generating random inputs on host for bls12377..."); - let scalars_bls12377 = HostOrDeviceSlice::Host(BLS12377ScalarCfg::generate_random(size)); - let mut ntt_results_bls12377: HostOrDeviceSlice<'_, BLS12377ScalarField> = - HostOrDeviceSlice::cuda_malloc(size).unwrap(); + let scalars_bls12377 = BLS12377ScalarCfg::generate_random(size); + let mut ntt_results_bls12377 = DeviceVec::::cuda_malloc(size).unwrap(); println!("Setting up bn254 Domain..."); let icicle_omega = ::get_root_of_unity( @@ -57,11 +60,11 @@ fn main() { ) .unwrap(); let ctx = DeviceContext::default(); - ScalarCfg::initialize_domain(ScalarField::from_ark(icicle_omega), &ctx).unwrap(); + initialize_domain(ScalarField::from_ark(icicle_omega), &ctx, true).unwrap(); println!("Configuring bn254 NTT..."); let stream = CudaStream::create().unwrap(); - let mut cfg = ntt::NTTConfig::default(); + let mut cfg = ntt::NTTConfig::<'_, ScalarField>::default(); cfg.ctx .stream = &stream; cfg.is_async = true; @@ -73,11 +76,11 @@ fn main() { ) .unwrap(); // reusing ctx from above - BLS12377ScalarCfg::initialize_domain(BLS12377ScalarField::from_ark(icicle_omega), &ctx).unwrap(); + initialize_domain(BLS12377ScalarField::from_ark(icicle_omega), &ctx, true).unwrap(); println!("Configuring bls12377 NTT..."); let stream_bls12377 = CudaStream::create().unwrap(); - let mut cfg_bls12377 = ntt::NTTConfig::default(); + let mut cfg_bls12377 = ntt::NTTConfig::<'_, BLS12377ScalarField>::default(); cfg_bls12377 .ctx .stream = &stream_bls12377; @@ -86,7 +89,13 @@ fn main() { println!("Executing bn254 NTT on device..."); #[cfg(feature = "profile")] let start = Instant::now(); - ntt::ntt(&scalars, ntt::NTTDir::kForward, &cfg, &mut ntt_results).unwrap(); + ntt::ntt( + HostSlice::from_slice(&scalars), + ntt::NTTDir::kForward, + &cfg, + &mut ntt_results[..], + ) + .unwrap(); #[cfg(feature = "profile")] println!( "ICICLE BN254 NTT on size 2^{log_size} took: {} μs", @@ -99,10 +108,10 @@ fn main() { #[cfg(feature = "profile")] let start = Instant::now(); ntt::ntt( - &scalars_bls12377, + HostSlice::from_slice(&scalars_bls12377), ntt::NTTDir::kForward, &cfg_bls12377, - &mut ntt_results_bls12377, + &mut ntt_results_bls12377[..], ) .unwrap(); #[cfg(feature = "profile")] @@ -119,7 +128,7 @@ fn main() { .unwrap(); let mut host_bn254_results = vec![ScalarField::zero(); size]; ntt_results - .copy_to_host(&mut host_bn254_results[..]) + .copy_to_host(HostSlice::from_mut_slice(&mut host_bn254_results[..])) .unwrap(); stream_bls12377 @@ -127,19 +136,17 @@ fn main() { .unwrap(); let mut host_bls12377_results = vec![BLS12377ScalarField::zero(); size]; ntt_results_bls12377 - .copy_to_host(&mut host_bls12377_results[..]) + .copy_to_host(HostSlice::from_mut_slice(&mut host_bls12377_results[..])) .unwrap(); println!("Checking against arkworks..."); let mut ark_scalars: Vec = scalars - .as_slice() .iter() .map(|scalar| scalar.to_ark()) .collect(); let bn254_domain = as EvaluationDomain>::new(size).unwrap(); let mut ark_scalars_bls12377: Vec = scalars_bls12377 - .as_slice() .iter() .map(|scalar| scalar.to_ark()) .collect(); diff --git a/examples/rust/polynomials/Cargo.toml b/examples/rust/polynomials/Cargo.toml new file mode 100644 index 00000000..2e60481a --- /dev/null +++ b/examples/rust/polynomials/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "polynomials" +version = "1.2.0" +edition = "2018" + +[dependencies] +icicle-cuda-runtime = { path = "../../../wrappers/rust/icicle-cuda-runtime" } +icicle-core = { path = "../../../wrappers/rust/icicle-core" } +icicle-bn254 = { path = "../../../wrappers/rust/icicle-curves/icicle-bn254" } +icicle-babybear = { path = "../../../wrappers/rust/icicle-fields/icicle-babybear" } +clap = { version = "<=4.4.12", features = ["derive"] } + +[features] +profile = [] diff --git a/examples/rust/polynomials/src/main.rs b/examples/rust/polynomials/src/main.rs new file mode 100644 index 00000000..1e0e6772 --- /dev/null +++ b/examples/rust/polynomials/src/main.rs @@ -0,0 +1,101 @@ +use icicle_babybear::field::ScalarField as babybearScalar; +use icicle_babybear::polynomials::DensePolynomial as PolynomialBabyBear; +use icicle_bn254::curve::ScalarField as bn254Scalar; +use icicle_bn254::polynomials::DensePolynomial as PolynomialBn254; + +use icicle_cuda_runtime::{ + device_context::DeviceContext, + memory::{DeviceVec, HostSlice}, +}; + +use icicle_core::{ + ntt::{get_root_of_unity, initialize_domain}, + polynomials::UnivariatePolynomial, + traits::{FieldImpl, GenerateRandom}, +}; + +#[cfg(feature = "profile")] +use std::time::Instant; + +use clap::Parser; + +#[derive(Parser, Debug)] +struct Args { + /// Size of NTT to run (20 for 2^20) + #[arg(short, long, default_value_t = 20)] + max_ntt_log_size: u8, + #[arg(short, long, default_value_t = 15)] + poly_log_size: u8, +} + +fn init(max_ntt_size: u64) { + // initialize NTT domain for all fields!. Polynomials ops relies on NTT. + let rou_bn254: bn254Scalar = get_root_of_unity(max_ntt_size); + let ctx = DeviceContext::default(); + initialize_domain(rou_bn254, &ctx, false /*=fast twiddles mode*/).unwrap(); + + let rou_babybear: babybearScalar = get_root_of_unity(max_ntt_size); + initialize_domain(rou_babybear, &ctx, false /*=fast twiddles mode*/).unwrap(); + + // initialize the cuda backend for polynomials + // make sure to initialize it per field + PolynomialBn254::init_cuda_backend(); + PolynomialBabyBear::init_cuda_backend(); +} + +fn randomize_poly

(size: usize, from_coeffs: bool) -> P +where + P: UnivariatePolynomial, + P::Field: FieldImpl, + P::FieldConfig: GenerateRandom, +{ + let coeffs_or_evals = P::FieldConfig::generate_random(size); + let p = if from_coeffs { + P::from_coeffs(HostSlice::from_slice(&coeffs_or_evals), size) + } else { + P::from_rou_evals(HostSlice::from_slice(&coeffs_or_evals), size) + }; + p +} + +fn main() { + let args = Args::parse(); + init(1 << args.max_ntt_log_size); + + // randomize three polynomials f,g,h over bn254 scalar field + let poly_size = 1 << args.poly_log_size; + let f = randomize_poly::(poly_size, true /*from random coeffs*/); + let g = randomize_poly::(poly_size / 2, true /*from random coeffs*/); + let h = randomize_poly::(poly_size / 4, false /*from random evaluations on rou*/); + + // randomize two polynomials over babybear field + let f_babybear = randomize_poly::(poly_size, true /*from random coeffs*/); + let g_babybear = randomize_poly::(poly_size / 2, true /*from random coeffs*/); + + // Arithmetic + let t0 = &f + &g; + let t1 = &f * &h; + let (q, r) = t1.divide(&t0); // computes q,r for t1(x)=q(x)*t0(x)+r(x) + + let _r_babybear = &f_babybear * &g_babybear; + + // check degree + let _r_degree = r.degree(); + + // evaluate in single domain point + let five = bn254Scalar::from_u32(5); + let q_at_five = q.eval(&five); + + // evaluate on domain. Note: domain and image can be either Host or Device slice. + // in this example domain in on host and evals on device. + let host_domain = [five, bn254Scalar::from_u32(30)]; + let mut device_image = DeviceVec::::cuda_malloc(host_domain.len()).unwrap(); + t1.eval_on_domain(HostSlice::from_slice(&host_domain), &mut device_image[..]); + + // slicing + let o = h.odd(); + let e = h.even(); + let fold = &e + &(&o * &q_at_five); // e(x) + o(x)*scalar + + let _coeff = fold.get_coeff(2); // coeff of x^2 +} diff --git a/examples/rust/poseidon/src/main.rs b/examples/rust/poseidon/src/main.rs index 5b4f79c4..fdad0f29 100644 --- a/examples/rust/poseidon/src/main.rs +++ b/examples/rust/poseidon/src/main.rs @@ -4,7 +4,7 @@ use icicle_cuda_runtime::device_context::DeviceContext; use icicle_core::poseidon::{load_optimized_poseidon_constants, poseidon_hash_many, PoseidonConfig}; use icicle_core::traits::FieldImpl; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::HostSlice; #[cfg(feature = "profile")] use std::time::Instant; @@ -25,23 +25,29 @@ fn main() { println!("Running Icicle Examples: Rust Poseidon Hash"); let arity = 2u32; - println!("---------------------- Loading optimized Poseidon constants for arity={} ------------------------", arity); + println!( + "---------------------- Loading optimized Poseidon constants for arity={} ------------------------", + arity + ); let ctx = DeviceContext::default(); let constants = load_optimized_poseidon_constants::(arity, &ctx).unwrap(); let config = PoseidonConfig::default(); - println!("---------------------- Input size 2^{}={} ------------------------", size, test_size); - let inputs = vec![F::one(); test_size * arity as usize]; - let outputs = vec![F::zero(); test_size]; - let mut input_slice = HostOrDeviceSlice::on_host(inputs); - let mut output_slice = HostOrDeviceSlice::on_host(outputs); + println!( + "---------------------- Input size 2^{}={} ------------------------", + size, test_size + ); + let mut inputs = vec![F::one(); test_size * arity as usize]; + let mut outputs = vec![F::zero(); test_size]; + let input_slice = HostSlice::from_mut_slice(&mut inputs); + let output_slice = HostSlice::from_mut_slice(&mut outputs); println!("Executing BLS12-381 Poseidon Hash on device..."); #[cfg(feature = "profile")] let start = Instant::now(); poseidon_hash_many::( - &mut input_slice, - &mut output_slice, + input_slice, + output_slice, test_size as u32, arity as u32, &constants, @@ -49,5 +55,10 @@ fn main() { ) .unwrap(); #[cfg(feature = "profile")] - println!("ICICLE BLS12-381 Poseidon Hash on size 2^{size} took: {} μs", start.elapsed().as_micros()); -} \ No newline at end of file + println!( + "ICICLE BLS12-381 Poseidon Hash on size 2^{size} took: {} μs", + start + .elapsed() + .as_micros() + ); +} diff --git a/icicle/CMakeLists.txt b/icicle/CMakeLists.txt index 3b45783f..8d240d4a 100644 --- a/icicle/CMakeLists.txt +++ b/icicle/CMakeLists.txt @@ -1,169 +1,62 @@ cmake_minimum_required(VERSION 3.18) -# GoogleTest requires at least C++14 -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CUDA_STANDARD 17) -set(CMAKE_CUDA_STANDARD_REQUIRED TRUE) -set(CMAKE_CXX_STANDARD_REQUIRED TRUE) - -if("$ENV{ICICLE_PIC}" STREQUAL "OFF" OR ICICLE_PIC STREQUAL "OFF") - message(WARNING "Note that PIC (position-independent code) is disabled.") -else() - set(CMAKE_POSITION_INDEPENDENT_CODE ON) -endif() - -# add the target cuda architectures -# each additional architecture increases the compilation time and output file size -if(${CMAKE_VERSION} VERSION_LESS "3.24.0") - set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCH}) -else() - find_program(_nvidia_smi "nvidia-smi") - - if(_nvidia_smi) - set(DETECT_GPU_COUNT_NVIDIA_SMI 0) - - # execute nvidia-smi -L to get a short list of GPUs available - exec_program(${_nvidia_smi_path} ARGS -L - OUTPUT_VARIABLE _nvidia_smi_out - RETURN_VALUE _nvidia_smi_ret) - - # process the stdout of nvidia-smi - if(_nvidia_smi_ret EQUAL 0) - # convert string with newlines to list of strings - string(REGEX REPLACE "\n" ";" _nvidia_smi_out "${_nvidia_smi_out}") - - foreach(_line ${_nvidia_smi_out}) - if(_line MATCHES "^GPU [0-9]+:") - math(EXPR DETECT_GPU_COUNT_NVIDIA_SMI "${DETECT_GPU_COUNT_NVIDIA_SMI}+1") - - # the UUID is not very useful for the user, remove it - string(REGEX REPLACE " \\(UUID:.*\\)" "" _gpu_info "${_line}") - - if(NOT _gpu_info STREQUAL "") - list(APPEND DETECT_GPU_INFO "${_gpu_info}") - endif() - endif() - endforeach() - - check_num_gpu_info(${DETECT_GPU_COUNT_NVIDIA_SMI} DETECT_GPU_INFO) - set(DETECT_GPU_COUNT ${DETECT_GPU_COUNT_NVIDIA_SMI}) - endif() - endif() - - # ## - if(DETECT_GPU_COUNT GREATER 0) - set(CMAKE_CUDA_ARCHITECTURES native) # do native - else() - # no GPUs found, like on Github CI runners - set(CMAKE_CUDA_ARCHITECTURES 50) # some safe value - endif() -endif() - project(icicle LANGUAGES CUDA CXX) -# Check CUDA version and, if possible, enable multi-threaded compilation -if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "12.2") - message(STATUS "Using multi-threaded CUDA compilation.") - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --split-compile 0") -else() - message(STATUS "Can't use multi-threaded CUDA compilation.") -endif() -set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr") -set(CMAKE_CUDA_FLAGS_RELEASE "") -set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -g -lineinfo") -include_directories("${CMAKE_SOURCE_DIR}") +include(cmake/Common.cmake) +include(cmake/FieldsCommon.cmake) +include(cmake/CurvesCommon.cmake) -# when adding a new curve/field, append its name to the end of this list -set(SUPPORTED_CURVES bn254;bls12_381;bls12_377;bw6_761;grumpkin) -set(SUPPORTED_CURVES_WITH_POSEIDON bn254;bls12_381;bls12_377;bw6_761;grumpkin) -SET(SUPPORTED_CURVES_WITHOUT_NTT grumpkin) +set_env() +set_gpu_env() -set(IS_CURVE_SUPPORTED FALSE) -set(I 0) -foreach (SUPPORTED_CURVE ${SUPPORTED_CURVES}) - math(EXPR I "${I} + 1") - if (CURVE STREQUAL SUPPORTED_CURVE) - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DCURVE_ID=${I}") - set(IS_CURVE_SUPPORTED TRUE) - endif () -endforeach() +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -if (NOT IS_CURVE_SUPPORTED) - message( FATAL_ERROR "The value of CURVE variable: ${CURVE} is not one of the supported curves: ${SUPPORTED_CURVES}" ) +option(DEVMODE "Enable development mode" OFF) +option(EXT_FIELD "Build extension field" OFF) +option(G2 "Build G2" OFF) +option(MSM "Build MSM" ON) +option(ECNTT "Build ECNTT" OFF) +option(BUILD_HASH "Build hash functions" OFF) +option(BUILD_TESTS "Build unit tests" OFF) +option(BUILD_BENCHMARKS "Build benchmarks" OFF) +# add options here + +if((DEFINED CURVE) AND (DEFINED FIELD)) + message( FATAL_ERROR "CURVE and FIELD cannot be defined at the same time" ) endif () -if (DEVMODE STREQUAL "ON") +if (DEVMODE) set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -O0 --ptxas-options=-O0 --ptxas-options=-allow-expensive-optimizations=false -DDEVMODE=ON") endif () -if (G2_DEFINED STREQUAL "ON") - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DG2_DEFINED=ON") +if(DEFINED FIELD) + check_field() + add_subdirectory(src/fields) endif () -if (ECNTT_DEFINED STREQUAL "ON") - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DECNTT_DEFINED=ON") +if(DEFINED CURVE) + check_curve() + set(FIELD ${CURVE}) + add_subdirectory(src/fields) + add_subdirectory(src/curves) endif () -option(BUILD_TESTS "Build tests" OFF) - -if (NOT BUILD_TESTS) - - message(STATUS "Building without tests.") - - if (CURVE IN_LIST SUPPORTED_CURVES_WITH_POSEIDON) - list(APPEND ICICLE_SOURCES appUtils/poseidon/poseidon.cu) - list(APPEND ICICLE_SOURCES appUtils/tree/merkle.cu) - endif() - - if (NOT CURVE IN_LIST SUPPORTED_CURVES_WITHOUT_NTT) - list(APPEND ICICLE_SOURCES appUtils/ntt/ntt.cu) - list(APPEND ICICLE_SOURCES appUtils/ntt/kernel_ntt.cu) - if(ECNTT_DEFINED STREQUAL "ON") - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DECNTT_DEFINED=ON") - endif() - endif() - - add_library( - icicle - utils/vec_ops.cu - utils/mont.cu - primitives/field.cu - primitives/projective.cu - appUtils/msm/msm.cu - ${ICICLE_SOURCES} - ) - set_target_properties(icicle PROPERTIES OUTPUT_NAME "ingo_${CURVE}") - target_compile_definitions(icicle PRIVATE CURVE=${CURVE}) - -else() - - message(STATUS "Building tests.") - - include(FetchContent) - FetchContent_Declare( - googletest - URL https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip - ) - # For Windows: Prevent overriding the parent project's compiler/linker settings - - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - FetchContent_MakeAvailable(googletest) - - enable_testing() - - add_executable( - runner - tests/runner.cu - ) - - target_link_libraries( - runner - GTest::gtest_main - ) - - include(GoogleTest) - set_target_properties(runner PROPERTIES CUDA_SEPARABLE_COMPILATION ON) - - gtest_discover_tests(runner) - +if (G2) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DG2") endif () + +if (EXT_FIELD) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DEXT_FIELD") +endif () + +if(BUILD_HASH) + add_subdirectory(src/hash) +endif () + +if (BUILD_TESTS) + add_subdirectory(tests) +endif() + +if (BUILD_BENCHMARKS) + add_subdirectory(benchmarks) +endif() diff --git a/icicle/appUtils/msm/Makefile b/icicle/appUtils/msm/Makefile deleted file mode 100644 index c30bdb52..00000000 --- a/icicle/appUtils/msm/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -test_msm: - mkdir -p work - nvcc -o work/test_msm -std=c++17 -I. -I../.. tests/msm_test.cu - work/test_msm diff --git a/icicle/appUtils/tree/Makefile b/icicle/appUtils/tree/Makefile deleted file mode 100644 index fc0c01d9..00000000 --- a/icicle/appUtils/tree/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -test_merkle: - nvcc -o test_merkle -I. -I../.. test.cu - ./test_merkle \ No newline at end of file diff --git a/icicle/benchmarks/CMakeLists.txt b/icicle/benchmarks/CMakeLists.txt new file mode 100644 index 00000000..2f8b8a49 --- /dev/null +++ b/icicle/benchmarks/CMakeLists.txt @@ -0,0 +1,5 @@ + +add_executable(benches benches.cu) +target_link_libraries(benches benchmark::benchmark) +target_include_directories(benches PUBLIC ${CMAKE_SOURCE_DIR}/include/) +find_package(benchmark REQUIRED) diff --git a/icicle/benchmarks/README.md b/icicle/benchmarks/README.md new file mode 100644 index 00000000..0e56cc8c --- /dev/null +++ b/icicle/benchmarks/README.md @@ -0,0 +1,25 @@ +# How to use benchmarks + +ICICLE uses [google benchmarks](https://github.com/google/benchmark) to measure the performance of primitives. + +To run benchmarks, make sure you have everything installed to run ICICLE (see top-level README for that). Next, you need to install google benchmarks library as described in their [documentation](https://github.com/google/benchmark?tab=readme-ov-file#installation). When running benchmarks, export the path to this installation: + +``` +export CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH: +``` + +Then to benchmark field arithmetic, say, on `babybear` field, run: + +``` +cmake -UCURVE -UFIELD -UG2 -UEXT_FIELD -DFIELD=babybear -DEXT_FIELD=ON -S . -B build; +cmake --build build; +build/benches --benchmark_counters_tabular=true +``` + +`-U` parameters are needed to clear variables from previous runs and `EXT_FIELD` can be disabled if benhcmarking the extension field is not needed. To benchmark a curve, say, `bn254`, change the first `cmake` call to: + +``` +cmake -UCURVE -UFIELD -UG2 -UEXT_FIELD -DCURVE=bn254 -S . -B build; +``` + +Benchmarks measure throughput of very cheap operations like field multiplication or EC addition by repeating them very many times in parallel, so throughput is the main metric to look at. \ No newline at end of file diff --git a/icicle/benchmarks/benches.cu b/icicle/benchmarks/benches.cu new file mode 100644 index 00000000..f7ff8635 --- /dev/null +++ b/icicle/benchmarks/benches.cu @@ -0,0 +1,6 @@ +#include "field_benchmarks.cu" +#ifdef CURVE_ID +#include "curve_benchmarks.cu" +#endif + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/icicle/benchmarks/curve_benchmarks.cu b/icicle/benchmarks/curve_benchmarks.cu new file mode 100644 index 00000000..379793ba --- /dev/null +++ b/icicle/benchmarks/curve_benchmarks.cu @@ -0,0 +1,79 @@ +#include +#include "utils/test_functions.cuh" +#include "curves/curve_config.cuh" + +using namespace curve_config; +using namespace benchmark; + +static void BM_MixedECAdd(State& state) +{ + constexpr int N = 128; + int n = state.range(0) / N; + projective_t* points1; + affine_t* points2; + assert(!cudaMalloc(&points1, n * sizeof(projective_t))); + assert(!cudaMalloc(&points2, n * sizeof(affine_t))); + + projective_t* h_points1 = (projective_t*)malloc(n * sizeof(projective_t)); + affine_t* h_points2 = (affine_t*)malloc(n * sizeof(affine_t)); + projective_t::rand_host_many(h_points1, n); + projective_t::rand_host_many_affine(h_points2, n); + cudaMemcpy(points1, h_points1, sizeof(projective_t) * n, cudaMemcpyHostToDevice); + cudaMemcpy(points2, h_points2, sizeof(affine_t) * n, cudaMemcpyHostToDevice); + + for (auto _ : state) { + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + assert((vec_add(points1, points2, points1, n)) == cudaSuccess); + assert(cudaStreamSynchronize(0) == cudaSuccess); + cudaEventRecord(stop); + + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + + state.SetIterationTime((double)(milliseconds / 1000)); + } + state.counters["Throughput"] = Counter(state.range(0), Counter::kIsRate | Counter::kIsIterationInvariant); + cudaFree(points1); + cudaFree(points2); +} + +static void BM_FullECAdd(benchmark::State& state) +{ + constexpr int N = 128; + int n = state.range(0) / N; + projective_t* points1; + projective_t* points2; + assert(!cudaMalloc(&points1, n * sizeof(projective_t))); + assert(!cudaMalloc(&points2, n * sizeof(projective_t))); + + projective_t* h_points1 = (projective_t*)malloc(n * sizeof(projective_t)); + projective_t* h_points2 = (projective_t*)malloc(n * sizeof(projective_t)); + projective_t::rand_host_many(h_points1, n); + projective_t::rand_host_many(h_points2, n); + cudaMemcpy(points1, h_points1, sizeof(projective_t) * n, cudaMemcpyHostToDevice); + cudaMemcpy(points2, h_points2, sizeof(projective_t) * n, cudaMemcpyHostToDevice); + + for (auto _ : state) { + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + assert((vec_add(points1, points2, points1, n)) == cudaSuccess); + assert(cudaStreamSynchronize(0) == cudaSuccess); + cudaEventRecord(stop); + + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + + state.SetIterationTime((double)(milliseconds / 1000)); + } + state.counters["Throughput"] = Counter(state.range(0), Counter::kIsRate | Counter::kIsIterationInvariant); + cudaFree(points1); + cudaFree(points2); +} + +BENCHMARK(BM_FullECAdd)->Range(1 << 27, 1 << 27)->Unit(benchmark::kMillisecond); +BENCHMARK(BM_MixedECAdd)->Range(1 << 27, 1 << 27)->Unit(benchmark::kMillisecond); diff --git a/icicle/benchmarks/field_benchmarks.cu b/icicle/benchmarks/field_benchmarks.cu new file mode 100644 index 00000000..cf5f2a24 --- /dev/null +++ b/icicle/benchmarks/field_benchmarks.cu @@ -0,0 +1,108 @@ +#include +#include "utils/test_functions.cuh" +#include "fields/field_config.cuh" + +using namespace field_config; +using namespace benchmark; + +template +static void BM_FieldAdd(State& state) +{ + constexpr int N = 256; + int n = state.range(0) / N; + T* scalars1; + T* scalars2; + assert(!cudaMalloc(&scalars1, n * sizeof(T))); + assert(!cudaMalloc(&scalars2, n * sizeof(T))); + + assert(device_populate_random(scalars1, n) == cudaSuccess); + assert(device_populate_random(scalars2, n) == cudaSuccess); + + for (auto _ : state) { + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + assert((vec_add(scalars1, scalars2, scalars1, n)) == cudaSuccess); + assert(cudaStreamSynchronize(0) == cudaSuccess); + cudaEventRecord(stop); + + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + + state.SetIterationTime((double)(milliseconds / 1000)); + } + state.counters["Throughput"] = Counter(state.range(0), Counter::kIsRate | Counter::kIsIterationInvariant); + cudaFree(scalars1); + cudaFree(scalars2); +} + +template +static void BM_FieldMul(State& state) +{ + constexpr int N = 128; + int n = state.range(0) / N; + T* scalars1; + T* scalars2; + assert(!cudaMalloc(&scalars1, n * sizeof(T))); + assert(!cudaMalloc(&scalars2, n * sizeof(T))); + + assert(device_populate_random(scalars1, n) == cudaSuccess); + assert(device_populate_random(scalars2, n) == cudaSuccess); + + for (auto _ : state) { + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + assert((vec_mul(scalars1, scalars2, scalars1, n)) == cudaSuccess); + assert(cudaStreamSynchronize(0) == cudaSuccess); + cudaEventRecord(stop); + + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + + state.SetIterationTime((double)(milliseconds / 1000)); + } + state.counters["Throughput"] = Counter(state.range(0), Counter::kIsRate | Counter::kIsIterationInvariant); + cudaFree(scalars1); + cudaFree(scalars2); +} + +template +static void BM_FieldSqr(State& state) +{ + constexpr int N = 128; + int n = state.range(0) / N; + T* scalars; + assert(!cudaMalloc(&scalars, n * sizeof(T))); + + assert(device_populate_random(scalars, n) == cudaSuccess); + + for (auto _ : state) { + cudaEvent_t start, stop; + cudaEventCreate(&start); + cudaEventCreate(&stop); + cudaEventRecord(start); + assert((field_vec_sqr(scalars, scalars, n)) == cudaSuccess); + assert(cudaStreamSynchronize(0) == cudaSuccess); + cudaEventRecord(stop); + + float milliseconds = 0; + cudaEventElapsedTime(&milliseconds, start, stop); + + state.SetIterationTime((double)(milliseconds / 1000)); + } + state.counters["Throughput"] = Counter(state.range(0), Counter::kIsRate | Counter::kIsIterationInvariant); + cudaFree(scalars); +} + +BENCHMARK(BM_FieldAdd)->Range(1 << 28, 1 << 28)->Unit(kMicrosecond); +BENCHMARK(BM_FieldMul)->Range(1 << 27, 1 << 27)->Unit(kMicrosecond); +BENCHMARK(BM_FieldSqr)->Range(1 << 27, 1 << 27)->Unit(kMicrosecond); + +#ifdef EXT_FIELD +BENCHMARK(BM_FieldAdd)->Range(1 << 28, 1 << 28)->Unit(kMicrosecond); +BENCHMARK(BM_FieldMul)->Range(1 << 27, 1 << 27)->Unit(kMicrosecond); +BENCHMARK(BM_FieldSqr)->Range(1 << 27, 1 << 27)->Unit(kMicrosecond); +#endif diff --git a/icicle/cmake/Common.cmake b/icicle/cmake/Common.cmake new file mode 100644 index 00000000..b7cf088e --- /dev/null +++ b/icicle/cmake/Common.cmake @@ -0,0 +1,72 @@ +function(set_env) + set(CMAKE_CXX_STANDARD 17 PARENT_SCOPE) + set(CMAKE_CUDA_STANDARD 17 PARENT_SCOPE) + set(CMAKE_CUDA_STANDARD_REQUIRED TRUE PARENT_SCOPE) + set(CMAKE_CXX_STANDARD_REQUIRED TRUE PARENT_SCOPE) + + if("$ENV{ICICLE_PIC}" STREQUAL "OFF" OR ICICLE_PIC STREQUAL "OFF") + message(WARNING "Note that PIC (position-independent code) is disabled.") + else() + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() +endfunction() + +function(set_gpu_env) + # add the target cuda architectures + # each additional architecture increases the compilation time and output file size + if(${CMAKE_VERSION} VERSION_LESS "3.24.0") + set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCH} PARENT_SCOPE) + else() + find_program(_nvidia_smi "nvidia-smi") + + if(_nvidia_smi) + set(DETECT_GPU_COUNT_NVIDIA_SMI 0) + + # execute nvidia-smi -L to get a short list of GPUs available + exec_program(${_nvidia_smi_path} ARGS -L + OUTPUT_VARIABLE _nvidia_smi_out + RETURN_VALUE _nvidia_smi_ret) + + # process the stdout of nvidia-smi + if(_nvidia_smi_ret EQUAL 0) + # convert string with newlines to list of strings + string(REGEX REPLACE "\n" ";" _nvidia_smi_out "${_nvidia_smi_out}") + + foreach(_line ${_nvidia_smi_out}) + if(_line MATCHES "^GPU [0-9]+:") + math(EXPR DETECT_GPU_COUNT_NVIDIA_SMI "${DETECT_GPU_COUNT_NVIDIA_SMI}+1") + + # the UUID is not very useful for the user, remove it + string(REGEX REPLACE " \\(UUID:.*\\)" "" _gpu_info "${_line}") + + if(NOT _gpu_info STREQUAL "") + list(APPEND DETECT_GPU_INFO "${_gpu_info}") + endif() + endif() + endforeach() + + check_num_gpu_info(${DETECT_GPU_COUNT_NVIDIA_SMI} DETECT_GPU_INFO) + set(DETECT_GPU_COUNT ${DETECT_GPU_COUNT_NVIDIA_SMI}) + endif() + endif() + + # ## + if(DETECT_GPU_COUNT GREATER 0) + set(CMAKE_CUDA_ARCHITECTURES native PARENT_SCOPE) # do native + else() + # no GPUs found, like on Github CI runners + set(CMAKE_CUDA_ARCHITECTURES 50 PARENT_SCOPE) # some safe value + endif() + endif() + + # Check CUDA version and, if possible, enable multi-threaded compilation + if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL "12.2") + message(STATUS "Using multi-threaded CUDA compilation.") + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --split-compile 0" PARENT_SCOPE) + else() + message(STATUS "Can't use multi-threaded CUDA compilation.") + endif() + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr" PARENT_SCOPE) + set(CMAKE_CUDA_FLAGS_RELEASE "" PARENT_SCOPE) + set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -g -lineinfo" PARENT_SCOPE) +endfunction() \ No newline at end of file diff --git a/icicle/cmake/CurvesCommon.cmake b/icicle/cmake/CurvesCommon.cmake new file mode 100644 index 00000000..952b47a4 --- /dev/null +++ b/icicle/cmake/CurvesCommon.cmake @@ -0,0 +1,17 @@ +function(check_curve) + set(SUPPORTED_CURVES bn254;bls12_381;bls12_377;bw6_761;grumpkin) + + set(IS_CURVE_SUPPORTED FALSE) + set(I 0) + foreach (SUPPORTED_CURVE ${SUPPORTED_CURVES}) + math(EXPR I "${I} + 1") + if (CURVE STREQUAL SUPPORTED_CURVE) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DCURVE_ID=${I} -DFIELD_ID=${I}" PARENT_SCOPE) + set(IS_CURVE_SUPPORTED TRUE) + endif () + endforeach() + + if (NOT IS_CURVE_SUPPORTED) + message( FATAL_ERROR "The value of CURVE variable: ${CURVE} is not one of the supported curves: ${SUPPORTED_CURVES}" ) + endif () +endfunction() diff --git a/icicle/cmake/FieldsCommon.cmake b/icicle/cmake/FieldsCommon.cmake new file mode 100644 index 00000000..85b25492 --- /dev/null +++ b/icicle/cmake/FieldsCommon.cmake @@ -0,0 +1,17 @@ +function(check_field) + set(SUPPORTED_FIELDS babybear) + + set(IS_FIELD_SUPPORTED FALSE) + set(I 1000) + foreach (SUPPORTED_FIELD ${SUPPORTED_FIELDS}) + math(EXPR I "${I} + 1") + if (FIELD STREQUAL SUPPORTED_FIELD) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DFIELD_ID=${I}" PARENT_SCOPE) + set(IS_FIELD_SUPPORTED TRUE) + endif () + endforeach() + + if (NOT IS_FIELD_SUPPORTED) + message( FATAL_ERROR "The value of FIELD variable: ${FIELD} is not one of the supported fields: ${SUPPORTED_FIELDS}" ) + endif () +endfunction() diff --git a/icicle/curves/curve_config.cuh b/icicle/curves/curve_config.cuh deleted file mode 100644 index 751d1456..00000000 --- a/icicle/curves/curve_config.cuh +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once -#ifndef INDEX_H -#define INDEX_H - -#define BN254 1 -#define BLS12_381 2 -#define BLS12_377 3 -#define BW6_761 4 -#define GRUMPKIN 5 - -#include "primitives/field.cuh" -#include "primitives/projective.cuh" -#if defined(G2_DEFINED) -#include "primitives/extension_field.cuh" -#endif - -#if CURVE_ID == BN254 -#include "bn254_params.cuh" -using namespace bn254; -#elif CURVE_ID == BLS12_381 -#include "bls12_381_params.cuh" -using namespace bls12_381; -#elif CURVE_ID == BLS12_377 -#include "bls12_377_params.cuh" -using namespace bls12_377; -#elif CURVE_ID == BW6_761 -#include "bw6_761_params.cuh" -using namespace bw6_761; -#elif CURVE_ID == GRUMPKIN -#include "grumpkin_params.cuh" -using namespace grumpkin; -#endif - -/** - * @namespace curve_config - * Namespace with type definitions for short Weierstrass pairing-friendly [elliptic - * curves](https://hyperelliptic.org/EFD/g1p/auto-shortw.html). Here, concrete types are created in accordance - * with the `-DCURVE` env variable passed during build. - */ -namespace curve_config { - /** - * Scalar field of the curve. Is always a prime field. - */ - typedef Field scalar_t; - /** - * Base field of G1 curve. Is always a prime field. - */ - typedef Field point_field_t; - static constexpr point_field_t generator_x = point_field_t{g1_gen_x}; - static constexpr point_field_t generator_y = point_field_t{g1_gen_y}; - static constexpr point_field_t b = point_field_t{weierstrass_b}; - /** - * [Projective representation](https://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html) - * of G1 curve consisting of three coordinates of type [point_field_t](point_field_t). - */ - typedef Projective projective_t; - /** - * Affine representation of G1 curve consisting of two coordinates of type [point_field_t](point_field_t). - */ - typedef Affine affine_t; - -#if defined(G2_DEFINED) -#if CURVE_ID == BW6_761 - typedef point_field_t g2_point_field_t; - static constexpr g2_point_field_t g2_generator_x = g2_point_field_t{g2_gen_x}; - static constexpr g2_point_field_t g2_generator_y = g2_point_field_t{g2_gen_y}; - static constexpr g2_point_field_t g2_b = g2_point_field_t{g2_weierstrass_b}; -#else - typedef ExtensionField g2_point_field_t; - static constexpr g2_point_field_t g2_generator_x = - g2_point_field_t{point_field_t{g2_gen_x_re}, point_field_t{g2_gen_x_im}}; - static constexpr g2_point_field_t g2_generator_y = - g2_point_field_t{point_field_t{g2_gen_y_re}, point_field_t{g2_gen_y_im}}; - static constexpr g2_point_field_t g2_b = - g2_point_field_t{point_field_t{weierstrass_b_g2_re}, point_field_t{weierstrass_b_g2_im}}; -#endif - /** - * [Projective representation](https://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html) of G2 curve. - */ - typedef Projective g2_projective_t; - /** - * Affine representation of G1 curve. - */ - typedef Affine g2_affine_t; -#endif - -} // namespace curve_config - -#endif \ No newline at end of file diff --git a/icicle/include/api/babybear.h b/icicle/include/api/babybear.h new file mode 100644 index 00000000..507f3213 --- /dev/null +++ b/icicle/include/api/babybear.h @@ -0,0 +1,73 @@ +// WARNING: This file is auto-generated by a script. +// Any changes made to this file may be overwritten. +// Please modify the code generation script instead. +// Path to the code generation script: scripts/gen_c_api.py + +#pragma once +#ifndef BABYBEAR_API_H +#define BABYBEAR_API_H + +#include +#include "gpu-utils/device_context.cuh" +#include "fields/stark_fields/babybear.cuh" +#include "ntt/ntt.cuh" +#include "vec_ops/vec_ops.cuh" + +extern "C" cudaError_t babybear_extension_ntt_cuda( + const babybear::extension_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, babybear::extension_t* output); + +extern "C" cudaError_t babybear_mul_cuda( + babybear::scalar_t* vec_a, babybear::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, babybear::scalar_t* result); + +extern "C" cudaError_t babybear_add_cuda( + babybear::scalar_t* vec_a, babybear::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, babybear::scalar_t* result); + +extern "C" cudaError_t babybear_sub_cuda( + babybear::scalar_t* vec_a, babybear::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, babybear::scalar_t* result); + +extern "C" cudaError_t babybear_transpose_matrix_cuda( + const babybear::scalar_t* input, + uint32_t row_size, + uint32_t column_size, + babybear::scalar_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); + +extern "C" void babybear_generate_scalars(babybear::scalar_t* scalars, int size); + +extern "C" cudaError_t babybear_scalar_convert_montgomery( + babybear::scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t babybear_initialize_domain( + babybear::scalar_t* primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode); + +extern "C" cudaError_t babybear_ntt_cuda( + const babybear::scalar_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, babybear::scalar_t* output); + +extern "C" cudaError_t babybear_release_domain(device_context::DeviceContext& ctx); + +extern "C" void babybear_extension_generate_scalars(babybear::extension_t* scalars, int size); + +extern "C" cudaError_t babybear_extension_scalar_convert_montgomery( + babybear::extension_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t babybear_extension_mul_cuda( + babybear::extension_t* vec_a, babybear::extension_t* vec_b, int n, vec_ops::VecOpsConfig& config, babybear::extension_t* result); + +extern "C" cudaError_t babybear_extension_add_cuda( + babybear::extension_t* vec_a, babybear::extension_t* vec_b, int n, vec_ops::VecOpsConfig& config, babybear::extension_t* result); + +extern "C" cudaError_t babybear_extension_sub_cuda( + babybear::extension_t* vec_a, babybear::extension_t* vec_b, int n, vec_ops::VecOpsConfig& config, babybear::extension_t* result); + +extern "C" cudaError_t babybear_extension_transpose_matrix_cuda( + const babybear::extension_t* input, + uint32_t row_size, + uint32_t column_size, + babybear::extension_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); + +#endif \ No newline at end of file diff --git a/icicle/include/api/bls12_377.h b/icicle/include/api/bls12_377.h new file mode 100644 index 00000000..800dc681 --- /dev/null +++ b/icicle/include/api/bls12_377.h @@ -0,0 +1,132 @@ +// WARNING: This file is auto-generated by a script. +// Any changes made to this file may be overwritten. +// Please modify the code generation script instead. +// Path to the code generation script: scripts/gen_c_api.py + +#pragma once +#ifndef BLS12_377_API_H +#define BLS12_377_API_H + +#include +#include "gpu-utils/device_context.cuh" +#include "curves/params/bls12_377.cuh" +#include "ntt/ntt.cuh" +#include "msm/msm.cuh" +#include "vec_ops/vec_ops.cuh" +#include "poseidon/poseidon.cuh" +#include "poseidon/tree/merkle.cuh" + +extern "C" cudaError_t bls12_377_g2_precompute_msm_bases_cuda( + bls12_377::g2_affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + bls12_377::g2_affine_t* output_bases); + +extern "C" cudaError_t bls12_377_g2_msm_cuda( + const bls12_377::scalar_t* scalars, const bls12_377::g2_affine_t* points, int msm_size, msm::MSMConfig& config, bls12_377::g2_projective_t* out); + +extern "C" cudaError_t bls12_377_precompute_msm_bases_cuda( + bls12_377::affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + bls12_377::affine_t* output_bases); + +extern "C" cudaError_t bls12_377_msm_cuda( + const bls12_377::scalar_t* scalars, const bls12_377::affine_t* points, int msm_size, msm::MSMConfig& config, bls12_377::projective_t* out); + +extern "C" bool bls12_377_g2_eq(bls12_377::g2_projective_t* point1, bls12_377::g2_projective_t* point2); + +extern "C" void bls12_377_g2_to_affine(bls12_377::g2_projective_t* point, bls12_377::g2_affine_t* point_out); + +extern "C" void bls12_377_g2_generate_projective_points(bls12_377::g2_projective_t* points, int size); + +extern "C" void bls12_377_g2_generate_affine_points(bls12_377::g2_affine_t* points, int size); + +extern "C" cudaError_t bls12_377_g2_affine_convert_montgomery( + bls12_377::g2_affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_377_g2_projective_convert_montgomery( + bls12_377::g2_projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_377_ecntt_cuda( + const bls12_377::projective_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, bls12_377::projective_t* output); + +extern "C" bool bls12_377_eq(bls12_377::projective_t* point1, bls12_377::projective_t* point2); + +extern "C" void bls12_377_to_affine(bls12_377::projective_t* point, bls12_377::affine_t* point_out); + +extern "C" void bls12_377_generate_projective_points(bls12_377::projective_t* points, int size); + +extern "C" void bls12_377_generate_affine_points(bls12_377::affine_t* points, int size); + +extern "C" cudaError_t bls12_377_affine_convert_montgomery( + bls12_377::affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_377_projective_convert_montgomery( + bls12_377::projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_377_create_optimized_poseidon_constants_cuda( + int arity, + int full_rounds_half, + int partial_rounds, + const bls12_377::scalar_t* constants, + device_context::DeviceContext& ctx, + poseidon::PoseidonConstants* poseidon_constants); + +extern "C" cudaError_t bls12_377_init_optimized_poseidon_constants_cuda( + int arity, device_context::DeviceContext& ctx, poseidon::PoseidonConstants* constants); + +extern "C" cudaError_t bls12_377_poseidon_hash_cuda( + bls12_377::scalar_t* input, + bls12_377::scalar_t* output, + int number_of_states, + int arity, + const poseidon::PoseidonConstants& constants, + poseidon::PoseidonConfig& config); + +extern "C" cudaError_t bls12_377_build_poseidon_merkle_tree( + const bls12_377::scalar_t* leaves, + bls12_377::scalar_t* digests, + uint32_t height, + int arity, + poseidon::PoseidonConstants& constants, + merkle::TreeBuilderConfig& config); + +extern "C" cudaError_t bls12_377_mul_cuda( + bls12_377::scalar_t* vec_a, bls12_377::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bls12_377::scalar_t* result); + +extern "C" cudaError_t bls12_377_add_cuda( + bls12_377::scalar_t* vec_a, bls12_377::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bls12_377::scalar_t* result); + +extern "C" cudaError_t bls12_377_sub_cuda( + bls12_377::scalar_t* vec_a, bls12_377::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bls12_377::scalar_t* result); + +extern "C" cudaError_t bls12_377_transpose_matrix_cuda( + const bls12_377::scalar_t* input, + uint32_t row_size, + uint32_t column_size, + bls12_377::scalar_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); + +extern "C" void bls12_377_generate_scalars(bls12_377::scalar_t* scalars, int size); + +extern "C" cudaError_t bls12_377_scalar_convert_montgomery( + bls12_377::scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_377_initialize_domain( + bls12_377::scalar_t* primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode); + +extern "C" cudaError_t bls12_377_ntt_cuda( + const bls12_377::scalar_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, bls12_377::scalar_t* output); + +extern "C" cudaError_t bls12_377_release_domain(device_context::DeviceContext& ctx); + +#endif \ No newline at end of file diff --git a/icicle/include/api/bls12_381.h b/icicle/include/api/bls12_381.h new file mode 100644 index 00000000..f96c886c --- /dev/null +++ b/icicle/include/api/bls12_381.h @@ -0,0 +1,132 @@ +// WARNING: This file is auto-generated by a script. +// Any changes made to this file may be overwritten. +// Please modify the code generation script instead. +// Path to the code generation script: scripts/gen_c_api.py + +#pragma once +#ifndef BLS12_381_API_H +#define BLS12_381_API_H + +#include +#include "gpu-utils/device_context.cuh" +#include "curves/params/bls12_381.cuh" +#include "ntt/ntt.cuh" +#include "msm/msm.cuh" +#include "vec_ops/vec_ops.cuh" +#include "poseidon/poseidon.cuh" +#include "poseidon/tree/merkle.cuh" + +extern "C" cudaError_t bls12_381_g2_precompute_msm_bases_cuda( + bls12_381::g2_affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + bls12_381::g2_affine_t* output_bases); + +extern "C" cudaError_t bls12_381_g2_msm_cuda( + const bls12_381::scalar_t* scalars, const bls12_381::g2_affine_t* points, int msm_size, msm::MSMConfig& config, bls12_381::g2_projective_t* out); + +extern "C" cudaError_t bls12_381_precompute_msm_bases_cuda( + bls12_381::affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + bls12_381::affine_t* output_bases); + +extern "C" cudaError_t bls12_381_msm_cuda( + const bls12_381::scalar_t* scalars, const bls12_381::affine_t* points, int msm_size, msm::MSMConfig& config, bls12_381::projective_t* out); + +extern "C" bool bls12_381_g2_eq(bls12_381::g2_projective_t* point1, bls12_381::g2_projective_t* point2); + +extern "C" void bls12_381_g2_to_affine(bls12_381::g2_projective_t* point, bls12_381::g2_affine_t* point_out); + +extern "C" void bls12_381_g2_generate_projective_points(bls12_381::g2_projective_t* points, int size); + +extern "C" void bls12_381_g2_generate_affine_points(bls12_381::g2_affine_t* points, int size); + +extern "C" cudaError_t bls12_381_g2_affine_convert_montgomery( + bls12_381::g2_affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_381_g2_projective_convert_montgomery( + bls12_381::g2_projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_381_ecntt_cuda( + const bls12_381::projective_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, bls12_381::projective_t* output); + +extern "C" bool bls12_381_eq(bls12_381::projective_t* point1, bls12_381::projective_t* point2); + +extern "C" void bls12_381_to_affine(bls12_381::projective_t* point, bls12_381::affine_t* point_out); + +extern "C" void bls12_381_generate_projective_points(bls12_381::projective_t* points, int size); + +extern "C" void bls12_381_generate_affine_points(bls12_381::affine_t* points, int size); + +extern "C" cudaError_t bls12_381_affine_convert_montgomery( + bls12_381::affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_381_projective_convert_montgomery( + bls12_381::projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_381_create_optimized_poseidon_constants_cuda( + int arity, + int full_rounds_half, + int partial_rounds, + const bls12_381::scalar_t* constants, + device_context::DeviceContext& ctx, + poseidon::PoseidonConstants* poseidon_constants); + +extern "C" cudaError_t bls12_381_init_optimized_poseidon_constants_cuda( + int arity, device_context::DeviceContext& ctx, poseidon::PoseidonConstants* constants); + +extern "C" cudaError_t bls12_381_poseidon_hash_cuda( + bls12_381::scalar_t* input, + bls12_381::scalar_t* output, + int number_of_states, + int arity, + const poseidon::PoseidonConstants& constants, + poseidon::PoseidonConfig& config); + +extern "C" cudaError_t bls12_381_build_poseidon_merkle_tree( + const bls12_381::scalar_t* leaves, + bls12_381::scalar_t* digests, + uint32_t height, + int arity, + poseidon::PoseidonConstants& constants, + merkle::TreeBuilderConfig& config); + +extern "C" cudaError_t bls12_381_mul_cuda( + bls12_381::scalar_t* vec_a, bls12_381::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bls12_381::scalar_t* result); + +extern "C" cudaError_t bls12_381_add_cuda( + bls12_381::scalar_t* vec_a, bls12_381::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bls12_381::scalar_t* result); + +extern "C" cudaError_t bls12_381_sub_cuda( + bls12_381::scalar_t* vec_a, bls12_381::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bls12_381::scalar_t* result); + +extern "C" cudaError_t bls12_381_transpose_matrix_cuda( + const bls12_381::scalar_t* input, + uint32_t row_size, + uint32_t column_size, + bls12_381::scalar_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); + +extern "C" void bls12_381_generate_scalars(bls12_381::scalar_t* scalars, int size); + +extern "C" cudaError_t bls12_381_scalar_convert_montgomery( + bls12_381::scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bls12_381_initialize_domain( + bls12_381::scalar_t* primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode); + +extern "C" cudaError_t bls12_381_ntt_cuda( + const bls12_381::scalar_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, bls12_381::scalar_t* output); + +extern "C" cudaError_t bls12_381_release_domain(device_context::DeviceContext& ctx); + +#endif \ No newline at end of file diff --git a/icicle/include/api/bn254.h b/icicle/include/api/bn254.h new file mode 100644 index 00000000..0c44fb46 --- /dev/null +++ b/icicle/include/api/bn254.h @@ -0,0 +1,132 @@ +// WARNING: This file is auto-generated by a script. +// Any changes made to this file may be overwritten. +// Please modify the code generation script instead. +// Path to the code generation script: scripts/gen_c_api.py + +#pragma once +#ifndef BN254_API_H +#define BN254_API_H + +#include +#include "gpu-utils/device_context.cuh" +#include "curves/params/bn254.cuh" +#include "ntt/ntt.cuh" +#include "msm/msm.cuh" +#include "vec_ops/vec_ops.cuh" +#include "poseidon/poseidon.cuh" +#include "poseidon/tree/merkle.cuh" + +extern "C" cudaError_t bn254_g2_precompute_msm_bases_cuda( + bn254::g2_affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + bn254::g2_affine_t* output_bases); + +extern "C" cudaError_t bn254_g2_msm_cuda( + const bn254::scalar_t* scalars, const bn254::g2_affine_t* points, int msm_size, msm::MSMConfig& config, bn254::g2_projective_t* out); + +extern "C" cudaError_t bn254_precompute_msm_bases_cuda( + bn254::affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + bn254::affine_t* output_bases); + +extern "C" cudaError_t bn254_msm_cuda( + const bn254::scalar_t* scalars, const bn254::affine_t* points, int msm_size, msm::MSMConfig& config, bn254::projective_t* out); + +extern "C" bool bn254_g2_eq(bn254::g2_projective_t* point1, bn254::g2_projective_t* point2); + +extern "C" void bn254_g2_to_affine(bn254::g2_projective_t* point, bn254::g2_affine_t* point_out); + +extern "C" void bn254_g2_generate_projective_points(bn254::g2_projective_t* points, int size); + +extern "C" void bn254_g2_generate_affine_points(bn254::g2_affine_t* points, int size); + +extern "C" cudaError_t bn254_g2_affine_convert_montgomery( + bn254::g2_affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bn254_g2_projective_convert_montgomery( + bn254::g2_projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bn254_ecntt_cuda( + const bn254::projective_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, bn254::projective_t* output); + +extern "C" bool bn254_eq(bn254::projective_t* point1, bn254::projective_t* point2); + +extern "C" void bn254_to_affine(bn254::projective_t* point, bn254::affine_t* point_out); + +extern "C" void bn254_generate_projective_points(bn254::projective_t* points, int size); + +extern "C" void bn254_generate_affine_points(bn254::affine_t* points, int size); + +extern "C" cudaError_t bn254_affine_convert_montgomery( + bn254::affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bn254_projective_convert_montgomery( + bn254::projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bn254_create_optimized_poseidon_constants_cuda( + int arity, + int full_rounds_half, + int partial_rounds, + const bn254::scalar_t* constants, + device_context::DeviceContext& ctx, + poseidon::PoseidonConstants* poseidon_constants); + +extern "C" cudaError_t bn254_init_optimized_poseidon_constants_cuda( + int arity, device_context::DeviceContext& ctx, poseidon::PoseidonConstants* constants); + +extern "C" cudaError_t bn254_poseidon_hash_cuda( + bn254::scalar_t* input, + bn254::scalar_t* output, + int number_of_states, + int arity, + const poseidon::PoseidonConstants& constants, + poseidon::PoseidonConfig& config); + +extern "C" cudaError_t bn254_build_poseidon_merkle_tree( + const bn254::scalar_t* leaves, + bn254::scalar_t* digests, + uint32_t height, + int arity, + poseidon::PoseidonConstants& constants, + merkle::TreeBuilderConfig& config); + +extern "C" cudaError_t bn254_mul_cuda( + bn254::scalar_t* vec_a, bn254::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bn254::scalar_t* result); + +extern "C" cudaError_t bn254_add_cuda( + bn254::scalar_t* vec_a, bn254::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bn254::scalar_t* result); + +extern "C" cudaError_t bn254_sub_cuda( + bn254::scalar_t* vec_a, bn254::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bn254::scalar_t* result); + +extern "C" cudaError_t bn254_transpose_matrix_cuda( + const bn254::scalar_t* input, + uint32_t row_size, + uint32_t column_size, + bn254::scalar_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); + +extern "C" void bn254_generate_scalars(bn254::scalar_t* scalars, int size); + +extern "C" cudaError_t bn254_scalar_convert_montgomery( + bn254::scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bn254_initialize_domain( + bn254::scalar_t* primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode); + +extern "C" cudaError_t bn254_ntt_cuda( + const bn254::scalar_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, bn254::scalar_t* output); + +extern "C" cudaError_t bn254_release_domain(device_context::DeviceContext& ctx); + +#endif \ No newline at end of file diff --git a/icicle/include/api/bw6_761.h b/icicle/include/api/bw6_761.h new file mode 100644 index 00000000..d10d93d7 --- /dev/null +++ b/icicle/include/api/bw6_761.h @@ -0,0 +1,132 @@ +// WARNING: This file is auto-generated by a script. +// Any changes made to this file may be overwritten. +// Please modify the code generation script instead. +// Path to the code generation script: scripts/gen_c_api.py + +#pragma once +#ifndef BW6_761_API_H +#define BW6_761_API_H + +#include +#include "gpu-utils/device_context.cuh" +#include "curves/params/bw6_761.cuh" +#include "ntt/ntt.cuh" +#include "msm/msm.cuh" +#include "vec_ops/vec_ops.cuh" +#include "poseidon/poseidon.cuh" +#include "poseidon/tree/merkle.cuh" + +extern "C" cudaError_t bw6_761_g2_precompute_msm_bases_cuda( + bw6_761::g2_affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + bw6_761::g2_affine_t* output_bases); + +extern "C" cudaError_t bw6_761_g2_msm_cuda( + const bw6_761::scalar_t* scalars, const bw6_761::g2_affine_t* points, int msm_size, msm::MSMConfig& config, bw6_761::g2_projective_t* out); + +extern "C" cudaError_t bw6_761_precompute_msm_bases_cuda( + bw6_761::affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + bw6_761::affine_t* output_bases); + +extern "C" cudaError_t bw6_761_msm_cuda( + const bw6_761::scalar_t* scalars, const bw6_761::affine_t* points, int msm_size, msm::MSMConfig& config, bw6_761::projective_t* out); + +extern "C" bool bw6_761_g2_eq(bw6_761::g2_projective_t* point1, bw6_761::g2_projective_t* point2); + +extern "C" void bw6_761_g2_to_affine(bw6_761::g2_projective_t* point, bw6_761::g2_affine_t* point_out); + +extern "C" void bw6_761_g2_generate_projective_points(bw6_761::g2_projective_t* points, int size); + +extern "C" void bw6_761_g2_generate_affine_points(bw6_761::g2_affine_t* points, int size); + +extern "C" cudaError_t bw6_761_g2_affine_convert_montgomery( + bw6_761::g2_affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bw6_761_g2_projective_convert_montgomery( + bw6_761::g2_projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bw6_761_ecntt_cuda( + const bw6_761::projective_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, bw6_761::projective_t* output); + +extern "C" bool bw6_761_eq(bw6_761::projective_t* point1, bw6_761::projective_t* point2); + +extern "C" void bw6_761_to_affine(bw6_761::projective_t* point, bw6_761::affine_t* point_out); + +extern "C" void bw6_761_generate_projective_points(bw6_761::projective_t* points, int size); + +extern "C" void bw6_761_generate_affine_points(bw6_761::affine_t* points, int size); + +extern "C" cudaError_t bw6_761_affine_convert_montgomery( + bw6_761::affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bw6_761_projective_convert_montgomery( + bw6_761::projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bw6_761_create_optimized_poseidon_constants_cuda( + int arity, + int full_rounds_half, + int partial_rounds, + const bw6_761::scalar_t* constants, + device_context::DeviceContext& ctx, + poseidon::PoseidonConstants* poseidon_constants); + +extern "C" cudaError_t bw6_761_init_optimized_poseidon_constants_cuda( + int arity, device_context::DeviceContext& ctx, poseidon::PoseidonConstants* constants); + +extern "C" cudaError_t bw6_761_poseidon_hash_cuda( + bw6_761::scalar_t* input, + bw6_761::scalar_t* output, + int number_of_states, + int arity, + const poseidon::PoseidonConstants& constants, + poseidon::PoseidonConfig& config); + +extern "C" cudaError_t bw6_761_build_poseidon_merkle_tree( + const bw6_761::scalar_t* leaves, + bw6_761::scalar_t* digests, + uint32_t height, + int arity, + poseidon::PoseidonConstants& constants, + merkle::TreeBuilderConfig& config); + +extern "C" cudaError_t bw6_761_mul_cuda( + bw6_761::scalar_t* vec_a, bw6_761::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bw6_761::scalar_t* result); + +extern "C" cudaError_t bw6_761_add_cuda( + bw6_761::scalar_t* vec_a, bw6_761::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bw6_761::scalar_t* result); + +extern "C" cudaError_t bw6_761_sub_cuda( + bw6_761::scalar_t* vec_a, bw6_761::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, bw6_761::scalar_t* result); + +extern "C" cudaError_t bw6_761_transpose_matrix_cuda( + const bw6_761::scalar_t* input, + uint32_t row_size, + uint32_t column_size, + bw6_761::scalar_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); + +extern "C" void bw6_761_generate_scalars(bw6_761::scalar_t* scalars, int size); + +extern "C" cudaError_t bw6_761_scalar_convert_montgomery( + bw6_761::scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t bw6_761_initialize_domain( + bw6_761::scalar_t* primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode); + +extern "C" cudaError_t bw6_761_ntt_cuda( + const bw6_761::scalar_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig& config, bw6_761::scalar_t* output); + +extern "C" cudaError_t bw6_761_release_domain(device_context::DeviceContext& ctx); + +#endif \ No newline at end of file diff --git a/icicle/include/api/grumpkin.h b/icicle/include/api/grumpkin.h new file mode 100644 index 00000000..821bed6a --- /dev/null +++ b/icicle/include/api/grumpkin.h @@ -0,0 +1,94 @@ +// WARNING: This file is auto-generated by a script. +// Any changes made to this file may be overwritten. +// Please modify the code generation script instead. +// Path to the code generation script: scripts/gen_c_api.py + +#pragma once +#ifndef GRUMPKIN_API_H +#define GRUMPKIN_API_H + +#include +#include "gpu-utils/device_context.cuh" +#include "curves/params/grumpkin.cuh" +#include "msm/msm.cuh" +#include "vec_ops/vec_ops.cuh" +#include "poseidon/poseidon.cuh" +#include "poseidon/tree/merkle.cuh" + +extern "C" cudaError_t grumpkin_precompute_msm_bases_cuda( + grumpkin::affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + grumpkin::affine_t* output_bases); + +extern "C" cudaError_t grumpkin_msm_cuda( + const grumpkin::scalar_t* scalars, const grumpkin::affine_t* points, int msm_size, msm::MSMConfig& config, grumpkin::projective_t* out); + +extern "C" bool grumpkin_eq(grumpkin::projective_t* point1, grumpkin::projective_t* point2); + +extern "C" void grumpkin_to_affine(grumpkin::projective_t* point, grumpkin::affine_t* point_out); + +extern "C" void grumpkin_generate_projective_points(grumpkin::projective_t* points, int size); + +extern "C" void grumpkin_generate_affine_points(grumpkin::affine_t* points, int size); + +extern "C" cudaError_t grumpkin_affine_convert_montgomery( + grumpkin::affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t grumpkin_projective_convert_montgomery( + grumpkin::projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t grumpkin_create_optimized_poseidon_constants_cuda( + int arity, + int full_rounds_half, + int partial_rounds, + const grumpkin::scalar_t* constants, + device_context::DeviceContext& ctx, + poseidon::PoseidonConstants* poseidon_constants); + +extern "C" cudaError_t grumpkin_init_optimized_poseidon_constants_cuda( + int arity, device_context::DeviceContext& ctx, poseidon::PoseidonConstants* constants); + +extern "C" cudaError_t grumpkin_poseidon_hash_cuda( + grumpkin::scalar_t* input, + grumpkin::scalar_t* output, + int number_of_states, + int arity, + const poseidon::PoseidonConstants& constants, + poseidon::PoseidonConfig& config); + +extern "C" cudaError_t grumpkin_build_poseidon_merkle_tree( + const grumpkin::scalar_t* leaves, + grumpkin::scalar_t* digests, + uint32_t height, + int arity, + poseidon::PoseidonConstants& constants, + merkle::TreeBuilderConfig& config); + +extern "C" cudaError_t grumpkin_mul_cuda( + grumpkin::scalar_t* vec_a, grumpkin::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, grumpkin::scalar_t* result); + +extern "C" cudaError_t grumpkin_add_cuda( + grumpkin::scalar_t* vec_a, grumpkin::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, grumpkin::scalar_t* result); + +extern "C" cudaError_t grumpkin_sub_cuda( + grumpkin::scalar_t* vec_a, grumpkin::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, grumpkin::scalar_t* result); + +extern "C" cudaError_t grumpkin_transpose_matrix_cuda( + const grumpkin::scalar_t* input, + uint32_t row_size, + uint32_t column_size, + grumpkin::scalar_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); + +extern "C" void grumpkin_generate_scalars(grumpkin::scalar_t* scalars, int size); + +extern "C" cudaError_t grumpkin_scalar_convert_montgomery( + grumpkin::scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +#endif \ No newline at end of file diff --git a/icicle/include/api/hash.h b/icicle/include/api/hash.h new file mode 100644 index 00000000..70649811 --- /dev/null +++ b/icicle/include/api/hash.h @@ -0,0 +1,16 @@ +#pragma once + +#ifndef HASH_API_H +#define HASH_API_H + +#include +#include "gpu-utils/device_context.cuh" +#include "hash/keccak/keccak.cuh" + +extern "C" cudaError_t + keccak256_cuda(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, KeccakConfig config); + +extern "C" cudaError_t + keccak512_cuda(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, KeccakConfig config); + +#endif \ No newline at end of file diff --git a/icicle/include/api/templates/curves/curve.h b/icicle/include/api/templates/curves/curve.h new file mode 100644 index 00000000..5606051c --- /dev/null +++ b/icicle/include/api/templates/curves/curve.h @@ -0,0 +1,13 @@ +extern "C" bool ${CURVE}_eq(${CURVE}::projective_t* point1, ${CURVE}::projective_t* point2); + +extern "C" void ${CURVE}_to_affine(${CURVE}::projective_t* point, ${CURVE}::affine_t* point_out); + +extern "C" void ${CURVE}_generate_projective_points(${CURVE}::projective_t* points, int size); + +extern "C" void ${CURVE}_generate_affine_points(${CURVE}::affine_t* points, int size); + +extern "C" cudaError_t ${CURVE}_affine_convert_montgomery( + ${CURVE}::affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t ${CURVE}_projective_convert_montgomery( + ${CURVE}::projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); \ No newline at end of file diff --git a/icicle/include/api/templates/curves/curve_g2.h b/icicle/include/api/templates/curves/curve_g2.h new file mode 100644 index 00000000..250aec94 --- /dev/null +++ b/icicle/include/api/templates/curves/curve_g2.h @@ -0,0 +1,13 @@ +extern "C" bool ${CURVE}_g2_eq(${CURVE}::g2_projective_t* point1, ${CURVE}::g2_projective_t* point2); + +extern "C" void ${CURVE}_g2_to_affine(${CURVE}::g2_projective_t* point, ${CURVE}::g2_affine_t* point_out); + +extern "C" void ${CURVE}_g2_generate_projective_points(${CURVE}::g2_projective_t* points, int size); + +extern "C" void ${CURVE}_g2_generate_affine_points(${CURVE}::g2_affine_t* points, int size); + +extern "C" cudaError_t ${CURVE}_g2_affine_convert_montgomery( + ${CURVE}::g2_affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); + +extern "C" cudaError_t ${CURVE}_g2_projective_convert_montgomery( + ${CURVE}::g2_projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); \ No newline at end of file diff --git a/icicle/include/api/templates/curves/ecntt.h b/icicle/include/api/templates/curves/ecntt.h new file mode 100644 index 00000000..d155d7ea --- /dev/null +++ b/icicle/include/api/templates/curves/ecntt.h @@ -0,0 +1,2 @@ +extern "C" cudaError_t ${CURVE}_ecntt_cuda( + const ${CURVE}::projective_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig<${CURVE}::scalar_t>& config, ${CURVE}::projective_t* output); \ No newline at end of file diff --git a/icicle/include/api/templates/curves/msm.h b/icicle/include/api/templates/curves/msm.h new file mode 100644 index 00000000..7cf67eb1 --- /dev/null +++ b/icicle/include/api/templates/curves/msm.h @@ -0,0 +1,11 @@ +extern "C" cudaError_t ${CURVE}_precompute_msm_bases_cuda( + ${CURVE}::affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + ${CURVE}::affine_t* output_bases); + +extern "C" cudaError_t ${CURVE}_msm_cuda( + const ${CURVE}::scalar_t* scalars, const ${CURVE}::affine_t* points, int msm_size, msm::MSMConfig& config, ${CURVE}::projective_t* out); \ No newline at end of file diff --git a/icicle/include/api/templates/curves/msm_g2.h b/icicle/include/api/templates/curves/msm_g2.h new file mode 100644 index 00000000..31aceb24 --- /dev/null +++ b/icicle/include/api/templates/curves/msm_g2.h @@ -0,0 +1,11 @@ +extern "C" cudaError_t ${CURVE}_g2_precompute_msm_bases_cuda( + ${CURVE}::g2_affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + ${CURVE}::g2_affine_t* output_bases); + +extern "C" cudaError_t ${CURVE}_g2_msm_cuda( + const ${CURVE}::scalar_t* scalars, const ${CURVE}::g2_affine_t* points, int msm_size, msm::MSMConfig& config, ${CURVE}::g2_projective_t* out); \ No newline at end of file diff --git a/icicle/include/api/templates/fields/field.h b/icicle/include/api/templates/fields/field.h new file mode 100644 index 00000000..b02e2386 --- /dev/null +++ b/icicle/include/api/templates/fields/field.h @@ -0,0 +1,4 @@ +extern "C" void ${FIELD}_generate_scalars(${FIELD}::scalar_t* scalars, int size); + +extern "C" cudaError_t ${FIELD}_scalar_convert_montgomery( + ${FIELD}::scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); \ No newline at end of file diff --git a/icicle/include/api/templates/fields/field_ext.h b/icicle/include/api/templates/fields/field_ext.h new file mode 100644 index 00000000..fa93fb1c --- /dev/null +++ b/icicle/include/api/templates/fields/field_ext.h @@ -0,0 +1,4 @@ +extern "C" void ${FIELD}_extension_generate_scalars(${FIELD}::extension_t* scalars, int size); + +extern "C" cudaError_t ${FIELD}_extension_scalar_convert_montgomery( + ${FIELD}::extension_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx); \ No newline at end of file diff --git a/icicle/include/api/templates/fields/ntt.h b/icicle/include/api/templates/fields/ntt.h new file mode 100644 index 00000000..07b096f4 --- /dev/null +++ b/icicle/include/api/templates/fields/ntt.h @@ -0,0 +1,7 @@ +extern "C" cudaError_t ${FIELD}_initialize_domain( + ${FIELD}::scalar_t* primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode); + +extern "C" cudaError_t ${FIELD}_ntt_cuda( + const ${FIELD}::scalar_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig<${FIELD}::scalar_t>& config, ${FIELD}::scalar_t* output); + +extern "C" cudaError_t ${FIELD}_release_domain(device_context::DeviceContext& ctx); \ No newline at end of file diff --git a/icicle/include/api/templates/fields/ntt_ext.h b/icicle/include/api/templates/fields/ntt_ext.h new file mode 100644 index 00000000..74543140 --- /dev/null +++ b/icicle/include/api/templates/fields/ntt_ext.h @@ -0,0 +1,2 @@ +extern "C" cudaError_t ${FIELD}_extension_ntt_cuda( + const ${FIELD}::extension_t* input, int size, ntt::NTTDir dir, ntt::NTTConfig<${FIELD}::scalar_t>& config, ${FIELD}::extension_t* output); \ No newline at end of file diff --git a/icicle/include/api/templates/fields/poseidon.h b/icicle/include/api/templates/fields/poseidon.h new file mode 100644 index 00000000..29ed6291 --- /dev/null +++ b/icicle/include/api/templates/fields/poseidon.h @@ -0,0 +1,26 @@ +extern "C" cudaError_t ${FIELD}_create_optimized_poseidon_constants_cuda( + int arity, + int full_rounds_half, + int partial_rounds, + const ${FIELD}::scalar_t* constants, + device_context::DeviceContext& ctx, + poseidon::PoseidonConstants<${FIELD}::scalar_t>* poseidon_constants); + +extern "C" cudaError_t ${FIELD}_init_optimized_poseidon_constants_cuda( + int arity, device_context::DeviceContext& ctx, poseidon::PoseidonConstants<${FIELD}::scalar_t>* constants); + +extern "C" cudaError_t ${FIELD}_poseidon_hash_cuda( + ${FIELD}::scalar_t* input, + ${FIELD}::scalar_t* output, + int number_of_states, + int arity, + const poseidon::PoseidonConstants<${FIELD}::scalar_t>& constants, + poseidon::PoseidonConfig& config); + +extern "C" cudaError_t ${FIELD}_build_poseidon_merkle_tree( + const ${FIELD}::scalar_t* leaves, + ${FIELD}::scalar_t* digests, + uint32_t height, + int arity, + poseidon::PoseidonConstants<${FIELD}::scalar_t>& constants, + merkle::TreeBuilderConfig& config); \ No newline at end of file diff --git a/icicle/include/api/templates/fields/vec_ops.h b/icicle/include/api/templates/fields/vec_ops.h new file mode 100644 index 00000000..d740c8f7 --- /dev/null +++ b/icicle/include/api/templates/fields/vec_ops.h @@ -0,0 +1,17 @@ +extern "C" cudaError_t ${FIELD}_mul_cuda( + ${FIELD}::scalar_t* vec_a, ${FIELD}::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, ${FIELD}::scalar_t* result); + +extern "C" cudaError_t ${FIELD}_add_cuda( + ${FIELD}::scalar_t* vec_a, ${FIELD}::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, ${FIELD}::scalar_t* result); + +extern "C" cudaError_t ${FIELD}_sub_cuda( + ${FIELD}::scalar_t* vec_a, ${FIELD}::scalar_t* vec_b, int n, vec_ops::VecOpsConfig& config, ${FIELD}::scalar_t* result); + +extern "C" cudaError_t ${FIELD}_transpose_matrix_cuda( + const ${FIELD}::scalar_t* input, + uint32_t row_size, + uint32_t column_size, + ${FIELD}::scalar_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); \ No newline at end of file diff --git a/icicle/include/api/templates/fields/vec_ops_ext.h b/icicle/include/api/templates/fields/vec_ops_ext.h new file mode 100644 index 00000000..5a6513f9 --- /dev/null +++ b/icicle/include/api/templates/fields/vec_ops_ext.h @@ -0,0 +1,17 @@ +extern "C" cudaError_t ${FIELD}_extension_mul_cuda( + ${FIELD}::extension_t* vec_a, ${FIELD}::extension_t* vec_b, int n, vec_ops::VecOpsConfig& config, ${FIELD}::extension_t* result); + +extern "C" cudaError_t ${FIELD}_extension_add_cuda( + ${FIELD}::extension_t* vec_a, ${FIELD}::extension_t* vec_b, int n, vec_ops::VecOpsConfig& config, ${FIELD}::extension_t* result); + +extern "C" cudaError_t ${FIELD}_extension_sub_cuda( + ${FIELD}::extension_t* vec_a, ${FIELD}::extension_t* vec_b, int n, vec_ops::VecOpsConfig& config, ${FIELD}::extension_t* result); + +extern "C" cudaError_t ${FIELD}_extension_transpose_matrix_cuda( + const ${FIELD}::extension_t* input, + uint32_t row_size, + uint32_t column_size, + ${FIELD}::extension_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async); \ No newline at end of file diff --git a/icicle/primitives/affine.cuh b/icicle/include/curves/affine.cuh similarity index 51% rename from icicle/primitives/affine.cuh rename to icicle/include/curves/affine.cuh index 6ab0e8eb..2b1253e4 100644 --- a/icicle/primitives/affine.cuh +++ b/icicle/include/curves/affine.cuh @@ -1,6 +1,8 @@ #pragma once -#include "field.cuh" +#include "gpu-utils/sharedmem.cuh" +#include "gpu-utils/modifiers.cuh" +#include template class Affine @@ -13,14 +15,14 @@ public: static HOST_DEVICE_INLINE Affine zero() { return {FF::zero(), FF::zero()}; } - static HOST_DEVICE_INLINE Affine ToMontgomery(const Affine& point) + static HOST_DEVICE_INLINE Affine to_montgomery(const Affine& point) { - return {FF::ToMontgomery(point.x), FF::ToMontgomery(point.y)}; + return {FF::to_montgomery(point.x), FF::to_montgomery(point.y)}; } - static HOST_DEVICE_INLINE Affine FromMontgomery(const Affine& point) + static HOST_DEVICE_INLINE Affine from_montgomery(const Affine& point) { - return {FF::FromMontgomery(point.x), FF::FromMontgomery(point.y)}; + return {FF::from_montgomery(point.x), FF::from_montgomery(point.y)}; } friend HOST_DEVICE_INLINE bool operator==(const Affine& xs, const Affine& ys) @@ -33,4 +35,13 @@ public: os << "x: " << point.x << "; y: " << point.y; return os; } -}; \ No newline at end of file +}; + +template +struct SharedMemory> { + __device__ Affine* getPointer() + { + extern __shared__ Affine s_affine_[]; + return s_affine_; + } +}; diff --git a/icicle/include/curves/curve_config.cuh b/icicle/include/curves/curve_config.cuh new file mode 100644 index 00000000..c9fe109b --- /dev/null +++ b/icicle/include/curves/curve_config.cuh @@ -0,0 +1,34 @@ +#pragma once +#ifndef CURVE_CONFIG_H +#define CURVE_CONFIG_H + +#include "fields/id.h" +#include "curves/projective.cuh" + +/** + * @namespace curve_config + * Namespace with type definitions for short Weierstrass pairing-friendly [elliptic + * curves](https://hyperelliptic.org/EFD/g1p/auto-shortw.html). Here, concrete types are created in accordance + * with the `-DCURVE` env variable passed during build. + */ +#if CURVE_ID == BN254 +#include "curves/params/bn254.cuh" +namespace curve_config = bn254; + +#elif CURVE_ID == BLS12_381 +#include "curves/params/bls12_381.cuh" +namespace curve_config = bls12_381; + +#elif CURVE_ID == BLS12_377 +#include "curves/params/bls12_377.cuh" +namespace curve_config = bls12_377; + +#elif CURVE_ID == BW6_761 +#include "curves/params/bw6_761.cuh" +namespace curve_config = bw6_761; + +#elif CURVE_ID == GRUMPKIN +#include "curves/params/grumpkin.cuh" +namespace curve_config = grumpkin; +#endif +#endif \ No newline at end of file diff --git a/icicle/include/curves/macro.h b/icicle/include/curves/macro.h new file mode 100644 index 00000000..9f25ed28 --- /dev/null +++ b/icicle/include/curves/macro.h @@ -0,0 +1,42 @@ +#pragma once +#ifndef CURVE_MACRO_H +#define CURVE_MACRO_H + +#define CURVE_DEFINITIONS \ + /** \ + * Base field of G1 curve. Is always a prime field. \ + */ \ + typedef Field point_field_t; \ + \ + static constexpr point_field_t generator_x = point_field_t{g1_gen_x}; \ + static constexpr point_field_t generator_y = point_field_t{g1_gen_y}; \ + static constexpr point_field_t b = point_field_t{weierstrass_b}; \ + /** \ + * [Projective representation](https://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html) \ + * of G1 curve consisting of three coordinates of type [point_field_t](point_field_t). \ + */ \ + typedef Projective projective_t; \ + /** \ + * Affine representation of G1 curve consisting of two coordinates of type [point_field_t](point_field_t). \ + */ \ + typedef Affine affine_t; + +#define G2_CURVE_DEFINITIONS \ + typedef ExtensionField g2_point_field_t; \ + static constexpr g2_point_field_t g2_generator_x = \ + g2_point_field_t{point_field_t{g2_gen_x_re}, point_field_t{g2_gen_x_im}}; \ + static constexpr g2_point_field_t g2_generator_y = \ + g2_point_field_t{point_field_t{g2_gen_y_re}, point_field_t{g2_gen_y_im}}; \ + static constexpr g2_point_field_t g2_b = \ + g2_point_field_t{point_field_t{weierstrass_b_g2_re}, point_field_t{weierstrass_b_g2_im}}; \ + \ + /** \ + * [Projective representation](https://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html) of G2 curve. \ + */ \ + typedef Projective g2_projective_t; \ + /** \ + * Affine representation of G1 curve. \ + */ \ + typedef Affine g2_affine_t; + +#endif \ No newline at end of file diff --git a/icicle/include/curves/params/bls12_377.cuh b/icicle/include/curves/params/bls12_377.cuh new file mode 100644 index 00000000..3e81fb17 --- /dev/null +++ b/icicle/include/curves/params/bls12_377.cuh @@ -0,0 +1,48 @@ +#pragma once +#ifndef BLS12_377_PARAMS_H +#define BLS12_377_PARAMS_H + +#include "fields/storage.cuh" + +#include "curves/macro.h" +#include "curves/projective.cuh" +#include "fields/snark_fields/bls12_377_base.cuh" +#include "fields/snark_fields/bls12_377_scalar.cuh" +#include "fields/quadratic_extension.cuh" + +namespace bls12_377 { + // G1 and G2 generators + static constexpr storage g1_gen_x = {0xb21be9ef, 0xeab9b16e, 0xffcd394e, 0xd5481512, + 0xbd37cb5c, 0x188282c8, 0xaa9d41bb, 0x85951e2c, + 0xbf87ff54, 0xc8fc6225, 0xfe740a67, 0x008848de}; + static constexpr storage g1_gen_y = {0x559c8ea6, 0xfd82de55, 0x34a9591a, 0xc2fe3d36, + 0x4fb82305, 0x6d182ad4, 0xca3e52d9, 0xbd7fb348, + 0x30afeec4, 0x1f674f5d, 0xc5102eff, 0x01914a69}; + static constexpr storage g2_gen_x_re = {0x7c005196, 0x74e3e48f, 0xbb535402, 0x71889f52, + 0x57db6b9b, 0x7ea501f5, 0x203e5031, 0xc565f071, + 0xa3841d01, 0xc89630a2, 0x71c785fe, 0x018480be}; + static constexpr storage g2_gen_x_im = {0x6ea16afe, 0xb26bfefa, 0xbff76fe6, 0x5cf89984, + 0x0799c9de, 0xe7223ece, 0x6651cecb, 0x532777ee, + 0xb1b140d5, 0x70dc5a51, 0xe7004031, 0x00ea6040}; + static constexpr storage g2_gen_y_re = {0x09fd4ddf, 0xf0940944, 0x6d8c7c2e, 0xf2cf8888, + 0xf832d204, 0xe458c282, 0x74b49a58, 0xde03ed72, + 0xcbb2efb4, 0xd960736b, 0x5d446f7b, 0x00690d66}; + static constexpr storage g2_gen_y_im = {0x85eb8f93, 0xd9a1cdd1, 0x5e52270b, 0x4279b83f, + 0xcee304c2, 0x2463b01a, 0x3d591bf1, 0x61ef11ac, + 0x151a70aa, 0x9e549da3, 0xd2835518, 0x00f8169f}; + + static constexpr storage weierstrass_b = {0x00000001, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage weierstrass_b_g2_re = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage weierstrass_b_g2_im = { + 0x9999999a, 0x1c9ed999, 0x1ccccccd, 0x0dd39e5c, 0x3c6bf800, 0x129207b6, + 0xcd5fd889, 0xdc7b4f91, 0x7460c589, 0x43bd0373, 0xdb0fd6f3, 0x010222f6}; + + CURVE_DEFINITIONS + G2_CURVE_DEFINITIONS +} // namespace bls12_377 + +#endif diff --git a/icicle/include/curves/params/bls12_381.cuh b/icicle/include/curves/params/bls12_381.cuh new file mode 100644 index 00000000..86c7ef6a --- /dev/null +++ b/icicle/include/curves/params/bls12_381.cuh @@ -0,0 +1,48 @@ +#pragma once +#ifndef BLS12_381_PARAMS_H +#define BLS12_381_PARAMS_H + +#include "fields/storage.cuh" + +#include "curves/macro.h" +#include "curves/projective.cuh" +#include "fields/snark_fields/bls12_381_base.cuh" +#include "fields/snark_fields/bls12_381_scalar.cuh" +#include "fields/quadratic_extension.cuh" + +namespace bls12_381 { + // G1 and G2 generators + static constexpr storage g1_gen_x = {0xdb22c6bb, 0xfb3af00a, 0xf97a1aef, 0x6c55e83f, + 0x171bac58, 0xa14e3a3f, 0x9774b905, 0xc3688c4f, + 0x4fa9ac0f, 0x2695638c, 0x3197d794, 0x17f1d3a7}; + static constexpr storage g1_gen_y = {0x46c5e7e1, 0x0caa2329, 0xa2888ae4, 0xd03cc744, + 0x2c04b3ed, 0x00db18cb, 0xd5d00af6, 0xfcf5e095, + 0x741d8ae4, 0xa09e30ed, 0xe3aaa0f1, 0x08b3f481}; + static constexpr storage g2_gen_x_re = {0xc121bdb8, 0xd48056c8, 0xa805bbef, 0x0bac0326, + 0x7ae3d177, 0xb4510b64, 0xfa403b02, 0xc6e47ad4, + 0x2dc51051, 0x26080527, 0xf08f0a91, 0x024aa2b2}; + static constexpr storage g2_gen_x_im = {0x5d042b7e, 0xe5ac7d05, 0x13945d57, 0x334cf112, + 0xdc7f5049, 0xb5da61bb, 0x9920b61a, 0x596bd0d0, + 0x88274f65, 0x7dacd3a0, 0x52719f60, 0x13e02b60}; + static constexpr storage g2_gen_y_re = {0x08b82801, 0xe1935486, 0x3baca289, 0x923ac9cc, + 0x5160d12c, 0x6d429a69, 0x8cbdd3a7, 0xadfd9baa, + 0xda2e351a, 0x8cc9cdc6, 0x727d6e11, 0x0ce5d527}; + static constexpr storage g2_gen_y_im = {0xf05f79be, 0xaaa9075f, 0x5cec1da1, 0x3f370d27, + 0x572e99ab, 0x267492ab, 0x85a763af, 0xcb3e287e, + 0x2bc28b99, 0x32acd2b0, 0x2ea734cc, 0x0606c4a0}; + + static constexpr storage weierstrass_b = {0x00000004, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage weierstrass_b_g2_re = { + 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage weierstrass_b_g2_im = { + 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + + CURVE_DEFINITIONS + G2_CURVE_DEFINITIONS +} // namespace bls12_381 + +#endif diff --git a/icicle/include/curves/params/bn254.cuh b/icicle/include/curves/params/bn254.cuh new file mode 100644 index 00000000..4ae43760 --- /dev/null +++ b/icicle/include/curves/params/bn254.cuh @@ -0,0 +1,39 @@ +#pragma once +#ifndef BN254_PARAMS_H +#define BN254_PARAMS_H + +#include "fields/storage.cuh" + +#include "curves/macro.h" +#include "curves/projective.cuh" +#include "fields/snark_fields/bn254_base.cuh" +#include "fields/snark_fields/bn254_scalar.cuh" +#include "fields/quadratic_extension.cuh" + +namespace bn254 { + // G1 and G2 generators + static constexpr storage g1_gen_x = {0x00000001, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage g1_gen_y = {0x00000002, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage g2_gen_x_re = {0xd992f6ed, 0x46debd5c, 0xf75edadd, 0x674322d4, + 0x5e5c4479, 0x426a0066, 0x121f1e76, 0x1800deef}; + static constexpr storage g2_gen_x_im = {0xaef312c2, 0x97e485b7, 0x35a9e712, 0xf1aa4933, + 0x31fb5d25, 0x7260bfb7, 0x920d483a, 0x198e9393}; + static constexpr storage g2_gen_y_re = {0x66fa7daa, 0x4ce6cc01, 0x0c43d37b, 0xe3d1e769, + 0x8dcb408f, 0x4aab7180, 0xdb8c6deb, 0x12c85ea5}; + static constexpr storage g2_gen_y_im = {0xd122975b, 0x55acdadc, 0x70b38ef3, 0xbc4b3133, + 0x690c3395, 0xec9e99ad, 0x585ff075, 0x090689d0}; + + static constexpr storage weierstrass_b = {0x00000003, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage weierstrass_b_g2_re = { + 0x24a138e5, 0x3267e6dc, 0x59dbefa3, 0xb5b4c5e5, 0x1be06ac3, 0x81be1899, 0xceb8aaae, 0x2b149d40}; + static constexpr storage weierstrass_b_g2_im = { + 0x85c315d2, 0xe4a2bd06, 0xe52d1852, 0xa74fa084, 0xeed8fdf4, 0xcd2cafad, 0x3af0fed4, 0x009713b0}; + + CURVE_DEFINITIONS + G2_CURVE_DEFINITIONS +} // namespace bn254 + +#endif diff --git a/icicle/include/curves/params/bw6_761.cuh b/icicle/include/curves/params/bw6_761.cuh new file mode 100644 index 00000000..ce649f5e --- /dev/null +++ b/icicle/include/curves/params/bw6_761.cuh @@ -0,0 +1,58 @@ +#pragma once +#ifndef BW6_761_PARAMS_H +#define BW6_761_PARAMS_H + +#include "fields/storage.cuh" + +#include "curves/macro.h" +#include "curves/projective.cuh" +#include "fields/snark_fields/bw6_761_base.cuh" +#include "fields/snark_fields/bw6_761_scalar.cuh" +#include "fields/quadratic_extension.cuh" + +namespace bw6_761 { + // G1 and G2 generators + static constexpr storage g1_gen_x = { + 0x66e5b43d, 0x4088f3af, 0xa6af603f, 0x055928ac, 0x56133e82, 0x6750dd03, 0x280ca27f, 0x03758f9a, + 0xc9ea0971, 0x5bd71fa0, 0x47729b90, 0xa17a54ce, 0x94c2e746, 0x11dbfcd2, 0xc15520ac, 0x79017ffa, + 0x85f56fc7, 0xee05c54b, 0x551b27f0, 0xe6a0cfb7, 0xa477beae, 0xb277ce98, 0x0ea190c8, 0x01075b02}; + static constexpr storage g1_gen_y = { + 0xb4e95363, 0xbafc8f2d, 0x0b20d2a1, 0xad1cb2be, 0xcad0fb93, 0xb2b08119, 0xb3053253, 0x9f9df141, + 0x6fc2cdd4, 0xbe3fb90b, 0x717a4c55, 0xcc685d31, 0x71b5b806, 0xc5b8fa17, 0xaf7e0dba, 0x265909f1, + 0xa2e573a3, 0x1a7348d2, 0x884c9ec6, 0x0f952589, 0x45cc2a42, 0xe6fd637b, 0x0a6fc574, 0x0058b84e}; + static constexpr storage g2_gen_x = { + 0xcd025f1c, 0xa830c194, 0xe1bf995b, 0x6410cf4f, 0xc2ad54b0, 0x00e96efb, 0x3cd208d7, 0xce6948cb, + 0x00e1b6ba, 0x963317a3, 0xac70e7c7, 0xc5bbcae9, 0xf09feb58, 0x734ec3f1, 0xab3da268, 0x26b41c5d, + 0x13890f6d, 0x4c062010, 0xc5a7115f, 0xd61053aa, 0x69d660f9, 0xc852a82e, 0x41d9b816, 0x01101332}; + static constexpr storage g2_gen_y = { + 0x28c73b61, 0xeb70a167, 0xf9eac689, 0x91ec0594, 0x3c5a02a5, 0x58aa2d3a, 0x504affc7, 0x3ea96fcd, + 0xffa82300, 0x8906c170, 0xd2c712b8, 0x64f293db, 0x33293fef, 0x94c97eb7, 0x0b95a59c, 0x0a1d86c8, + 0x53ffe316, 0x81a78e27, 0xcec2181c, 0x26b7cf9a, 0xe4b6d2dc, 0x8179eb10, 0x7761369f, 0x0017c335}; + + static constexpr storage weierstrass_b = { + 0x0000008a, 0xf49d0000, 0x70000082, 0xe6913e68, 0xeaf0a437, 0x160cf8ae, 0x5667a8f8, 0x98a116c2, + 0x73ebff2e, 0x71dcd3dc, 0x12f9fd90, 0x8689c8ed, 0x25b42304, 0x03cebaff, 0xe584e919, 0x707ba638, + 0x8087be41, 0x528275ef, 0x81d14688, 0xb926186a, 0x04faff3e, 0xd187c940, 0xfb83ce0a, 0x0122e824}; + static constexpr storage g2_weierstrass_b = { + 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + + CURVE_DEFINITIONS + + typedef point_field_t g2_point_field_t; + static constexpr g2_point_field_t g2_generator_x = g2_point_field_t{g2_gen_x}; + static constexpr g2_point_field_t g2_generator_y = g2_point_field_t{g2_gen_y}; + static constexpr g2_point_field_t g2_b = g2_point_field_t{g2_weierstrass_b}; + + /** + * [Projective representation](https://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html) of G2 curve. + */ + typedef Projective g2_projective_t; + /** + * Affine representation of G1 curve. + */ + typedef Affine g2_affine_t; +} // namespace bw6_761 + +#endif diff --git a/icicle/curves/grumpkin_params.cuh b/icicle/include/curves/params/grumpkin.cuh similarity index 80% rename from icicle/curves/grumpkin_params.cuh rename to icicle/include/curves/params/grumpkin.cuh index cf3da38f..855897a1 100644 --- a/icicle/curves/grumpkin_params.cuh +++ b/icicle/include/curves/params/grumpkin.cuh @@ -2,13 +2,15 @@ #ifndef GRUMPKIN_PARAMS_H #define GRUMPKIN_PARAMS_H -#include "utils/storage.cuh" -#include "bn254_params.cuh" +#include "fields/storage.cuh" + +#include "curves/macro.h" +#include "curves/projective.cuh" +#include "fields/snark_fields/grumpkin_base.cuh" +#include "fields/snark_fields/grumpkin_scalar.cuh" namespace grumpkin { - typedef bn254::fq_config fp_config; typedef bn254::fp_config fq_config; - // G1 generator static constexpr storage g1_gen_x = {0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; @@ -17,6 +19,8 @@ namespace grumpkin { static constexpr storage weierstrass_b = {0xeffffff0, 0x43e1f593, 0x79b97091, 0x2833e848, 0x8181585d, 0xb85045b6, 0xe131a029, 0x30644e72}; + + CURVE_DEFINITIONS } // namespace grumpkin #endif diff --git a/icicle/primitives/projective.cuh b/icicle/include/curves/projective.cuh similarity index 92% rename from icicle/primitives/projective.cuh rename to icicle/include/curves/projective.cuh index 29a772d5..7cc7f2c1 100644 --- a/icicle/primitives/projective.cuh +++ b/icicle/include/curves/projective.cuh @@ -1,6 +1,7 @@ #pragma once #include "affine.cuh" +#include "gpu-utils/sharedmem.cuh" template class Projective @@ -8,6 +9,9 @@ class Projective friend Affine; public: + typedef Affine Aff; + typedef SCALAR_FF Scalar; + static constexpr unsigned SCALAR_FF_NBITS = SCALAR_FF::NBITS; static constexpr unsigned FF_NBITS = FF::NBITS; @@ -23,16 +27,19 @@ public: return {point.x * denom, point.y * denom}; } - static HOST_DEVICE_INLINE Projective from_affine(const Affine& point) { return {point.x, point.y, FF::one()}; } - - static HOST_DEVICE_INLINE Projective ToMontgomery(const Projective& point) + static HOST_DEVICE_INLINE Projective from_affine(const Affine& point) { - return {FF::ToMontgomery(point.x), FF::ToMontgomery(point.y), FF::ToMontgomery(point.z)}; + return point == Affine::zero() ? zero() : Projective{point.x, point.y, FF::one()}; } - static HOST_DEVICE_INLINE Projective FromMontgomery(const Projective& point) + static HOST_DEVICE_INLINE Projective to_montgomery(const Projective& point) { - return {FF::FromMontgomery(point.x), FF::FromMontgomery(point.y), FF::FromMontgomery(point.z)}; + return {FF::to_montgomery(point.x), FF::to_montgomery(point.y), FF::to_montgomery(point.z)}; + } + + static HOST_DEVICE_INLINE Projective from_montgomery(const Projective& point) + { + return {FF::from_montgomery(point.x), FF::from_montgomery(point.y), FF::from_montgomery(point.z)}; } static HOST_DEVICE_INLINE Projective generator() { return {GENERATOR_X, GENERATOR_Y, FF::one()}; } @@ -209,15 +216,24 @@ public: return rand_scalar * generator(); } - static void RandHostMany(Projective* out, int size) + static void rand_host_many(Projective* out, int size) { for (int i = 0; i < size; i++) out[i] = (i % size < 100) ? rand_host() : out[i - 100]; } - static void RandHostManyAffine(Affine* out, int size) + static void rand_host_many_affine(Affine* out, int size) { for (int i = 0; i < size; i++) out[i] = (i % size < 100) ? to_affine(rand_host()) : out[i - 100]; } }; + +template +struct SharedMemory> { + __device__ Projective* getPointer() + { + extern __shared__ Projective s_projective_[]; + return s_projective_; + } +}; diff --git a/icicle/primitives/field.cuh b/icicle/include/fields/field.cuh similarity index 83% rename from icicle/primitives/field.cuh rename to icicle/include/fields/field.cuh index 29754f34..11558e35 100644 --- a/icicle/primitives/field.cuh +++ b/icicle/include/fields/field.cuh @@ -18,10 +18,13 @@ #pragma once -#include "utils/error_handler.cuh" -#include "utils/host_math.cuh" -#include "utils/ptx.cuh" -#include "utils/storage.cuh" +#include "gpu-utils/error_handler.cuh" +#include "gpu-utils/modifiers.cuh" +#include "gpu-utils/sharedmem.cuh" +#include "host_math.cuh" +#include "ptx.cuh" +#include "storage.cuh" + #include #include #include @@ -244,10 +247,10 @@ public: add_sub_u32_device(const uint32_t* x, const uint32_t* y, uint32_t* r, size_t n = (TLC >> 1)) { r[0] = SUBTRACT ? ptx::sub_cc(x[0], y[0]) : ptx::add_cc(x[0], y[0]); - for (unsigned i = 1; i < (CARRY_OUT ? n : n - 1); i++) + for (unsigned i = 1; i < n; i++) r[i] = SUBTRACT ? ptx::subc_cc(x[i], y[i]) : ptx::addc_cc(x[i], y[i]); if (!CARRY_OUT) { - r[n - 1] = SUBTRACT ? ptx::subc(x[n - 1], y[n - 1]) : ptx::addc(x[n - 1], y[n - 1]); + ptx::addc(0, 0); return 0; } return SUBTRACT ? ptx::subc(0, 0) : ptx::addc(0, 0); @@ -466,67 +469,84 @@ public: */ static DEVICE_INLINE void multiply_msb_raw_device(const ff_storage& as, const ff_storage& bs, ff_wide_storage& rs) { - const uint32_t* a = as.limbs; - const uint32_t* b = bs.limbs; - uint32_t* even = rs.limbs; - __align__(16) uint32_t odd[2 * TLC - 2]; + if constexpr (TLC > 1) { + const uint32_t* a = as.limbs; + const uint32_t* b = bs.limbs; + uint32_t* even = rs.limbs; + __align__(16) uint32_t odd[2 * TLC - 2]; - even[TLC - 1] = ptx::mul_hi(a[TLC - 2], b[0]); - odd[TLC - 2] = ptx::mul_lo(a[TLC - 1], b[0]); - odd[TLC - 1] = ptx::mul_hi(a[TLC - 1], b[0]); - size_t i; - UNROLL - for (i = 2; i < TLC - 1; i += 2) { - mad_row_msb(&even[TLC - 2], &odd[TLC - 2], &a[TLC - i - 1], b[i - 1], i + 1); - mad_row_msb(&odd[TLC - 2], &even[TLC - 2], &a[TLC - i - 2], b[i], i + 2); + even[TLC - 1] = ptx::mul_hi(a[TLC - 2], b[0]); + odd[TLC - 2] = ptx::mul_lo(a[TLC - 1], b[0]); + odd[TLC - 1] = ptx::mul_hi(a[TLC - 1], b[0]); + size_t i; + UNROLL + for (i = 2; i < TLC - 1; i += 2) { + mad_row_msb(&even[TLC - 2], &odd[TLC - 2], &a[TLC - i - 1], b[i - 1], i + 1); + mad_row_msb(&odd[TLC - 2], &even[TLC - 2], &a[TLC - i - 2], b[i], i + 2); + } + mad_row(&even[TLC], &odd[TLC - 2], a, b[TLC - 1]); + + // merge |even| and |odd| + ptx::add_cc(even[TLC - 1], odd[TLC - 2]); + for (i = TLC - 1; i < 2 * TLC - 2; i++) + even[i + 1] = ptx::addc_cc(even[i + 1], odd[i]); + even[i + 1] = ptx::addc(even[i + 1], 0); + } else { + multiply_raw_device(as, bs, rs); } - mad_row(&even[TLC], &odd[TLC - 2], a, b[TLC - 1]); - - // merge |even| and |odd| - ptx::add_cc(even[TLC - 1], odd[TLC - 2]); - for (i = TLC - 1; i < 2 * TLC - 2; i++) - even[i + 1] = ptx::addc_cc(even[i + 1], odd[i]); - even[i + 1] = ptx::addc(even[i + 1], 0); } /** - * A function that computes the low half of the fused multiply-and-add \f$ rs = as \cdot bs + cs \f$. + * A function that computes the low half of the fused multiply-and-add \f$ rs = as \cdot bs + cs \f$ where + * \f$ bs = 2^{32*nof_limbs} \f$. * * For efficiency, this method does not include terms that are too large. Namely, limb product \f$ a_i \cdot b_j \f$ * is excluded if \f$ i + j > TLC - 1 \f$ and only the lower half is included if \f$ i + j = TLC - 1 \f$. All other * limb products are included. */ static DEVICE_INLINE void - multiply_and_add_lsb_raw_device(const ff_storage& as, const ff_storage& bs, ff_storage& cs, ff_storage& rs) + multiply_and_add_lsb_neg_modulus_raw_device(const ff_storage& as, ff_storage& cs, ff_storage& rs) { + ff_storage bs = get_neg_modulus(); const uint32_t* a = as.limbs; const uint32_t* b = bs.limbs; + uint32_t* c = cs.limbs; uint32_t* even = rs.limbs; - __align__(16) uint32_t odd[TLC - 1]; - size_t i; - // `b[0]` is \f$ 2^{32} \f$ minus the last limb of prime modulus. Because most scalar (and some base) primes - // are necessarily NTT-friendly, `b[0]` often turns out to be \f$ 2^{32} - 1 \f$. This actually leads to - // less efficient SASS generated by nvcc, so this case needed separate handling. - if (b[0] == UINT32_MAX) { - add_sub_u32_device(cs.limbs, a, even, TLC); - for (i = 0; i < TLC - 1; i++) - odd[i] = a[i]; - } else { - mul_n_and_add(even, a, b[0], cs.limbs, TLC); - mul_n(odd, a + 1, b[0], TLC - 1); - } - mad_row_lsb(&even[2], &odd[0], a, b[1], TLC - 1); - UNROLL - for (i = 2; i < TLC - 1; i += 2) { - mad_row_lsb(&odd[i], &even[i], a, b[i], TLC - i); - mad_row_lsb(&even[i + 2], &odd[i], a, b[i + 1], TLC - i - 1); - } - // merge |even| and |odd| - even[1] = ptx::add_cc(even[1], odd[0]); - for (i = 1; i < TLC - 2; i++) - even[i + 1] = ptx::addc_cc(even[i + 1], odd[i]); - even[i + 1] = ptx::addc(even[i + 1], odd[i]); + if constexpr (TLC > 2) { + __align__(16) uint32_t odd[TLC - 1]; + size_t i; + // `b[0]` is \f$ 2^{32} \f$ minus the last limb of prime modulus. Because most scalar (and some base) primes + // are necessarily NTT-friendly, `b[0]` often turns out to be \f$ 2^{32} - 1 \f$. This actually leads to + // less efficient SASS generated by nvcc, so this case needed separate handling. + if (b[0] == UINT32_MAX) { + add_sub_u32_device(c, a, even, TLC); + for (i = 0; i < TLC - 1; i++) + odd[i] = a[i]; + } else { + mul_n_and_add(even, a, b[0], c, TLC); + mul_n(odd, a + 1, b[0], TLC - 1); + } + mad_row_lsb(&even[2], &odd[0], a, b[1], TLC - 1); + UNROLL + for (i = 2; i < TLC - 1; i += 2) { + mad_row_lsb(&odd[i], &even[i], a, b[i], TLC - i); + mad_row_lsb(&even[i + 2], &odd[i], a, b[i + 1], TLC - i - 1); + } + + // merge |even| and |odd| + even[1] = ptx::add_cc(even[1], odd[0]); + for (i = 1; i < TLC - 2; i++) + even[i + 1] = ptx::addc_cc(even[i + 1], odd[i]); + even[i + 1] = ptx::addc(even[i + 1], odd[i]); + } else if (TLC == 2) { + even[0] = ptx::mad_lo(a[0], b[0], c[0]); + even[1] = ptx::mad_hi(a[0], b[0], c[0]); + even[1] = ptx::mad_lo(a[0], b[1], even[1]); + even[1] = ptx::mad_lo(a[1], b[0], even[1]); + } else if (TLC == 1) { + even[0] = ptx::mad_lo(a[0], b[0], c[0]); + } } /** @@ -599,29 +619,47 @@ public: const uint32_t* a = as.limbs; const uint32_t* b = bs.limbs; uint32_t* r = rs.limbs; - // Next two lines multiply high and low halves of operands (\f$ a_{lo} \cdot b_{lo}; a_{hi} \cdot b_{hi} \$f) and - // write the results into `r`. - multiply_short_raw_device(a, b, r); - multiply_short_raw_device(&a[TLC >> 1], &b[TLC >> 1], &r[TLC]); - __align__(16) uint32_t middle_part[TLC]; - __align__(16) uint32_t diffs[TLC]; - // Differences of halves \f$ a_{hi} - a_{lo}; b_{lo} - b_{hi} \$f are written into `diffs`, signs written to - // `carry1` and `carry2`. - uint32_t carry1 = add_sub_u32_device(&a[TLC >> 1], a, diffs); - uint32_t carry2 = add_sub_u32_device(b, &b[TLC >> 1], &diffs[TLC >> 1]); - // Compute the "middle part" of Karatsuba: \f$ a_{lo} \cdot b_{hi} + b_{lo} \cdot a_{hi} \f$. - // This is where the assumption about unset high bit of `a` and `b` is relevant. - multiply_and_add_short_raw_device(diffs, &diffs[TLC >> 1], middle_part, r, &r[TLC]); - // Corrections that need to be performed when differences are negative. - // Again, carry doesn't need to be propagated due to unset high bits of `a` and `b`. - if (carry1) add_sub_u32_device(&middle_part[TLC >> 1], &diffs[TLC >> 1], &middle_part[TLC >> 1]); - if (carry2) add_sub_u32_device(&middle_part[TLC >> 1], diffs, &middle_part[TLC >> 1]); - // Now that middle part is fully correct, it can be added to the result. - add_sub_u32_device(&r[TLC >> 1], middle_part, &r[TLC >> 1], TLC); + if constexpr (TLC > 2) { + // Next two lines multiply high and low halves of operands (\f$ a_{lo} \cdot b_{lo}; a_{hi} \cdot b_{hi} \$f) and + // write the results into `r`. + multiply_short_raw_device(a, b, r); + multiply_short_raw_device(&a[TLC >> 1], &b[TLC >> 1], &r[TLC]); + __align__(16) uint32_t middle_part[TLC]; + __align__(16) uint32_t diffs[TLC]; + // Differences of halves \f$ a_{hi} - a_{lo}; b_{lo} - b_{hi} \$f are written into `diffs`, signs written to + // `carry1` and `carry2`. + uint32_t carry1 = add_sub_u32_device(&a[TLC >> 1], a, diffs); + uint32_t carry2 = add_sub_u32_device(b, &b[TLC >> 1], &diffs[TLC >> 1]); + // Compute the "middle part" of Karatsuba: \f$ a_{lo} \cdot b_{hi} + b_{lo} \cdot a_{hi} \f$. + // This is where the assumption about unset high bit of `a` and `b` is relevant. + multiply_and_add_short_raw_device(diffs, &diffs[TLC >> 1], middle_part, r, &r[TLC]); + // Corrections that need to be performed when differences are negative. + // Again, carry doesn't need to be propagated due to unset high bits of `a` and `b`. + if (carry1) add_sub_u32_device(&middle_part[TLC >> 1], &diffs[TLC >> 1], &middle_part[TLC >> 1]); + if (carry2) add_sub_u32_device(&middle_part[TLC >> 1], diffs, &middle_part[TLC >> 1]); + // Now that middle part is fully correct, it can be added to the result. + add_sub_u32_device(&r[TLC >> 1], middle_part, &r[TLC >> 1], TLC); - // Carry from adding middle part has to be propagated to the highest limb. - for (size_t i = TLC + (TLC >> 1); i < 2 * TLC; i++) - r[i] = ptx::addc_cc(r[i], 0); + // Carry from adding middle part has to be propagated to the highest limb. + for (size_t i = TLC + (TLC >> 1); i < 2 * TLC; i++) + r[i] = ptx::addc_cc(r[i], 0); + } else if (TLC == 2) { + __align__(8) uint32_t odd[2]; + r[0] = ptx::mul_lo(a[0], b[0]); + r[1] = ptx::mul_hi(a[0], b[0]); + r[2] = ptx::mul_lo(a[1], b[1]); + r[3] = ptx::mul_hi(a[1], b[1]); + odd[0] = ptx::mul_lo(a[0], b[1]); + odd[1] = ptx::mul_hi(a[0], b[1]); + odd[0] = ptx::mad_lo(a[1], b[0], odd[0]); + odd[1] = ptx::mad_hi(a[1], b[0], odd[1]); + r[1] = ptx::add_cc(r[1], odd[0]); + r[2] = ptx::addc_cc(r[2], odd[1]); + r[3] = ptx::addc(r[3], 0); + } else if (TLC == 1) { + r[0] = ptx::mul_lo(a[0], b[0]); + r[1] = ptx::mul_hi(a[0], b[0]); + } } static HOST_INLINE void multiply_raw_host(const ff_storage& as, const ff_storage& bs, ff_wide_storage& rs) @@ -647,13 +685,13 @@ public: } static HOST_DEVICE_INLINE void - multiply_and_add_lsb_raw(const ff_storage& as, const ff_storage& bs, ff_storage& cs, ff_storage& rs) + multiply_and_add_lsb_neg_modulus_raw(const ff_storage& as, ff_storage& cs, ff_storage& rs) { #ifdef __CUDA_ARCH__ - return multiply_and_add_lsb_raw_device(as, bs, cs, rs); + return multiply_and_add_lsb_neg_modulus_raw_device(as, cs, rs); #else Wide r_wide = {}; - multiply_raw_host(as, bs, r_wide.limbs_storage); + multiply_raw_host(as, get_neg_modulus(), r_wide.limbs_storage); Field r = Wide::get_lower(r_wide); add_limbs(cs, r.limbs_storage, rs); #endif @@ -698,7 +736,7 @@ public: return value; } - static void RandHostMany(Field* out, int size) + static void rand_host_many(Field* out, int size) { for (int i = 0; i < size; i++) out[i] = rand_host(); @@ -784,7 +822,7 @@ public: Field xs_lo = Wide::get_lower(xs); // Here we need to compute the lsb of `xs - l \cdot p` and to make use of fused multiply-and-add, we rewrite it as // `xs + l \cdot (2^{32 \cdot TLC}-p)` which is the same as original (up to higher limbs which we don't care about). - multiply_and_add_lsb_raw(l_hi.limbs_storage, get_neg_modulus(), xs_lo.limbs_storage, r.limbs_storage); + multiply_and_add_lsb_neg_modulus_raw(l_hi.limbs_storage, xs_lo.limbs_storage, r.limbs_storage); ff_storage r_reduced = {}; uint32_t carry; // As mentioned, either 2 or 1 reduction can be performed depending on the field in question. @@ -872,9 +910,9 @@ public: return xs * xs; } - static constexpr HOST_DEVICE_INLINE Field ToMontgomery(const Field& xs) { return xs * Field{CONFIG::montgomery_r}; } + static constexpr HOST_DEVICE_INLINE Field to_montgomery(const Field& xs) { return xs * Field{CONFIG::montgomery_r}; } - static constexpr HOST_DEVICE_INLINE Field FromMontgomery(const Field& xs) + static constexpr HOST_DEVICE_INLINE Field from_montgomery(const Field& xs) { return xs * Field{CONFIG::montgomery_r_inv}; } @@ -888,21 +926,24 @@ public: return rs; } + // Assumes the number is even! template static constexpr HOST_DEVICE_INLINE Field div2(const Field& xs) { const uint32_t* x = xs.limbs_storage.limbs; Field rs = {}; uint32_t* r = rs.limbs_storage.limbs; + if constexpr (TLC > 1) { #ifdef __CUDA_ARCH__ - UNROLL + UNROLL #endif - for (unsigned i = 0; i < TLC - 1; i++) { + for (unsigned i = 0; i < TLC - 1; i++) { #ifdef __CUDA_ARCH__ - r[i] = __funnelshift_rc(x[i], x[i + 1], 1); + r[i] = __funnelshift_rc(x[i], x[i + 1], 1); #else - r[i] = (x[i] >> 1) | (x[i + 1] << 31); + r[i] = (x[i] >> 1) | (x[i + 1] << 31); #endif + } } r[TLC - 1] = x[TLC - 1] >> 1; return sub_modulus(rs); @@ -962,4 +1003,13 @@ struct std::hash> { hash ^= std::hash()(key.limbs_storage.limbs[i]) + 0x9e3779b9 + (hash << 6) + (hash >> 2); return hash; } +}; + +template +struct SharedMemory> { + __device__ Field* getPointer() + { + extern __shared__ Field s_scalar_[]; + return s_scalar_; + } }; \ No newline at end of file diff --git a/icicle/include/fields/field_config.cuh b/icicle/include/fields/field_config.cuh new file mode 100644 index 00000000..c7d5bf22 --- /dev/null +++ b/icicle/include/fields/field_config.cuh @@ -0,0 +1,35 @@ +#pragma once +#ifndef FIELD_CONFIG_H +#define FIELD_CONFIG_H + +#include "fields/id.h" +#include "fields/field.cuh" + +/** + * @namespace field_config + * Namespace with type definitions for finite fields. Here, concrete types are created in accordance + * with the `-DFIELD` env variable passed during build. + */ +#if FIELD_ID == BN254 +#include "fields/snark_fields/bn254_scalar.cuh" +namespace field_config = bn254; +#elif FIELD_ID == BLS12_381 +#include "fields/snark_fields/bls12_381_scalar.cuh" +using bls12_381::fp_config; +namespace field_config = bls12_381; +#elif FIELD_ID == BLS12_377 +#include "fields/snark_fields/bls12_377_scalar.cuh" +namespace field_config = bls12_377; +#elif FIELD_ID == BW6_761 +#include "fields/snark_fields/bw6_761_scalar.cuh" +namespace field_config = bw6_761; +#elif FIELD_ID == GRUMPKIN +#include "fields/snark_fields/grumpkin_scalar.cuh" +namespace field_config = grumpkin; + +#elif FIELD_ID == BABY_BEAR +#include "fields/stark_fields/babybear.cuh" +namespace field_config = babybear; +#endif + +#endif \ No newline at end of file diff --git a/icicle/utils/host_math.cuh b/icicle/include/fields/host_math.cuh similarity index 98% rename from icicle/utils/host_math.cuh rename to icicle/include/fields/host_math.cuh index a9eb8e21..47a712fd 100644 --- a/icicle/utils/host_math.cuh +++ b/icicle/include/fields/host_math.cuh @@ -4,7 +4,7 @@ #include #include -#include "common.cuh" +#include "gpu-utils/modifiers.cuh" namespace host_math { // return x + y with uint32_t operands diff --git a/icicle/include/fields/id.h b/icicle/include/fields/id.h new file mode 100644 index 00000000..54e7341c --- /dev/null +++ b/icicle/include/fields/id.h @@ -0,0 +1,13 @@ +#pragma once +#ifndef FIELD_ID_H +#define FIELD_ID_H + +#define BN254 1 +#define BLS12_381 2 +#define BLS12_377 3 +#define BW6_761 4 +#define GRUMPKIN 5 + +#define BABY_BEAR 1001 + +#endif \ No newline at end of file diff --git a/icicle/utils/ptx.cuh b/icicle/include/fields/ptx.cuh similarity index 100% rename from icicle/utils/ptx.cuh rename to icicle/include/fields/ptx.cuh diff --git a/icicle/primitives/extension_field.cuh b/icicle/include/fields/quadratic_extension.cuh similarity index 64% rename from icicle/primitives/extension_field.cuh rename to icicle/include/fields/quadratic_extension.cuh index a56d6d1a..36249a39 100644 --- a/icicle/primitives/extension_field.cuh +++ b/icicle/include/fields/quadratic_extension.cuh @@ -1,12 +1,15 @@ #pragma once #include "field.cuh" -#include "common.cuh" +#include "gpu-utils/modifiers.cuh" +#include "gpu-utils/sharedmem.cuh" template class ExtensionField { private: + friend Field; + typedef typename Field::Wide FWide; struct ExtensionWide { @@ -35,18 +38,24 @@ public: static constexpr HOST_DEVICE_INLINE ExtensionField one() { return ExtensionField{FF::one(), FF::zero()}; } - static constexpr HOST_DEVICE_INLINE ExtensionField ToMontgomery(const ExtensionField& xs) + static constexpr HOST_DEVICE_INLINE ExtensionField to_montgomery(const ExtensionField& xs) { return ExtensionField{xs.real * FF{CONFIG::montgomery_r}, xs.imaginary * FF{CONFIG::montgomery_r}}; } - static constexpr HOST_DEVICE_INLINE ExtensionField FromMontgomery(const ExtensionField& xs) + static constexpr HOST_DEVICE_INLINE ExtensionField from_montgomery(const ExtensionField& xs) { return ExtensionField{xs.real * FF{CONFIG::montgomery_r_inv}, xs.imaginary * FF{CONFIG::montgomery_r_inv}}; } static HOST_INLINE ExtensionField rand_host() { return ExtensionField{FF::rand_host(), FF::rand_host()}; } + static void rand_host_many(ExtensionField* out, int size) + { + for (int i = 0; i < size; i++) + out[i] = rand_host(); + } + template static constexpr HOST_DEVICE_INLINE ExtensionField sub_modulus(const ExtensionField& xs) { @@ -69,15 +78,47 @@ public: return ExtensionField{xs.real - ys.real, xs.imaginary - ys.imaginary}; } + friend HOST_DEVICE_INLINE ExtensionField operator+(FF xs, const ExtensionField& ys) + { + return ExtensionField{xs + ys.real, ys.imaginary}; + } + + friend HOST_DEVICE_INLINE ExtensionField operator-(FF xs, const ExtensionField& ys) + { + return ExtensionField{xs - ys.real, FF::neg(ys.imaginary)}; + } + + friend HOST_DEVICE_INLINE ExtensionField operator+(ExtensionField xs, const FF& ys) + { + return ExtensionField{xs.real + ys, xs.imaginary}; + } + + friend HOST_DEVICE_INLINE ExtensionField operator-(ExtensionField xs, const FF& ys) + { + return ExtensionField{xs.real - ys, xs.imaginary}; + } + template static constexpr HOST_DEVICE_INLINE ExtensionWide mul_wide(const ExtensionField& xs, const ExtensionField& ys) { FWide real_prod = FF::mul_wide(xs.real, ys.real); FWide imaginary_prod = FF::mul_wide(xs.imaginary, ys.imaginary); FWide prod_of_sums = FF::mul_wide(xs.real + xs.imaginary, ys.real + ys.imaginary); - FWide i_sq_times_im = FF::template mul_unsigned(imaginary_prod); - i_sq_times_im = CONFIG::i_squared_is_negative ? FWide::neg(i_sq_times_im) : i_sq_times_im; - return ExtensionWide{real_prod + i_sq_times_im, prod_of_sums - real_prod - imaginary_prod}; + FWide nonresidue_times_im = FF::template mul_unsigned(imaginary_prod); + nonresidue_times_im = CONFIG::nonresidue_is_negative ? FWide::neg(nonresidue_times_im) : nonresidue_times_im; + return ExtensionWide{real_prod + nonresidue_times_im, prod_of_sums - real_prod - imaginary_prod}; + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionWide mul_wide(const ExtensionField& xs, const FF& ys) + { + return ExtensionWide{FF::mul_wide(xs.real, ys), FF::mul_wide(xs.imaginary, ys)}; + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionWide mul_wide(const FF& xs, const ExtensionField& ys) + { + return mul_wide(ys, xs); } template @@ -87,7 +128,8 @@ public: FF::template reduce(xs.real), FF::template reduce(xs.imaginary)}; } - friend HOST_DEVICE_INLINE ExtensionField operator*(const ExtensionField& xs, const ExtensionField& ys) + template + friend HOST_DEVICE_INLINE ExtensionField operator*(const T1& xs, const T2& ys) { ExtensionWide xy = mul_wide(xs, ys); return reduce(xy); @@ -111,9 +153,9 @@ public: FF imaginary_prod = FF::template mul_const(xs_imaginary); FF re_im = FF::template mul_const(xs_imaginary); FF im_re = FF::template mul_const(xs_real); - FF i_sq_times_im = FF::template mul_unsigned(imaginary_prod); - i_sq_times_im = CONFIG::i_squared_is_negative ? FF::neg(i_sq_times_im) : i_sq_times_im; - return ExtensionField{real_prod + i_sq_times_im, re_im + im_re}; + FF nonresidue_times_im = FF::template mul_unsigned(imaginary_prod); + nonresidue_times_im = CONFIG::nonresidue_is_negative ? FF::neg(nonresidue_times_im) : nonresidue_times_im; + return ExtensionField{real_prod + nonresidue_times_im, re_im + im_re}; } template @@ -142,14 +184,23 @@ public: return ExtensionField{FF::neg(xs.real), FF::neg(xs.imaginary)}; } - // inverse assumes that xs is nonzero + // inverse of zero is set to be zero which is what we want most of the time static constexpr HOST_DEVICE_INLINE ExtensionField inverse(const ExtensionField& xs) { ExtensionField xs_conjugate = {xs.real, FF::neg(xs.imaginary)}; - FF i_sq_times_im = FF::template mul_unsigned(FF::sqr(xs.imaginary)); - i_sq_times_im = CONFIG::i_squared_is_negative ? FF::neg(i_sq_times_im) : i_sq_times_im; + FF nonresidue_times_im = FF::template mul_unsigned(FF::sqr(xs.imaginary)); + nonresidue_times_im = CONFIG::nonresidue_is_negative ? FF::neg(nonresidue_times_im) : nonresidue_times_im; // TODO: wide here - FF xs_norm_squared = FF::sqr(xs.real) - i_sq_times_im; + FF xs_norm_squared = FF::sqr(xs.real) - nonresidue_times_im; return xs_conjugate * ExtensionField{FF::inverse(xs_norm_squared), FF::zero()}; } +}; + +template +struct SharedMemory> { + __device__ ExtensionField* getPointer() + { + extern __shared__ ExtensionField s_ext2_scalar_[]; + return s_ext2_scalar_; + } }; \ No newline at end of file diff --git a/icicle/include/fields/quartic_extension.cuh b/icicle/include/fields/quartic_extension.cuh new file mode 100644 index 00000000..1f73adc8 --- /dev/null +++ b/icicle/include/fields/quartic_extension.cuh @@ -0,0 +1,257 @@ +#pragma once + +#include "field.cuh" +#include "gpu-utils/modifiers.cuh" +#include "gpu-utils/sharedmem.cuh" + +template +class ExtensionField +{ +private: + typedef typename Field::Wide FWide; + + struct ExtensionWide { + FWide real; + FWide im1; + FWide im2; + FWide im3; + + friend HOST_DEVICE_INLINE ExtensionWide operator+(ExtensionWide xs, const ExtensionWide& ys) + { + return ExtensionWide{xs.real + ys.real, xs.im1 + ys.im1, xs.im2 + ys.im2, xs.im3 + ys.im3}; + } + + friend HOST_DEVICE_INLINE ExtensionWide operator-(ExtensionWide xs, const ExtensionWide& ys) + { + return ExtensionWide{xs.real - ys.real, xs.im1 - ys.im1, xs.im2 - ys.im2, xs.im3 - ys.im3}; + } + }; + +public: + typedef Field FF; + static constexpr unsigned TLC = 4 * CONFIG::limbs_count; + + FF real; + FF im1; + FF im2; + FF im3; + + static constexpr HOST_DEVICE_INLINE ExtensionField zero() + { + return ExtensionField{FF::zero(), FF::zero(), FF::zero(), FF::zero()}; + } + + static constexpr HOST_DEVICE_INLINE ExtensionField one() + { + return ExtensionField{FF::one(), FF::zero(), FF::zero(), FF::zero()}; + } + + static constexpr HOST_DEVICE_INLINE ExtensionField to_montgomery(const ExtensionField& xs) + { + return ExtensionField{ + xs.real * FF{CONFIG::montgomery_r}, xs.im1 * FF{CONFIG::montgomery_r}, xs.im2 * FF{CONFIG::montgomery_r}, + xs.im3 * FF{CONFIG::montgomery_r}}; + } + + static constexpr HOST_DEVICE_INLINE ExtensionField from_montgomery(const ExtensionField& xs) + { + return ExtensionField{ + xs.real * FF{CONFIG::montgomery_r_inv}, xs.im1 * FF{CONFIG::montgomery_r_inv}, + xs.im2 * FF{CONFIG::montgomery_r_inv}, xs.im3 * FF{CONFIG::montgomery_r_inv}}; + } + + static HOST_INLINE ExtensionField rand_host() + { + return ExtensionField{FF::rand_host(), FF::rand_host(), FF::rand_host(), FF::rand_host()}; + } + + static void rand_host_many(ExtensionField* out, int size) + { + for (int i = 0; i < size; i++) + out[i] = rand_host(); + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionField sub_modulus(const ExtensionField& xs) + { + return ExtensionField{ + FF::sub_modulus(&xs.real), FF::sub_modulus(&xs.im1), + FF::sub_modulus(&xs.im2), FF::sub_modulus(&xs.im3)}; + } + + friend std::ostream& operator<<(std::ostream& os, const ExtensionField& xs) + { + os << "{ Real: " << xs.real << " }; { Im1: " << xs.im1 << " }; { Im2: " << xs.im2 << " }; { Im3: " << xs.im3 + << " };"; + return os; + } + + friend HOST_DEVICE_INLINE ExtensionField operator+(ExtensionField xs, const ExtensionField& ys) + { + return ExtensionField{xs.real + ys.real, xs.im1 + ys.im1, xs.im2 + ys.im2, xs.im3 + ys.im3}; + } + + friend HOST_DEVICE_INLINE ExtensionField operator-(ExtensionField xs, const ExtensionField& ys) + { + return ExtensionField{xs.real - ys.real, xs.im1 - ys.im1, xs.im2 - ys.im2, xs.im3 - ys.im3}; + } + + friend HOST_DEVICE_INLINE ExtensionField operator+(FF xs, const ExtensionField& ys) + { + return ExtensionField{xs + ys.real, ys.im1, ys.im2, ys.im3}; + } + + friend HOST_DEVICE_INLINE ExtensionField operator-(FF xs, const ExtensionField& ys) + { + return ExtensionField{xs - ys.real, FF::neg(ys.im1), FF::neg(ys.im2), FF::neg(ys.im3)}; + } + + friend HOST_DEVICE_INLINE ExtensionField operator+(ExtensionField xs, const FF& ys) + { + return ExtensionField{xs.real + ys, xs.im1, xs.im2, xs.im3}; + } + + friend HOST_DEVICE_INLINE ExtensionField operator-(ExtensionField xs, const FF& ys) + { + return ExtensionField{xs.real - ys, xs.im1, xs.im2, xs.im3}; + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionWide mul_wide(const ExtensionField& xs, const ExtensionField& ys) + { + if (CONFIG::nonresidue_is_negative) + return ExtensionWide{ + FF::mul_wide(xs.real, ys.real) - + FF::template mul_unsigned( + FF::mul_wide(xs.im1, ys.im3) + FF::mul_wide(xs.im2, ys.im2) + FF::mul_wide(xs.im3, ys.im1)), + FF::mul_wide(xs.real, ys.im1) + FF::mul_wide(xs.im1, ys.real) - + FF::template mul_unsigned(FF::mul_wide(xs.im2, ys.im3) + FF::mul_wide(xs.im3, ys.im2)), + FF::mul_wide(xs.real, ys.im2) + FF::mul_wide(xs.im1, ys.im1) + FF::mul_wide(xs.im2, ys.real) - + FF::template mul_unsigned(FF::mul_wide(xs.im3, ys.im3)), + FF::mul_wide(xs.real, ys.im3) + FF::mul_wide(xs.im1, ys.im2) + FF::mul_wide(xs.im2, ys.im1) + + FF::mul_wide(xs.im3, ys.real)}; + else + return ExtensionWide{ + FF::mul_wide(xs.real, ys.real) + + FF::template mul_unsigned( + FF::mul_wide(xs.im1, ys.im3) + FF::mul_wide(xs.im2, ys.im2) + FF::mul_wide(xs.im3, ys.im1)), + FF::mul_wide(xs.real, ys.im1) + FF::mul_wide(xs.im1, ys.real) + + FF::template mul_unsigned(FF::mul_wide(xs.im2, ys.im3) + FF::mul_wide(xs.im3, ys.im2)), + FF::mul_wide(xs.real, ys.im2) + FF::mul_wide(xs.im1, ys.im1) + FF::mul_wide(xs.im2, ys.real) + + FF::template mul_unsigned(FF::mul_wide(xs.im3, ys.im3)), + FF::mul_wide(xs.real, ys.im3) + FF::mul_wide(xs.im1, ys.im2) + FF::mul_wide(xs.im2, ys.im1) + + FF::mul_wide(xs.im3, ys.real)}; + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionWide mul_wide(const ExtensionField& xs, const FF& ys) + { + return ExtensionWide{ + FF::mul_wide(xs.real, ys), FF::mul_wide(xs.im1, ys), FF::mul_wide(xs.im2, ys), FF::mul_wide(xs.im3, ys)}; + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionWide mul_wide(const FF& xs, const ExtensionField& ys) + { + return ExtensionWide{ + FF::mul_wide(xs, ys.real), FF::mul_wide(xs, ys.im1), FF::mul_wide(xs, ys.im2), FF::mul_wide(xs, ys.im3)}; + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionField reduce(const ExtensionWide& xs) + { + return ExtensionField{ + FF::template reduce(xs.real), FF::template reduce(xs.im1), + FF::template reduce(xs.im2), FF::template reduce(xs.im3)}; + } + + template + friend HOST_DEVICE_INLINE ExtensionField operator*(const T1& xs, const T2& ys) + { + ExtensionWide xy = mul_wide(xs, ys); + return reduce(xy); + } + + friend HOST_DEVICE_INLINE bool operator==(const ExtensionField& xs, const ExtensionField& ys) + { + return (xs.real == ys.real) && (xs.im1 == ys.im1) && (xs.im2 == ys.im2) && (xs.im3 == ys.im3); + } + + friend HOST_DEVICE_INLINE bool operator!=(const ExtensionField& xs, const ExtensionField& ys) { return !(xs == ys); } + + template + static constexpr HOST_DEVICE_INLINE ExtensionField mul_unsigned(const ExtensionField& xs) + { + return { + FF::template mul_unsigned(xs.real), FF::template mul_unsigned(xs.im1), + FF::template mul_unsigned(xs.im2), FF::template mul_unsigned(xs.im3)}; + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionWide sqr_wide(const ExtensionField& xs) + { + // TODO: change to a more efficient squaring + return mul_wide(xs, xs); + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionField sqr(const ExtensionField& xs) + { + // TODO: change to a more efficient squaring + return xs * xs; + } + + template + static constexpr HOST_DEVICE_INLINE ExtensionField neg(const ExtensionField& xs) + { + return {FF::neg(xs.real), FF::neg(xs.im1), FF::neg(xs.im2), FF::neg(xs.im3)}; + } + + // inverse of zero is set to be zero which is what we want most of the time + static constexpr HOST_DEVICE_INLINE ExtensionField inverse(const ExtensionField& xs) + { + FF x, x0, x2; + if (CONFIG::nonresidue_is_negative) { + x0 = FF::reduce( + FF::sqr_wide(xs.real) + + FF::template mul_unsigned(FF::mul_wide(xs.im1, xs.im3 + xs.im3) - FF::sqr_wide(xs.im2))); + x2 = FF::reduce( + FF::mul_wide(xs.real, xs.im2 + xs.im2) - FF::sqr_wide(xs.im1) + + FF::template mul_unsigned(FF::sqr_wide(xs.im3))); + x = FF::reduce(FF::sqr_wide(x0) + FF::template mul_unsigned(FF::sqr_wide(x2))); + } else { + x0 = FF::reduce( + FF::sqr_wide(xs.real) - + FF::template mul_unsigned(FF::mul_wide(xs.im1, xs.im3 + xs.im3) - FF::sqr_wide(xs.im2))); + x2 = FF::reduce( + FF::mul_wide(xs.real, xs.im2 + xs.im2) - FF::sqr_wide(xs.im1) - + FF::template mul_unsigned(FF::sqr_wide(xs.im3))); + x = FF::reduce(FF::sqr_wide(x0) - FF::template mul_unsigned(FF::sqr_wide(x2))); + } + FF x_inv = FF::inverse(x); + x0 = x0 * x_inv; + x2 = x2 * x_inv; + return { + FF::reduce( + (CONFIG::nonresidue_is_negative + ? (FF::mul_wide(xs.real, x0) + FF::template mul_unsigned(FF::mul_wide(xs.im2, x2))) + : (FF::mul_wide(xs.real, x0)) - FF::template mul_unsigned(FF::mul_wide(xs.im2, x2)))), + FF::reduce( + (CONFIG::nonresidue_is_negative + ? FWide::neg(FF::template mul_unsigned(FF::mul_wide(xs.im3, x2))) + : FF::template mul_unsigned(FF::mul_wide(xs.im3, x2))) - + FF::mul_wide(xs.im1, x0)), + FF::reduce(FF::mul_wide(xs.im2, x0) - FF::mul_wide(xs.real, x2)), + FF::reduce(FF::mul_wide(xs.im1, x2) - FF::mul_wide(xs.im3, x0)), + }; + } +}; + +template +struct SharedMemory> { + __device__ ExtensionField* getPointer() + { + extern __shared__ ExtensionField s_ext4_scalar_[]; + return s_ext4_scalar_; + } +}; \ No newline at end of file diff --git a/icicle/curves/bls12_377_params.cuh b/icicle/include/fields/snark_fields/bls12_377_base.cuh similarity index 55% rename from icicle/curves/bls12_377_params.cuh rename to icicle/include/fields/snark_fields/bls12_377_base.cuh index aafd8497..fe9f3df3 100644 --- a/icicle/curves/bls12_377_params.cuh +++ b/icicle/include/fields/snark_fields/bls12_377_base.cuh @@ -1,196 +1,10 @@ #pragma once -#ifndef BLS12_377_PARAMS_H -#define BLS12_377_PARAMS_H +#ifndef BLS12_377_BASE_PARAMS_H +#define BLS12_377_BASE_PARAMS_H -#include "utils/storage.cuh" +#include "fields/storage.cuh" namespace bls12_377 { - struct fp_config { - static constexpr unsigned limbs_count = 8; - static constexpr unsigned omegas_count = 47; - static constexpr unsigned modulus_bit_count = 253; - static constexpr unsigned num_of_reductions = 1; - - static constexpr storage modulus = {0x00000001, 0x0a118000, 0xd0000001, 0x59aa76fe, - 0x5c37b001, 0x60b44d1e, 0x9a2ca556, 0x12ab655e}; - static constexpr storage modulus_2 = {0x00000002, 0x14230000, 0xa0000002, 0xb354edfd, - 0xb86f6002, 0xc1689a3c, 0x34594aac, 0x2556cabd}; - static constexpr storage modulus_4 = {0x00000004, 0x28460000, 0x40000004, 0x66a9dbfb, - 0x70dec005, 0x82d13479, 0x68b29559, 0x4aad957a}; - static constexpr storage neg_modulus = {0xffffffff, 0xf5ee7fff, 0x2ffffffe, 0xa6558901, - 0xa3c84ffe, 0x9f4bb2e1, 0x65d35aa9, 0xed549aa1}; - static constexpr storage<2 * limbs_count> modulus_wide = { - 0x00000001, 0x0a118000, 0xd0000001, 0x59aa76fe, 0x5c37b001, 0x60b44d1e, 0x9a2ca556, 0x12ab655e, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage<2 * limbs_count> modulus_squared = { - 0x00000001, 0x14230000, 0xe0000002, 0xc7dd4d2f, 0x8585d003, 0x08ee1bd4, 0xe57fc56e, 0x7e7557e3, - 0x483a709d, 0x1fdebb41, 0x5678f4e6, 0x8ea77334, 0xc19c3ec5, 0xd717de29, 0xe2340781, 0x015c8d01}; - static constexpr storage<2 * limbs_count> modulus_squared_2 = { - 0x00000002, 0x28460000, 0xc0000004, 0x8fba9a5f, 0x0b0ba007, 0x11dc37a9, 0xcaff8adc, 0xfceaafc7, - 0x9074e13a, 0x3fbd7682, 0xacf1e9cc, 0x1d4ee668, 0x83387d8b, 0xae2fbc53, 0xc4680f03, 0x02b91a03}; - static constexpr storage<2 * limbs_count> modulus_squared_4 = { - 0x00000004, 0x508c0000, 0x80000008, 0x1f7534bf, 0x1617400f, 0x23b86f52, 0x95ff15b8, 0xf9d55f8f, - 0x20e9c275, 0x7f7aed05, 0x59e3d398, 0x3a9dccd1, 0x0670fb16, 0x5c5f78a7, 0x88d01e07, 0x05723407}; - - static constexpr storage m = {0x151e79ea, 0xf5204c21, 0x8d69e258, 0xfd0a180b, - 0xfaa80548, 0xe4e51e49, 0xc40b2c9e, 0x36d9491e}; - static constexpr storage one = {0x00000001, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage zero = {0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage montgomery_r = {0xfffffff3, 0x7d1c7fff, 0x6ffffff2, 0x7257f50f, - 0x512c0fee, 0x16d81575, 0x2bbb9a9d, 0x0d4bda32}; - static constexpr storage montgomery_r_inv = {0x1beeec02, 0x4122dd1a, 0x74fee875, 0xbd1eae95, - 0x27b28e2f, 0x838557e2, 0x2290c02c, 0x07b30191}; - - static constexpr storage_array omega = { - {{0x00000000, 0x0a118000, 0xd0000001, 0x59aa76fe, 0x5c37b001, 0x60b44d1e, 0x9a2ca556, 0x12ab655e}, - {0x00000001, 0x8f1a4000, 0xb0000001, 0xcf664765, 0x970dec00, 0x23ed1347, 0x00000000, 0x00000000}, - {0xfbfa0a01, 0x0f830f7e, 0xd75769a0, 0x20f8b46c, 0xf05d5033, 0x7108bd18, 0x0788de01, 0x07405e08}, - {0x60b9bdae, 0xc78085a6, 0x789094f5, 0x3116ec22, 0xce87d660, 0x0a02a81d, 0xc2a94856, 0x0ead8236}, - {0x3e83a7cc, 0x6ffc39d9, 0x958a0a74, 0x117d996e, 0x0b92e8c9, 0xc242289d, 0x29d977d6, 0x0484efb4}, - {0x0111ec3f, 0x15455b00, 0xc5f6be6f, 0x6b62d7af, 0x337f2d07, 0xfcba0365, 0x43fccd26, 0x0f151842}, - {0xc31ec69b, 0x57951b2e, 0x2a37ce1f, 0x3e0a4be7, 0xcf3b198a, 0x960aeb4a, 0x341fd5cd, 0x04fb0673}, - {0xa921851f, 0x71c1b78e, 0x7808f239, 0x3c26340c, 0x976fb990, 0xbcc8f69b, 0xe880dc71, 0x06a5edb2}, - {0xc0f5679e, 0x7619eab5, 0x0dc0b9cd, 0x1f4cd10e, 0xbf6a480a, 0x7e1b70aa, 0x7f5461bb, 0x0ffc66da}, - {0xec5cbab2, 0x8159806d, 0x498264a3, 0x14ea1333, 0xe3abfaa6, 0x56bbe1d8, 0x02aa031f, 0x09d2b5c4}, - {0xc010c48a, 0xd2aa9562, 0x3b004b60, 0x447e5c11, 0x11e243bb, 0xd5a21c13, 0x0ab418b1, 0x01eab23e}, - {0xacff6986, 0x08715ee8, 0xa93924d0, 0xab01878a, 0x6e9ae5c4, 0xbfbc5e71, 0x26b08d6e, 0x0f8000bf}, - {0x3ddbc679, 0x06bc13b0, 0x615256ce, 0x7269a1f1, 0x1f5221a2, 0xf7716fbf, 0x8c66c14f, 0x0fa1f02c}, - {0x906f531f, 0xdd40f131, 0x30728eff, 0xb06b29c7, 0x88839294, 0xc891fd19, 0x646978e8, 0x04e88447}, - {0x6e259cdc, 0xb1e4b769, 0x00514e5e, 0xbcb0b709, 0x05113e7f, 0x74edb7c0, 0xe92e22af, 0x10c88511}, - {0x240ede5b, 0xebb2e898, 0x42cd84c6, 0xc2639185, 0x9408f956, 0xf79e8391, 0x94e87a7d, 0x06872fa1}, - {0x260678ff, 0xf8522249, 0xa8de9973, 0x6148cb16, 0x5a4e8d56, 0x5750f3f4, 0xbaeaf0c3, 0x0e805156}, - {0x3d766f80, 0x1b4b71cf, 0x1069012d, 0x47d21195, 0x9151ebec, 0x5635235f, 0x2b13c808, 0x093f7d91}, - {0x4637701d, 0x0848f958, 0x4c8353af, 0x8a750076, 0x0ef6174a, 0x485f4e4f, 0xf38db632, 0x078d97a1}, - {0x66a16869, 0x50c487c1, 0xd1fd4525, 0x380a66ab, 0x265e8539, 0xd455a01a, 0x064b5334, 0x0cd62875}, - {0x3358eb25, 0xdbc547bc, 0x722037db, 0x8909d398, 0x5e705b6d, 0x8b7075b5, 0x9bdaf407, 0x02694bb2}, - {0xf45b9621, 0x102fbfb0, 0xf04faac0, 0xe80f4241, 0x7ca61177, 0x0b830bfd, 0x7033169d, 0x10521892}, - {0xcc943028, 0xed2576ad, 0xfa4c6090, 0x846e49bc, 0x0049d8e6, 0xc74c1865, 0x665d7be5, 0x0e9c5a12}, - {0xafeb494b, 0x97319dcd, 0x1d78404c, 0xab30c83e, 0xf26ffe90, 0x452d8a48, 0xa36452c7, 0x0bfc2e92}, - {0xedc626c3, 0xf30e312d, 0xcf1f3a94, 0x8367a7ca, 0x917a1b28, 0x621e15e1, 0xf2e93b82, 0x07cd59f8}, - {0xf02ba42c, 0x553085d9, 0x1119b10d, 0x59662159, 0x6b8ea03f, 0xaa670958, 0x7ce92983, 0x066f6f5f}, - {0x4dd87a5e, 0xf423a283, 0xd9a4c364, 0x1fe46601, 0xbfdc7e9b, 0xda4addbf, 0x3bf94b2b, 0x0a7f2bd8}, - {0xe5f8848a, 0x270a2326, 0xa727567d, 0x97d14afa, 0x48746fc7, 0x1a3a5a4e, 0xa42f077a, 0x0044e4b1}, - {0x20b7298a, 0xd7652451, 0x65013b06, 0xc7c9a0b7, 0xad0d8457, 0x479b82a9, 0x0c99f5ce, 0x0bef1e5a}, - {0x1912f7fa, 0x77d7da1d, 0x299fd7d6, 0xbcb7a5b2, 0x142a4480, 0x705e45dd, 0xb492dbd8, 0x0dc835fd}, - {0xa0234d2d, 0xe943054c, 0xe5f5be5e, 0x673b0ee0, 0x5048a19a, 0xcdd48e41, 0xabc3cb99, 0x0997d277}, - {0xa9966ac4, 0x1ae0ea67, 0xda83fb3b, 0x4e2dbb1c, 0x0b51380e, 0xf77cf749, 0xb28a7670, 0x048b4b0e}, - {0xb14361d4, 0x7f1db43f, 0x25ab6d51, 0x7927e578, 0x383bf21e, 0xb43e52a5, 0xd27fa99f, 0x077595e9}, - {0xa90a2740, 0xfe3ca4f0, 0x512a7c7a, 0xd259ff36, 0xb41fe696, 0xbca3176a, 0xf33132ce, 0x05bd5ea3}, - {0xf284f768, 0xdeee484b, 0xe26a0475, 0x2a02e015, 0x88d968c2, 0xf0eb4925, 0x82a391c9, 0x0620ce9e}, - {0xbd83a3da, 0xd3b69b29, 0xe02ce197, 0x9543950f, 0xc2f87783, 0x80799665, 0xc15be215, 0x11ce8199}, - {0x1b29736e, 0x8f267f19, 0x1d5a0c3a, 0xa2e04d58, 0x1ae99514, 0x76803064, 0x57f7c806, 0x12129439}, - {0xf32d6bac, 0xa0b973d4, 0xf0d81b72, 0xae951889, 0x2e2daa0a, 0x51dbe098, 0x40d9af8f, 0x04679474}, - {0x22df9f13, 0x56313de8, 0x599e7536, 0xe2e75200, 0x6d163e50, 0xa1b4fce7, 0xc8111763, 0x0aec2172}, - {0x355dd694, 0x4258374d, 0x44c76a20, 0x5c31e8ac, 0xaa5fd062, 0x9b473969, 0x1a37b6b4, 0x0a693d77}, - {0x44ddbbdc, 0xbafb92a6, 0x26b01974, 0x63c7a02d, 0x5f28a274, 0x0ff86e13, 0x867f2e29, 0x0a7b462a}, - {0xd5fba57b, 0x90684fea, 0xe0defe98, 0xed237883, 0x030ae924, 0xc502b692, 0xe7a1ec2c, 0x08aa58e8}, - {0x5e9020dd, 0xade9d4b4, 0x87db8813, 0x489259d2, 0x25051238, 0x5ddce740, 0xb5bc4d11, 0x0c775db1}, - {0x293f8481, 0xd52cc17a, 0x6f133205, 0x041178fb, 0xb2961832, 0xbbc70d18, 0x481760cd, 0x073d34d1}, - {0xfdacff58, 0x8215b91d, 0x98331645, 0xd8d9177d, 0x439e803c, 0xe85223ad, 0xcca42c1f, 0x04aa8ef0}, - {0x01ab3a4d, 0x006f60fa, 0x814ba450, 0xe6600e15, 0xdf9eb147, 0xbde4df36, 0x33760d7b, 0x055d58fa}, - {0xec2a895e, 0x476ef4a4, 0x63e3f04a, 0x9b506ee3, 0xd1a8a12f, 0x60c69477, 0x0cb92cc1, 0x11d4b7f6}}}; - - static constexpr storage_array omega_inv = { - {{0x00000000, 0x0a118000, 0xd0000001, 0x59aa76fe, 0x5c37b001, 0x60b44d1e, 0x9a2ca556, 0x12ab655e}, - {0x00000000, 0x7af74000, 0x1fffffff, 0x8a442f99, 0xc529c400, 0x3cc739d6, 0x9a2ca556, 0x12ab655e}, - {0xd60fb046, 0xc9fa190c, 0xc5b4674e, 0xdb5c179b, 0xbc7b8726, 0x2b2bce0b, 0xbf6e69bf, 0x0e4eb338}, - {0x8ffc4ed5, 0x74732d1f, 0xb7f2eefc, 0x42d9f590, 0xa24dd4dd, 0xf70461e5, 0xef64676f, 0x03b6eba4}, - {0x102bbab0, 0x5a21f98a, 0x8d8e2efb, 0xa6a147a9, 0x7612906f, 0x0eb4f005, 0x47d8d2e3, 0x0e1a5481}, - {0xd01e5aa8, 0x6e509add, 0x6e3f123d, 0xe1582468, 0x8274db24, 0xbd6313ee, 0xd173a634, 0x05d5836e}, - {0xe975c0cf, 0x6aab3344, 0x6f1dc38e, 0xca362e0e, 0x1dd1743a, 0x2fe72cda, 0xc1b4c4c2, 0x0c1c956e}, - {0xec89a64f, 0x59fe97a0, 0xe8de5d4c, 0x579617d7, 0xc9c1ea7b, 0x256a305b, 0x53fa131b, 0x01ffae4e}, - {0x29bcb088, 0x463a73ff, 0xe1438e80, 0xee9e9a5e, 0x3c9369e4, 0x2a00951f, 0x80a32052, 0x09711183}, - {0x4bec8dd2, 0xa36899db, 0x96393687, 0x2946872e, 0x842df3c8, 0xd4b5734f, 0x5f5cd8fb, 0x0834098f}, - {0xe3c711b9, 0x4bc485f6, 0x648d1d7e, 0xf43a2598, 0xee88abaa, 0x7f981a0e, 0xec6a3f27, 0x0c88c9c3}, - {0x49046b52, 0x42bcc6c2, 0x56ab9ecc, 0xcc77294a, 0xe4df3ddd, 0x02ecb41a, 0x67f76726, 0x0e567d22}, - {0x91c64fc2, 0x1cc56cc3, 0xd16a490b, 0x8cb71e65, 0x14fac366, 0x984be37e, 0xa25d7ba5, 0x0a08e032}, - {0xd4f5941e, 0x966d9739, 0xe5772a73, 0x5805deb6, 0x5c1f970c, 0xe4eb0d33, 0xbdf35409, 0x039715db}, - {0xcc6518ac, 0x8419686c, 0x9c7a2366, 0x96dec3a8, 0x71724384, 0xefbfcac6, 0xaf34c239, 0x0c44b99a}, - {0xc18ff4fd, 0xcb66fe1b, 0x86c8d586, 0x588e18b3, 0x1dfab57c, 0xc6e6d2a3, 0x7d7d4efd, 0x10918ad2}, - {0x97a18f58, 0x56d6cf22, 0xd0d7abd9, 0x11710758, 0x5eb7a9c5, 0xd1a6608b, 0xc4937e38, 0x04059bdb}, - {0x4b1b63a9, 0x12998cbc, 0xcf420c9f, 0x0f780c6c, 0x129289ad, 0xa5e48723, 0x240a141d, 0x0a3a1223}, - {0x00db2b48, 0xa43c0e02, 0x933d10ee, 0x76585489, 0xc0ba6a80, 0x12d64af1, 0x2fad8d8e, 0x01940f43}, - {0x1d75bec9, 0xe29ef6c0, 0xd4b0183b, 0xead287a2, 0xedfd3795, 0x75a017cf, 0x64427c8e, 0x107f8d0f}, - {0xa26c8c12, 0xa6f4e1d1, 0xf6610f7e, 0x13571553, 0x56701caf, 0xd95e5df6, 0x2263d69d, 0x050e7b89}, - {0xc161761f, 0x271d7caf, 0xc369a371, 0xf1001d6f, 0x00e60f51, 0x65286415, 0xb74d14b8, 0x00b918f9}, - {0x03ad3139, 0x01d3f431, 0xa137ce16, 0xe56f6002, 0x1deb42e8, 0x97f53369, 0xaa37cddd, 0x033fa9ac}, - {0x60cf1330, 0x840f913b, 0x1df5ed87, 0x5610cde6, 0x72b36ddf, 0x858381b0, 0x6f64e0b7, 0x109bf66c}, - {0x930cee0b, 0x432d3626, 0xf26e8ba3, 0x55ed3efb, 0x14c5457f, 0x802eebcc, 0xe2310f22, 0x00d300e3}, - {0x4b9ac952, 0x3d29f5ba, 0xc8ea8f94, 0x7c7f2662, 0xcefc3052, 0x736ccb63, 0x0981f3cb, 0x04bfce2f}, - {0x5d4e643c, 0x3da791ea, 0x85bff013, 0xb6a956ef, 0xd73de6a3, 0x86c629a8, 0x6b8c48a9, 0x0a5a5f55}, - {0x49c6284a, 0x9ba6aa00, 0xeacbdc63, 0x0b8429fb, 0xedafdf37, 0x9b9c6c5b, 0xad0c78c6, 0x009907e8}, - {0x3e47b53f, 0x50380ce2, 0x3a9613fc, 0x6ea3c2d3, 0x4c87ab50, 0xfe743105, 0xd192221c, 0x07871979}, - {0xe978594b, 0x4ddd3320, 0x3abe3f79, 0xe5f36fbe, 0xe4dcff8e, 0x5dba9ef2, 0x7105148f, 0x0bfc27e2}, - {0x498fb549, 0xd5993cd5, 0x09da9272, 0x718adcee, 0x72bd5bc0, 0x9e03cbb4, 0xc592813f, 0x07206942}, - {0x78fd3239, 0xaf29730b, 0x40c3e723, 0xbd907ac9, 0x77f214f7, 0x5dcc0aad, 0xb05fb3a1, 0x02d958da}, - {0xdf80223d, 0x55f432c9, 0x11a2fed9, 0x23daf2f6, 0x41ae8c34, 0x9e43e003, 0x95f22373, 0x0d51533b}, - {0x7998b62c, 0xbb53132b, 0x22c9b4aa, 0x064a9186, 0x71d61334, 0xd56de253, 0x04e416f6, 0x10fcf25f}, - {0xdddb58ec, 0x41f8042f, 0x10886d85, 0x7dd54384, 0x622ff4b4, 0x19544f90, 0x050cc539, 0x02f0b49a}, - {0xa39b02a3, 0x8a3de898, 0xdc94422c, 0x068b2992, 0xf493db31, 0x1c5f019a, 0x11b0f668, 0x066b1790}, - {0x78500f1a, 0x98310dd7, 0x735ccb27, 0x1c6050bf, 0xb2081df4, 0x07b6fa7f, 0xfa0f1e20, 0x003edf24}, - {0x89b0ca6f, 0xb4d938e2, 0x2c897570, 0x0214eb59, 0x2d4cf27a, 0x56c45327, 0x3ed546a4, 0x10a2f358}, - {0xef01ed78, 0xf2828212, 0xf103c9ca, 0xa66094ac, 0x7a2d5573, 0xdceb481d, 0x8af46aab, 0x0190fcde}, - {0x526bf9fc, 0x023031cc, 0x79c209ba, 0x0e4136c0, 0x3ec42e5c, 0xe5234df1, 0x1d455234, 0x00cb9592}, - {0x33bf2a1c, 0x842b0c9c, 0xa29b9236, 0x1fd43c95, 0xc06795d3, 0x6b37a603, 0x0c1b712a, 0x00017b17}, - {0xaf858193, 0x2b955be2, 0x5fb5e378, 0xa513d8be, 0xa326aeb9, 0x88c4ebeb, 0xf3d45990, 0x00c378e2}, - {0x6464580f, 0x33e6c8c0, 0x3c4aa09f, 0x9d560eb3, 0xcc98f404, 0xb3f1a899, 0x8ca24b48, 0x012c1ea5}, - {0xe3b4dc56, 0xa0594a67, 0x91b698e1, 0xc8e6b582, 0x8df78057, 0x711cadbf, 0x396466f8, 0x0049abdf}, - {0x4ffa086a, 0xecc89610, 0xca06afc6, 0x4db82291, 0x8f3a6426, 0x9ae7c68c, 0x2a874432, 0x0b3dae8c}, - {0x3b3625b6, 0x1e62401f, 0x28471e5a, 0xd0692164, 0x5cad6b77, 0xb85aa9ec, 0xaa95acf2, 0x063e4b66}, - {0xb9112c51, 0x2542c2b2, 0x6e23b3ce, 0x36ead8da, 0x76476754, 0x9a268d13, 0xa1ad7cf1, 0x121f44ad}}}; - - static constexpr storage_array inv = { - {{0x00000001, 0x8508c000, 0x68000000, 0xacd53b7f, 0x2e1bd800, 0x305a268f, 0x4d1652ab, 0x0955b2af}, - {0x00000001, 0xc78d2000, 0x1c000000, 0x033fd93f, 0xc529c401, 0xc88739d6, 0xf3a17c00, 0x0e008c06}, - {0x00000001, 0xe8cf5000, 0xf6000000, 0x2e75281e, 0x90b0ba01, 0x949dc37a, 0xc6e710ab, 0x1055f8b2}, - {0x00000001, 0xf9706800, 0xe3000000, 0x440fcf8e, 0x76743501, 0xfaa9084c, 0xb089db00, 0x1180af08}, - {0x00000001, 0x01c0f400, 0xd9800001, 0x4edd2346, 0x6955f281, 0xadaeaab5, 0xa55b402b, 0x12160a33}, - {0x00000001, 0x05e93a00, 0xd4c00001, 0x5443cd22, 0xe2c6d141, 0x07317be9, 0x1fc3f2c1, 0x1260b7c9}, - {0x00000001, 0x07fd5d00, 0xd2600001, 0x56f72210, 0x1f7f40a1, 0xb3f2e484, 0xdcf84c0b, 0x12860e93}, - {0x00000001, 0x09076e80, 0xd1300001, 0x5850cc87, 0x3ddb7851, 0x0a5398d1, 0x3b9278b1, 0x1298b9f9}, - {0x00000001, 0x098c7740, 0x50980001, 0x58fda1c3, 0xcd099429, 0xb583f2f7, 0xeadf8f03, 0x12a20fab}, - {0x00000001, 0x09cefba0, 0x104c0001, 0x59540c61, 0x14a0a215, 0x0b1c200b, 0x42861a2d, 0x12a6ba85}, - {0x00000001, 0x09f03dd0, 0xf0260001, 0x597f41af, 0xb86c290b, 0xb5e83694, 0xee595fc1, 0x12a90ff1}, - {0x00000001, 0x0a00dee8, 0x60130001, 0x5994dc57, 0x8a51ec86, 0x0b4e41d9, 0x4443028c, 0x12aa3aa8}, - {0x00000001, 0x0a092f74, 0x18098001, 0xd99fa9ab, 0xf344ce43, 0x3601477b, 0x6f37d3f1, 0x12aad003}, - {0x00000001, 0x0a0d57ba, 0xf404c001, 0x99a51054, 0x27be3f22, 0xcb5aca4d, 0x04b23ca3, 0x12ab1ab1}, - {0x00000001, 0x0a0f6bdd, 0xe2026001, 0xf9a7c3a9, 0xc1faf791, 0x16078bb5, 0xcf6f70fd, 0x12ab4007}, - {0x80000001, 0x0a1075ee, 0x59013001, 0xa9a91d54, 0x0f1953c9, 0xbb5dec6a, 0x34ce0b29, 0x12ab52b3}, - {0x40000001, 0x0a10faf7, 0x94809801, 0x81a9ca29, 0x35a881e5, 0x0e091cc4, 0xe77d5840, 0x12ab5c08}, - {0xa0000001, 0x0a113d7b, 0x32404c01, 0x6daa2094, 0x48f018f3, 0x375eb4f1, 0xc0d4fecb, 0x12ab60b3}, - {0xd0000001, 0x0a115ebd, 0x81202601, 0x63aa4bc9, 0xd293e47a, 0xcc098107, 0x2d80d210, 0x12ab6309}, - {0xe8000001, 0x0a116f5e, 0x28901301, 0xdeaa6164, 0x1765ca3d, 0x965ee713, 0xe3d6bbb3, 0x12ab6433}, - {0x74000001, 0x0a1177af, 0x7c480981, 0x9c2a6c31, 0xb9cebd1f, 0xfb899a18, 0x3f01b084, 0x12ab64c9}, - {0xba000001, 0x0a117bd7, 0x262404c1, 0x7aea7198, 0x8b033690, 0xae1ef39b, 0xec972aed, 0x12ab6513}, - {0xdd000001, 0x0a117deb, 0x7b120261, 0xea4a744b, 0xf39d7348, 0x0769a05c, 0x4361e822, 0x12ab6539}, - {0xee800001, 0x0a117ef5, 0x25890131, 0x21fa75a5, 0xa7ea91a5, 0x340ef6bd, 0xeec746bc, 0x12ab654b}, - {0xf7400001, 0x0a117f7a, 0xfac48099, 0x3dd27651, 0x021120d3, 0x4a61a1ee, 0x4479f609, 0x12ab6555}, - {0x7ba00001, 0x0a117fbd, 0x6562404d, 0x4bbe76a8, 0x2f24686a, 0xd58af786, 0xef534daf, 0x12ab6559}, - {0xbdd00001, 0x0a117fde, 0x9ab12027, 0xd2b476d3, 0x45ae0c35, 0x1b1fa252, 0x44bff983, 0x12ab655c}, - {0x5ee80001, 0x0a117fef, 0x35589014, 0x962f76e9, 0x50f2de1b, 0xbde9f7b8, 0x6f764f6c, 0x12ab655d}, - {0xaf740001, 0x8a117ff7, 0x02ac480a, 0x77ecf6f4, 0x5695470e, 0x8f4f226b, 0x04d17a61, 0x12ab655e}, - {0xd7ba0001, 0xca117ffb, 0x69562405, 0xe8cbb6f9, 0xd9667b87, 0xf801b7c4, 0x4f7f0fdb, 0x12ab655e}, - {0xebdd0001, 0x6a117ffd, 0x1cab1203, 0xa13b16fc, 0x9acf15c4, 0x2c5b0271, 0x74d5da99, 0x12ab655e}, - {0xf5ee8001, 0x3a117ffe, 0x76558902, 0xfd72c6fd, 0xfb8362e2, 0xc687a7c7, 0x87813ff7, 0x12ab655e}, - {0x7af74001, 0xa2117fff, 0x232ac481, 0x2b8e9efe, 0x2bdd8972, 0x139dfa73, 0x90d6f2a7, 0x12ab655e}, - {0xbd7ba001, 0x56117fff, 0x79956241, 0xc29c8afe, 0xc40a9cb9, 0xba2923c8, 0x9581cbfe, 0x12ab655e}, - {0xdebdd001, 0x30117fff, 0xa4cab121, 0x8e2380fe, 0x9021265d, 0x8d6eb873, 0x97d738aa, 0x12ab655e}, - {0xef5ee801, 0x1d117fff, 0xba655891, 0x73e6fbfe, 0xf62c6b2f, 0x771182c8, 0x9901ef00, 0x12ab655e}, - {0xf7af7401, 0x13917fff, 0xc532ac49, 0x66c8b97e, 0xa9320d98, 0x6be2e7f3, 0x99974a2b, 0x12ab655e}, - {0xfbd7ba01, 0x0ed17fff, 0xca995625, 0xe039983e, 0x02b4decc, 0xe64b9a89, 0x99e1f7c0, 0x12ab655e}, - {0xfdebdd01, 0x0c717fff, 0xcd4cab13, 0x1cf2079e, 0xaf764767, 0xa37ff3d3, 0x9a074e8b, 0x12ab655e}, - {0xfef5ee81, 0x0b417fff, 0xcea6558a, 0x3b4e3f4e, 0x05d6fbb4, 0x021a2079, 0x9a19f9f1, 0x12ab655e}, - {0xff7af741, 0x8aa97fff, 0xcf532ac5, 0xca7c5b26, 0xb10755da, 0xb16736cb, 0x9a234fa3, 0x12ab655e}, - {0xffbd7ba1, 0x4a5d7fff, 0xcfa99563, 0x12136912, 0x069f82ee, 0x090dc1f5, 0x9a27fa7d, 0x12ab655e}, - {0xffdebdd1, 0x2a377fff, 0xcfd4cab2, 0xb5def008, 0xb16b9977, 0xb4e10789, 0x9a2a4fe9, 0x12ab655e}, - {0xffef5ee9, 0x9a247fff, 0xcfea6559, 0x87c4b383, 0x06d1a4bc, 0x0acaaa54, 0x9a2b7aa0, 0x12ab655e}, - {0xfff7af75, 0x521affff, 0x4ff532ad, 0xf0b79541, 0x3184aa5e, 0x35bf7bb9, 0x9a2c0ffb, 0x12ab655e}, - {0xfffbd7bb, 0x2e163fff, 0x0ffa9957, 0x25310620, 0xc6de2d30, 0xcb39e46b, 0x9a2c5aa8, 0x12ab655e}, - {0xfffdebde, 0x1c13dfff, 0x6ffd4cac, 0xbf6dbe8f, 0x118aee98, 0x95f718c5, 0x9a2c7fff, 0x12ab655e}}}; - }; - struct fq_config { static constexpr unsigned limbs_count = 12; static constexpr unsigned omegas_count = 48; @@ -521,41 +335,11 @@ namespace bls12_377 { {0xfffdebde, 0x0ff7ffff, 0x0fffa3d3, 0x8e4c751f, 0x6bcccc32, 0xb7275e5b, 0xdc08ab03, 0x0321276d, 0x28f6304f, 0xdd22a6ac, 0x17c50a31, 0x01ae3a46}}}; - // i^2, the square of the imaginary unit for the extension field - static constexpr uint32_t i_squared = 5; - // true if i^2 is negative - static constexpr bool i_squared_is_negative = true; + // nonresidue to generate the extension field + static constexpr uint32_t nonresidue = 5; + // true if nonresidue is negative + static constexpr bool nonresidue_is_negative = true; }; - - // G1 and G2 generators - static constexpr storage g1_gen_x = {0xb21be9ef, 0xeab9b16e, 0xffcd394e, 0xd5481512, - 0xbd37cb5c, 0x188282c8, 0xaa9d41bb, 0x85951e2c, - 0xbf87ff54, 0xc8fc6225, 0xfe740a67, 0x008848de}; - static constexpr storage g1_gen_y = {0x559c8ea6, 0xfd82de55, 0x34a9591a, 0xc2fe3d36, - 0x4fb82305, 0x6d182ad4, 0xca3e52d9, 0xbd7fb348, - 0x30afeec4, 0x1f674f5d, 0xc5102eff, 0x01914a69}; - static constexpr storage g2_gen_x_re = {0x7c005196, 0x74e3e48f, 0xbb535402, 0x71889f52, - 0x57db6b9b, 0x7ea501f5, 0x203e5031, 0xc565f071, - 0xa3841d01, 0xc89630a2, 0x71c785fe, 0x018480be}; - static constexpr storage g2_gen_x_im = {0x6ea16afe, 0xb26bfefa, 0xbff76fe6, 0x5cf89984, - 0x0799c9de, 0xe7223ece, 0x6651cecb, 0x532777ee, - 0xb1b140d5, 0x70dc5a51, 0xe7004031, 0x00ea6040}; - static constexpr storage g2_gen_y_re = {0x09fd4ddf, 0xf0940944, 0x6d8c7c2e, 0xf2cf8888, - 0xf832d204, 0xe458c282, 0x74b49a58, 0xde03ed72, - 0xcbb2efb4, 0xd960736b, 0x5d446f7b, 0x00690d66}; - static constexpr storage g2_gen_y_im = {0x85eb8f93, 0xd9a1cdd1, 0x5e52270b, 0x4279b83f, - 0xcee304c2, 0x2463b01a, 0x3d591bf1, 0x61ef11ac, - 0x151a70aa, 0x9e549da3, 0xd2835518, 0x00f8169f}; - - static constexpr storage weierstrass_b = {0x00000001, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage weierstrass_b_g2_re = { - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage weierstrass_b_g2_im = { - 0x9999999a, 0x1c9ed999, 0x1ccccccd, 0x0dd39e5c, 0x3c6bf800, 0x129207b6, - 0xcd5fd889, 0xdc7b4f91, 0x7460c589, 0x43bd0373, 0xdb0fd6f3, 0x010222f6}; } // namespace bls12_377 -#endif +#endif \ No newline at end of file diff --git a/icicle/include/fields/snark_fields/bls12_377_scalar.cuh b/icicle/include/fields/snark_fields/bls12_377_scalar.cuh new file mode 100644 index 00000000..9fce0370 --- /dev/null +++ b/icicle/include/fields/snark_fields/bls12_377_scalar.cuh @@ -0,0 +1,202 @@ +#pragma once +#ifndef BLS12_377_SCALAR_PARAMS_H +#define BLS12_377_SCALAR_PARAMS_H + +#include "fields/storage.cuh" +#include "fields/field.cuh" +#include "fields/quadratic_extension.cuh" + +namespace bls12_377 { + struct fp_config { + static constexpr unsigned limbs_count = 8; + static constexpr unsigned omegas_count = 47; + static constexpr unsigned modulus_bit_count = 253; + static constexpr unsigned num_of_reductions = 1; + + static constexpr storage modulus = {0x00000001, 0x0a118000, 0xd0000001, 0x59aa76fe, + 0x5c37b001, 0x60b44d1e, 0x9a2ca556, 0x12ab655e}; + static constexpr storage modulus_2 = {0x00000002, 0x14230000, 0xa0000002, 0xb354edfd, + 0xb86f6002, 0xc1689a3c, 0x34594aac, 0x2556cabd}; + static constexpr storage modulus_4 = {0x00000004, 0x28460000, 0x40000004, 0x66a9dbfb, + 0x70dec005, 0x82d13479, 0x68b29559, 0x4aad957a}; + static constexpr storage neg_modulus = {0xffffffff, 0xf5ee7fff, 0x2ffffffe, 0xa6558901, + 0xa3c84ffe, 0x9f4bb2e1, 0x65d35aa9, 0xed549aa1}; + static constexpr storage<2 * limbs_count> modulus_wide = { + 0x00000001, 0x0a118000, 0xd0000001, 0x59aa76fe, 0x5c37b001, 0x60b44d1e, 0x9a2ca556, 0x12ab655e, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage<2 * limbs_count> modulus_squared = { + 0x00000001, 0x14230000, 0xe0000002, 0xc7dd4d2f, 0x8585d003, 0x08ee1bd4, 0xe57fc56e, 0x7e7557e3, + 0x483a709d, 0x1fdebb41, 0x5678f4e6, 0x8ea77334, 0xc19c3ec5, 0xd717de29, 0xe2340781, 0x015c8d01}; + static constexpr storage<2 * limbs_count> modulus_squared_2 = { + 0x00000002, 0x28460000, 0xc0000004, 0x8fba9a5f, 0x0b0ba007, 0x11dc37a9, 0xcaff8adc, 0xfceaafc7, + 0x9074e13a, 0x3fbd7682, 0xacf1e9cc, 0x1d4ee668, 0x83387d8b, 0xae2fbc53, 0xc4680f03, 0x02b91a03}; + static constexpr storage<2 * limbs_count> modulus_squared_4 = { + 0x00000004, 0x508c0000, 0x80000008, 0x1f7534bf, 0x1617400f, 0x23b86f52, 0x95ff15b8, 0xf9d55f8f, + 0x20e9c275, 0x7f7aed05, 0x59e3d398, 0x3a9dccd1, 0x0670fb16, 0x5c5f78a7, 0x88d01e07, 0x05723407}; + + static constexpr storage m = {0x151e79ea, 0xf5204c21, 0x8d69e258, 0xfd0a180b, + 0xfaa80548, 0xe4e51e49, 0xc40b2c9e, 0x36d9491e}; + static constexpr storage one = {0x00000001, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage zero = {0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage montgomery_r = {0xfffffff3, 0x7d1c7fff, 0x6ffffff2, 0x7257f50f, + 0x512c0fee, 0x16d81575, 0x2bbb9a9d, 0x0d4bda32}; + static constexpr storage montgomery_r_inv = {0x1beeec02, 0x4122dd1a, 0x74fee875, 0xbd1eae95, + 0x27b28e2f, 0x838557e2, 0x2290c02c, 0x07b30191}; + + static constexpr storage_array omega = { + {{0x00000000, 0x0a118000, 0xd0000001, 0x59aa76fe, 0x5c37b001, 0x60b44d1e, 0x9a2ca556, 0x12ab655e}, + {0x00000001, 0x8f1a4000, 0xb0000001, 0xcf664765, 0x970dec00, 0x23ed1347, 0x00000000, 0x00000000}, + {0xfbfa0a01, 0x0f830f7e, 0xd75769a0, 0x20f8b46c, 0xf05d5033, 0x7108bd18, 0x0788de01, 0x07405e08}, + {0x60b9bdae, 0xc78085a6, 0x789094f5, 0x3116ec22, 0xce87d660, 0x0a02a81d, 0xc2a94856, 0x0ead8236}, + {0x3e83a7cc, 0x6ffc39d9, 0x958a0a74, 0x117d996e, 0x0b92e8c9, 0xc242289d, 0x29d977d6, 0x0484efb4}, + {0x0111ec3f, 0x15455b00, 0xc5f6be6f, 0x6b62d7af, 0x337f2d07, 0xfcba0365, 0x43fccd26, 0x0f151842}, + {0xc31ec69b, 0x57951b2e, 0x2a37ce1f, 0x3e0a4be7, 0xcf3b198a, 0x960aeb4a, 0x341fd5cd, 0x04fb0673}, + {0xa921851f, 0x71c1b78e, 0x7808f239, 0x3c26340c, 0x976fb990, 0xbcc8f69b, 0xe880dc71, 0x06a5edb2}, + {0xc0f5679e, 0x7619eab5, 0x0dc0b9cd, 0x1f4cd10e, 0xbf6a480a, 0x7e1b70aa, 0x7f5461bb, 0x0ffc66da}, + {0xec5cbab2, 0x8159806d, 0x498264a3, 0x14ea1333, 0xe3abfaa6, 0x56bbe1d8, 0x02aa031f, 0x09d2b5c4}, + {0xc010c48a, 0xd2aa9562, 0x3b004b60, 0x447e5c11, 0x11e243bb, 0xd5a21c13, 0x0ab418b1, 0x01eab23e}, + {0xacff6986, 0x08715ee8, 0xa93924d0, 0xab01878a, 0x6e9ae5c4, 0xbfbc5e71, 0x26b08d6e, 0x0f8000bf}, + {0x3ddbc679, 0x06bc13b0, 0x615256ce, 0x7269a1f1, 0x1f5221a2, 0xf7716fbf, 0x8c66c14f, 0x0fa1f02c}, + {0x906f531f, 0xdd40f131, 0x30728eff, 0xb06b29c7, 0x88839294, 0xc891fd19, 0x646978e8, 0x04e88447}, + {0x6e259cdc, 0xb1e4b769, 0x00514e5e, 0xbcb0b709, 0x05113e7f, 0x74edb7c0, 0xe92e22af, 0x10c88511}, + {0x240ede5b, 0xebb2e898, 0x42cd84c6, 0xc2639185, 0x9408f956, 0xf79e8391, 0x94e87a7d, 0x06872fa1}, + {0x260678ff, 0xf8522249, 0xa8de9973, 0x6148cb16, 0x5a4e8d56, 0x5750f3f4, 0xbaeaf0c3, 0x0e805156}, + {0x3d766f80, 0x1b4b71cf, 0x1069012d, 0x47d21195, 0x9151ebec, 0x5635235f, 0x2b13c808, 0x093f7d91}, + {0x4637701d, 0x0848f958, 0x4c8353af, 0x8a750076, 0x0ef6174a, 0x485f4e4f, 0xf38db632, 0x078d97a1}, + {0x66a16869, 0x50c487c1, 0xd1fd4525, 0x380a66ab, 0x265e8539, 0xd455a01a, 0x064b5334, 0x0cd62875}, + {0x3358eb25, 0xdbc547bc, 0x722037db, 0x8909d398, 0x5e705b6d, 0x8b7075b5, 0x9bdaf407, 0x02694bb2}, + {0xf45b9621, 0x102fbfb0, 0xf04faac0, 0xe80f4241, 0x7ca61177, 0x0b830bfd, 0x7033169d, 0x10521892}, + {0xcc943028, 0xed2576ad, 0xfa4c6090, 0x846e49bc, 0x0049d8e6, 0xc74c1865, 0x665d7be5, 0x0e9c5a12}, + {0xafeb494b, 0x97319dcd, 0x1d78404c, 0xab30c83e, 0xf26ffe90, 0x452d8a48, 0xa36452c7, 0x0bfc2e92}, + {0xedc626c3, 0xf30e312d, 0xcf1f3a94, 0x8367a7ca, 0x917a1b28, 0x621e15e1, 0xf2e93b82, 0x07cd59f8}, + {0xf02ba42c, 0x553085d9, 0x1119b10d, 0x59662159, 0x6b8ea03f, 0xaa670958, 0x7ce92983, 0x066f6f5f}, + {0x4dd87a5e, 0xf423a283, 0xd9a4c364, 0x1fe46601, 0xbfdc7e9b, 0xda4addbf, 0x3bf94b2b, 0x0a7f2bd8}, + {0xe5f8848a, 0x270a2326, 0xa727567d, 0x97d14afa, 0x48746fc7, 0x1a3a5a4e, 0xa42f077a, 0x0044e4b1}, + {0x20b7298a, 0xd7652451, 0x65013b06, 0xc7c9a0b7, 0xad0d8457, 0x479b82a9, 0x0c99f5ce, 0x0bef1e5a}, + {0x1912f7fa, 0x77d7da1d, 0x299fd7d6, 0xbcb7a5b2, 0x142a4480, 0x705e45dd, 0xb492dbd8, 0x0dc835fd}, + {0xa0234d2d, 0xe943054c, 0xe5f5be5e, 0x673b0ee0, 0x5048a19a, 0xcdd48e41, 0xabc3cb99, 0x0997d277}, + {0xa9966ac4, 0x1ae0ea67, 0xda83fb3b, 0x4e2dbb1c, 0x0b51380e, 0xf77cf749, 0xb28a7670, 0x048b4b0e}, + {0xb14361d4, 0x7f1db43f, 0x25ab6d51, 0x7927e578, 0x383bf21e, 0xb43e52a5, 0xd27fa99f, 0x077595e9}, + {0xa90a2740, 0xfe3ca4f0, 0x512a7c7a, 0xd259ff36, 0xb41fe696, 0xbca3176a, 0xf33132ce, 0x05bd5ea3}, + {0xf284f768, 0xdeee484b, 0xe26a0475, 0x2a02e015, 0x88d968c2, 0xf0eb4925, 0x82a391c9, 0x0620ce9e}, + {0xbd83a3da, 0xd3b69b29, 0xe02ce197, 0x9543950f, 0xc2f87783, 0x80799665, 0xc15be215, 0x11ce8199}, + {0x1b29736e, 0x8f267f19, 0x1d5a0c3a, 0xa2e04d58, 0x1ae99514, 0x76803064, 0x57f7c806, 0x12129439}, + {0xf32d6bac, 0xa0b973d4, 0xf0d81b72, 0xae951889, 0x2e2daa0a, 0x51dbe098, 0x40d9af8f, 0x04679474}, + {0x22df9f13, 0x56313de8, 0x599e7536, 0xe2e75200, 0x6d163e50, 0xa1b4fce7, 0xc8111763, 0x0aec2172}, + {0x355dd694, 0x4258374d, 0x44c76a20, 0x5c31e8ac, 0xaa5fd062, 0x9b473969, 0x1a37b6b4, 0x0a693d77}, + {0x44ddbbdc, 0xbafb92a6, 0x26b01974, 0x63c7a02d, 0x5f28a274, 0x0ff86e13, 0x867f2e29, 0x0a7b462a}, + {0xd5fba57b, 0x90684fea, 0xe0defe98, 0xed237883, 0x030ae924, 0xc502b692, 0xe7a1ec2c, 0x08aa58e8}, + {0x5e9020dd, 0xade9d4b4, 0x87db8813, 0x489259d2, 0x25051238, 0x5ddce740, 0xb5bc4d11, 0x0c775db1}, + {0x293f8481, 0xd52cc17a, 0x6f133205, 0x041178fb, 0xb2961832, 0xbbc70d18, 0x481760cd, 0x073d34d1}, + {0xfdacff58, 0x8215b91d, 0x98331645, 0xd8d9177d, 0x439e803c, 0xe85223ad, 0xcca42c1f, 0x04aa8ef0}, + {0x01ab3a4d, 0x006f60fa, 0x814ba450, 0xe6600e15, 0xdf9eb147, 0xbde4df36, 0x33760d7b, 0x055d58fa}, + {0xec2a895e, 0x476ef4a4, 0x63e3f04a, 0x9b506ee3, 0xd1a8a12f, 0x60c69477, 0x0cb92cc1, 0x11d4b7f6}}}; + + static constexpr storage_array omega_inv = { + {{0x00000000, 0x0a118000, 0xd0000001, 0x59aa76fe, 0x5c37b001, 0x60b44d1e, 0x9a2ca556, 0x12ab655e}, + {0x00000000, 0x7af74000, 0x1fffffff, 0x8a442f99, 0xc529c400, 0x3cc739d6, 0x9a2ca556, 0x12ab655e}, + {0xd60fb046, 0xc9fa190c, 0xc5b4674e, 0xdb5c179b, 0xbc7b8726, 0x2b2bce0b, 0xbf6e69bf, 0x0e4eb338}, + {0x8ffc4ed5, 0x74732d1f, 0xb7f2eefc, 0x42d9f590, 0xa24dd4dd, 0xf70461e5, 0xef64676f, 0x03b6eba4}, + {0x102bbab0, 0x5a21f98a, 0x8d8e2efb, 0xa6a147a9, 0x7612906f, 0x0eb4f005, 0x47d8d2e3, 0x0e1a5481}, + {0xd01e5aa8, 0x6e509add, 0x6e3f123d, 0xe1582468, 0x8274db24, 0xbd6313ee, 0xd173a634, 0x05d5836e}, + {0xe975c0cf, 0x6aab3344, 0x6f1dc38e, 0xca362e0e, 0x1dd1743a, 0x2fe72cda, 0xc1b4c4c2, 0x0c1c956e}, + {0xec89a64f, 0x59fe97a0, 0xe8de5d4c, 0x579617d7, 0xc9c1ea7b, 0x256a305b, 0x53fa131b, 0x01ffae4e}, + {0x29bcb088, 0x463a73ff, 0xe1438e80, 0xee9e9a5e, 0x3c9369e4, 0x2a00951f, 0x80a32052, 0x09711183}, + {0x4bec8dd2, 0xa36899db, 0x96393687, 0x2946872e, 0x842df3c8, 0xd4b5734f, 0x5f5cd8fb, 0x0834098f}, + {0xe3c711b9, 0x4bc485f6, 0x648d1d7e, 0xf43a2598, 0xee88abaa, 0x7f981a0e, 0xec6a3f27, 0x0c88c9c3}, + {0x49046b52, 0x42bcc6c2, 0x56ab9ecc, 0xcc77294a, 0xe4df3ddd, 0x02ecb41a, 0x67f76726, 0x0e567d22}, + {0x91c64fc2, 0x1cc56cc3, 0xd16a490b, 0x8cb71e65, 0x14fac366, 0x984be37e, 0xa25d7ba5, 0x0a08e032}, + {0xd4f5941e, 0x966d9739, 0xe5772a73, 0x5805deb6, 0x5c1f970c, 0xe4eb0d33, 0xbdf35409, 0x039715db}, + {0xcc6518ac, 0x8419686c, 0x9c7a2366, 0x96dec3a8, 0x71724384, 0xefbfcac6, 0xaf34c239, 0x0c44b99a}, + {0xc18ff4fd, 0xcb66fe1b, 0x86c8d586, 0x588e18b3, 0x1dfab57c, 0xc6e6d2a3, 0x7d7d4efd, 0x10918ad2}, + {0x97a18f58, 0x56d6cf22, 0xd0d7abd9, 0x11710758, 0x5eb7a9c5, 0xd1a6608b, 0xc4937e38, 0x04059bdb}, + {0x4b1b63a9, 0x12998cbc, 0xcf420c9f, 0x0f780c6c, 0x129289ad, 0xa5e48723, 0x240a141d, 0x0a3a1223}, + {0x00db2b48, 0xa43c0e02, 0x933d10ee, 0x76585489, 0xc0ba6a80, 0x12d64af1, 0x2fad8d8e, 0x01940f43}, + {0x1d75bec9, 0xe29ef6c0, 0xd4b0183b, 0xead287a2, 0xedfd3795, 0x75a017cf, 0x64427c8e, 0x107f8d0f}, + {0xa26c8c12, 0xa6f4e1d1, 0xf6610f7e, 0x13571553, 0x56701caf, 0xd95e5df6, 0x2263d69d, 0x050e7b89}, + {0xc161761f, 0x271d7caf, 0xc369a371, 0xf1001d6f, 0x00e60f51, 0x65286415, 0xb74d14b8, 0x00b918f9}, + {0x03ad3139, 0x01d3f431, 0xa137ce16, 0xe56f6002, 0x1deb42e8, 0x97f53369, 0xaa37cddd, 0x033fa9ac}, + {0x60cf1330, 0x840f913b, 0x1df5ed87, 0x5610cde6, 0x72b36ddf, 0x858381b0, 0x6f64e0b7, 0x109bf66c}, + {0x930cee0b, 0x432d3626, 0xf26e8ba3, 0x55ed3efb, 0x14c5457f, 0x802eebcc, 0xe2310f22, 0x00d300e3}, + {0x4b9ac952, 0x3d29f5ba, 0xc8ea8f94, 0x7c7f2662, 0xcefc3052, 0x736ccb63, 0x0981f3cb, 0x04bfce2f}, + {0x5d4e643c, 0x3da791ea, 0x85bff013, 0xb6a956ef, 0xd73de6a3, 0x86c629a8, 0x6b8c48a9, 0x0a5a5f55}, + {0x49c6284a, 0x9ba6aa00, 0xeacbdc63, 0x0b8429fb, 0xedafdf37, 0x9b9c6c5b, 0xad0c78c6, 0x009907e8}, + {0x3e47b53f, 0x50380ce2, 0x3a9613fc, 0x6ea3c2d3, 0x4c87ab50, 0xfe743105, 0xd192221c, 0x07871979}, + {0xe978594b, 0x4ddd3320, 0x3abe3f79, 0xe5f36fbe, 0xe4dcff8e, 0x5dba9ef2, 0x7105148f, 0x0bfc27e2}, + {0x498fb549, 0xd5993cd5, 0x09da9272, 0x718adcee, 0x72bd5bc0, 0x9e03cbb4, 0xc592813f, 0x07206942}, + {0x78fd3239, 0xaf29730b, 0x40c3e723, 0xbd907ac9, 0x77f214f7, 0x5dcc0aad, 0xb05fb3a1, 0x02d958da}, + {0xdf80223d, 0x55f432c9, 0x11a2fed9, 0x23daf2f6, 0x41ae8c34, 0x9e43e003, 0x95f22373, 0x0d51533b}, + {0x7998b62c, 0xbb53132b, 0x22c9b4aa, 0x064a9186, 0x71d61334, 0xd56de253, 0x04e416f6, 0x10fcf25f}, + {0xdddb58ec, 0x41f8042f, 0x10886d85, 0x7dd54384, 0x622ff4b4, 0x19544f90, 0x050cc539, 0x02f0b49a}, + {0xa39b02a3, 0x8a3de898, 0xdc94422c, 0x068b2992, 0xf493db31, 0x1c5f019a, 0x11b0f668, 0x066b1790}, + {0x78500f1a, 0x98310dd7, 0x735ccb27, 0x1c6050bf, 0xb2081df4, 0x07b6fa7f, 0xfa0f1e20, 0x003edf24}, + {0x89b0ca6f, 0xb4d938e2, 0x2c897570, 0x0214eb59, 0x2d4cf27a, 0x56c45327, 0x3ed546a4, 0x10a2f358}, + {0xef01ed78, 0xf2828212, 0xf103c9ca, 0xa66094ac, 0x7a2d5573, 0xdceb481d, 0x8af46aab, 0x0190fcde}, + {0x526bf9fc, 0x023031cc, 0x79c209ba, 0x0e4136c0, 0x3ec42e5c, 0xe5234df1, 0x1d455234, 0x00cb9592}, + {0x33bf2a1c, 0x842b0c9c, 0xa29b9236, 0x1fd43c95, 0xc06795d3, 0x6b37a603, 0x0c1b712a, 0x00017b17}, + {0xaf858193, 0x2b955be2, 0x5fb5e378, 0xa513d8be, 0xa326aeb9, 0x88c4ebeb, 0xf3d45990, 0x00c378e2}, + {0x6464580f, 0x33e6c8c0, 0x3c4aa09f, 0x9d560eb3, 0xcc98f404, 0xb3f1a899, 0x8ca24b48, 0x012c1ea5}, + {0xe3b4dc56, 0xa0594a67, 0x91b698e1, 0xc8e6b582, 0x8df78057, 0x711cadbf, 0x396466f8, 0x0049abdf}, + {0x4ffa086a, 0xecc89610, 0xca06afc6, 0x4db82291, 0x8f3a6426, 0x9ae7c68c, 0x2a874432, 0x0b3dae8c}, + {0x3b3625b6, 0x1e62401f, 0x28471e5a, 0xd0692164, 0x5cad6b77, 0xb85aa9ec, 0xaa95acf2, 0x063e4b66}, + {0xb9112c51, 0x2542c2b2, 0x6e23b3ce, 0x36ead8da, 0x76476754, 0x9a268d13, 0xa1ad7cf1, 0x121f44ad}}}; + + static constexpr storage_array inv = { + {{0x00000001, 0x8508c000, 0x68000000, 0xacd53b7f, 0x2e1bd800, 0x305a268f, 0x4d1652ab, 0x0955b2af}, + {0x00000001, 0xc78d2000, 0x1c000000, 0x033fd93f, 0xc529c401, 0xc88739d6, 0xf3a17c00, 0x0e008c06}, + {0x00000001, 0xe8cf5000, 0xf6000000, 0x2e75281e, 0x90b0ba01, 0x949dc37a, 0xc6e710ab, 0x1055f8b2}, + {0x00000001, 0xf9706800, 0xe3000000, 0x440fcf8e, 0x76743501, 0xfaa9084c, 0xb089db00, 0x1180af08}, + {0x00000001, 0x01c0f400, 0xd9800001, 0x4edd2346, 0x6955f281, 0xadaeaab5, 0xa55b402b, 0x12160a33}, + {0x00000001, 0x05e93a00, 0xd4c00001, 0x5443cd22, 0xe2c6d141, 0x07317be9, 0x1fc3f2c1, 0x1260b7c9}, + {0x00000001, 0x07fd5d00, 0xd2600001, 0x56f72210, 0x1f7f40a1, 0xb3f2e484, 0xdcf84c0b, 0x12860e93}, + {0x00000001, 0x09076e80, 0xd1300001, 0x5850cc87, 0x3ddb7851, 0x0a5398d1, 0x3b9278b1, 0x1298b9f9}, + {0x00000001, 0x098c7740, 0x50980001, 0x58fda1c3, 0xcd099429, 0xb583f2f7, 0xeadf8f03, 0x12a20fab}, + {0x00000001, 0x09cefba0, 0x104c0001, 0x59540c61, 0x14a0a215, 0x0b1c200b, 0x42861a2d, 0x12a6ba85}, + {0x00000001, 0x09f03dd0, 0xf0260001, 0x597f41af, 0xb86c290b, 0xb5e83694, 0xee595fc1, 0x12a90ff1}, + {0x00000001, 0x0a00dee8, 0x60130001, 0x5994dc57, 0x8a51ec86, 0x0b4e41d9, 0x4443028c, 0x12aa3aa8}, + {0x00000001, 0x0a092f74, 0x18098001, 0xd99fa9ab, 0xf344ce43, 0x3601477b, 0x6f37d3f1, 0x12aad003}, + {0x00000001, 0x0a0d57ba, 0xf404c001, 0x99a51054, 0x27be3f22, 0xcb5aca4d, 0x04b23ca3, 0x12ab1ab1}, + {0x00000001, 0x0a0f6bdd, 0xe2026001, 0xf9a7c3a9, 0xc1faf791, 0x16078bb5, 0xcf6f70fd, 0x12ab4007}, + {0x80000001, 0x0a1075ee, 0x59013001, 0xa9a91d54, 0x0f1953c9, 0xbb5dec6a, 0x34ce0b29, 0x12ab52b3}, + {0x40000001, 0x0a10faf7, 0x94809801, 0x81a9ca29, 0x35a881e5, 0x0e091cc4, 0xe77d5840, 0x12ab5c08}, + {0xa0000001, 0x0a113d7b, 0x32404c01, 0x6daa2094, 0x48f018f3, 0x375eb4f1, 0xc0d4fecb, 0x12ab60b3}, + {0xd0000001, 0x0a115ebd, 0x81202601, 0x63aa4bc9, 0xd293e47a, 0xcc098107, 0x2d80d210, 0x12ab6309}, + {0xe8000001, 0x0a116f5e, 0x28901301, 0xdeaa6164, 0x1765ca3d, 0x965ee713, 0xe3d6bbb3, 0x12ab6433}, + {0x74000001, 0x0a1177af, 0x7c480981, 0x9c2a6c31, 0xb9cebd1f, 0xfb899a18, 0x3f01b084, 0x12ab64c9}, + {0xba000001, 0x0a117bd7, 0x262404c1, 0x7aea7198, 0x8b033690, 0xae1ef39b, 0xec972aed, 0x12ab6513}, + {0xdd000001, 0x0a117deb, 0x7b120261, 0xea4a744b, 0xf39d7348, 0x0769a05c, 0x4361e822, 0x12ab6539}, + {0xee800001, 0x0a117ef5, 0x25890131, 0x21fa75a5, 0xa7ea91a5, 0x340ef6bd, 0xeec746bc, 0x12ab654b}, + {0xf7400001, 0x0a117f7a, 0xfac48099, 0x3dd27651, 0x021120d3, 0x4a61a1ee, 0x4479f609, 0x12ab6555}, + {0x7ba00001, 0x0a117fbd, 0x6562404d, 0x4bbe76a8, 0x2f24686a, 0xd58af786, 0xef534daf, 0x12ab6559}, + {0xbdd00001, 0x0a117fde, 0x9ab12027, 0xd2b476d3, 0x45ae0c35, 0x1b1fa252, 0x44bff983, 0x12ab655c}, + {0x5ee80001, 0x0a117fef, 0x35589014, 0x962f76e9, 0x50f2de1b, 0xbde9f7b8, 0x6f764f6c, 0x12ab655d}, + {0xaf740001, 0x8a117ff7, 0x02ac480a, 0x77ecf6f4, 0x5695470e, 0x8f4f226b, 0x04d17a61, 0x12ab655e}, + {0xd7ba0001, 0xca117ffb, 0x69562405, 0xe8cbb6f9, 0xd9667b87, 0xf801b7c4, 0x4f7f0fdb, 0x12ab655e}, + {0xebdd0001, 0x6a117ffd, 0x1cab1203, 0xa13b16fc, 0x9acf15c4, 0x2c5b0271, 0x74d5da99, 0x12ab655e}, + {0xf5ee8001, 0x3a117ffe, 0x76558902, 0xfd72c6fd, 0xfb8362e2, 0xc687a7c7, 0x87813ff7, 0x12ab655e}, + {0x7af74001, 0xa2117fff, 0x232ac481, 0x2b8e9efe, 0x2bdd8972, 0x139dfa73, 0x90d6f2a7, 0x12ab655e}, + {0xbd7ba001, 0x56117fff, 0x79956241, 0xc29c8afe, 0xc40a9cb9, 0xba2923c8, 0x9581cbfe, 0x12ab655e}, + {0xdebdd001, 0x30117fff, 0xa4cab121, 0x8e2380fe, 0x9021265d, 0x8d6eb873, 0x97d738aa, 0x12ab655e}, + {0xef5ee801, 0x1d117fff, 0xba655891, 0x73e6fbfe, 0xf62c6b2f, 0x771182c8, 0x9901ef00, 0x12ab655e}, + {0xf7af7401, 0x13917fff, 0xc532ac49, 0x66c8b97e, 0xa9320d98, 0x6be2e7f3, 0x99974a2b, 0x12ab655e}, + {0xfbd7ba01, 0x0ed17fff, 0xca995625, 0xe039983e, 0x02b4decc, 0xe64b9a89, 0x99e1f7c0, 0x12ab655e}, + {0xfdebdd01, 0x0c717fff, 0xcd4cab13, 0x1cf2079e, 0xaf764767, 0xa37ff3d3, 0x9a074e8b, 0x12ab655e}, + {0xfef5ee81, 0x0b417fff, 0xcea6558a, 0x3b4e3f4e, 0x05d6fbb4, 0x021a2079, 0x9a19f9f1, 0x12ab655e}, + {0xff7af741, 0x8aa97fff, 0xcf532ac5, 0xca7c5b26, 0xb10755da, 0xb16736cb, 0x9a234fa3, 0x12ab655e}, + {0xffbd7ba1, 0x4a5d7fff, 0xcfa99563, 0x12136912, 0x069f82ee, 0x090dc1f5, 0x9a27fa7d, 0x12ab655e}, + {0xffdebdd1, 0x2a377fff, 0xcfd4cab2, 0xb5def008, 0xb16b9977, 0xb4e10789, 0x9a2a4fe9, 0x12ab655e}, + {0xffef5ee9, 0x9a247fff, 0xcfea6559, 0x87c4b383, 0x06d1a4bc, 0x0acaaa54, 0x9a2b7aa0, 0x12ab655e}, + {0xfff7af75, 0x521affff, 0x4ff532ad, 0xf0b79541, 0x3184aa5e, 0x35bf7bb9, 0x9a2c0ffb, 0x12ab655e}, + {0xfffbd7bb, 0x2e163fff, 0x0ffa9957, 0x25310620, 0xc6de2d30, 0xcb39e46b, 0x9a2c5aa8, 0x12ab655e}, + {0xfffdebde, 0x1c13dfff, 0x6ffd4cac, 0xbf6dbe8f, 0x118aee98, 0x95f718c5, 0x9a2c7fff, 0x12ab655e}}}; + }; + + /** + * Scalar field. Is always a prime field. + */ + typedef Field scalar_t; +} // namespace bls12_377 + +#endif \ No newline at end of file diff --git a/icicle/include/fields/snark_fields/bls12_381_base.cuh b/icicle/include/fields/snark_fields/bls12_381_base.cuh new file mode 100644 index 00000000..8b0a8bbb --- /dev/null +++ b/icicle/include/fields/snark_fields/bls12_381_base.cuh @@ -0,0 +1,61 @@ +#pragma once +#ifndef BLS12_381_BASE_PARAMS_H +#define BLS12_381_BASE_PARAMS_H + +#include "fields/storage.cuh" + +namespace bls12_381 { + struct fq_config { + static constexpr unsigned limbs_count = 12; + static constexpr unsigned modulus_bit_count = 381; + static constexpr unsigned num_of_reductions = 1; + static constexpr storage modulus = {0xffffaaab, 0xb9feffff, 0xb153ffff, 0x1eabfffe, + 0xf6b0f624, 0x6730d2a0, 0xf38512bf, 0x64774b84, + 0x434bacd7, 0x4b1ba7b6, 0x397fe69a, 0x1a0111ea}; + static constexpr storage modulus_2 = {0xffff5556, 0x73fdffff, 0x62a7ffff, 0x3d57fffd, + 0xed61ec48, 0xce61a541, 0xe70a257e, 0xc8ee9709, + 0x869759ae, 0x96374f6c, 0x72ffcd34, 0x340223d4}; + static constexpr storage modulus_4 = {0xfffeaaac, 0xe7fbffff, 0xc54ffffe, 0x7aaffffa, + 0xdac3d890, 0x9cc34a83, 0xce144afd, 0x91dd2e13, + 0x0d2eb35d, 0x2c6e9ed9, 0xe5ff9a69, 0x680447a8}; + static constexpr storage neg_modulus = {0x00005555, 0x46010000, 0x4eac0000, 0xe1540001, + 0x094f09db, 0x98cf2d5f, 0x0c7aed40, 0x9b88b47b, + 0xbcb45328, 0xb4e45849, 0xc6801965, 0xe5feee15}; + static constexpr storage<2 * limbs_count> modulus_wide = { + 0xffffaaab, 0xb9feffff, 0xb153ffff, 0x1eabfffe, 0xf6b0f624, 0x6730d2a0, 0xf38512bf, 0x64774b84, + 0x434bacd7, 0x4b1ba7b6, 0x397fe69a, 0x1a0111ea, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage<2 * limbs_count> modulus_squared = { + 0x1c718e39, 0x26aa0000, 0x76382eab, 0x7ced6b1d, 0x62113cfd, 0x162c3383, 0x3e71b743, 0x66bf91ed, + 0x7091a049, 0x292e85a8, 0x86185c7b, 0x1d68619c, 0x0978ef01, 0xf5314933, 0x16ddca6e, 0x50a62cfd, + 0x349e8bd0, 0x66e59e49, 0x0e7046b4, 0xe2dc90e5, 0xa22f25e9, 0x4bd278ea, 0xb8c35fc7, 0x02a437a4}; + static constexpr storage<2 * limbs_count> modulus_squared_2 = { + 0x38e31c72, 0x4d540000, 0xec705d56, 0xf9dad63a, 0xc42279fa, 0x2c586706, 0x7ce36e86, 0xcd7f23da, + 0xe1234092, 0x525d0b50, 0x0c30b8f6, 0x3ad0c339, 0x12f1de02, 0xea629266, 0x2dbb94dd, 0xa14c59fa, + 0x693d17a0, 0xcdcb3c92, 0x1ce08d68, 0xc5b921ca, 0x445e4bd3, 0x97a4f1d5, 0x7186bf8e, 0x05486f49}; + static constexpr storage<2 * limbs_count> modulus_squared_4 = { + 0x71c638e4, 0x9aa80000, 0xd8e0baac, 0xf3b5ac75, 0x8844f3f5, 0x58b0ce0d, 0xf9c6dd0c, 0x9afe47b4, + 0xc2468125, 0xa4ba16a1, 0x186171ec, 0x75a18672, 0x25e3bc04, 0xd4c524cc, 0x5b7729bb, 0x4298b3f4, + 0xd27a2f41, 0x9b967924, 0x39c11ad1, 0x8b724394, 0x88bc97a7, 0x2f49e3aa, 0xe30d7f1d, 0x0a90de92}; + static constexpr storage m = {0xd59646e8, 0xec4f881f, 0x8163c701, 0x4e65c59e, 0x80a19de7, 0x2f7d1dc7, + 0x7fda82a5, 0xa46e09d0, 0x331e9ae8, 0x38a0406c, 0xcf327917, 0x2760d74b}; + static constexpr storage one = {0x00000001, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage zero = {0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage montgomery_r = {0x0002fffd, 0x76090000, 0xc40c0002, 0xebf4000b, + 0x53c758ba, 0x5f489857, 0x70525745, 0x77ce5853, + 0xa256ec6d, 0x5c071a97, 0xfa80e493, 0x15f65ec3}; + static constexpr storage montgomery_r_inv = {0x380b4820, 0xf4d38259, 0xd898fafb, 0x7fe11274, + 0x14956dc8, 0x343ea979, 0x58a88de9, 0x1797ab14, + 0x3c4f538b, 0xed5e6427, 0xe8fb0ce9, 0x14fec701}; + // nonresidue to generate the extension field + static constexpr uint32_t nonresidue = 1; + // true if nonresidue is negative + static constexpr bool nonresidue_is_negative = true; + }; +} // namespace bls12_381 + +#endif diff --git a/icicle/curves/bls12_381_params.cuh b/icicle/include/fields/snark_fields/bls12_381_scalar.cuh similarity index 64% rename from icicle/curves/bls12_381_params.cuh rename to icicle/include/fields/snark_fields/bls12_381_scalar.cuh index dd9e0a5f..3e6e1508 100644 --- a/icicle/curves/bls12_381_params.cuh +++ b/icicle/include/fields/snark_fields/bls12_381_scalar.cuh @@ -1,8 +1,10 @@ #pragma once -#ifndef BLS12_381_PARAMS_H -#define BLS12_381_PARAMS_H +#ifndef BLS12_381_SCALAR_PARAMS_H +#define BLS12_381_SCALAR_PARAMS_H -#include "utils/storage.cuh" +#include "fields/storage.cuh" +#include "fields/field.cuh" +#include "fields/quadratic_extension.cuh" namespace bls12_381 { struct fp_config { @@ -146,87 +148,10 @@ namespace bls12_381 { {0x00000002, 0x0001a400, 0xac40b7fc, 0x4a1bcbfd, 0xd667fffd, 0x099c5abf, 0xb5afd5f5, 0x73eda752}}}; }; - struct fq_config { - static constexpr unsigned limbs_count = 12; - static constexpr unsigned modulus_bit_count = 381; - static constexpr unsigned num_of_reductions = 1; - static constexpr storage modulus = {0xffffaaab, 0xb9feffff, 0xb153ffff, 0x1eabfffe, - 0xf6b0f624, 0x6730d2a0, 0xf38512bf, 0x64774b84, - 0x434bacd7, 0x4b1ba7b6, 0x397fe69a, 0x1a0111ea}; - static constexpr storage modulus_2 = {0xffff5556, 0x73fdffff, 0x62a7ffff, 0x3d57fffd, - 0xed61ec48, 0xce61a541, 0xe70a257e, 0xc8ee9709, - 0x869759ae, 0x96374f6c, 0x72ffcd34, 0x340223d4}; - static constexpr storage modulus_4 = {0xfffeaaac, 0xe7fbffff, 0xc54ffffe, 0x7aaffffa, - 0xdac3d890, 0x9cc34a83, 0xce144afd, 0x91dd2e13, - 0x0d2eb35d, 0x2c6e9ed9, 0xe5ff9a69, 0x680447a8}; - static constexpr storage neg_modulus = {0x00005555, 0x46010000, 0x4eac0000, 0xe1540001, - 0x094f09db, 0x98cf2d5f, 0x0c7aed40, 0x9b88b47b, - 0xbcb45328, 0xb4e45849, 0xc6801965, 0xe5feee15}; - static constexpr storage<2 * limbs_count> modulus_wide = { - 0xffffaaab, 0xb9feffff, 0xb153ffff, 0x1eabfffe, 0xf6b0f624, 0x6730d2a0, 0xf38512bf, 0x64774b84, - 0x434bacd7, 0x4b1ba7b6, 0x397fe69a, 0x1a0111ea, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage<2 * limbs_count> modulus_squared = { - 0x1c718e39, 0x26aa0000, 0x76382eab, 0x7ced6b1d, 0x62113cfd, 0x162c3383, 0x3e71b743, 0x66bf91ed, - 0x7091a049, 0x292e85a8, 0x86185c7b, 0x1d68619c, 0x0978ef01, 0xf5314933, 0x16ddca6e, 0x50a62cfd, - 0x349e8bd0, 0x66e59e49, 0x0e7046b4, 0xe2dc90e5, 0xa22f25e9, 0x4bd278ea, 0xb8c35fc7, 0x02a437a4}; - static constexpr storage<2 * limbs_count> modulus_squared_2 = { - 0x38e31c72, 0x4d540000, 0xec705d56, 0xf9dad63a, 0xc42279fa, 0x2c586706, 0x7ce36e86, 0xcd7f23da, - 0xe1234092, 0x525d0b50, 0x0c30b8f6, 0x3ad0c339, 0x12f1de02, 0xea629266, 0x2dbb94dd, 0xa14c59fa, - 0x693d17a0, 0xcdcb3c92, 0x1ce08d68, 0xc5b921ca, 0x445e4bd3, 0x97a4f1d5, 0x7186bf8e, 0x05486f49}; - static constexpr storage<2 * limbs_count> modulus_squared_4 = { - 0x71c638e4, 0x9aa80000, 0xd8e0baac, 0xf3b5ac75, 0x8844f3f5, 0x58b0ce0d, 0xf9c6dd0c, 0x9afe47b4, - 0xc2468125, 0xa4ba16a1, 0x186171ec, 0x75a18672, 0x25e3bc04, 0xd4c524cc, 0x5b7729bb, 0x4298b3f4, - 0xd27a2f41, 0x9b967924, 0x39c11ad1, 0x8b724394, 0x88bc97a7, 0x2f49e3aa, 0xe30d7f1d, 0x0a90de92}; - static constexpr storage m = {0xd59646e8, 0xec4f881f, 0x8163c701, 0x4e65c59e, 0x80a19de7, 0x2f7d1dc7, - 0x7fda82a5, 0xa46e09d0, 0x331e9ae8, 0x38a0406c, 0xcf327917, 0x2760d74b}; - static constexpr storage one = {0x00000001, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage zero = {0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage montgomery_r = {0x0002fffd, 0x76090000, 0xc40c0002, 0xebf4000b, - 0x53c758ba, 0x5f489857, 0x70525745, 0x77ce5853, - 0xa256ec6d, 0x5c071a97, 0xfa80e493, 0x15f65ec3}; - static constexpr storage montgomery_r_inv = {0x380b4820, 0xf4d38259, 0xd898fafb, 0x7fe11274, - 0x14956dc8, 0x343ea979, 0x58a88de9, 0x1797ab14, - 0x3c4f538b, 0xed5e6427, 0xe8fb0ce9, 0x14fec701}; - // i^2, the square of the imaginary unit for the extension field - static constexpr uint32_t i_squared = 1; - // true if i^2 is negative - static constexpr bool i_squared_is_negative = true; - }; - - // G1 and G2 generators - static constexpr storage g1_gen_x = {0xdb22c6bb, 0xfb3af00a, 0xf97a1aef, 0x6c55e83f, - 0x171bac58, 0xa14e3a3f, 0x9774b905, 0xc3688c4f, - 0x4fa9ac0f, 0x2695638c, 0x3197d794, 0x17f1d3a7}; - static constexpr storage g1_gen_y = {0x46c5e7e1, 0x0caa2329, 0xa2888ae4, 0xd03cc744, - 0x2c04b3ed, 0x00db18cb, 0xd5d00af6, 0xfcf5e095, - 0x741d8ae4, 0xa09e30ed, 0xe3aaa0f1, 0x08b3f481}; - static constexpr storage g2_gen_x_re = {0xc121bdb8, 0xd48056c8, 0xa805bbef, 0x0bac0326, - 0x7ae3d177, 0xb4510b64, 0xfa403b02, 0xc6e47ad4, - 0x2dc51051, 0x26080527, 0xf08f0a91, 0x024aa2b2}; - static constexpr storage g2_gen_x_im = {0x5d042b7e, 0xe5ac7d05, 0x13945d57, 0x334cf112, - 0xdc7f5049, 0xb5da61bb, 0x9920b61a, 0x596bd0d0, - 0x88274f65, 0x7dacd3a0, 0x52719f60, 0x13e02b60}; - static constexpr storage g2_gen_y_re = {0x08b82801, 0xe1935486, 0x3baca289, 0x923ac9cc, - 0x5160d12c, 0x6d429a69, 0x8cbdd3a7, 0xadfd9baa, - 0xda2e351a, 0x8cc9cdc6, 0x727d6e11, 0x0ce5d527}; - static constexpr storage g2_gen_y_im = {0xf05f79be, 0xaaa9075f, 0x5cec1da1, 0x3f370d27, - 0x572e99ab, 0x267492ab, 0x85a763af, 0xcb3e287e, - 0x2bc28b99, 0x32acd2b0, 0x2ea734cc, 0x0606c4a0}; - - static constexpr storage weierstrass_b = {0x00000004, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage weierstrass_b_g2_re = { - 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage weierstrass_b_g2_im = { - 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + /** + * Scalar field. Is always a prime field. + */ + typedef Field scalar_t; } // namespace bls12_381 #endif diff --git a/icicle/include/fields/snark_fields/bn254_base.cuh b/icicle/include/fields/snark_fields/bn254_base.cuh new file mode 100644 index 00000000..891b91e8 --- /dev/null +++ b/icicle/include/fields/snark_fields/bn254_base.cuh @@ -0,0 +1,49 @@ +#pragma once +#ifndef BN254_BASE_PARAMS_H +#define BN254_BASE_PARAMS_H + +#include "fields/storage.cuh" + +namespace bn254 { + struct fq_config { + static constexpr unsigned limbs_count = 8; + static constexpr unsigned modulus_bit_count = 254; + static constexpr unsigned num_of_reductions = 1; + static constexpr storage modulus = {0xd87cfd47, 0x3c208c16, 0x6871ca8d, 0x97816a91, + 0x8181585d, 0xb85045b6, 0xe131a029, 0x30644e72}; + static constexpr storage modulus_2 = {0xb0f9fa8e, 0x7841182d, 0xd0e3951a, 0x2f02d522, + 0x0302b0bb, 0x70a08b6d, 0xc2634053, 0x60c89ce5}; + static constexpr storage modulus_4 = {0x61f3f51c, 0xf082305b, 0xa1c72a34, 0x5e05aa45, + 0x06056176, 0xe14116da, 0x84c680a6, 0xc19139cb}; + static constexpr storage neg_modulus = {0x278302b9, 0xc3df73e9, 0x978e3572, 0x687e956e, + 0x7e7ea7a2, 0x47afba49, 0x1ece5fd6, 0xcf9bb18d}; + static constexpr storage<2 * limbs_count> modulus_wide = { + 0xd87cfd47, 0x3c208c16, 0x6871ca8d, 0x97816a91, 0x8181585d, 0xb85045b6, 0xe131a029, 0x30644e72, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage<2 * limbs_count> modulus_squared = { + 0x275d69b1, 0x3b5458a2, 0x09eac101, 0xa602072d, 0x6d96cadc, 0x4a50189c, 0x7a1242c8, 0x04689e95, + 0x34c6b38d, 0x26edfa5c, 0x16375606, 0xb00b8551, 0x0348d21c, 0x599a6f7c, 0x763cbf9c, 0x0925c4b8}; + static constexpr storage<2 * limbs_count> modulus_squared_2 = { + 0x4ebad362, 0x76a8b144, 0x13d58202, 0x4c040e5a, 0xdb2d95b9, 0x94a03138, 0xf4248590, 0x08d13d2a, + 0x698d671a, 0x4ddbf4b8, 0x2c6eac0c, 0x60170aa2, 0x0691a439, 0xb334def8, 0xec797f38, 0x124b8970}; + static constexpr storage<2 * limbs_count> modulus_squared_4 = { + 0x9d75a6c4, 0xed516288, 0x27ab0404, 0x98081cb4, 0xb65b2b72, 0x29406271, 0xe8490b21, 0x11a27a55, + 0xd31ace34, 0x9bb7e970, 0x58dd5818, 0xc02e1544, 0x0d234872, 0x6669bdf0, 0xd8f2fe71, 0x249712e1}; + static constexpr storage m = {0x19bf90e5, 0x6f3aed8a, 0x67cd4c08, 0xae965e17, + 0x68073013, 0xab074a58, 0x623a04a7, 0x54a47462}; + static constexpr storage one = {0x00000001, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage zero = {0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000}; + static constexpr storage montgomery_r = {0xc58f0d9d, 0xd35d438d, 0xf5c70b3d, 0x0a78eb28, + 0x7879462c, 0x666ea36f, 0x9a07df2f, 0x0e0a77c1}; + static constexpr storage montgomery_r_inv = {0x014afa37, 0xed84884a, 0x0278edf8, 0xeb202285, + 0xb74492d9, 0xcf63e9cf, 0x59e5c639, 0x2e671571}; + // nonresidue to generate the extension field + static constexpr uint32_t nonresidue = 1; + // true if nonresidue is negative + static constexpr bool nonresidue_is_negative = true; + }; +} // namespace bn254 + +#endif diff --git a/icicle/curves/bn254_params.cuh b/icicle/include/fields/snark_fields/bn254_scalar.cuh similarity index 69% rename from icicle/curves/bn254_params.cuh rename to icicle/include/fields/snark_fields/bn254_scalar.cuh index 71323f37..a5c9b00c 100644 --- a/icicle/curves/bn254_params.cuh +++ b/icicle/include/fields/snark_fields/bn254_scalar.cuh @@ -1,8 +1,10 @@ #pragma once -#ifndef BN254_PARAMS_H -#define BN254_PARAMS_H +#ifndef BN254_SCALAR_PARAMS_H +#define BN254_SCALAR_PARAMS_H -#include "utils/storage.cuh" +#include "fields/storage.cuh" +#include "fields/field.cuh" +#include "fields/quadratic_extension.cuh" namespace bn254 { struct fp_config { @@ -134,66 +136,10 @@ namespace bn254 { {0xb1e0a6c2, 0xa84aec7f, 0xf67aec09, 0x101e6275, 0xfc7cfcf5, 0xa536431a, 0xdaecb8fb, 0x30644e6f}}}; }; - struct fq_config { - static constexpr unsigned limbs_count = 8; - static constexpr unsigned modulus_bit_count = 254; - static constexpr unsigned num_of_reductions = 1; - static constexpr storage modulus = {0xd87cfd47, 0x3c208c16, 0x6871ca8d, 0x97816a91, - 0x8181585d, 0xb85045b6, 0xe131a029, 0x30644e72}; - static constexpr storage modulus_2 = {0xb0f9fa8e, 0x7841182d, 0xd0e3951a, 0x2f02d522, - 0x0302b0bb, 0x70a08b6d, 0xc2634053, 0x60c89ce5}; - static constexpr storage modulus_4 = {0x61f3f51c, 0xf082305b, 0xa1c72a34, 0x5e05aa45, - 0x06056176, 0xe14116da, 0x84c680a6, 0xc19139cb}; - static constexpr storage neg_modulus = {0x278302b9, 0xc3df73e9, 0x978e3572, 0x687e956e, - 0x7e7ea7a2, 0x47afba49, 0x1ece5fd6, 0xcf9bb18d}; - static constexpr storage<2 * limbs_count> modulus_wide = { - 0xd87cfd47, 0x3c208c16, 0x6871ca8d, 0x97816a91, 0x8181585d, 0xb85045b6, 0xe131a029, 0x30644e72, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage<2 * limbs_count> modulus_squared = { - 0x275d69b1, 0x3b5458a2, 0x09eac101, 0xa602072d, 0x6d96cadc, 0x4a50189c, 0x7a1242c8, 0x04689e95, - 0x34c6b38d, 0x26edfa5c, 0x16375606, 0xb00b8551, 0x0348d21c, 0x599a6f7c, 0x763cbf9c, 0x0925c4b8}; - static constexpr storage<2 * limbs_count> modulus_squared_2 = { - 0x4ebad362, 0x76a8b144, 0x13d58202, 0x4c040e5a, 0xdb2d95b9, 0x94a03138, 0xf4248590, 0x08d13d2a, - 0x698d671a, 0x4ddbf4b8, 0x2c6eac0c, 0x60170aa2, 0x0691a439, 0xb334def8, 0xec797f38, 0x124b8970}; - static constexpr storage<2 * limbs_count> modulus_squared_4 = { - 0x9d75a6c4, 0xed516288, 0x27ab0404, 0x98081cb4, 0xb65b2b72, 0x29406271, 0xe8490b21, 0x11a27a55, - 0xd31ace34, 0x9bb7e970, 0x58dd5818, 0xc02e1544, 0x0d234872, 0x6669bdf0, 0xd8f2fe71, 0x249712e1}; - static constexpr storage m = {0x19bf90e5, 0x6f3aed8a, 0x67cd4c08, 0xae965e17, - 0x68073013, 0xab074a58, 0x623a04a7, 0x54a47462}; - static constexpr storage one = {0x00000001, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage zero = {0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage montgomery_r = {0xc58f0d9d, 0xd35d438d, 0xf5c70b3d, 0x0a78eb28, - 0x7879462c, 0x666ea36f, 0x9a07df2f, 0x0e0a77c1}; - static constexpr storage montgomery_r_inv = {0x014afa37, 0xed84884a, 0x0278edf8, 0xeb202285, - 0xb74492d9, 0xcf63e9cf, 0x59e5c639, 0x2e671571}; - // i^2, the square of the imaginary unit for the extension field - static constexpr uint32_t i_squared = 1; - // true if i^2 is negative - static constexpr bool i_squared_is_negative = true; - }; - - // G1 and G2 generators - static constexpr storage g1_gen_x = {0x00000001, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage g1_gen_y = {0x00000002, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage g2_gen_x_re = {0xd992f6ed, 0x46debd5c, 0xf75edadd, 0x674322d4, - 0x5e5c4479, 0x426a0066, 0x121f1e76, 0x1800deef}; - static constexpr storage g2_gen_x_im = {0xaef312c2, 0x97e485b7, 0x35a9e712, 0xf1aa4933, - 0x31fb5d25, 0x7260bfb7, 0x920d483a, 0x198e9393}; - static constexpr storage g2_gen_y_re = {0x66fa7daa, 0x4ce6cc01, 0x0c43d37b, 0xe3d1e769, - 0x8dcb408f, 0x4aab7180, 0xdb8c6deb, 0x12c85ea5}; - static constexpr storage g2_gen_y_im = {0xd122975b, 0x55acdadc, 0x70b38ef3, 0xbc4b3133, - 0x690c3395, 0xec9e99ad, 0x585ff075, 0x090689d0}; - - static constexpr storage weierstrass_b = {0x00000003, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000}; - static constexpr storage weierstrass_b_g2_re = { - 0x24a138e5, 0x3267e6dc, 0x59dbefa3, 0xb5b4c5e5, 0x1be06ac3, 0x81be1899, 0xceb8aaae, 0x2b149d40}; - static constexpr storage weierstrass_b_g2_im = { - 0x85c315d2, 0xe4a2bd06, 0xe52d1852, 0xa74fa084, 0xeed8fdf4, 0xcd2cafad, 0x3af0fed4, 0x009713b0}; + /** + * Scalar field. Is always a prime field. + */ + typedef Field scalar_t; } // namespace bn254 #endif diff --git a/icicle/curves/bw6_761_params.cuh b/icicle/include/fields/snark_fields/bw6_761_base.cuh similarity index 72% rename from icicle/curves/bw6_761_params.cuh rename to icicle/include/fields/snark_fields/bw6_761_base.cuh index c516892f..24615786 100644 --- a/icicle/curves/bw6_761_params.cuh +++ b/icicle/include/fields/snark_fields/bw6_761_base.cuh @@ -1,13 +1,10 @@ #pragma once -#ifndef BW6_761_PARAMS_H -#define BW6_761_PARAMS_H +#ifndef BW6_761_BASE_BASE_H +#define BW6_761_BASE_BASE_H -#include "utils/storage.cuh" -#include "bls12_377_params.cuh" +#include "fields/storage.cuh" namespace bw6_761 { - typedef bls12_377::fq_config fp_config; - struct fq_config { static constexpr unsigned limbs_count = 24; static constexpr unsigned modulus_bit_count = 761; @@ -77,33 +74,6 @@ namespace bw6_761 { 0x7695ef18, 0x5e763565, 0x4fae56bb, 0x226022c2, 0xb70d7652, 0x80e7f067, 0x72116b89, 0x435a8b4a, 0x5d84e0d4, 0xac258fd6, 0x4427c7b2, 0x47ee8ac5, 0xd04e621b, 0x478c4048, 0x2add3e93, 0x00e0aa7d}; }; - - // G1 and G2 generators - static constexpr storage g1_gen_x = { - 0x66e5b43d, 0x4088f3af, 0xa6af603f, 0x055928ac, 0x56133e82, 0x6750dd03, 0x280ca27f, 0x03758f9a, - 0xc9ea0971, 0x5bd71fa0, 0x47729b90, 0xa17a54ce, 0x94c2e746, 0x11dbfcd2, 0xc15520ac, 0x79017ffa, - 0x85f56fc7, 0xee05c54b, 0x551b27f0, 0xe6a0cfb7, 0xa477beae, 0xb277ce98, 0x0ea190c8, 0x01075b02}; - static constexpr storage g1_gen_y = { - 0xb4e95363, 0xbafc8f2d, 0x0b20d2a1, 0xad1cb2be, 0xcad0fb93, 0xb2b08119, 0xb3053253, 0x9f9df141, - 0x6fc2cdd4, 0xbe3fb90b, 0x717a4c55, 0xcc685d31, 0x71b5b806, 0xc5b8fa17, 0xaf7e0dba, 0x265909f1, - 0xa2e573a3, 0x1a7348d2, 0x884c9ec6, 0x0f952589, 0x45cc2a42, 0xe6fd637b, 0x0a6fc574, 0x0058b84e}; - static constexpr storage g2_gen_x = { - 0xcd025f1c, 0xa830c194, 0xe1bf995b, 0x6410cf4f, 0xc2ad54b0, 0x00e96efb, 0x3cd208d7, 0xce6948cb, - 0x00e1b6ba, 0x963317a3, 0xac70e7c7, 0xc5bbcae9, 0xf09feb58, 0x734ec3f1, 0xab3da268, 0x26b41c5d, - 0x13890f6d, 0x4c062010, 0xc5a7115f, 0xd61053aa, 0x69d660f9, 0xc852a82e, 0x41d9b816, 0x01101332}; - static constexpr storage g2_gen_y = { - 0x28c73b61, 0xeb70a167, 0xf9eac689, 0x91ec0594, 0x3c5a02a5, 0x58aa2d3a, 0x504affc7, 0x3ea96fcd, - 0xffa82300, 0x8906c170, 0xd2c712b8, 0x64f293db, 0x33293fef, 0x94c97eb7, 0x0b95a59c, 0x0a1d86c8, - 0x53ffe316, 0x81a78e27, 0xcec2181c, 0x26b7cf9a, 0xe4b6d2dc, 0x8179eb10, 0x7761369f, 0x0017c335}; - - static constexpr storage weierstrass_b = { - 0x0000008a, 0xf49d0000, 0x70000082, 0xe6913e68, 0xeaf0a437, 0x160cf8ae, 0x5667a8f8, 0x98a116c2, - 0x73ebff2e, 0x71dcd3dc, 0x12f9fd90, 0x8689c8ed, 0x25b42304, 0x03cebaff, 0xe584e919, 0x707ba638, - 0x8087be41, 0x528275ef, 0x81d14688, 0xb926186a, 0x04faff3e, 0xd187c940, 0xfb83ce0a, 0x0122e824}; - static constexpr storage g2_weierstrass_b = { - 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}; } // namespace bw6_761 -#endif +#endif \ No newline at end of file diff --git a/icicle/include/fields/snark_fields/bw6_761_scalar.cuh b/icicle/include/fields/snark_fields/bw6_761_scalar.cuh new file mode 100644 index 00000000..f5fc5dad --- /dev/null +++ b/icicle/include/fields/snark_fields/bw6_761_scalar.cuh @@ -0,0 +1,19 @@ +#pragma once +#ifndef BW6_761_SCALAR_PARAMS_H +#define BW6_761_SCALAR_PARAMS_H + +#include "fields/storage.cuh" +#include "fields/field.cuh" +#include "fields/quadratic_extension.cuh" +#include "fields/snark_fields/bls12_377_base.cuh" + +namespace bw6_761 { + typedef bls12_377::fq_config fp_config; + + /** + * Scalar field. Is always a prime field. + */ + typedef Field scalar_t; +} // namespace bw6_761 + +#endif \ No newline at end of file diff --git a/icicle/include/fields/snark_fields/grumpkin_base.cuh b/icicle/include/fields/snark_fields/grumpkin_base.cuh new file mode 100644 index 00000000..355f2f30 --- /dev/null +++ b/icicle/include/fields/snark_fields/grumpkin_base.cuh @@ -0,0 +1,12 @@ +#pragma once +#ifndef GRUMPKIN_BASE_PARAMS_H +#define GRUMPKIN_BASE_PARAMS_H + +#include "fields/storage.cuh" +#include "fields/snark_fields/bn254_scalar.cuh" + +namespace grumpkin { + typedef bn254::fp_config fq_config; +} + +#endif \ No newline at end of file diff --git a/icicle/include/fields/snark_fields/grumpkin_scalar.cuh b/icicle/include/fields/snark_fields/grumpkin_scalar.cuh new file mode 100644 index 00000000..4354c8de --- /dev/null +++ b/icicle/include/fields/snark_fields/grumpkin_scalar.cuh @@ -0,0 +1,18 @@ +#pragma once +#ifndef GRUMPKIN_SCALAR_PARAMS_H +#define GRUMPKIN_SCALAR_PARAMS_H + +#include "fields/storage.cuh" +#include "fields/field.cuh" +#include "fields/snark_fields/bn254_base.cuh" + +namespace grumpkin { + typedef bn254::fq_config fp_config; + + /** + * Scalar field. Is always a prime field. + */ + typedef Field scalar_t; +} // namespace grumpkin + +#endif \ No newline at end of file diff --git a/icicle/include/fields/stark_fields/babybear.cuh b/icicle/include/fields/stark_fields/babybear.cuh new file mode 100644 index 00000000..ad3db0e2 --- /dev/null +++ b/icicle/include/fields/stark_fields/babybear.cuh @@ -0,0 +1,62 @@ +#pragma once + +#include "fields/storage.cuh" +#include "fields/field.cuh" +#include "fields/quartic_extension.cuh" + +namespace babybear { + struct fp_config { + static constexpr unsigned limbs_count = 1; + static constexpr unsigned omegas_count = 28; + static constexpr unsigned modulus_bit_count = 31; + static constexpr unsigned num_of_reductions = 1; + + static constexpr storage modulus = {0x78000001}; + static constexpr storage modulus_2 = {0xf0000002}; + static constexpr storage modulus_4 = {0x00000000}; + static constexpr storage neg_modulus = {0x87ffffff}; + static constexpr storage<2 * limbs_count> modulus_wide = {0x78000001, 0x00000000}; + static constexpr storage<2 * limbs_count> modulus_squared = {0xf0000001, 0x38400000}; + static constexpr storage<2 * limbs_count> modulus_squared_2 = {0xe0000002, 0x70800001}; + static constexpr storage<2 * limbs_count> modulus_squared_4 = {0xc0000004, 0xe1000003}; + + static constexpr storage m = {0x88888887}; + static constexpr storage one = {0x00000001}; + static constexpr storage zero = {0x00000000}; + static constexpr storage montgomery_r = {0xffffffe}; + static constexpr storage montgomery_r_inv = {0x38400000}; + + static constexpr storage_array omega = { + {{0x78000000}, {0x10faa3e0}, {0x6b615c47}, {0x21ceed5a}, {0x2c1c3348}, {0x36c54c86}, {0x701dd01c}, + {0x56a9a28e}, {0x03e4cabf}, {0x5bacde79}, {0x1eb53838}, {0x1cd781af}, {0x0961a0b7}, {0x65098a87}, + {0x77851a0b}, {0x5bcba331}, {0x053fc0f5}, {0x5bf816e5}, {0x4bb124ab}, {0x571e9d4e}, {0x313732cb}, + {0x28aca172}, {0x4e319b52}, {0x45692d95}, {0x14ff4ba1}, {0x00004951}, {0x00000089}}}; + + static constexpr storage_array omega_inv = { + {{0x78000000}, {0x67055c21}, {0x5ee99486}, {0x0bb4c4e4}, {0x4ab33b27}, {0x044b4497}, {0x410e23aa}, + {0x08a7ee2b}, {0x563cb93d}, {0x3d70b4b7}, {0x77d999f1}, {0x6ceb65b5}, {0x49e7f635}, {0x0eae3a8c}, + {0x238b8a78}, {0x70d71b0a}, {0x0eaacc45}, {0x5af0f193}, {0x47303308}, {0x573cbfad}, {0x29ff72c0}, + {0x05af9dac}, {0x00ef24df}, {0x26985530}, {0x22d1ce4b}, {0x08359375}, {0x2cabe994}}}; + + static constexpr storage_array inv = { + {{0x3c000001}, {0x5a000001}, {0x69000001}, {0x70800001}, {0x74400001}, {0x76200001}, {0x77100001}, + {0x77880001}, {0x77c40001}, {0x77e20001}, {0x77f10001}, {0x77f88001}, {0x77fc4001}, {0x77fe2001}, + {0x77ff1001}, {0x77ff8801}, {0x77ffc401}, {0x77ffe201}, {0x77fff101}, {0x77fff881}, {0x77fffc41}, + {0x77fffe21}, {0x77ffff11}, {0x77ffff89}, {0x77ffffc5}, {0x77ffffe3}, {0x77fffff2}}}; + + // nonresidue to generate the extension field + static constexpr uint32_t nonresidue = 11; + // true if nonresidue is negative. + static constexpr bool nonresidue_is_negative = false; + }; + + /** + * Scalar field. Is always a prime field. + */ + typedef Field scalar_t; + + /** + * Extension field of `scalar_t` enabled if `-DEXT_FIELD` env variable is. + */ + typedef ExtensionField extension_t; +} // namespace babybear diff --git a/icicle/utils/storage.cuh b/icicle/include/fields/storage.cuh similarity index 100% rename from icicle/utils/storage.cuh rename to icicle/include/fields/storage.cuh diff --git a/icicle/utils/device_context.cuh b/icicle/include/gpu-utils/device_context.cuh similarity index 100% rename from icicle/utils/device_context.cuh rename to icicle/include/gpu-utils/device_context.cuh diff --git a/icicle/utils/error_handler.cuh b/icicle/include/gpu-utils/error_handler.cuh similarity index 100% rename from icicle/utils/error_handler.cuh rename to icicle/include/gpu-utils/error_handler.cuh diff --git a/icicle/common.cuh b/icicle/include/gpu-utils/modifiers.cuh similarity index 100% rename from icicle/common.cuh rename to icicle/include/gpu-utils/modifiers.cuh diff --git a/icicle/utils/sharedmem.cuh b/icicle/include/gpu-utils/sharedmem.cuh similarity index 89% rename from icicle/utils/sharedmem.cuh rename to icicle/include/gpu-utils/sharedmem.cuh index a6e5d02b..404af55e 100644 --- a/icicle/utils/sharedmem.cuh +++ b/icicle/include/gpu-utils/sharedmem.cuh @@ -58,8 +58,6 @@ #ifndef _SHAREDMEM_H_ #define _SHAREDMEM_H_ -#include "curves/curve_config.cuh" - /** @brief Wrapper class for templatized dynamic shared memory arrays. * * This struct uses template specialization on the type \a T to declare @@ -77,8 +75,8 @@ struct SharedMemory { //! @returns Pointer to runtime-sized shared memory array __device__ T* getPointer() { - extern __device__ void Error_UnsupportedType(); // Ensure that we won't compile any un-specialized types - Error_UnsupportedType(); + // extern __device__ void Error_UnsupportedType(); // Ensure that we won't compile any un-specialized types + // Error_UnsupportedType(); return (T*)0; } // TODO: Use operator overloading to make this class look like a regular array @@ -214,24 +212,6 @@ struct SharedMemory { } }; -template <> -struct SharedMemory { - __device__ curve_config::scalar_t* getPointer() - { - extern __shared__ curve_config::scalar_t s_scalar_[]; - return s_scalar_; - } -}; - -template <> -struct SharedMemory { - __device__ curve_config::projective_t* getPointer() - { - extern __shared__ curve_config::projective_t s_projective_[]; - return s_projective_; - } -}; - #endif //_SHAREDMEM_H_ // Leave this at the end of the file diff --git a/icicle/appUtils/keccak/keccak.cuh b/icicle/include/hash/keccak/keccak.cuh similarity index 96% rename from icicle/appUtils/keccak/keccak.cuh rename to icicle/include/hash/keccak/keccak.cuh index 15a73a0e..24c856d6 100644 --- a/icicle/appUtils/keccak/keccak.cuh +++ b/icicle/include/hash/keccak/keccak.cuh @@ -3,8 +3,8 @@ #define KECCAK_H #include -#include "utils/device_context.cuh" -#include "utils/error_handler.cuh" +#include "gpu-utils/device_context.cuh" +#include "gpu-utils/error_handler.cuh" namespace keccak { /** diff --git a/icicle/appUtils/msm/msm.cuh b/icicle/include/msm/msm.cuh similarity index 87% rename from icicle/appUtils/msm/msm.cuh rename to icicle/include/msm/msm.cuh index b6f4b7aa..a18800d2 100644 --- a/icicle/appUtils/msm/msm.cuh +++ b/icicle/include/msm/msm.cuh @@ -4,12 +4,11 @@ #include -#include "curves/curve_config.cuh" -#include "primitives/affine.cuh" -#include "primitives/field.cuh" -#include "primitives/projective.cuh" -#include "utils/device_context.cuh" -#include "utils/error_handler.cuh" +#include "curves/affine.cuh" +#include "curves/projective.cuh" +#include "fields/field.cuh" +#include "gpu-utils/device_context.cuh" +#include "gpu-utils/error_handler.cuh" /** * @namespace msm @@ -33,7 +32,7 @@ namespace msm { /** * @struct MSMConfig * Struct that encodes MSM parameters to be passed into the [MSM](@ref MSM) function. The intended use of this struct - * is to create it using [DefaultMSMConfig](@ref DefaultMSMConfig) function and then you'll hopefully only need to + * is to create it using [default_msm_config](@ref default_msm_config) function and then you'll hopefully only need to * change a small number of default values for each of your MSMs. */ struct MSMConfig { @@ -44,7 +43,7 @@ namespace msm { * points, it should be set to the product of MSM size and [batch_size](@ref * batch_size). Default value: 0 (meaning it's equal to the MSM size). */ int precompute_factor; /**< The number of extra points to pre-compute for each point. See the - * [PrecomputeMSMBases](@ref PrecomputeMSMBases) function, `precompute_factor` passed + * [precompute_msm_bases](@ref precompute_msm_bases) function, `precompute_factor` passed * there needs to be equal to the one used here. Larger values decrease the * number of computations to make, on-line memory footprint, but increase the static * memory footprint. Default value: 1 (i.e. don't pre-compute). */ @@ -53,7 +52,7 @@ namespace msm { * means more on-line memory footprint but also more parallelism and less computational * complexity (up to a certain point). Currently pre-computation is independent of * \f$ c \f$, however in the future value of \f$ c \f$ here and the one passed into the - * [PrecomputeMSMBases](@ref PrecomputeMSMBases) function will need to be identical. + * [precompute_msm_bases](@ref precompute_msm_bases) function will need to be identical. * Default value: 0 (the optimal value of \f$ c \f$ is chosen automatically). */ int bitsize; /**< Number of bits of the largest scalar. Typically equals the bitsize of scalar field, * but if a different (better) upper bound is known, it should be reflected in this @@ -86,8 +85,27 @@ namespace msm { * A function that returns the default value of [MSMConfig](@ref MSMConfig) for the [MSM](@ref MSM) function. * @return Default value of [MSMConfig](@ref MSMConfig). */ - template - MSMConfig DefaultMSMConfig(); + static MSMConfig + default_msm_config(const device_context::DeviceContext& ctx = device_context::get_default_device_context()) + { + MSMConfig config = { + ctx, // ctx + 0, // points_size + 1, // precompute_factor + 0, // c + 0, // bitsize + 10, // large_bucket_factor + 1, // batch_size + false, // are_scalars_on_device + false, // are_scalars_montgomery_form + false, // are_points_on_device + false, // are_points_montgomery_form + false, // are_results_on_device + false, // is_big_triangle + false, // is_async + }; + return config; + } /** * A function that computes MSM: \f$ MSM(s_i, P_i) = \sum_{i=1}^N s_i \cdot P_i \f$. @@ -107,7 +125,7 @@ namespace msm { * */ template - cudaError_t MSM(const S* scalars, const A* points, int msm_size, MSMConfig& config, P* results); + cudaError_t msm(const S* scalars, const A* points, int msm_size, MSMConfig& config, P* results); /** * A function that precomputes MSM bases by extending them with their shifted copies. @@ -130,7 +148,7 @@ namespace msm { * */ template - cudaError_t PrecomputeMSMBases( + cudaError_t precompute_msm_bases( A* bases, int bases_size, int precompute_factor, diff --git a/icicle/appUtils/ntt/ntt.cuh b/icicle/include/ntt/ntt.cuh similarity index 85% rename from icicle/appUtils/ntt/ntt.cuh rename to icicle/include/ntt/ntt.cuh index 7b9e4454..1f032bb4 100644 --- a/icicle/appUtils/ntt/ntt.cuh +++ b/icicle/include/ntt/ntt.cuh @@ -4,10 +4,9 @@ #include -#include "curves/curve_config.cuh" -#include "utils/device_context.cuh" -#include "utils/error_handler.cuh" -#include "utils/sharedmem.cuh" +#include "gpu-utils/device_context.cuh" +#include "gpu-utils/error_handler.cuh" +#include "gpu-utils/sharedmem.cuh" #include "utils/utils_kernels.cuh" #include "utils/utils.h" @@ -38,13 +37,13 @@ namespace ntt { * @return `cudaSuccess` if the execution was successful and an error code otherwise. */ template - cudaError_t InitDomain(S primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode = false); + cudaError_t init_domain(S primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode = false); /** * Releases and deallocates resources associated with the domain initialized for performing NTTs. * This function should be called to clean up resources once they are no longer needed. * It's important to note that after calling this function, any operation that relies on the released domain will - * fail unless InitDomain is called again to reinitialize the resources. Therefore, ensure that ReleaseDomain is + * fail unless init_domain is called again to reinitialize the resources. Therefore, ensure that release_domain is * only called when the operations requiring the NTT domain are completely finished and the domain is no longer * needed. * Also note that it is releasing the domain associated to the specific device. @@ -54,7 +53,24 @@ namespace ntt { * the resources. The error code can be used to diagnose the problem. * */ template - cudaError_t ReleaseDomain(device_context::DeviceContext& ctx); + cudaError_t release_domain(device_context::DeviceContext& ctx); + + /* Returns the basic root of unity Wn + * @param logn log size of the required root. + * @return Wn root of unity + */ + template + S get_root_of_unity(uint64_t max_size); + + /* Returns the basic root of unity Wn corresponding to the basic root used to initialize the domain. + * This function can be called only after InitializeDomain()! + * Useful when computing NTT on cosets. In that case we must use the root W_2n that is between W_n and W_n+1. + * @param logn log size of the required root. + * @param ctx Details related to the device such as its id and stream id. + * @return Wn root of unity corresponding to logn and the basic root used for initDomain(root) + */ + template + S get_root_of_unity_from_domain(uint64_t logn, device_context::DeviceContext& ctx); /** * @enum NTTDir @@ -130,11 +146,12 @@ namespace ntt { * @return Default value of [NTTConfig](@ref NTTConfig). */ template - NTTConfig DefaultNTTConfig(); + NTTConfig + default_ntt_config(const device_context::DeviceContext& ctx = device_context::get_default_device_context()); /** - * A function that computes NTT or iNTT in-place. It's necessary to call [InitDomain](@ref InitDomain) with an - * appropriate primitive root before calling this function (only one call to `InitDomain` should suffice for all + * A function that computes NTT or iNTT in-place. It's necessary to call [init_domain](@ref init_domain) with an + * appropriate primitive root before calling this function (only one call to `init_domain` should suffice for all * NTTs). * @param input Input of the NTT. Length of this array needs to be \f$ size \cdot config.batch\_size \f$. Note * that if inputs are in Montgomery form, the outputs will be as well and vice-versa: non-Montgomery inputs produce @@ -150,7 +167,7 @@ namespace ntt { * @return `cudaSuccess` if the execution was successful and an error code otherwise. */ template - cudaError_t NTT(const E* input, int size, NTTDir dir, NTTConfig& config, E* output); + cudaError_t ntt(const E* input, int size, NTTDir dir, NTTConfig& config, E* output); } // namespace ntt diff --git a/icicle/appUtils/ntt/ntt_impl.cuh b/icicle/include/ntt/ntt_impl.cuh similarity index 95% rename from icicle/appUtils/ntt/ntt_impl.cuh rename to icicle/include/ntt/ntt_impl.cuh index c9809db5..986436ab 100644 --- a/icicle/appUtils/ntt/ntt_impl.cuh +++ b/icicle/include/ntt/ntt_impl.cuh @@ -3,7 +3,7 @@ #define _NTT_IMPL_H #include -#include "appUtils/ntt/ntt.cuh" // for enum Ordering +#include "ntt/ntt.cuh" // for enum Ordering namespace mxntt { diff --git a/icicle/include/polynomials/cuda_backend/polynomial_cuda_backend.cuh b/icicle/include/polynomials/cuda_backend/polynomial_cuda_backend.cuh new file mode 100644 index 00000000..da07254a --- /dev/null +++ b/icicle/include/polynomials/cuda_backend/polynomial_cuda_backend.cuh @@ -0,0 +1,23 @@ +#pragma once + +#include "gpu-utils/device_context.cuh" +#include "fields/field_config.cuh" +#include "polynomials/polynomials.h" + +using device_context::DeviceContext; + +namespace polynomials { + template + class CUDAPolynomialFactory : public AbstractPolynomialFactory + { + std::vector m_device_contexts; // device-id --> device context + std::vector m_device_streams; // device-id --> device stream. Storing the streams here as workaround + // since DeviceContext has a reference to a stream. + + public: + CUDAPolynomialFactory(); + ~CUDAPolynomialFactory(); + std::shared_ptr> create_context() override; + std::shared_ptr> create_backend() override; + }; +} // namespace polynomials \ No newline at end of file diff --git a/icicle/include/polynomials/polynomial_abstract_factory.h b/icicle/include/polynomials/polynomial_abstract_factory.h new file mode 100644 index 00000000..528e393b --- /dev/null +++ b/icicle/include/polynomials/polynomial_abstract_factory.h @@ -0,0 +1,49 @@ +#pragma once + +#include "polynomial_context.h" +#include "polynomial_backend.h" +#include // For std::shared_ptr + +namespace polynomials { + + /** + * @brief Abstract factory for creating polynomial contexts and backends. + * + * The `AbstractPolynomialFactory` serves as an interface for factories capable of creating + * instances of `IPolynomialContext` and `IPolynomialBackend`. This design allows for the + * decoupling of object creation from their usage, facilitating the implementation of various + * computational strategies (e.g., GPU, ZPU) without altering client code. Each concrete factory + * is expected to provide tailored implementations of polynomial contexts and backends that + * are optimized for specific computational environments. + * + * @tparam C Type of the coefficients. + * @tparam D Domain type, representing the input space of the polynomial. + * @tparam I Image type, representing the output space of the polynomial. + */ + template + class AbstractPolynomialFactory + { + public: + /** + * @brief Creates and returns a shared pointer to an `IPolynomialContext` instance. + * + * @return std::shared_ptr> A shared pointer to the created + * polynomial context instance. + */ + virtual std::shared_ptr> create_context() = 0; + + /** + * @brief Creates and returns a shared pointer to an `IPolynomialBackend` instance. + * + * @return std::shared_ptr> A shared pointer to the created + * polynomial backend instance. + */ + virtual std::shared_ptr> create_backend() = 0; + + /** + * @brief Virtual destructor for the `AbstractPolynomialFactory`. + */ + virtual ~AbstractPolynomialFactory() = default; + }; + +} // namespace polynomials diff --git a/icicle/include/polynomials/polynomial_backend.h b/icicle/include/polynomials/polynomial_backend.h new file mode 100644 index 00000000..786b4f60 --- /dev/null +++ b/icicle/include/polynomials/polynomial_backend.h @@ -0,0 +1,71 @@ +#pragma once + +#include // for uint64_t, int64_t + +namespace polynomials { + + /** + * @brief Interface for the polynomial computational backend. + * + * The `IPolynomialBackend` interface defines the set of operations for polynomial arithmetic + * and manipulation that can be performed on a given computational device or platform (e.g., GPU, ZPU). + * This interface abstracts the computational logic, allowing for implementation-specific optimizations + * and hardware utilization. It interacts closely with `IPolynomialContext` to manage polynomial data + * states and perform computations. + * + * @tparam C Type of the coefficients. + * @tparam D Domain type, representing the input space of the polynomial. + * @tparam I Image type, representing the output space of the polynomial. + */ + template + class IPolynomialBackend + { + public: + IPolynomialBackend() = default; + virtual ~IPolynomialBackend() = default; + + typedef std::shared_ptr> PolyContext; + + // Initialization methods + virtual void from_coefficients(PolyContext p, uint64_t nof_coefficients, const C* coefficients = nullptr) = 0; + virtual void from_rou_evaluations(PolyContext p, uint64_t nof_evaluations, const I* evaluations = nullptr) = 0; + virtual void clone(PolyContext out, PolyContext in) = 0; + + // Arithmetic operations + virtual void add(PolyContext& out, PolyContext op_a, PolyContext op_b) = 0; + virtual void subtract(PolyContext out, PolyContext op_a, PolyContext op_b) = 0; + virtual void multiply(PolyContext out, PolyContext op_a, PolyContext op_b) = 0; + virtual void multiply(PolyContext out, PolyContext p, D scalar) = 0; // scalar multiplication + virtual void divide(PolyContext Quotient_out, PolyContext Remainder_out, PolyContext op_a, PolyContext op_b) = 0; + virtual void quotient(PolyContext out, PolyContext op_a, PolyContext op_b) = 0; + virtual void remainder(PolyContext out, PolyContext op_a, PolyContext op_b) = 0; + virtual void divide_by_vanishing_polynomial(PolyContext out, PolyContext op_a, uint64_t vanishing_poly_degree) = 0; + + // Operations specific to monomials + virtual void add_monomial_inplace(PolyContext& poly, C monomial_coeff, uint64_t monomial) = 0; + virtual void sub_monomial_inplace(PolyContext& poly, C monomial_coeff, uint64_t monomial) = 0; + + // Utility methods + virtual void slice(PolyContext out, PolyContext in, uint64_t offset, uint64_t stride, uint64_t size) = 0; + virtual int64_t degree(PolyContext op) = 0; + + // Method to access mutable storage within the context + void* get_context_storage_mutable(PolyContext ctxt) { return ctxt->get_storage_mutable(); } + const void* get_context_storage_immutable(PolyContext ctxt) { return ctxt->get_storage_immutable(); } + + // Evaluation methods + virtual void evaluate(PolyContext op, const D* domain_x, I* eval /*OUT*/) = 0; + virtual void evaluate_on_domain(PolyContext op, const D* domain, uint64_t size, I* evaluations /*OUT*/) = 0; + + // Methods to copy coefficients to host memory + virtual C get_coeff(PolyContext op, uint64_t coeff_idx) = 0; + virtual uint64_t copy_coeffs(PolyContext op, C* coeffs, uint64_t start_idx, uint64_t end_idx) = 0; + + // Methods to get views of coefficients and evaluations, including device id + virtual std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> + get_coefficients_view(PolyContext p) = 0; + virtual std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> + get_rou_evaluations_view(PolyContext p, uint64_t nof_evaluations = 0, bool is_reversed = false) = 0; + }; + +} // namespace polynomials diff --git a/icicle/include/polynomials/polynomial_context.h b/icicle/include/polynomials/polynomial_context.h new file mode 100644 index 00000000..bd8e2dba --- /dev/null +++ b/icicle/include/polynomials/polynomial_context.h @@ -0,0 +1,93 @@ +#pragma once + +#include // for std::pair +#include // for std::tuple +#include // for std::ostream +#include // for std::max +#include // for uint64_t, etc. +#include +#include "utils/integrity_pointer.h" + +namespace polynomials { + + template + class IPolynomialBackend; + + /** + * @brief Interface for polynomial context, encapsulating state, memory, and device context. + * + * This interface is designed to manage the state of polynomials including their coefficients and + * evaluations in both natural and reversed order. It supports operations for converting between + * these forms, allocating and releasing resources, and accessing the underlying data. The context + * abstracts over the specifics of memory and execution context, allowing polynomials to be managed + * in a way that is agnostic to the underlying hardware or software infrastructure. + * + * @tparam C Type of the coefficients. + * @tparam D Domain type, representing the input space of the polynomial. + * @tparam I Image type, representing the output space of the polynomial. + */ + template + class IPolynomialContext + { + public: + friend class IPolynomialBackend; + + // Enumerates the possible states of a polynomial context. + enum State { Invalid, Coefficients, EvaluationsOnRou_Natural, EvaluationsOnRou_Reversed }; + + // The size of the largest element among coefficients and evaluations. + static constexpr size_t ElementSize = std::max(sizeof(C), sizeof(I)); + + /** + * @brief Construct a new IPolynomialContext object. + */ + IPolynomialContext() : m_id{s_id++} {} + + /** + * @brief Virtual destructor for IPolynomialContext. + */ + virtual ~IPolynomialContext() = default; + + // Methods for initializing the context from coefficients or evaluations. + virtual void from_coefficients(uint64_t nof_coefficients, const C* coefficients = nullptr) = 0; + virtual void from_rou_evaluations(uint64_t nof_evaluations, const I* evaluations = nullptr) = 0; + + // Method for cloning the context from another instance. + virtual void clone(IPolynomialContext& from) = 0; + + // Methods for resource management. + virtual void allocate(uint64_t nof_elements, State init_state = State::Coefficients, bool memset_zeros = true) = 0; + virtual void release() = 0; + + // Methods for transforming between coefficients and evaluations. + virtual void transform_to_coefficients(uint64_t nof_coefficients = 0) = 0; + virtual void transform_to_evaluations(uint64_t nof_evaluations = 0, bool is_reversed = false) = 0; + + // Accessors for the state and number of elements. + virtual State get_state() const = 0; + virtual uint64_t get_nof_elements() const = 0; + + // Methods to get direct access to coefficients and evaluations. + virtual std::pair get_coefficients() = 0; + virtual std::pair get_rou_evaluations() = 0; + + // Methods to get views of coefficients and evaluations, including device id. + virtual std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> get_coefficients_view() = 0; + virtual std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> + get_rou_evaluations_view(uint64_t nof_evaluations = 0, bool is_reversed = false) = 0; + + // Method for printing the context state to an output stream. + virtual void print(std::ostream& os) = 0; + + protected: + // Provides mutable access to the underlying storage for backend computations. + virtual void* get_storage_mutable() = 0; + virtual const void* get_storage_immutable() = 0; + + // Static and instance variables for debug id management. + static inline uint64_t s_id = 0; // Global id counter. + + public: + const uint64_t m_id; + }; +} // namespace polynomials diff --git a/icicle/include/polynomials/polynomials.h b/icicle/include/polynomials/polynomials.h new file mode 100644 index 00000000..3a436eea --- /dev/null +++ b/icicle/include/polynomials/polynomials.h @@ -0,0 +1,133 @@ +#pragma once + +#include +#include +#include "utils/integrity_pointer.h" +#include "fields/field_config.cuh" + +#include "polynomial_context.h" +#include "polynomial_backend.h" +#include "polynomial_abstract_factory.h" + +using namespace field_config; + +namespace polynomials { + + /** + * @brief Represents a polynomial and provides operations for polynomial arithmetic, evaluation, and manipulation. + * + * This class models a polynomial with coefficients of type `Coeff`, defined over a domain `Domain` and producing + * outputs of type `Image`. It supports a range of operations including basic arithmetic (addition, subtraction, + * multiplication, division), evaluation at points or over domains, and manipulation (slicing, adding monomials). + * The implementation abstracts over the specifics of computation and storage through the use of an abstract factory, + * contexts, and backends, allowing for efficient execution across various computational environments. + * + * @tparam Coeff Type of the coefficients of the polynomial. + * @tparam Domain Type representing the input space of the polynomial (defaults to `Coeff`). + * @tparam Image Type representing the output space of the polynomial (defaults to `Coeff`). + */ + template + class Polynomial + { + public: + // Initialization (coefficients/evaluations can reside on host or device) + static Polynomial from_coefficients(const Coeff* coefficients, uint64_t nof_coefficients); + static Polynomial from_rou_evaluations(const Image* evaluations, uint64_t nof_evaluations); + + // Clone the polynomial + Polynomial clone() const; + + // Arithmetic ops + Polynomial operator+(const Polynomial& rhs) const; + Polynomial& operator+=(const Polynomial& rhs); + + Polynomial operator-(const Polynomial& rhs) const; + + Polynomial operator*(const Polynomial& rhs) const; + Polynomial operator*(const Domain& scalar) const; // scalar multiplication + template + friend Polynomial operator*(const D& scalar, const Polynomial& rhs); + + std::pair divide(const Polynomial& rhs) const; // returns (Q(x), R(x)) + Polynomial operator/(const Polynomial& rhs) const; // returns Quotient Q(x) for A(x) = Q(x)B(x) + R(x) + Polynomial operator%(const Polynomial& rhs) const; // returns Remainder R(x) for A(x) = Q(x)B(x) + R(x) + Polynomial divide_by_vanishing_polynomial(uint64_t degree) const; + + // arithmetic ops with monomial + Polynomial& add_monomial_inplace(Coeff monomial_coeff, uint64_t monomial = 0); + Polynomial& sub_monomial_inplace(Coeff monomial_coeff, uint64_t monomial = 0); + + // Slicing and selecting even or odd components. + Polynomial slice(uint64_t offset, uint64_t stride, uint64_t size = 0 /*0 means take all elements*/); + Polynomial even(); + Polynomial odd(); + + // Note: Following ops cannot be traced. Calling them invokes polynomial evaluation + + // Evaluation methods + Image operator()(const Domain& x) const; + void evaluate(const Domain* x, Image* eval /*OUT*/) const; + void evaluate_on_domain(Domain* domain, uint64_t size, Image* evals /*OUT*/) const; // caller allocates memory + + // Method to obtain the degree of the polynomial + int64_t degree(); + + // Methods for copying coefficients to host memory. + Coeff get_coeff(uint64_t idx) const; // single coefficient + // caller is allocating output memory. If coeff==nullptr, returning nof_coeff only + uint64_t copy_coeffs(Coeff* host_coeffs, uint64_t start_idx, uint64_t end_idx) const; + + // Methods for obtaining a view of the coefficients or evaluations + std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> get_coefficients_view(); + std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> + get_rou_evaluations_view(uint64_t nof_evaluations = 0, bool is_reversed = false); + + // Overload stream insertion operator for printing. + friend std::ostream& operator<<(std::ostream& os, Polynomial& poly) + { + poly.m_context->print(os); + return os; + } + + // Static method to initialize the polynomial class with a factory for context and backend creation. + static void initialize(std::shared_ptr> factory) + { + std::atexit(cleanup); + s_factory = factory; + } + + // Cleanup method for releasing factory resources. + static void cleanup() { s_factory = nullptr; } + + private: + // The context of the polynomial, encapsulating its state. + std::shared_ptr> m_context = nullptr; + // The computational backend for the polynomial operations. + std::shared_ptr> m_backend = nullptr; + + // Factory for constructing the context and backend instances. + static inline std::shared_ptr> s_factory = nullptr; + + public: + Polynomial(); + ~Polynomial() = default; + + // Ensures polynomials can be moved but not copied, to manage resources efficiently. + Polynomial(Polynomial&&) = default; + Polynomial& operator=(Polynomial&&) = default; + Polynomial(const Polynomial&) = delete; + Polynomial& operator=(const Polynomial&) = delete; + + std::shared_ptr> get_context() { return m_context; } + }; + + // explicit instantiation + + // Friend operator to allow multiplication with a scalar from the left-hand side + template + Polynomial operator*(const D& scalar, const Polynomial& rhs); + + // External template instantiation to ensure the template is compiled for specific types. + extern template class Polynomial; + +} // namespace polynomials diff --git a/icicle/appUtils/poseidon/constants/bls12_377_poseidon.h b/icicle/include/poseidon/constants/bls12_377_poseidon.h similarity index 100% rename from icicle/appUtils/poseidon/constants/bls12_377_poseidon.h rename to icicle/include/poseidon/constants/bls12_377_poseidon.h diff --git a/icicle/appUtils/poseidon/constants/bls12_381_poseidon.h b/icicle/include/poseidon/constants/bls12_381_poseidon.h similarity index 100% rename from icicle/appUtils/poseidon/constants/bls12_381_poseidon.h rename to icicle/include/poseidon/constants/bls12_381_poseidon.h diff --git a/icicle/appUtils/poseidon/constants/bn254_poseidon.h b/icicle/include/poseidon/constants/bn254_poseidon.h similarity index 100% rename from icicle/appUtils/poseidon/constants/bn254_poseidon.h rename to icicle/include/poseidon/constants/bn254_poseidon.h diff --git a/icicle/appUtils/poseidon/constants/bw6_761_poseidon.h b/icicle/include/poseidon/constants/bw6_761_poseidon.h similarity index 100% rename from icicle/appUtils/poseidon/constants/bw6_761_poseidon.h rename to icicle/include/poseidon/constants/bw6_761_poseidon.h diff --git a/icicle/appUtils/poseidon/constants/constants_template.h b/icicle/include/poseidon/constants/constants_template.h similarity index 100% rename from icicle/appUtils/poseidon/constants/constants_template.h rename to icicle/include/poseidon/constants/constants_template.h diff --git a/icicle/appUtils/poseidon/constants/generate_parameters.py b/icicle/include/poseidon/constants/generate_parameters.py similarity index 100% rename from icicle/appUtils/poseidon/constants/generate_parameters.py rename to icicle/include/poseidon/constants/generate_parameters.py diff --git a/icicle/appUtils/poseidon/constants/grumpkin_poseidon.h b/icicle/include/poseidon/constants/grumpkin_poseidon.h similarity index 100% rename from icicle/appUtils/poseidon/constants/grumpkin_poseidon.h rename to icicle/include/poseidon/constants/grumpkin_poseidon.h diff --git a/icicle/appUtils/poseidon/poseidon.cuh b/icicle/include/poseidon/poseidon.cuh similarity index 95% rename from icicle/appUtils/poseidon/poseidon.cuh rename to icicle/include/poseidon/poseidon.cuh index 5ac1454b..9dd3beea 100644 --- a/icicle/appUtils/poseidon/poseidon.cuh +++ b/icicle/include/poseidon/poseidon.cuh @@ -4,9 +4,8 @@ #include #include -#include "utils/device_context.cuh" -#include "curves/curve_config.cuh" -#include "utils/error_handler.cuh" +#include "gpu-utils/device_context.cuh" +#include "gpu-utils/error_handler.cuh" #include "utils/utils.h" /** @@ -100,10 +99,9 @@ namespace poseidon { * function will block the current CPU thread. */ }; - template - PoseidonConfig default_poseidon_config(int t) + static PoseidonConfig default_poseidon_config( + int t, const device_context::DeviceContext& ctx = device_context::get_default_device_context()) { - device_context::DeviceContext ctx = device_context::get_default_device_context(); PoseidonConfig config = { ctx, // ctx false, // are_inputes_on_device diff --git a/icicle/appUtils/tree/merkle.cuh b/icicle/include/poseidon/tree/merkle.cuh similarity index 90% rename from icicle/appUtils/tree/merkle.cuh rename to icicle/include/poseidon/tree/merkle.cuh index 8dbee83e..73405d36 100644 --- a/icicle/appUtils/tree/merkle.cuh +++ b/icicle/include/poseidon/tree/merkle.cuh @@ -2,10 +2,10 @@ #ifndef MERKLE_H #define MERKLE_H -#include "utils/device_context.cuh" -#include "utils/error_handler.cuh" +#include "gpu-utils/device_context.cuh" +#include "gpu-utils/error_handler.cuh" #include "utils/utils.h" -#include "appUtils/poseidon/poseidon.cuh" +#include "poseidon/poseidon.cuh" #include #include @@ -37,10 +37,9 @@ namespace merkle { * function will block the current CPU thread. */ }; - template - TreeBuilderConfig default_merkle_config() + static TreeBuilderConfig + default_merkle_config(const device_context::DeviceContext& ctx = device_context::get_default_device_context()) { - device_context::DeviceContext ctx = device_context::get_default_device_context(); TreeBuilderConfig config = { ctx, // ctx 0, // keep_rows diff --git a/icicle/include/utils/integrity_pointer.h b/icicle/include/utils/integrity_pointer.h new file mode 100644 index 00000000..809c8418 --- /dev/null +++ b/icicle/include/utils/integrity_pointer.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include + +/** + * @brief A template class that wraps a raw pointer with additional checks for data integrity. + * + * IntegrityPointer is designed to wrap a raw pointer and associate it with a validation + * mechanism based on a counter. This counter is monitored via a std::weak_ptr, allowing + * the IntegrityPointer to check if the data it points to has potentially been invalidated. + * It is intended for scenarios where there's a need to ensure the integrity of the pointed-to + * data throughout the lifetime of the pointer, particularly useful in complex systems where + * data validity can change over time due to external factors. + * + * Usage involves providing the raw pointer to be wrapped, a std::weak_ptr to a counter, and + * the expected value of that counter. The IntegrityPointer can then be used much like a normal + * pointer, with the addition of integrity checks before access. + * + * @tparam T The type of the pointed-to object. + */ + +template +class IntegrityPointer +{ +public: + /** + * Constructs an IntegrityPointer wrapping a raw pointer with a validity check based on a counter. + * + * @param ptr A raw pointer to the data of type T. + * @param counterWeakPtr A std::weak_ptr to an int counter, used for validation. + * @param expectedCounterValue The expected value of the counter for the pointer to be considered valid. + */ + IntegrityPointer(const T* ptr, std::weak_ptr counterWeakPtr, int expectedCounterValue) + : m_ptr(ptr), m_counterWeakPtr(counterWeakPtr), m_expectedCounterValue(expectedCounterValue) + { + } + IntegrityPointer(const IntegrityPointer& other) = default; + IntegrityPointer(IntegrityPointer&& other) = default; + + /** + * Retrieves the raw pointer. Use with caution, as direct access bypasses validity checks. + * @return T* The raw pointer to the data. + */ + const T* get() const { return isValid() ? m_ptr : nullptr; } + + /** + * Dereferences the pointer. Throws std::runtime_error if the pointer is invalid. + * @return A reference to the data pointed to by the raw pointer. + */ + const T& operator*() const + { + assertValid(); + return *m_ptr; + } + + /** + * Provides access to the member of the pointed-to object. Throws std::runtime_error if the pointer is invalid. + * @return T* The raw pointer to the data. + */ + const T* operator->() const + { + assertValid(); + return m_ptr; + } + + /** + * Checks whether the pointer is still considered valid by comparing the current value of the counter + * to the expected value. + * @return true if the pointer is valid, false otherwise. + */ + bool isValid() const + { + if (auto counterSharedPtr = m_counterWeakPtr.lock()) { return *counterSharedPtr == m_expectedCounterValue; } + return false; + } + +private: + const T* m_ptr; ///< The raw pointer to the data. + std::weak_ptr m_counterWeakPtr; ///< A weak pointer to the counter used for validation. + const int m_expectedCounterValue; ///< The expected value of the counter for the pointer to be valid. + + /** + * Asserts the validity of the pointer. Throws std::runtime_error if the pointer is invalid. + */ + void assertValid() const + { + if (!isValid()) { + logInvalidAccess(); + throw std::runtime_error("Attempted to access invalidated IntegrityPointer."); + } + } + + /** + * Logs an attempt to access an invalidated pointer. + */ + static void logInvalidAccess() + { + std::cerr << "Warning: Attempted to access invalidated IntegrityPointer." << std::endl; + } +}; diff --git a/icicle/utils/mont.cuh b/icicle/include/utils/mont.cuh similarity index 77% rename from icicle/utils/mont.cuh rename to icicle/include/utils/mont.cuh index 1686fba8..d69f30cb 100644 --- a/icicle/utils/mont.cuh +++ b/icicle/include/utils/mont.cuh @@ -12,7 +12,7 @@ namespace mont { __global__ void MontgomeryKernel(const E* input, int n, E* output) { int tid = blockIdx.x * blockDim.x + threadIdx.x; - if (tid < n) { output[tid] = is_into ? E::ToMontgomery(input[tid]) : E::FromMontgomery(input[tid]); } + if (tid < n) { output[tid] = is_into ? E::to_montgomery(input[tid]) : E::from_montgomery(input[tid]); } } template @@ -29,13 +29,13 @@ namespace mont { } // namespace template - cudaError_t ToMontgomery(const E* d_input, int n, cudaStream_t stream, E* d_output) + cudaError_t to_montgomery(const E* d_input, int n, cudaStream_t stream, E* d_output) { return ConvertMontgomery(d_input, n, stream, d_output); } template - cudaError_t FromMontgomery(const E* d_input, int n, cudaStream_t stream, E* d_output) + cudaError_t from_montgomery(const E* d_input, int n, cudaStream_t stream, E* d_output) { return ConvertMontgomery(d_input, n, stream, d_output); } diff --git a/icicle/include/utils/test_functions.cuh b/icicle/include/utils/test_functions.cuh new file mode 100644 index 00000000..8005a795 --- /dev/null +++ b/icicle/include/utils/test_functions.cuh @@ -0,0 +1,142 @@ +#pragma once + +template +__global__ void add_elements_kernel(const T1* x, const T2* y, T1* result, const unsigned count) +{ + const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; + if (gid >= count) return; + T1 res = x[gid]; + T2 y_gid = y[gid]; + for (int i = 0; i < N_REP; i++) + res = res + y_gid; + result[gid] = res; +} + +template +int vec_add(const T1* x, const T2* y, T1* result, const unsigned count) +{ + add_elements_kernel<<<(count - 1) / 256 + 1, 256>>>(x, y, result, count); + int error = cudaGetLastError(); + return (error || (N_REP > 1)) ? error : cudaDeviceSynchronize(); +} + +template +__global__ void sub_elements_kernel(const T1* x, const T2* y, T1* result, const unsigned count) +{ + const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; + if (gid >= count) return; + result[gid] = x[gid] - y[gid]; +} + +template +int vec_sub(const T1* x, const T2* y, T1* result, const unsigned count) +{ + sub_elements_kernel<<<(count - 1) / 256 + 1, 256>>>(x, y, result, count); + int error = cudaGetLastError(); + return error ? error : cudaDeviceSynchronize(); +} + +template +__global__ void neg_elements_kernel(const T* x, T* result, const unsigned count) +{ + const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; + if (gid >= count) return; + result[gid] = T::neg(x[gid]); +} + +template +int vec_neg(const T* x, T* result, const unsigned count) +{ + neg_elements_kernel<<<(count - 1) / 256 + 1, 256>>>(x, result, count); + int error = cudaGetLastError(); + return error ? error : cudaDeviceSynchronize(); +} + +template +__global__ void mul_elements_kernel(const F* x, const G* y, G* result, const unsigned count) +{ + const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; + if (gid >= count) return; + F x_gid = x[gid]; + G res = y[gid]; + for (int i = 0; i < N_REP; i++) + res = res * x_gid; + result[gid] = res; +} + +template +int vec_mul(const F* x, const G* y, G* result, const unsigned count) +{ + mul_elements_kernel<<<(count - 1) / 256 + 1, 256>>>(x, y, result, count); + int error = cudaGetLastError(); + return (error || (N_REP > 1)) ? error : cudaDeviceSynchronize(); +} + +template +__global__ void inv_field_elements_kernel(const F* x, F* result, const unsigned count) +{ + const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; + if (gid >= count) return; + result[gid] = F::inverse(x[gid]); +} + +template +int field_vec_inv(const F* x, F* result, const unsigned count) +{ + inv_field_elements_kernel<<<(count - 1) / 256 + 1, 256>>>(x, result, count); + int error = cudaGetLastError(); + return error ? error : cudaDeviceSynchronize(); +} + +template +__global__ void sqr_field_elements_kernel(const F* x, F* result, const unsigned count) +{ + const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; + if (gid >= count) return; + F x_gid = x[gid]; + for (int i = 0; i < N_REP; i++) + x_gid = F::sqr(x_gid); + result[gid] = x_gid; +} + +template +int field_vec_sqr(const F* x, F* result, const unsigned count) +{ + sqr_field_elements_kernel<<<(count - 1) / 256 + 1, 256>>>(x, result, count); + int error = cudaGetLastError(); + return (error || (N_REP > 1)) ? error : cudaDeviceSynchronize(); +} + +template +__global__ void to_affine_points_kernel(const P* x, A* result, const unsigned count) +{ + const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; + if (gid >= count) return; + result[gid] = P::to_affine(x[gid]); +} + +template +int point_vec_to_affine(const P* x, A* result, const unsigned count) +{ + to_affine_points_kernel<<<(count - 1) / 256 + 1, 256>>>(x, result, count); + int error = cudaGetLastError(); + return error ? error : cudaDeviceSynchronize(); +} + +template +int device_populate_random(T* d_elements, unsigned n) +{ + T* h_elements = (T*)malloc(n * sizeof(T)); + for (unsigned i = 0; i < n; i++) + h_elements[i] = T::rand_host(); + return cudaMemcpy(d_elements, h_elements, sizeof(T) * n, cudaMemcpyHostToDevice); +} + +template +int device_set(T* d_elements, T el, unsigned n) +{ + T* h_elements = (T*)malloc(n * sizeof(T)); + for (unsigned i = 0; i < n; i++) + h_elements[i] = el; + return cudaMemcpy(d_elements, h_elements, sizeof(T) * n, cudaMemcpyHostToDevice); +} diff --git a/icicle/utils/utils.h b/icicle/include/utils/utils.h similarity index 82% rename from icicle/utils/utils.h rename to icicle/include/utils/utils.h index 1ec6395a..a4cb4655 100644 --- a/icicle/utils/utils.h +++ b/icicle/include/utils/utils.h @@ -2,7 +2,7 @@ #ifndef ICICLE_UTILS_H #define ICICLE_UTILS_H -#define CONCAT_DIRECT(a, b) a##b +#define CONCAT_DIRECT(a, b) a##_##b #define CONCAT_EXPAND(a, b) CONCAT_DIRECT(a, b) // expand a,b before concatenation #endif // ICICLE_UTILS_H \ No newline at end of file diff --git a/icicle/utils/utils_kernels.cuh b/icicle/include/utils/utils_kernels.cuh similarity index 100% rename from icicle/utils/utils_kernels.cuh rename to icicle/include/utils/utils_kernels.cuh diff --git a/icicle/utils/vec_ops.cuh b/icicle/include/vec_ops/vec_ops.cuh similarity index 86% rename from icicle/utils/vec_ops.cuh rename to icicle/include/vec_ops/vec_ops.cuh index 9fd39a97..f52ab686 100644 --- a/icicle/utils/vec_ops.cuh +++ b/icicle/include/vec_ops/vec_ops.cuh @@ -2,7 +2,7 @@ #ifndef LDE_H #define LDE_H -#include "device_context.cuh" +#include "gpu-utils/device_context.cuh" /** * @namespace vec_ops @@ -14,7 +14,6 @@ namespace vec_ops { * @struct VecOpsConfig * Struct that encodes NTT parameters to be passed into the [NTT](@ref NTT) function. */ - template struct VecOpsConfig { device_context::DeviceContext ctx; /**< Details related to the device such as its id and stream. */ @@ -24,9 +23,6 @@ namespace vec_ops { bool is_result_on_device; /**< If true, output is preserved on device, otherwise on host. Default value: false. */ - bool is_result_montgomery_form; /**< True if `result` vector should be in Montgomery form and false otherwise. - * Default value: false. */ - bool is_async; /**< Whether to run the vector operations asynchronously. If set to `true`, the function will be * non-blocking and you'd need to synchronize it explicitly by running * `cudaStreamSynchronize` or `cudaDeviceSynchronize`. If set to false, the @@ -37,16 +33,14 @@ namespace vec_ops { * A function that returns the default value of [VecOpsConfig](@ref VecOpsConfig). * @return Default value of [VecOpsConfig](@ref VecOpsConfig). */ - template - VecOpsConfig DefaultVecOpsConfig() + static VecOpsConfig + DefaultVecOpsConfig(const device_context::DeviceContext& ctx = device_context::get_default_device_context()) { - device_context::DeviceContext ctx = device_context::get_default_device_context(); - VecOpsConfig config = { + VecOpsConfig config = { ctx, // ctx false, // is_a_on_device false, // is_b_on_device false, // is_result_on_device - false, // is_result_montgomery_form false, // is_async }; return config; @@ -64,7 +58,7 @@ namespace vec_ops { * @return `cudaSuccess` if the execution was successful and an error code otherwise. */ template - cudaError_t Mul(S* vec_a, E* vec_b, int n, VecOpsConfig& config, E* result); + cudaError_t Mul(const S* vec_a, const E* vec_b, int n, VecOpsConfig& config, E* result); /** * A function that adds two vectors element-wise. @@ -79,7 +73,7 @@ namespace vec_ops { * @return `cudaSuccess` if the execution was successful and an error code otherwise. */ template - cudaError_t Add(E* vec_a, E* vec_b, int n, VecOpsConfig& config, E* result); + cudaError_t Add(const E* vec_a, const E* vec_b, int n, VecOpsConfig& config, E* result); /** * A function that subtracts two vectors element-wise. @@ -94,7 +88,7 @@ namespace vec_ops { * @return `cudaSuccess` if the execution was successful and an error code otherwise. */ template - cudaError_t Sub(E* vec_a, E* vec_b, int n, VecOpsConfig& config, E* result); + cudaError_t Sub(const E* vec_a, const E* vec_b, int n, VecOpsConfig& config, E* result); /** * Transposes an input matrix out-of-place inside GPU. diff --git a/icicle/primitives/field.cu b/icicle/primitives/field.cu deleted file mode 100644 index 291ac5a1..00000000 --- a/icicle/primitives/field.cu +++ /dev/null @@ -1,10 +0,0 @@ -#include "curves/curve_config.cuh" -#include "field.cuh" -#include "utils/utils.h" - -using namespace curve_config; - -extern "C" void CONCAT_EXPAND(CURVE, GenerateScalars)(scalar_t* scalars, int size) -{ - scalar_t::RandHostMany(scalars, size); -} diff --git a/icicle/primitives/projective.cu b/icicle/primitives/projective.cu deleted file mode 100644 index b94eb6ba..00000000 --- a/icicle/primitives/projective.cu +++ /dev/null @@ -1,60 +0,0 @@ -#include "curves/curve_config.cuh" -#include "projective.cuh" -#include -#include "utils/utils.h" - -using namespace curve_config; - -extern "C" bool CONCAT_EXPAND(CURVE, Eq)(projective_t* point1, projective_t* point2) -{ - return (*point1 == *point2) && - !((point1->x == point_field_t::zero()) && (point1->y == point_field_t::zero()) && - (point1->z == point_field_t::zero())) && - !((point2->x == point_field_t::zero()) && (point2->y == point_field_t::zero()) && - (point2->z == point_field_t::zero())); -} - -extern "C" void CONCAT_EXPAND(CURVE, ToAffine)(projective_t* point, affine_t* point_out) -{ - *point_out = projective_t::to_affine(*point); -} - -extern "C" void CONCAT_EXPAND(CURVE, GenerateProjectivePoints)(projective_t* points, int size) -{ - projective_t::RandHostMany(points, size); -} - -extern "C" void CONCAT_EXPAND(CURVE, GenerateAffinePoints)(affine_t* points, int size) -{ - projective_t::RandHostManyAffine(points, size); -} - -#if defined(G2_DEFINED) - -using namespace curve_config; - -extern "C" bool CONCAT_EXPAND(CURVE, G2Eq)(g2_projective_t* point1, g2_projective_t* point2) -{ - return (*point1 == *point2) && - !((point1->x == g2_point_field_t::zero()) && (point1->y == g2_point_field_t::zero()) && - (point1->z == g2_point_field_t::zero())) && - !((point2->x == g2_point_field_t::zero()) && (point2->y == g2_point_field_t::zero()) && - (point2->z == g2_point_field_t::zero())); -} - -extern "C" void CONCAT_EXPAND(CURVE, G2ToAffine)(g2_projective_t* point, g2_affine_t* point_out) -{ - *point_out = g2_projective_t::to_affine(*point); -} - -extern "C" void CONCAT_EXPAND(CURVE, G2GenerateProjectivePoints)(g2_projective_t* points, int size) -{ - g2_projective_t::RandHostMany(points, size); -} - -extern "C" void CONCAT_EXPAND(CURVE, G2GenerateAffinePoints)(g2_affine_t* points, int size) -{ - g2_projective_t::RandHostManyAffine(points, size); -} - -#endif diff --git a/icicle/primitives/test_kernels.cuh b/icicle/primitives/test_kernels.cuh deleted file mode 100644 index 5d848eb7..00000000 --- a/icicle/primitives/test_kernels.cuh +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include "curves/curve_config.cuh" - -using namespace curve_config; - -template -__global__ void add_elements_kernel(const T1* x, const T2* y, T1* result, const unsigned count) -{ - const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; - if (gid >= count) return; - result[gid] = x[gid] + y[gid]; -} - -template -int vec_add(const T1* x, const T2* y, T1* result, const unsigned count) -{ - add_elements_kernel<<<(count - 1) / 32 + 1, 32>>>(x, y, result, count); - int error = cudaGetLastError(); - return error ? error : cudaDeviceSynchronize(); -} - -template -__global__ void sub_elements_kernel(const T1* x, const T2* y, T1* result, const unsigned count) -{ - const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; - if (gid >= count) return; - result[gid] = x[gid] - y[gid]; -} - -template -int vec_sub(const T1* x, const T2* y, T1* result, const unsigned count) -{ - sub_elements_kernel<<<(count - 1) / 32 + 1, 32>>>(x, y, result, count); - int error = cudaGetLastError(); - return error ? error : cudaDeviceSynchronize(); -} - -template -__global__ void neg_elements_kernel(const T* x, T* result, const unsigned count) -{ - const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; - if (gid >= count) return; - result[gid] = T::neg(x[gid]); -} - -template -int vec_neg(const T* x, T* result, const unsigned count) -{ - neg_elements_kernel<<<(count - 1) / 32 + 1, 32>>>(x, result, count); - int error = cudaGetLastError(); - return error ? error : cudaDeviceSynchronize(); -} - -template -__global__ void mul_elements_kernel(const F* x, const G* y, G* result, const unsigned count) -{ - const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; - if (gid >= count) return; - result[gid] = x[gid] * y[gid]; -} - -template -int vec_mul(const F* x, const G* y, G* result, const unsigned count) -{ - mul_elements_kernel<<<(count - 1) / 32 + 1, 32>>>(x, y, result, count); - int error = cudaGetLastError(); - return error ? error : cudaDeviceSynchronize(); -} - -__global__ void inv_field_elements_kernel(const scalar_t* x, scalar_t* result, const unsigned count) -{ - const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; - if (gid >= count) return; - result[gid] = scalar_t::inverse(x[gid]); -} - -int field_vec_inv(const scalar_t* x, scalar_t* result, const unsigned count) -{ - inv_field_elements_kernel<<<(count - 1) / 32 + 1, 32>>>(x, result, count); - int error = cudaGetLastError(); - return error ? error : cudaDeviceSynchronize(); -} - -__global__ void sqr_field_elements_kernel(const scalar_t* x, scalar_t* result, const unsigned count) -{ - const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; - if (gid >= count) return; - result[gid] = scalar_t::sqr(x[gid]); -} - -int field_vec_sqr(const scalar_t* x, scalar_t* result, const unsigned count) -{ - sqr_field_elements_kernel<<<(count - 1) / 32 + 1, 32>>>(x, result, count); - int error = cudaGetLastError(); - return error ? error : cudaDeviceSynchronize(); -} - -template -__global__ void to_affine_points_kernel(const P* x, A* result, const unsigned count) -{ - const unsigned gid = blockIdx.x * blockDim.x + threadIdx.x; - if (gid >= count) return; - result[gid] = P::to_affine(x[gid]); -} - -template -int point_vec_to_affine(const P* x, A* result, const unsigned count) -{ - to_affine_points_kernel<<<(count - 1) / 32 + 1, 32>>>(x, result, count); - int error = cudaGetLastError(); - return error ? error : cudaDeviceSynchronize(); -} diff --git a/icicle/src/curves/CMakeLists.txt b/icicle/src/curves/CMakeLists.txt new file mode 100644 index 00000000..ee894bbb --- /dev/null +++ b/icicle/src/curves/CMakeLists.txt @@ -0,0 +1,29 @@ +if (G2) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DG2") +endif () + +set(TARGET icicle_curve) +set(FIELD_TARGET icicle_field) + +set(SRC ${CMAKE_SOURCE_DIR}/src) + +set(CURVE_SOURCE ${SRC}/curves/extern.cu) +if(G2) + list(APPEND CURVE_SOURCE ${SRC}/curves/extern_g2.cu) +endif() +if(MSM) + list(APPEND CURVE_SOURCE ${SRC}/msm/extern.cu) + if(G2) + list(APPEND CURVE_SOURCE ${SRC}/msm/extern_g2.cu) + endif() +endif() +if(ECNTT) + list(APPEND CURVE_SOURCE ${SRC}/ntt/extern_ecntt.cu) + list(APPEND CURVE_SOURCE ${SRC}/ntt/kernel_ntt.cu) +endif() + +add_library(${TARGET} STATIC ${CURVE_SOURCE}) +target_include_directories(${TARGET} PUBLIC ${CMAKE_SOURCE_DIR}/include/) +set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "ingo_curve_${CURVE}") +target_compile_definitions(${TARGET} PUBLIC CURVE=${CURVE}) +target_link_libraries(${TARGET} PRIVATE ${FIELD_TARGET}) \ No newline at end of file diff --git a/icicle/src/curves/extern.cu b/icicle/src/curves/extern.cu new file mode 100644 index 00000000..8ea5bce2 --- /dev/null +++ b/icicle/src/curves/extern.cu @@ -0,0 +1,51 @@ +#include "curves/curve_config.cuh" + +using namespace curve_config; + +#include "gpu-utils/device_context.cuh" +#include "utils/utils.h" +#include "utils/mont.cuh" + +extern "C" bool CONCAT_EXPAND(CURVE, eq)(projective_t* point1, projective_t* point2) +{ + return (*point1 == *point2) && + !((point1->x == point_field_t::zero()) && (point1->y == point_field_t::zero()) && + (point1->z == point_field_t::zero())) && + !((point2->x == point_field_t::zero()) && (point2->y == point_field_t::zero()) && + (point2->z == point_field_t::zero())); +} + +extern "C" void CONCAT_EXPAND(CURVE, to_affine)(projective_t* point, affine_t* point_out) +{ + *point_out = projective_t::to_affine(*point); +} + +extern "C" void CONCAT_EXPAND(CURVE, generate_projective_points)(projective_t* points, int size) +{ + projective_t::rand_host_many(points, size); +} + +extern "C" void CONCAT_EXPAND(CURVE, generate_affine_points)(affine_t* points, int size) +{ + projective_t::rand_host_many_affine(points, size); +} + +extern "C" cudaError_t CONCAT_EXPAND(CURVE, affine_convert_montgomery)( + affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) +{ + if (is_into) { + return mont::to_montgomery(d_inout, n, ctx.stream, d_inout); + } else { + return mont::from_montgomery(d_inout, n, ctx.stream, d_inout); + } +} + +extern "C" cudaError_t CONCAT_EXPAND(CURVE, projective_convert_montgomery)( + projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) +{ + if (is_into) { + return mont::to_montgomery(d_inout, n, ctx.stream, d_inout); + } else { + return mont::from_montgomery(d_inout, n, ctx.stream, d_inout); + } +} diff --git a/icicle/src/curves/extern_g2.cu b/icicle/src/curves/extern_g2.cu new file mode 100644 index 00000000..e8daa5e1 --- /dev/null +++ b/icicle/src/curves/extern_g2.cu @@ -0,0 +1,51 @@ +#include "curves/curve_config.cuh" + +using namespace curve_config; + +#include "gpu-utils/device_context.cuh" +#include "utils/utils.h" +#include "utils/mont.cuh" + +extern "C" bool CONCAT_EXPAND(CURVE, g2_eq)(g2_projective_t* point1, g2_projective_t* point2) +{ + return (*point1 == *point2) && + !((point1->x == g2_point_field_t::zero()) && (point1->y == g2_point_field_t::zero()) && + (point1->z == g2_point_field_t::zero())) && + !((point2->x == g2_point_field_t::zero()) && (point2->y == g2_point_field_t::zero()) && + (point2->z == g2_point_field_t::zero())); +} + +extern "C" void CONCAT_EXPAND(CURVE, g2_to_affine)(g2_projective_t* point, g2_affine_t* point_out) +{ + *point_out = g2_projective_t::to_affine(*point); +} + +extern "C" void CONCAT_EXPAND(CURVE, g2_generate_projective_points)(g2_projective_t* points, int size) +{ + g2_projective_t::rand_host_many(points, size); +} + +extern "C" void CONCAT_EXPAND(CURVE, g2_generate_affine_points)(g2_affine_t* points, int size) +{ + g2_projective_t::rand_host_many_affine(points, size); +} + +extern "C" cudaError_t CONCAT_EXPAND(CURVE, g2_affine_convert_montgomery)( + g2_affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) +{ + if (is_into) { + return mont::to_montgomery(d_inout, n, ctx.stream, d_inout); + } else { + return mont::from_montgomery(d_inout, n, ctx.stream, d_inout); + } +} + +extern "C" cudaError_t CONCAT_EXPAND(CURVE, g2_projective_convert_montgomery)( + g2_projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) +{ + if (is_into) { + return mont::to_montgomery(d_inout, n, ctx.stream, d_inout); + } else { + return mont::from_montgomery(d_inout, n, ctx.stream, d_inout); + } +} \ No newline at end of file diff --git a/icicle/src/fields/CMakeLists.txt b/icicle/src/fields/CMakeLists.txt new file mode 100644 index 00000000..68ab3be2 --- /dev/null +++ b/icicle/src/fields/CMakeLists.txt @@ -0,0 +1,40 @@ +if (EXT_FIELD) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -DEXT_FIELD") +endif () + +SET(SUPPORTED_FIELDS_WITHOUT_NTT grumpkin) + +set(TARGET icicle_field) + +set(SRC ${CMAKE_SOURCE_DIR}/src) + +set(FIELD_SOURCE ${SRC}/fields/extern.cu) +list(APPEND FIELD_SOURCE ${SRC}/vec_ops/extern.cu) +if(EXT_FIELD) + list(APPEND FIELD_SOURCE ${SRC}/fields/extern_extension.cu) + list(APPEND FIELD_SOURCE ${SRC}/ntt/extern_extension.cu) + list(APPEND FIELD_SOURCE ${SRC}/vec_ops/extern_extension.cu) +endif() + +set(POLYNOMIAL_SOURCE_FILES + ${SRC}/polynomials/polynomials.cu + ${SRC}/polynomials/cuda_backend/polynomial_cuda_backend.cu + ${SRC}/polynomials/polynomials_c_api.cu) + +list(APPEND FIELD_SOURCE ${POLYNOMIAL_SOURCE_FILES}) + +# TODO: impl poseidon for small fields. note that it needs to be defined over the extension field! +if (DEFINED CURVE) + list(APPEND FIELD_SOURCE ${SRC}/poseidon/poseidon.cu) + list(APPEND FIELD_SOURCE ${SRC}/poseidon/tree/merkle.cu) +endif() + +if (NOT FIELD IN_LIST SUPPORTED_FIELDS_WITHOUT_NTT) + list(APPEND FIELD_SOURCE ${SRC}/ntt/extern.cu) + list(APPEND FIELD_SOURCE ${SRC}/ntt/kernel_ntt.cu) +endif() + +add_library(${TARGET} STATIC ${FIELD_SOURCE}) +target_include_directories(${TARGET} PUBLIC ${CMAKE_SOURCE_DIR}/include/) +set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "ingo_field_${FIELD}") +target_compile_definitions(${TARGET} PUBLIC FIELD=${FIELD}) diff --git a/icicle/src/fields/extern.cu b/icicle/src/fields/extern.cu new file mode 100644 index 00000000..26c98ac3 --- /dev/null +++ b/icicle/src/fields/extern.cu @@ -0,0 +1,22 @@ +#include "fields/field_config.cuh" + +using namespace field_config; + +#include "utils/mont.cuh" +#include "utils/utils.h" +#include "gpu-utils/device_context.cuh" + +extern "C" void CONCAT_EXPAND(FIELD, generate_scalars)(scalar_t* scalars, int size) +{ + scalar_t::rand_host_many(scalars, size); +} + +extern "C" cudaError_t CONCAT_EXPAND(FIELD, scalar_convert_montgomery)( + scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) +{ + if (is_into) { + return mont::to_montgomery(d_inout, n, ctx.stream, d_inout); + } else { + return mont::from_montgomery(d_inout, n, ctx.stream, d_inout); + } +} diff --git a/icicle/src/fields/extern_extension.cu b/icicle/src/fields/extern_extension.cu new file mode 100644 index 00000000..8bc7bc5e --- /dev/null +++ b/icicle/src/fields/extern_extension.cu @@ -0,0 +1,22 @@ +#include "fields/field_config.cuh" + +using namespace field_config; + +#include "utils/mont.cuh" +#include "utils/utils.h" +#include "gpu-utils/device_context.cuh" + +extern "C" void CONCAT_EXPAND(FIELD, extension_generate_scalars)(extension_t* scalars, int size) +{ + extension_t::rand_host_many(scalars, size); +} + +extern "C" cudaError_t CONCAT_EXPAND(FIELD, extension_scalar_convert_montgomery)( + extension_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) +{ + if (is_into) { + return mont::to_montgomery(d_inout, n, ctx.stream, d_inout); + } else { + return mont::from_montgomery(d_inout, n, ctx.stream, d_inout); + } +} diff --git a/icicle/src/hash/CMakeLists.txt b/icicle/src/hash/CMakeLists.txt new file mode 100644 index 00000000..0b70083b --- /dev/null +++ b/icicle/src/hash/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET icicle_hash) + +add_library(${TARGET} STATIC keccak/keccak.cu) +target_include_directories(${TARGET} PUBLIC ${CMAKE_SOURCE_DIR}/include/) +set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "ingo_hash") \ No newline at end of file diff --git a/icicle/appUtils/keccak/Makefile b/icicle/src/hash/keccak/Makefile similarity index 100% rename from icicle/appUtils/keccak/Makefile rename to icicle/src/hash/keccak/Makefile diff --git a/icicle/appUtils/keccak/keccak.cu b/icicle/src/hash/keccak/keccak.cu similarity index 98% rename from icicle/appUtils/keccak/keccak.cu rename to icicle/src/hash/keccak/keccak.cu index 18b29968..572a96cf 100644 --- a/icicle/appUtils/keccak/keccak.cu +++ b/icicle/src/hash/keccak/keccak.cu @@ -1,4 +1,4 @@ -#include "keccak.cuh" +#include "hash/keccak/keccak.cuh" namespace keccak { #define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y)))) @@ -262,13 +262,13 @@ namespace keccak { } extern "C" cudaError_t - Keccak256(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, KeccakConfig config) + keccak256_cuda(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, KeccakConfig config) { return keccak_hash<512, 256>(input, input_block_size, number_of_blocks, output, config); } extern "C" cudaError_t - Keccak512(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, KeccakConfig config) + keccak512_cuda(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, KeccakConfig config) { return keccak_hash<1024, 512>(input, input_block_size, number_of_blocks, output, config); } diff --git a/icicle/appUtils/keccak/test.cu b/icicle/src/hash/keccak/test.cu similarity index 91% rename from icicle/appUtils/keccak/test.cu rename to icicle/src/hash/keccak/test.cu index a58d49fd..85e09f35 100644 --- a/icicle/appUtils/keccak/test.cu +++ b/icicle/src/hash/keccak/test.cu @@ -18,7 +18,7 @@ using namespace keccak; #define END_TIMER(timer, msg) \ printf("%s: %.0f ms\n", msg, FpMilliseconds(std::chrono::high_resolution_clock::now() - timer##_start).count()); -void uint8ToHexString(const uint8_t* values, int size) +void uint8_to_hex_string(const uint8_t* values, int size) { std::stringstream ss; @@ -51,12 +51,12 @@ int main(int argc, char* argv[]) START_TIMER(keccak_timer); KeccakConfig config = default_keccak_config(); - Keccak256(in_ptr, input_block_size, number_of_blocks, out_ptr, config); + keccak256(in_ptr, input_block_size, number_of_blocks, out_ptr, config); END_TIMER(keccak_timer, "Keccak") for (int i = 0; i < number_of_blocks; i++) { #ifdef DEBUG - uint8ToHexString(out_ptr + i * (D / 8), D / 8); + uint8_to_hex_string(out_ptr + i * (D / 8), D / 8); #endif } diff --git a/icicle/src/hash/keccak/test_keccak b/icicle/src/hash/keccak/test_keccak new file mode 100755 index 0000000000000000000000000000000000000000..a2c4b13e36a0ebcdbb8acbb1a74281f37311a9fd GIT binary patch literal 1052896 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�H+FBq!yjX^GD?B~T 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%0A8bf?k+} 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 + bases, bases_size, precompute_factor, _c, are_bases_on_device, ctx, output_bases); + } + + /** + * Extern "C" version of [msm](@ref msm) function with the following values of template parameters + * (where the curve is given by `-DCURVE` env variable during build): + * - `S` is the [scalar field](@ref scalar_t) of the curve; + * - `A` is the [affine representation](@ref affine_t) of curve points; + * - `P` is the [projective representation](@ref projective_t) of curve points. + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(CURVE, msm_cuda)( + const scalar_t* scalars, const affine_t* points, int msm_size, MSMConfig& config, projective_t* out) + { + return msm(scalars, points, msm_size, config, out); + } +} // namespace msm \ No newline at end of file diff --git a/icicle/src/msm/extern_g2.cu b/icicle/src/msm/extern_g2.cu new file mode 100644 index 00000000..077fb65d --- /dev/null +++ b/icicle/src/msm/extern_g2.cu @@ -0,0 +1,43 @@ +#include "curves/curve_config.cuh" +#include "fields/field_config.cuh" + +using namespace curve_config; +using namespace field_config; + +#include "msm.cu" +#include "utils/utils.h" + +namespace msm { + /** + * Extern "C" version of [precompute_msm_bases](@ref precompute_msm_bases) function with the following values of + * template parameters (where the curve is given by `-DCURVE` env variable during build): + * - `A` is the [affine representation](@ref g2_affine_t) of G2 curve points; + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(CURVE, g2_precompute_msm_bases_cuda)( + g2_affine_t* bases, + int bases_size, + int precompute_factor, + int _c, + bool are_bases_on_device, + device_context::DeviceContext& ctx, + g2_affine_t* output_bases) + { + return precompute_msm_bases( + bases, bases_size, precompute_factor, _c, are_bases_on_device, ctx, output_bases); + } + + /** + * Extern "C" version of [msm](@ref msm) function with the following values of template parameters + * (where the curve is given by `-DCURVE` env variable during build): + * - `S` is the [scalar field](@ref scalar_t) of the curve; + * - `A` is the [affine representation](@ref g2_affine_t) of G2 curve points; + * - `P` is the [projective representation](@ref g2_projective_t) of G2 curve points. + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(CURVE, g2_msm_cuda)( + const scalar_t* scalars, const g2_affine_t* points, int msm_size, MSMConfig& config, g2_projective_t* out) + { + return msm(scalars, points, msm_size, config, out); + } +} // namespace msm \ No newline at end of file diff --git a/icicle/appUtils/msm/msm.cu b/icicle/src/msm/msm.cu similarity index 90% rename from icicle/appUtils/msm/msm.cu rename to icicle/src/msm/msm.cu index 4c4068bb..449bb901 100644 --- a/icicle/appUtils/msm/msm.cu +++ b/icicle/src/msm/msm.cu @@ -1,4 +1,4 @@ -#include "msm.cuh" +#include "msm/msm.cuh" #include #include @@ -10,13 +10,11 @@ #include #include -#include "curves/curve_config.cuh" -#include "primitives/affine.cuh" -#include "primitives/field.cuh" -#include "primitives/projective.cuh" -#include "utils/error_handler.cuh" +#include "curves/affine.cuh" +#include "curves/projective.cuh" +#include "fields/field.cuh" +#include "gpu-utils/error_handler.cuh" #include "utils/mont.cuh" -#include "utils/utils.h" namespace msm { @@ -401,13 +399,13 @@ namespace msm { cudaMemcpyAsync(d_allocated_scalars, scalars, sizeof(S) * nof_scalars, cudaMemcpyHostToDevice, stream)); if (are_scalars_montgomery_form) { - CHK_IF_RETURN(mont::FromMontgomery(d_allocated_scalars, nof_scalars, stream, d_allocated_scalars)); + CHK_IF_RETURN(mont::from_montgomery(d_allocated_scalars, nof_scalars, stream, d_allocated_scalars)); } d_scalars = d_allocated_scalars; } else { // already on device if (are_scalars_montgomery_form) { CHK_IF_RETURN(cudaMallocAsync(&d_allocated_scalars, sizeof(S) * nof_scalars, stream)); - CHK_IF_RETURN(mont::FromMontgomery(scalars, nof_scalars, stream, d_allocated_scalars)); + CHK_IF_RETURN(mont::from_montgomery(scalars, nof_scalars, stream, d_allocated_scalars)); d_scalars = d_allocated_scalars; } else { d_scalars = scalars; @@ -510,13 +508,13 @@ namespace msm { cudaMemcpyAsync(d_allocated_points, points, sizeof(A) * nof_points, cudaMemcpyHostToDevice, stream_points)); if (are_points_montgomery_form) { - CHK_IF_RETURN(mont::FromMontgomery(d_allocated_points, nof_points, stream_points, d_allocated_points)); + CHK_IF_RETURN(mont::from_montgomery(d_allocated_points, nof_points, stream_points, d_allocated_points)); } d_points = d_allocated_points; } else { // already on device if (are_points_montgomery_form) { CHK_IF_RETURN(cudaMallocAsync(&d_allocated_points, sizeof(A) * nof_points, stream_points)); - CHK_IF_RETURN(mont::FromMontgomery(points, nof_points, stream_points, d_allocated_points)); + CHK_IF_RETURN(mont::from_montgomery(points, nof_points, stream_points, d_allocated_points)); d_points = d_allocated_points; } else { d_points = points; @@ -830,31 +828,8 @@ namespace msm { } } // namespace - template - MSMConfig DefaultMSMConfig() - { - device_context::DeviceContext ctx = device_context::get_default_device_context(); - MSMConfig config = { - ctx, // ctx - 0, // points_size - 1, // precompute_factor - 0, // c - 0, // bitsize - 10, // large_bucket_factor - 1, // batch_size - false, // are_scalars_on_device - false, // are_scalars_montgomery_form - false, // are_points_on_device - false, // are_points_montgomery_form - false, // are_results_on_device - false, // is_big_triangle - false, // is_async - }; - return config; - } - template - cudaError_t MSM(const S* scalars, const A* points, int msm_size, MSMConfig& config, P* results) + cudaError_t msm(const S* scalars, const A* points, int msm_size, MSMConfig& config, P* results) { const int bitsize = (config.bitsize == 0) ? S::NBITS : config.bitsize; cudaStream_t& stream = config.ctx.stream; @@ -876,7 +851,7 @@ namespace msm { } template - cudaError_t PrecomputeMSMBases( + cudaError_t precompute_msm_bases( A* bases, int bases_size, int precompute_factor, @@ -906,85 +881,4 @@ namespace msm { return CHK_LAST(); } - - /** - * Extern "C" version of [PrecomputeMSMBases](@ref PrecomputeMSMBases) function with the following values of - * template parameters (where the curve is given by `-DCURVE` env variable during build): - * - `A` is the [affine representation](@ref affine_t) of curve points; - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, PrecomputeMSMBases)( - curve_config::affine_t* bases, - int bases_size, - int precompute_factor, - int _c, - bool are_bases_on_device, - device_context::DeviceContext& ctx, - curve_config::affine_t* output_bases) - { - return PrecomputeMSMBases( - bases, bases_size, precompute_factor, _c, are_bases_on_device, ctx, output_bases); - } - - /** - * Extern "C" version of [MSM](@ref MSM) function with the following values of template parameters - * (where the curve is given by `-DCURVE` env variable during build): - * - `S` is the [scalar field](@ref scalar_t) of the curve; - * - `A` is the [affine representation](@ref affine_t) of curve points; - * - `P` is the [projective representation](@ref projective_t) of curve points. - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, MSMCuda)( - const curve_config::scalar_t* scalars, - const curve_config::affine_t* points, - int msm_size, - MSMConfig& config, - curve_config::projective_t* out) - { - return MSM( - scalars, points, msm_size, config, out); - } - -#if defined(G2_DEFINED) - - /** - * Extern "C" version of [PrecomputeMSMBases](@ref PrecomputeMSMBases) function with the following values of - * template parameters (where the curve is given by `-DCURVE` env variable during build): - * - `A` is the [affine representation](@ref g2_affine_t) of G2 curve points; - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, G2PrecomputeMSMBases)( - curve_config::g2_affine_t* bases, - int bases_size, - int precompute_factor, - int _c, - bool are_bases_on_device, - device_context::DeviceContext& ctx, - curve_config::g2_affine_t* output_bases) - { - return PrecomputeMSMBases( - bases, bases_size, precompute_factor, _c, are_bases_on_device, ctx, output_bases); - } - - /** - * Extern "C" version of [MSM](@ref MSM) function with the following values of template parameters - * (where the curve is given by `-DCURVE` env variable during build): - * - `S` is the [scalar field](@ref scalar_t) of the curve; - * - `A` is the [affine representation](@ref g2_affine_t) of G2 curve points; - * - `P` is the [projective representation](@ref g2_projective_t) of G2 curve points. - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, G2MSMCuda)( - const curve_config::scalar_t* scalars, - const curve_config::g2_affine_t* points, - int msm_size, - MSMConfig& config, - curve_config::g2_projective_t* out) - { - return MSM( - scalars, points, msm_size, config, out); - } - -#endif - -} // namespace msm +} // namespace msm \ No newline at end of file diff --git a/icicle/appUtils/msm/tests/msm_test.cu b/icicle/src/msm/tests/msm_test.cu similarity index 91% rename from icicle/appUtils/msm/tests/msm_test.cu rename to icicle/src/msm/tests/msm_test.cu index 1f9ead64..ad8a33d7 100644 --- a/icicle/appUtils/msm/tests/msm_test.cu +++ b/icicle/src/msm/tests/msm_test.cu @@ -1,15 +1,15 @@ -#define CURVE_ID 1 - #include "msm.cu" #include #include #include -#include "curves/curve_config.cuh" -#include "primitives/field.cuh" -#include "primitives/projective.cuh" -#include "utils/device_context.cuh" +#include "curves/params/bn254.cuh" +#include "fields/field.cuh" +#include "curves/projective.cuh" +#include "gpu-utils/device_context.cuh" + +using namespace bn254; class Dummy_Scalar { @@ -111,9 +111,9 @@ public: // switch between dummy and real: -typedef curve_config::scalar_t test_scalar; -typedef curve_config::projective_t test_projective; -typedef curve_config::affine_t test_affine; +typedef scalar_t test_scalar; +typedef projective_t test_projective; +typedef affine_t test_affine; // typedef Dummy_Scalar test_scalar; // typedef Dummy_Projective test_projective; @@ -129,14 +129,14 @@ int main() test_scalar* scalars = new test_scalar[N]; test_affine* points = new test_affine[N]; - test_scalar::RandHostMany(scalars, N); - test_projective::RandHostManyAffine(points, N); + test_scalar::rand_host_many(scalars, N); + test_projective::rand_host_many_affine(points, N); std::cout << "finished generating" << std::endl; // projective_t *short_res = (projective_t*)malloc(sizeof(projective_t)); // test_projective *large_res = (test_projective*)malloc(sizeof(test_projective)); - test_projective large_res[batch_size]; + test_projective large_res[2]; // test_projective batched_large_res[batch_size]; // fake_point *large_res = (fake_point*)malloc(sizeof(fake_point)); // fake_point batched_large_res[256]; @@ -187,7 +187,7 @@ int main() }; auto begin1 = std::chrono::high_resolution_clock::now(); - msm::MSM(scalars, points, msm_size, config, large_res_d); + msm::msm(scalars, points, msm_size, config, large_res_d); cudaEvent_t msm_end_event; cudaEventCreate(&msm_end_event); auto end1 = std::chrono::high_resolution_clock::now(); @@ -195,9 +195,11 @@ int main() printf("No Big Triangle : %.3f seconds.\n", elapsed1.count() * 1e-9); config.is_big_triangle = true; config.are_results_on_device = false; - std::cout << test_projective::to_affine(large_res[0]) << std::endl; + cudaMemcpy(&large_res[1], large_res_d, sizeof(test_projective), cudaMemcpyDeviceToHost); + std::cout << test_projective::to_affine(large_res[1]) << " " << test_projective::is_on_curve(large_res[1]) + << std::endl; auto begin = std::chrono::high_resolution_clock::now(); - msm::MSM(scalars_d, points_d, msm_size, config, large_res); + msm::msm(scalars_d, points_d, msm_size, config, large_res); // test_reduce_triangle(scalars); // test_reduce_rectangle(scalars); // test_reduce_single(scalars); @@ -208,10 +210,6 @@ int main() cudaStreamSynchronize(stream); cudaStreamDestroy(stream); - std::cout << test_projective::to_affine(large_res[0]) << std::endl; - - cudaMemcpy(&large_res[1], large_res_d, sizeof(test_projective), cudaMemcpyDeviceToHost); - // reference_msm(scalars, points, msm_size); // std::cout<<"final results batched large"<& config, scalar_t* output) + { + return ntt(input, size, dir, config, output); + } + + /** + * Extern "C" version of [release_domain](@ref release_domain) function with the following values of template + * parameters (where the field is given by `-DFIELD` env variable during build): + * - `S` is the [field](@ref scalar_t) - either a scalar field of the elliptic curve or a + * stand-alone "STARK field"; + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(FIELD, release_domain)(device_context::DeviceContext& ctx) + { + return release_domain(ctx); + } + + /** + * Extern "C" version of [get_root_of_unity](@ref get_root_of_unity) function with the following + * value of template parameter (where the field is given by `-DFIELD` env variable during build): + * - `S` is the [field](@ref scalar_t) - either a scalar field of the elliptic curve or a + * stand-alone "STARK field"; + */ + extern "C" scalar_t CONCAT_EXPAND(FIELD, get_root_of_unity)(uint32_t logn) + { + return get_root_of_unity(logn); + } +} // namespace ntt \ No newline at end of file diff --git a/icicle/src/ntt/extern_ecntt.cu b/icicle/src/ntt/extern_ecntt.cu new file mode 100644 index 00000000..72a4e42b --- /dev/null +++ b/icicle/src/ntt/extern_ecntt.cu @@ -0,0 +1,25 @@ +#include "curves/curve_config.cuh" +#include "fields/field_config.cuh" + +using namespace curve_config; +using namespace field_config; + +#include "ntt.cu" + +#include "gpu-utils/device_context.cuh" +#include "utils/utils.h" + +namespace ntt { + /** + * Extern "C" version of [ntt](@ref ntt) function with the following values of template parameters + * (where the curve is given by `-DCURVE` env variable during build): + * - `S` is the [projective representation](@ref projective_t) of the curve (i.e. EC NTT is computed); + * - `E` is the [scalar field](@ref scalar_t) of the curve; + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(CURVE, ecntt_cuda)( + const projective_t* input, int size, NTTDir dir, NTTConfig& config, projective_t* output) + { + return ntt(input, size, dir, config, output); + } +} // namespace ntt diff --git a/icicle/src/ntt/extern_extension.cu b/icicle/src/ntt/extern_extension.cu new file mode 100644 index 00000000..5def1ddd --- /dev/null +++ b/icicle/src/ntt/extern_extension.cu @@ -0,0 +1,23 @@ +#include "fields/field_config.cuh" + +using namespace field_config; + +#include "ntt.cu" + +#include "gpu-utils/device_context.cuh" +#include "utils/utils.h" + +namespace ntt { + /** + * Extern "C" version of [ntt](@ref ntt) function with the following values of template parameters + * (where the field is given by `-DFIELD` env variable during build): + * - `E` is the [field](@ref scalar_t); + * - `S` is the [extension](@ref extension_t) of `E` of appropriate degree; + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(FIELD, extension_ntt_cuda)( + const extension_t* input, int size, NTTDir dir, NTTConfig& config, extension_t* output) + { + return ntt(input, size, dir, config, output); + } +} // namespace ntt diff --git a/icicle/appUtils/ntt/kernel_ntt.cu b/icicle/src/ntt/kernel_ntt.cu similarity index 96% rename from icicle/appUtils/ntt/kernel_ntt.cu rename to icicle/src/ntt/kernel_ntt.cu index 5022a083..0c63fe08 100644 --- a/icicle/appUtils/ntt/kernel_ntt.cu +++ b/icicle/src/ntt/kernel_ntt.cu @@ -1,8 +1,10 @@ +#include "fields/field_config.cuh" -#include "appUtils/ntt/thread_ntt.cu" -#include "curves/curve_config.cuh" -#include "utils/sharedmem.cuh" -#include "appUtils/ntt/ntt.cuh" // for ntt::Ordering +using namespace field_config; + +#include "thread_ntt.cu" +#include "gpu-utils/sharedmem.cuh" +#include "ntt/ntt.cuh" // for ntt::Ordering namespace mxntt { @@ -998,27 +1000,27 @@ namespace mxntt { // Explicit instantiation for scalar type template cudaError_t generate_external_twiddles_generic( - const curve_config::scalar_t& basic_root, - curve_config::scalar_t* external_twiddles, - curve_config::scalar_t*& internal_twiddles, - curve_config::scalar_t*& basic_twiddles, + const scalar_t& basic_root, + scalar_t* external_twiddles, + scalar_t*& internal_twiddles, + scalar_t*& basic_twiddles, uint32_t log_size, cudaStream_t& stream); template cudaError_t generate_external_twiddles_fast_twiddles_mode( - const curve_config::scalar_t& basic_root, - curve_config::scalar_t* external_twiddles, - curve_config::scalar_t*& internal_twiddles, - curve_config::scalar_t*& basic_twiddles, + const scalar_t& basic_root, + scalar_t* external_twiddles, + scalar_t*& internal_twiddles, + scalar_t*& basic_twiddles, uint32_t log_size, cudaStream_t& stream); - template cudaError_t mixed_radix_ntt( - const curve_config::scalar_t* d_input, - curve_config::scalar_t* d_output, - curve_config::scalar_t* external_twiddles, - curve_config::scalar_t* internal_twiddles, - curve_config::scalar_t* basic_twiddles, + template cudaError_t mixed_radix_ntt( + const scalar_t* d_input, + scalar_t* d_output, + scalar_t* external_twiddles, + scalar_t* internal_twiddles, + scalar_t* basic_twiddles, int ntt_size, int max_logn, int batch_size, @@ -1026,17 +1028,37 @@ namespace mxntt { bool is_inverse, bool fast_tw, ntt::Ordering ordering, - curve_config::scalar_t* arbitrary_coset, + scalar_t* arbitrary_coset, int coset_gen_index, cudaStream_t cuda_stream); + +#if defined(EXT_FIELD) + template cudaError_t mixed_radix_ntt( + const extension_t* d_input, + extension_t* d_output, + scalar_t* external_twiddles, + scalar_t* internal_twiddles, + scalar_t* basic_twiddles, + int ntt_size, + int max_logn, + int batch_size, + bool columns_batch, + bool is_inverse, + bool fast_tw, + ntt::Ordering ordering, + scalar_t* arbitrary_coset, + int coset_gen_index, + cudaStream_t cuda_stream); +#endif + // TODO: we may reintroduce mixed-radix ECNTT based on upcoming benching PR - // #if defined(ECNTT_DEFINED) - // template cudaError_t mixed_radix_ntt( - // curve_config::projective_t* d_input, - // curve_config::projective_t* d_output, - // curve_config::scalar_t* external_twiddles, - // curve_config::scalar_t* internal_twiddles, - // curve_config::scalar_t* basic_twiddles, + // #if defined(ECNTT) + // template cudaError_t mixed_radix_ntt( + // projective_t* d_input, + // projective_t* d_output, + // scalar_t* external_twiddles, + // scalar_t* internal_twiddles, + // scalar_t* basic_twiddles, // int ntt_size, // int max_logn, // int batch_size, @@ -1044,8 +1066,8 @@ namespace mxntt { // bool is_inverse, // bool fast_tw, // ntt::Ordering ordering, - // curve_config::scalar_t* arbitrary_coset, + // scalar_t* arbitrary_coset, // int coset_gen_index, // cudaStream_t cuda_stream); - // #endif // ECNTT_DEFINED + // #endif // ECNTT } // namespace mxntt diff --git a/icicle/appUtils/ntt/ntt.cu b/icicle/src/ntt/ntt.cu similarity index 89% rename from icicle/appUtils/ntt/ntt.cu rename to icicle/src/ntt/ntt.cu index 8a52b02d..46781bf4 100644 --- a/icicle/appUtils/ntt/ntt.cu +++ b/icicle/src/ntt/ntt.cu @@ -1,19 +1,27 @@ -#include "ntt.cuh" +#include "fields/field_config.cuh" + +using namespace field_config; + +#include "ntt/ntt.cuh" #include #include #include -#include "curves/curve_config.cuh" -#include "utils/sharedmem.cuh" +#include "gpu-utils/sharedmem.cuh" #include "utils/utils_kernels.cuh" #include "utils/utils.h" -#include "appUtils/ntt/ntt_impl.cuh" -#include "appUtils/ntt/ntt.cuh" // for ntt::Ordering +#include "ntt/ntt_impl.cuh" #include -#define IS_ECNTT std::is_same_v +#ifdef CURVE_ID +#include "curves/curve_config.cuh" +using namespace curve_config; +#define IS_ECNTT std::is_same_v +#else +#define IS_ECNTT false +#endif namespace ntt { @@ -22,7 +30,8 @@ namespace ntt { const uint32_t MAX_NUM_THREADS = 512; // TODO: hotfix - should be 1024, currently limits shared memory size const uint32_t MAX_THREADS_BATCH = 512; const uint32_t MAX_THREADS_BATCH_ECNTT = - 256; // TODO: hardcodded - allows (2^18 x 64) ECNTT for sm86, decrease this to allow larger batch or ecntt length + 128; // TODO: hardcoded - allows (2^18 x 64) ECNTT for sm86, decrease this to allow larger ecntt length, batch + // size limited by on-device memory const uint32_t MAX_SHARED_MEM_ELEMENT_SIZE = 32; // TODO: occupancy calculator, hardcoded for sm_86..sm_89 const uint32_t MAX_SHARED_MEM = MAX_SHARED_MEM_ELEMENT_SIZE * MAX_NUM_THREADS; @@ -369,7 +378,7 @@ namespace ntt { /** * @struct Domain * Struct containing information about the domain on which (i)NTT is evaluated i.e. twiddle factors. - * Twiddle factors are private, static and can only be set using [InitDomain](@ref InitDomain) function. + * Twiddle factors are private, static and can only be set using [init_domain](@ref init_domain) function. * The internal representation of twiddles is prone to change in accordance with changing [NTT](@ref NTT) algorithm. * @tparam S The type of twiddle factors \f$ \{ \omega^i \} \f$. Must be a field. */ @@ -378,7 +387,7 @@ namespace ntt { { // Mutex for protecting access to the domain/device container array static inline std::mutex device_domain_mutex; - // The domain-per-device container - assumption is InitDomain is called once per device per program. + // The domain-per-device container - assumption is init_domain is called once per device per program. int max_size = 0; int max_log_size = 0; @@ -399,20 +408,26 @@ namespace ntt { public: template - friend cudaError_t InitDomain(U primitive_root, device_context::DeviceContext& ctx, bool fast_tw); + friend cudaError_t init_domain(U primitive_root, device_context::DeviceContext& ctx, bool fast_tw); template - friend cudaError_t ReleaseDomain(device_context::DeviceContext& ctx); + friend cudaError_t release_domain(device_context::DeviceContext& ctx); + + template + friend U get_root_of_unity(uint64_t logn, device_context::DeviceContext& ctx); + + template + friend U get_root_of_unity_from_domain(uint64_t logn, device_context::DeviceContext& ctx); template - friend cudaError_t NTT(const E* input, int size, NTTDir dir, NTTConfig& config, E* output); + friend cudaError_t ntt(const E* input, int size, NTTDir dir, NTTConfig& config, E* output); }; template static inline Domain domains_for_devices[device_context::MAX_DEVICES] = {}; template - cudaError_t InitDomain(S primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode) + cudaError_t init_domain(S primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode) { CHK_INIT_IF_RETURN(); @@ -496,7 +511,7 @@ namespace ntt { } template - cudaError_t ReleaseDomain(device_context::DeviceContext& ctx) + cudaError_t release_domain(device_context::DeviceContext& ctx) { CHK_INIT_IF_RETURN(); @@ -528,6 +543,32 @@ namespace ntt { return CHK_LAST(); } + template + S get_root_of_unity(uint64_t max_size) + { + // ceil up + const auto log_max_size = static_cast(std::ceil(std::log2(max_size))); + return S::omega(log_max_size); + } + // explicit instantiation to avoid having to include this file + template scalar_t get_root_of_unity(uint64_t logn); + + template + S get_root_of_unity_from_domain(uint64_t logn, device_context::DeviceContext& ctx) + { + Domain& domain = domains_for_devices[ctx.device_id]; + if (logn > domain.max_log_size) { + std::ostringstream oss; + oss << "NTT log_size=" << logn + << " is too large for the domain. Consider generating your domain with a higher order root of unity.\n"; + THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, oss.str().c_str()); + } + const size_t twiddles_idx = 1ULL << (domain.max_log_size - logn); + return domain.twiddles[twiddles_idx]; + } + // explicit instantiation to avoid having to include this file + template scalar_t get_root_of_unity_from_domain(uint64_t logn, device_context::DeviceContext& ctx); + template static bool is_choosing_radix2_algorithm(int logn, int batch_size, const NTTConfig& config) { @@ -588,7 +629,6 @@ namespace ntt { break; case Ordering::kRN: case Ordering::kMN: - dit = true; reverse_input = false; } @@ -602,7 +642,7 @@ namespace ntt { } template - cudaError_t NTT(const E* input, int size, NTTDir dir, NTTConfig& config, E* output) + cudaError_t ntt(const E* input, int size, NTTDir dir, NTTConfig& config, E* output) { CHK_INIT_IF_RETURN(); @@ -706,9 +746,8 @@ namespace ntt { } template - NTTConfig DefaultNTTConfig() + NTTConfig default_ntt_config(const device_context::DeviceContext& ctx) { - device_context::DeviceContext ctx = device_context::get_default_device_context(); NTTConfig config = { ctx, // ctx S::one(), // coset_gen @@ -722,63 +761,6 @@ namespace ntt { }; return config; } - - /** - * Extern "C" version of [InitDomain](@ref InitDomain) function with the following - * value of template parameter (where the curve is given by `-DCURVE` env variable during build): - * - `S` is the [scalar field](@ref scalar_t) of the curve; - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, InitializeDomain)( - curve_config::scalar_t* primitive_root, device_context::DeviceContext& ctx, bool fast_twiddles_mode) - { - return InitDomain(*primitive_root, ctx, fast_twiddles_mode); - } - - /** - * Extern "C" version of [NTT](@ref NTT) function with the following values of template parameters - * (where the curve is given by `-DCURVE` env variable during build): - * - `S` and `E` are both the [scalar field](@ref scalar_t) of the curve; - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, NTTCuda)( - const curve_config::scalar_t* input, - int size, - NTTDir dir, - NTTConfig& config, - curve_config::scalar_t* output) - { - return NTT(input, size, dir, config, output); - } - - /** - * Extern "C" version of [ReleaseDomain](@ref ReleaseDomain) function with the following values of template parameters - * (where the curve is given by `-DCURVE` env variable during build): - * - `S` is the [scalar field](@ref scalar_t) of the curve; - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, ReleaseDomain)(device_context::DeviceContext& ctx) - { - return ReleaseDomain(ctx); - } - -#if defined(ECNTT_DEFINED) - /** - * Extern "C" version of [NTT](@ref NTT) function with the following values of template parameters - * (where the curve is given by `-DCURVE` env variable during build): - * - `S` is the [projective representation](@ref projective_t) of the curve (i.e. EC NTT is computed); - * - `E` is the [scalar field](@ref scalar_t) of the curve; - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, ECNTTCuda)( - const curve_config::projective_t* input, - int size, - NTTDir dir, - NTTConfig& config, - curve_config::projective_t* output) - { - return NTT(input, size, dir, config, output); - } - -#endif - + // explicit instantiation to avoid having to include this file + template NTTConfig default_ntt_config(const device_context::DeviceContext& ctx); } // namespace ntt \ No newline at end of file diff --git a/icicle/appUtils/ntt/tests/verification.cu b/icicle/src/ntt/tests/verification.cu similarity index 90% rename from icicle/appUtils/ntt/tests/verification.cu rename to icicle/src/ntt/tests/verification.cu index ccf94714..21e143a0 100644 --- a/icicle/appUtils/ntt/tests/verification.cu +++ b/icicle/src/ntt/tests/verification.cu @@ -1,26 +1,26 @@ +#include "fields/id.h" +#define FIELD_ID BN254 -#define CURVE_ID BLS12_381 +#ifdef ECNTT +#define CURVE_ID BN254 +#include "curves/curve_config.cuh" +typedef field_config::scalar_t test_scalar; +typedef curve_config::projective_t test_data; +#else +#include "fields/field_config.cuh" +typedef field_config::scalar_t test_scalar; +typedef field_config::scalar_t test_data; +#endif -#include "primitives/field.cuh" -#include "primitives/projective.cuh" +#include "fields/field.cuh" +#include "curves/projective.cuh" #include #include #include -#include "curves/curve_config.cuh" -#include "ntt/ntt.cu" -#include "ntt/ntt_impl.cuh" -#include - -#ifdef ECNTT_DEFINED -typedef curve_config::scalar_t test_scalar; -typedef curve_config::projective_t test_data; -#else -typedef curve_config::scalar_t test_scalar; -typedef curve_config::scalar_t test_data; -#endif - +#include "ntt.cu" #include "kernel_ntt.cu" +#include void random_samples(test_data* res, uint32_t count) { @@ -72,7 +72,7 @@ int main(int argc, char** argv) CHK_IF_RETURN(cudaFree(nullptr)); // init GPU context (warmup) // init domain - auto ntt_config = ntt::DefaultNTTConfig(); + auto ntt_config = ntt::default_ntt_config(); ntt_config.ordering = ordering; ntt_config.are_inputs_on_device = true; ntt_config.are_outputs_on_device = true; @@ -85,8 +85,8 @@ int main(int argc, char** argv) CHK_IF_RETURN(cudaEventCreate(&new_stop)); auto start = std::chrono::high_resolution_clock::now(); - const test_scalar basic_root = test_scalar::omega(NTT_LOG_SIZE); - ntt::InitDomain(basic_root, ntt_config.ctx, FAST_TW); + const scalar_t basic_root = test_scalar::omega(NTT_LOG_SIZE); + ntt::init_domain(basic_root, ntt_config.ctx, FAST_TW); auto stop = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(stop - start).count(); std::cout << "initDomain took: " << duration / 1000 << " MS" << std::endl; @@ -97,8 +97,8 @@ int main(int argc, char** argv) auto CpuOutputNew = std::make_unique(NTT_SIZE * BATCH_SIZE); // gpu allocation - test_data *GpuScalars, *GpuOutputOld, *GpuOutputNew; - test_data* GpuScalarsTransposed; + scalar_t *GpuScalars, *GpuOutputOld, *GpuOutputNew; + scalar_t* GpuScalarsTransposed; CHK_IF_RETURN(cudaMalloc(&GpuScalars, sizeof(test_data) * NTT_SIZE * BATCH_SIZE)); CHK_IF_RETURN(cudaMalloc(&GpuScalarsTransposed, sizeof(test_data) * NTT_SIZE * BATCH_SIZE)); CHK_IF_RETURN(cudaMalloc(&GpuOutputOld, sizeof(test_data) * NTT_SIZE * BATCH_SIZE)); @@ -131,7 +131,7 @@ int main(int argc, char** argv) CHK_IF_RETURN(cudaEventRecord(new_start, ntt_config.ctx.stream)); ntt_config.ntt_algorithm = ntt::NttAlgorithm::MixedRadix; for (size_t i = 0; i < iterations; i++) { - CHK_IF_RETURN(ntt::NTT( + CHK_IF_RETURN(ntt::ntt( INPLACE ? GpuOutputNew : COLUMNS_BATCH ? GpuScalarsTransposed : GpuScalars, @@ -146,7 +146,7 @@ int main(int argc, char** argv) ntt_config.ntt_algorithm = ntt::NttAlgorithm::Radix2; for (size_t i = 0; i < iterations; i++) { CHK_IF_RETURN( - ntt::NTT(GpuScalars, NTT_SIZE, INV ? ntt::NTTDir::kInverse : ntt::NTTDir::kForward, ntt_config, GpuOutputOld)); + ntt::ntt(GpuScalars, NTT_SIZE, INV ? ntt::NTTDir::kInverse : ntt::NTTDir::kForward, ntt_config, GpuOutputOld)); } CHK_IF_RETURN(cudaEventRecord(icicle_stop, ntt_config.ctx.stream)); CHK_IF_RETURN(cudaStreamSynchronize(ntt_config.ctx.stream)); @@ -201,7 +201,7 @@ int main(int argc, char** argv) CHK_IF_RETURN(cudaFree(GpuOutputOld)); CHK_IF_RETURN(cudaFree(GpuOutputNew)); - ntt::ReleaseDomain(ntt_config.ctx); + ntt::release_domain(ntt_config.ctx); return CHK_LAST(); } \ No newline at end of file diff --git a/icicle/appUtils/ntt/thread_ntt.cu b/icicle/src/ntt/thread_ntt.cu similarity index 99% rename from icicle/appUtils/ntt/thread_ntt.cu rename to icicle/src/ntt/thread_ntt.cu index d6979280..8b3e4a56 100644 --- a/icicle/appUtils/ntt/thread_ntt.cu +++ b/icicle/src/ntt/thread_ntt.cu @@ -4,7 +4,7 @@ #include #include -#include "curves/curve_config.cuh" +#include "gpu-utils/modifiers.cuh" struct stage_metadata { uint32_t th_stride; diff --git a/icicle/src/polynomials/cuda_backend/kernels.cuh b/icicle/src/polynomials/cuda_backend/kernels.cuh new file mode 100644 index 00000000..61e7d881 --- /dev/null +++ b/icicle/src/polynomials/cuda_backend/kernels.cuh @@ -0,0 +1,104 @@ +#pragma once +#include "stdint.h" +#include "../../../src/vec_ops/vec_ops.cu" // TODO Yuval: avoid this + +namespace polynomials { + using namespace vec_ops; + + /*============================== add/sub ==============================*/ + template + __global__ void add_sub_kernel(const T* a_vec, const T* b_vec, int a_len, int b_len, bool add1_sub0, T* result) + { + const int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid >= max(a_len, b_len)) return; + + T a = tid >= a_len ? T::zero() : a_vec[tid]; + T b = tid >= b_len ? T::zero() : b_vec[tid]; + result[tid] = add1_sub0 ? a + b : a - b; + } + + // Note: must be called with 1 block, 1 thread + template + __global__ void add_single_element_inplace(T* self, T v) + { + *self = *self + v; + } + + /*============================== degree ==============================*/ + template + __global__ void highest_non_zero_idx(const T* vec, int len, int64_t* idx) + { + *idx = -1; // zero polynomial is defined with degree -1 + for (int64_t i = len - 1; i >= 0; --i) { + if (vec[i] != T::zero()) { + *idx = i; + return; + } + } + } + + /*============================== evaluate ==============================*/ + template + __device__ T pow(T base, int exp) + { + T result = T::one(); + while (exp > 0) { + if (exp & 1) result = result * base; + base = base * base; + exp >>= 1; + } + return result; + } + + // TODO Yuval: implement efficient reduction and support batch evaluation + template + __global__ void dummy_reduce(const T* arr, int size, T* output) + { + const int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid > 0) return; + + *output = arr[0]; + for (int i = 1; i < size; ++i) { + *output = *output + arr[i]; + } + } + + template + __global__ void evaluate_polynomial_without_reduction(const T* x, const T* coeffs, int num_coeffs, T* tmp) + { + const int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid < num_coeffs) { tmp[tid] = coeffs[tid] * pow(*x, tid); } + } + + /*============================== division ==============================*/ + template + __global__ void school_book_division_step(T* r, T* q, const T* b, int deg_r, int deg_b, T lc_b_inv) + { + // computing one step 'r = r-sb' (for 'a = q*b+r') where s is a monomial such that 'r-sb' removes the highest degree + // of r. + const int tid = blockIdx.x * blockDim.x + threadIdx.x; + int64_t monomial = deg_r - deg_b; // monomial=1 is 'x', monomial=2 is x^2 etc. + if (tid > deg_r) return; + + T lc_r = r[deg_r]; + T monomial_coeff = lc_r * lc_b_inv; // lc_r / lc_b + if (tid == 0) { + // adding monomial s to q (q=q+s) + q[monomial] = monomial_coeff; + } + + if (tid < monomial) return; + + T b_coeff = b[tid - monomial]; + r[tid] = r[tid] - monomial_coeff * b_coeff; + } + + /*============================== slice ==============================*/ + template + __global__ void slice_kernel(const T* in, T* out, int offset, int stride, int size) + { + int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid < size) { out[tid] = in[offset + tid * stride]; } + } + +} // namespace polynomials \ No newline at end of file diff --git a/icicle/src/polynomials/cuda_backend/polynomial_cuda_backend.cu b/icicle/src/polynomials/cuda_backend/polynomial_cuda_backend.cu new file mode 100644 index 00000000..c6c5ece5 --- /dev/null +++ b/icicle/src/polynomials/cuda_backend/polynomial_cuda_backend.cu @@ -0,0 +1,886 @@ + +#include "polynomials/polynomials.h" +#include "polynomials/cuda_backend/polynomial_cuda_backend.cuh" + +#include "gpu-utils/device_context.cuh" +#include "cuda_runtime.h" +#include "ntt/ntt.cuh" +#include "kernels.cuh" + +using device_context::DeviceContext; + +namespace polynomials { + + static uint64_t ceil_to_power_of_two(uint64_t x) { return 1ULL << uint64_t(ceil(log2(x))); } + /*============================== Polynomial CUDA-context ==============================*/ + + // checking whether a pointer is on host or device and asserts device matches the polynmoial device + static bool is_host_ptr(const void* p, int device_id) + { + // Note: device memory can me managed or not. Host memory can be registered or not. No distinction here. + cudaPointerAttributes attributes; + CHK_STICKY(cudaPointerGetAttributes(&attributes, p)); + const bool is_on_host = attributes.type == cudaMemoryTypeHost || + attributes.type == cudaMemoryTypeUnregistered; // unregistered is host memory + const bool is_on_cur_device = !is_on_host && attributes.device == device_id; + const bool is_valid_ptr = is_on_host || is_on_cur_device; + if (!is_valid_ptr) { THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, "Invalid ptr for polynomial"); } + + return is_on_host; + } + + template + class CUDAPolynomialContext : public IPolynomialContext + { + typedef IPolynomialContext PolyContext; + using typename IPolynomialContext::State; + using IPolynomialContext::ElementSize; + + protected: + State m_state = State::Invalid; // Current state of the polynomial context. + uint64_t m_nof_elements = 0; // Number of elements managed by the context. + + public: + const DeviceContext& m_device_context; + + CUDAPolynomialContext(const DeviceContext& dev_context) : m_device_context{dev_context} + { + m_integrity_counter = std::make_shared(0); + } + ~CUDAPolynomialContext() { release(); } + + void allocate(uint64_t nof_elements, State init_state, bool is_memset_zeros) override + { + const bool is_already_allocated = this->m_nof_elements >= nof_elements; + this->set_state(init_state); + + if (is_already_allocated) { + // zero the extra elements, if exist + memset_zeros(this->m_storage, nof_elements, this->m_nof_elements); + return; + } + + release(); // in case allocated mem is too small and need to reallocate + this->m_nof_elements = allocate_mem(nof_elements, &this->m_storage, is_memset_zeros); + modified(); + } + + void memset_zeros(void* storage, uint64_t element_start_idx, uint64_t element_end_idx) + { + const uint64_t size = (element_end_idx - element_start_idx) * ElementSize; + if (0 == size) { return; } + + modified(); + + const auto offset = (void*)((uint64_t)storage + element_start_idx * ElementSize); + CHK_STICKY(cudaMemsetAsync(offset, 0, size, m_device_context.stream)); + } + + uint64_t allocate_mem(uint64_t nof_elements, void** storage /*OUT*/, bool is_memset_zeros) + { + const uint64_t nof_elements_nearset_power_of_two = ceil_to_power_of_two(nof_elements); + const uint64_t mem_size = nof_elements_nearset_power_of_two * ElementSize; + + CHK_STICKY(cudaMallocAsync(storage, mem_size, m_device_context.stream)); + + if (is_memset_zeros) { + memset_zeros(*storage, 0, nof_elements_nearset_power_of_two); + } else { + // if allocating more memory than requested, memset only the pad area to avoid higher invalid coefficients + memset_zeros(*storage, nof_elements, nof_elements_nearset_power_of_two); + } + + return nof_elements_nearset_power_of_two; + } + + void set_storage(void* storage, uint64_t nof_elements) + { + release(); + m_storage = storage; + this->m_nof_elements = nof_elements; + + modified(); + } + + // Note: this is protected and only backend can call + void* get_storage_mutable() override + { + // since giving access to internal memory, cannot know if modified or not + // backend should not take it mutable if not mutating + modified(); + return m_storage; + } + const void* get_storage_immutable() override { return m_storage; } + + void extend_mem_and_pad(uint64_t nof_elements) + { + void* new_storage = nullptr; + const uint64_t new_nof_elements = allocate_mem(nof_elements, &new_storage, true /*=memset zeros*/); + const uint64_t old_mem_size = this->m_nof_elements * ElementSize; + + CHK_STICKY( + cudaMemcpyAsync(new_storage, m_storage, old_mem_size, cudaMemcpyDeviceToDevice, m_device_context.stream)); + + set_storage(new_storage, new_nof_elements); + } + + void release() override + { + if (m_storage != nullptr) { CHK_STICKY(cudaFreeAsync(m_storage, m_device_context.stream)); } + + m_storage = nullptr; + this->m_nof_elements = 0; + + modified(); + } + + State get_state() const override { return m_state; } + void set_state(State state) { m_state = state; } + uint64_t get_nof_elements() const override { return m_nof_elements; } + + void from_coefficients(uint64_t nof_coefficients, const C* coefficients) override + { + const bool is_memset_zeros = coefficients == nullptr; + allocate(nof_coefficients, State::Coefficients, is_memset_zeros); + if (coefficients) { + const bool is_ptr_on_host = is_host_ptr(coefficients, m_device_context.device_id); + + CHK_STICKY(cudaMemcpyAsync( + m_storage, coefficients, nof_coefficients * sizeof(C), + is_ptr_on_host ? cudaMemcpyHostToDevice : cudaMemcpyDeviceToDevice, m_device_context.stream)); + CHK_STICKY( + cudaStreamSynchronize(m_device_context.stream)); // protect against coefficients being released too soon + } + } + + void from_rou_evaluations(uint64_t nof_evaluations, const I* evaluations) override + { + const bool is_memset_zeros = evaluations == nullptr; + allocate(nof_evaluations, State::EvaluationsOnRou_Natural, is_memset_zeros); + if (evaluations) { + const bool is_ptr_on_host = is_host_ptr(evaluations, m_device_context.device_id); + + CHK_STICKY(cudaMemcpyAsync( + m_storage, evaluations, nof_evaluations * sizeof(C), + is_ptr_on_host ? cudaMemcpyHostToDevice : cudaMemcpyDeviceToDevice, m_device_context.stream)); + CHK_STICKY( + cudaStreamSynchronize(m_device_context.stream)); // protect against evaluations being released too soon + } + } + + void clone(IPolynomialContext& from) override + { + switch (from.get_state()) { + case State::Coefficients: { + auto [coeffs, N_coeffs] = from.get_coefficients(); + from_coefficients(N_coeffs, coeffs); + } break; + case State::EvaluationsOnRou_Natural: { + auto [evals, N_evals] = from.get_rou_evaluations(); + from_rou_evaluations(N_evals, evals); + } break; + default: + THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, "clone() from non implemented state"); + } + + this->set_state(from.get_state()); // to handle both reversed evaluations case + } + + std::pair get_coefficients() override + { + transform_to_coefficients(); + return std::make_pair(static_cast(m_storage), this->m_nof_elements); + } + + std::tuple, uint64_t, uint64_t> get_coefficients_view() override + { + auto [coeffs, N] = get_coefficients(); + // when reading the pointer, if the counter was modified, the pointer is invalid + IntegrityPointer integrity_pointer(coeffs, m_integrity_counter, *m_integrity_counter); + CHK_STICKY(cudaStreamSynchronize(m_device_context.stream)); + return {std::move(integrity_pointer), N, m_device_context.device_id}; + } + + std::tuple, uint64_t, uint64_t> + get_rou_evaluations_view(uint64_t nof_evaluations, bool is_reversed) + { + if (nof_evaluations != 0 && nof_evaluations < get_nof_elements()) { + THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, "get_rou_evaluations_view() can only expand #evals"); + } + transform_to_evaluations(nof_evaluations, is_reversed); + auto [evals, N] = get_rou_evaluations(); + // when reading the pointer, if the counter was modified, the pointer is invalid + IntegrityPointer integrity_pointer(evals, m_integrity_counter, *m_integrity_counter); + CHK_STICKY(cudaStreamSynchronize(m_device_context.stream)); + return {std::move(integrity_pointer), N, m_device_context.device_id}; + } + + std::pair get_rou_evaluations() override + { + const bool is_reversed = this->m_state == State::EvaluationsOnRou_Reversed; + transform_to_evaluations(0, is_reversed); + return std::make_pair(static_cast(m_storage), this->m_nof_elements); + } + + void transform_to_coefficients(uint64_t nof_coefficients = 0) override + { + // cannot really get more coefficients but sometimes want to pad for NTT. In that case + // nof_coefficients>m_nof_elements + nof_coefficients = (nof_coefficients == 0) ? this->m_nof_elements : ceil_to_power_of_two(nof_coefficients); + const bool is_same_nof_coefficients = this->m_nof_elements == nof_coefficients; + const bool is_already_in_state = this->m_state == State::Coefficients && is_same_nof_coefficients; + if (is_already_in_state) { return; } + + if (nof_coefficients < this->m_nof_elements) { + THROW_ICICLE_ERR( + IcicleError_t::InvalidArgument, "polynomial shrinking not supported. Probably encountered a bug"); + } + + modified(); + + const bool is_already_in_coeffs = this->m_state == State::Coefficients; + // case 1: already in coefficients. Need to allocate larger memory and zero pad + if (is_already_in_coeffs) { + extend_mem_and_pad(nof_coefficients); + return; + } + + // case 2: transform from evaluations. May need to allocate larger memory + I* evals = static_cast(m_storage); + C* coeffs = static_cast(m_storage); + const bool is_allocate_new_mem = nof_coefficients > this->m_nof_elements; + if (is_allocate_new_mem) { + void* new_mem = nullptr; + nof_coefficients = allocate_mem(nof_coefficients, &new_mem, true /*=memset zeros*/); + coeffs = static_cast(new_mem); + } + + // transform from evaluations to coefficients + auto ntt_config = ntt::default_ntt_config(m_device_context); + ntt_config.are_inputs_on_device = true; + ntt_config.are_outputs_on_device = true; + ntt_config.is_async = true; + + ntt_config.ordering = + (this->m_state == State::EvaluationsOnRou_Natural) ? ntt::Ordering::kNN : ntt::Ordering::kRN; + // Note: it is important to do the NTT with old size because padding in evaluations form is computing another + // (higher order) polynomial + CHK_STICKY(ntt::ntt(evals, this->m_nof_elements, ntt::NTTDir::kInverse, ntt_config, coeffs)); + this->set_state(State::Coefficients); + + if (is_allocate_new_mem) { set_storage(coeffs, nof_coefficients); } // release old memory and use new + } + + void transform_to_evaluations(uint64_t nof_evaluations = 0, bool is_reversed = false) override + { + // TODO Yuval: can maybe optimize this + nof_evaluations = (nof_evaluations == 0) ? this->m_nof_elements : ceil_to_power_of_two(nof_evaluations); + const bool is_same_nof_evaluations = nof_evaluations == this->m_nof_elements; + const bool is_same_order = is_reversed && this->m_state == State::EvaluationsOnRou_Reversed || + (!is_reversed && State::EvaluationsOnRou_Natural); + const bool is_already_in_state = is_same_nof_evaluations && is_same_order; + if (is_already_in_state) { return; } + + if (nof_evaluations < this->m_nof_elements) { + THROW_ICICLE_ERR( + IcicleError_t::InvalidArgument, "polynomial shrinking not supported. Probably encountered a bug"); + } + + modified(); + + // TODO Yuval: evaluations->evaluations with different ordering can be implemented via inplace reorder more + // efficiently than it is now + + // There are 3 cases: + // (1) coefficients to evaluations + // (1a) same size -> NTT (NR or NN) + // (1b) different_size -> alloc new mem, copy coeffs and NTT inplace + // (2) evaluations to evaluations (interpolation) + // transform to coefficients, extend memory, then NTT back to evals (NR or NN) + + const bool is_eval_to_eval = this->m_state != State::Coefficients; + // interpolating more points requires going back to coefficients first. Note that it muse be done with the + // original size. INTT after padding computes a higher degree polynomial + if (is_eval_to_eval) { transform_to_coefficients(); } + + // reaching this point means polynomial is in coefficient form + const bool is_allocate_new_mem = nof_evaluations > this->m_nof_elements; + // allocate more memory and copy+pad + if (is_allocate_new_mem) { extend_mem_and_pad(nof_evaluations); } + + C* coeffs = static_cast(m_storage); + I* evals = static_cast(m_storage); + auto ntt_config = ntt::default_ntt_config(m_device_context); + ntt_config.are_inputs_on_device = true; + ntt_config.are_outputs_on_device = true; + ntt_config.is_async = true; + // already copied the coefficients with padding. Now computing evaluations. + ntt_config.ordering = is_reversed ? ntt::Ordering::kNR : ntt::Ordering::kNN; + CHK_STICKY(ntt::ntt(coeffs, nof_evaluations, ntt::NTTDir::kForward, ntt_config, evals)); + + this->set_state(is_reversed ? State::EvaluationsOnRou_Reversed : State::EvaluationsOnRou_Natural); + } + + void print(std::ostream& os) override + { + if (this->get_state() == State::Coefficients) { + print_coeffs(os); + } else { + print_evals(os); + } + } + + void print_coeffs(std::ostream& os) + { + transform_to_coefficients(); + auto host_coeffs = std::make_unique(this->m_nof_elements); + // using stream since previous ops may still be in progress. Sync stream before reading CPU mem + CHK_STICKY(cudaMemcpyAsync( + host_coeffs.get(), m_storage, this->m_nof_elements * sizeof(C), cudaMemcpyDeviceToHost, + m_device_context.stream)); + CHK_STICKY(cudaStreamSynchronize(m_device_context.stream)); + + os << "(id=" << PolyContext::m_id << ")["; + for (size_t i = 0; i < this->m_nof_elements; ++i) { + os << host_coeffs[i]; + if (i < this->m_nof_elements - 1) { os << ", "; } + } + os << "] (state=coefficients)" << std::endl; + } + + void print_evals(std::ostream& os) + { + transform_to_evaluations(); + auto host_evals = std::make_unique(this->m_nof_elements); + // using stream since previous ops may still be in progress. Sync stream before reading CPU mem + CHK_STICKY(cudaMemcpyAsync( + host_evals.get(), m_storage, this->m_nof_elements * sizeof(I), cudaMemcpyDeviceToHost, + m_device_context.stream)); + CHK_STICKY(cudaStreamSynchronize(m_device_context.stream)); + + os << "(id=" << PolyContext::m_id << ")["; + for (size_t i = 0; i < this->m_nof_elements; ++i) { + os << host_evals[i]; + if (i < this->m_nof_elements - 1) { os << ", "; } + } + + if (this->get_state() == State::EvaluationsOnRou_Reversed) { + os << "] (state=rou evaluations Reversed)" << std::endl; + } else { + os << "] (state=rou evaluations )" << std::endl; + } + } + + private: + // Members + void* m_storage = nullptr; + std::shared_ptr m_integrity_counter; // used to implement integrity of coefficients pointer + + void modified() { (*m_integrity_counter)++; } + }; + + /*============================== Polynomial CUDA-backend ==============================*/ + + template + class CUDAPolynomialBackend : public IPolynomialBackend + { + typedef std::shared_ptr> PolyContext; + typedef typename IPolynomialContext::State State; + + int64_t* d_degree = nullptr; // used to avoid alloc/release every time + + public: + const DeviceContext& m_device_context; + CUDAPolynomialBackend(const DeviceContext& dev_context) : m_device_context{dev_context} + { + CHK_STICKY(cudaMallocAsync(&d_degree, sizeof(int64_t), m_device_context.stream)); + } + ~CUDAPolynomialBackend() { CHK_STICKY(cudaFreeAsync(d_degree, m_device_context.stream)); } + + void from_coefficients(PolyContext p, uint64_t nof_coefficients, const C* coefficients) override + { + p->from_coefficients(nof_coefficients, coefficients); + } + + void from_rou_evaluations(PolyContext p, uint64_t nof_evaluations, const I* evaluations) override + { + p->from_rou_evaluations(nof_evaluations, evaluations); + } + + void clone(PolyContext out, PolyContext in) override { out->clone(*in); } + + template + T* get_context_storage_mutable(PolyContext p) + { + return static_cast(IPolynomialBackend::get_context_storage_mutable(p)); + } + + template + const T* get_context_storage_immutable(PolyContext& p) + { + return static_cast(IPolynomialBackend::get_context_storage_immutable(p)); + } + + void slice(PolyContext out, PolyContext in, uint64_t offset, uint64_t stride, uint64_t size) override + { + assert_device_compatability(out, in); + auto [in_coeffs, in_size] = in->get_coefficients(); + // size=0 means take as much as elements as there are to take + uint64_t out_size = (size > 0) ? size : (1 + (in_size - 1 - offset) / stride); + + out->allocate(out_size, State::Coefficients, false /*=memset zeros*/); + auto out_coeffs = get_context_storage_mutable(out); + + const int NOF_THREADS = 128; + const int NOF_BLOCKS = (out_size + NOF_THREADS - 1) / NOF_THREADS; + slice_kernel<<>>( + in_coeffs, out_coeffs, offset, stride, out_size); + + CHK_LAST(); + } + + void add_sub(PolyContext& res, PolyContext a, PolyContext b, bool add1_sub0) + { + assert_device_compatability(a, b); + assert_device_compatability(a, res); + + // add/sub can be done in both coefficients or evaluations, but operands must be in the same state. + // For evaluations, same state also means same number of evaluations (and on same domain). + // If not same state, compute in coefficients since computing in evaluations may require to interpolate a large + // size. Consider a+b where a is degree 128 and b degree 4. In coefficients b has 4 elements but in evaluations + // need 128. + const bool is_same_size = a->get_nof_elements() == b->get_nof_elements(); + const bool is_same_state = a->get_state() == b->get_state(); + const auto output_state = (is_same_size && is_same_state) ? a->get_state() : State::Coefficients; + const auto output_size = max(a->get_nof_elements(), b->get_nof_elements()); + + if (State::Coefficients == output_state) { + a->transform_to_coefficients(); + b->transform_to_coefficients(); + } + const auto a_mem_p = get_context_storage_immutable(a); + const auto b_mem_p = get_context_storage_immutable(b); + + res->allocate(output_size, output_state); + auto res_mem_p = get_context_storage_mutable(res); + + const int NOF_THREADS = 128; + const int NOF_BLOCKS = (output_size + NOF_THREADS - 1) / NOF_THREADS; + add_sub_kernel<<>>( + a_mem_p, b_mem_p, a->get_nof_elements(), b->get_nof_elements(), add1_sub0, res_mem_p); + + CHK_LAST(); + } + + void add(PolyContext& res, PolyContext a, PolyContext b) override { add_sub(res, a, b, true /*=add*/); } + void subtract(PolyContext res, PolyContext a, PolyContext b) override { add_sub(res, a, b, false /*=sub*/); } + + void multiply(PolyContext c, PolyContext a, PolyContext b) override + { + assert_device_compatability(a, b); + assert_device_compatability(a, c); + + const bool is_a_scalar = a->get_nof_elements() == 1; + const bool is_b_scalar = b->get_nof_elements() == 1; + + // TODO: can add kernel that takes the scalar as device memory + if (is_a_scalar) { + return multiply(c, b, get_coeff(a, 0)); + } else if (is_b_scalar) { + return multiply(c, a, get_coeff(b, 0)); + } + + const bool is_multiply_with_cosets = true; // TODO Yuval: check when faster to do so. + if (is_multiply_with_cosets) { return multiply_with_cosets(c, a, b); } + return multiply_with_padding(c, a, b); + } + + void multiply(PolyContext out, PolyContext p, D scalar) override + { + assert_device_compatability(out, p); + + // element wise multiplication is similar both in coefficients and evaluations (regardless of order too) + const auto state = p->get_state(); + const auto N = p->get_nof_elements(); + + auto p_elements_p = + state == State::Coefficients ? get_context_storage_immutable(p) : get_context_storage_immutable(p); + + out->allocate(N, state, false /*=memset zeros*/); + auto out_evals_p = + state == State::Coefficients ? get_context_storage_mutable(out) : get_context_storage_mutable(out); + + const int NOF_THREADS = 128; + const int NOF_BLOCKS = (N + NOF_THREADS - 1) / NOF_THREADS; + mul_scalar_kernel<<>>(p_elements_p, scalar, N, out_evals_p); + + CHK_LAST(); + } + + void multiply_with_padding(PolyContext c, PolyContext a, PolyContext b) + { + // TODO Yuval: by using the degree I can optimize the memory size and avoid redundant computations too + const uint64_t a_N_orig = a->get_nof_elements(); + const uint64_t b_N_orig = b->get_nof_elements(); + const uint64_t N = max(a_N_orig, b_N_orig); + const uint64_t c_N = 2 * N; + + // (1) transform a,b to 2N evaluations + a->transform_to_evaluations(c_N, true /*=reversed*/); + b->transform_to_evaluations(c_N, true /*=reversed*/); + auto [a_evals_p, a_N] = a->get_rou_evaluations(); + auto [b_evals_p, b_N] = b->get_rou_evaluations(); + + // (2) allocate c (c=a*b) and compute element-wise multiplication on evaluations + c->allocate(c_N, State::EvaluationsOnRou_Reversed, false /*=memset zeros*/); + auto c_evals_p = get_context_storage_mutable(c); + + const int NOF_THREADS = 128; + const int NOF_BLOCKS = (c_N + NOF_THREADS - 1) / NOF_THREADS; + mul_kernel<<>>(a_evals_p, b_evals_p, c_N, c_evals_p); + + CHK_LAST(); + } + + void multiply_with_cosets(PolyContext c, PolyContext a, PolyContext b) + { + const uint64_t a_N = a->get_nof_elements(); + const uint64_t b_N = b->get_nof_elements(); + const uint64_t N = max(a_N, b_N); + + // (1) transform a,b to coefficients such that both have N coefficients + a->transform_to_coefficients(N); + b->transform_to_coefficients(N); + auto [a_coeff_p, _] = a->get_coefficients(); + auto [b_coeff_p, __] = b->get_coefficients(); + // (2) allocate c (c=a*b) + const uint64_t c_N = 2 * N; + c->allocate(c_N, State::EvaluationsOnRou_Reversed, false /*=memset zeros*/); + auto c_evals_low_p = get_context_storage_mutable(c); + I* c_evals_high_p = c_evals_low_p + N; + + // (3) compute NTT of a,b on coset and write to c + auto ntt_config = ntt::default_ntt_config(m_device_context); + ntt_config.are_inputs_on_device = true; + ntt_config.are_outputs_on_device = true; + ntt_config.is_async = true; + ntt_config.ordering = ntt::Ordering::kNR; + ntt_config.coset_gen = ntt::get_root_of_unity_from_domain((uint64_t)log2(c_N), ntt_config.ctx); + + CHK_STICKY(ntt::ntt(a_coeff_p, N, ntt::NTTDir::kForward, ntt_config, c_evals_low_p)); // a_H1 + CHK_STICKY(ntt::ntt(b_coeff_p, N, ntt::NTTDir::kForward, ntt_config, c_evals_high_p)); // b_H1 + + // (4) compute a_H1 * b_H1 inplace + const int NOF_THREADS = 128; + const int NOF_BLOCKS = (N + NOF_THREADS - 1) / NOF_THREADS; + mul_kernel<<>>( + c_evals_low_p, c_evals_high_p, N, c_evals_high_p); + // (5) transform a,b to evaluations + a->transform_to_evaluations(N, true /*=reversed*/); + b->transform_to_evaluations(N, true /*=reversed*/); + auto [a_evals_p, a_nof_evals] = a->get_rou_evaluations(); + auto [b_evals_p, b_nof_evals] = b->get_rou_evaluations(); + + // (6) compute a_H0 * b_H0 + mul_kernel<<>>(a_evals_p, b_evals_p, N, c_evals_low_p); + + CHK_LAST(); + } + + void divide(PolyContext Q /*OUT*/, PolyContext R /*OUT*/, PolyContext a, PolyContext b) override + { + assert_device_compatability(a, b); + assert_device_compatability(a, Q); + assert_device_compatability(a, R); + + auto [a_coeffs, a_N] = a->get_coefficients(); + auto [b_coeffs, b_N] = b->get_coefficients(); + + const int64_t deg_a = degree(a); + const int64_t deg_b = degree(b); + if (deg_a < deg_b || deg_b < 0) { + THROW_ICICLE_ERR( + IcicleError_t::InvalidArgument, "Polynomial division (CUDA backend): numerator degree must be " + "greater-or-equal to denumerator degree and denumerator must not be zero"); + } + + // init: Q=0, R=a + Q->allocate(deg_a - deg_b + 1, State::Coefficients, true /*=memset zeros*/); + auto Q_coeffs = get_context_storage_mutable(Q); + + // TODO Yuval: Can do better in terms of memory allocation? deg(R) <= deg(b) by definition but it starts as + R->allocate(a_N, State::Coefficients, false /*=memset_zeros*/); + auto R_coeffs = get_context_storage_mutable(R); + CHK_STICKY( + cudaMemcpyAsync(R_coeffs, a_coeffs, a_N * sizeof(C), cudaMemcpyDeviceToDevice, m_device_context.stream)); + + const C& lc_b_inv = C::inverse(get_coeff(b, deg_b)); // largest coeff of b + + int64_t deg_r = deg_a; + while (deg_r >= deg_b) { + // each iteration is removing the largest monomial in r until deg(r)>>( + R_coeffs, Q_coeffs, b_coeffs, deg_r, deg_b, lc_b_inv); + + // faster than degree(R) based on the fact that degree is decreasing + deg_r = degree_internal(R, deg_r + 1 /*size of R*/); + } + + CHK_LAST(); + } + + void quotient(PolyContext Q, PolyContext op_a, PolyContext op_b) override + { + // TODO: can implement more efficiently? + auto R = std::make_shared>(m_device_context); + divide(Q, R, op_a, op_b); + } + + void remainder(PolyContext R, PolyContext op_a, PolyContext op_b) override + { + // TODO: can implement more efficiently? + auto Q = std::make_shared>(m_device_context); + divide(Q, R, op_a, op_b); + } + + void divide_by_vanishing_polynomial(PolyContext out, PolyContext numerator, uint64_t vanishing_poly_degree) override + { + assert_device_compatability(numerator, out); + + // TODO Yuval: vanishing polynomial x^n-1 evaluates to zero on ROU + // Therefore constant on coset with u as coset generator ((wu)^n-1 = w^n*u^n-1 = u^n-1) + // This is true for a coset of size n but if numerator is of size >n, then I need a larger coset and it + // doesn't hold. Need to use this fact to optimize division + + // (1) allocate vanishing polynomial in coefficients form + // TODO Yuval: maybe instead of taking numerator memory and modiyfing it diretcly add a state for evaluations + // on coset of rou. In that case I can remain in this state and also won't need to access input memory + // directly + numerator->transform_to_coefficients(); + auto numerator_coeffs = get_context_storage_mutable(numerator); + const auto N = numerator->get_nof_elements(); + if (vanishing_poly_degree > N) { + THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, "divide_by_vanishing_polynomial(): degree is too large"); + } + out->allocate(N, State::Coefficients, true /*=set zeros*/); + add_monomial_inplace(out, C::zero() - C::one(), 0); //-1 + add_monomial_inplace(out, C::one(), vanishing_poly_degree); //+x^n + + // (2) NTT on coset. Note that NTT on ROU evaluates to zeros for vanihsing polynomials by definition. + // Therefore evaluation on coset is required to compute non-zero evaluations, which make element-wise division + // possible + auto out_coeffs = get_context_storage_mutable(out); + auto ntt_config = ntt::default_ntt_config(m_device_context); + ntt_config.are_inputs_on_device = true; + ntt_config.are_outputs_on_device = true; + ntt_config.is_async = true; + ntt_config.ordering = ntt::Ordering::kNM; + ntt_config.coset_gen = ntt::get_root_of_unity_from_domain((uint64_t)log2(2 * N), ntt_config.ctx); + + CHK_STICKY(ntt::ntt(out_coeffs, N, ntt::NTTDir::kForward, ntt_config, out_coeffs)); + CHK_STICKY(ntt::ntt(numerator_coeffs, N, ntt::NTTDir::kForward, ntt_config, numerator_coeffs)); + + // (3) element wise division + const int NOF_THREADS = 128; + const int NOF_BLOCKS = (N + NOF_THREADS - 1) / NOF_THREADS; + div_element_wise_kernel<<>>( + numerator_coeffs, out_coeffs, N, out_coeffs); + + // (4) INTT back both a and out + ntt_config.ordering = ntt::Ordering::kMN; + CHK_STICKY(ntt::ntt(out_coeffs, N, ntt::NTTDir::kInverse, ntt_config, out_coeffs)); + CHK_STICKY(ntt::ntt(numerator_coeffs, N, ntt::NTTDir::kInverse, ntt_config, numerator_coeffs)); + } + + // arithmetic with monomials + void add_monomial_inplace(PolyContext& poly, C monomial_coeff, uint64_t monomial) override + { + const uint64_t new_nof_elements = max(poly->get_nof_elements(), monomial + 1); + poly->transform_to_coefficients(new_nof_elements); + auto coeffs = get_context_storage_mutable(poly); + add_single_element_inplace<<<1, 1, 0, m_device_context.stream>>>(coeffs + monomial, monomial_coeff); + + CHK_LAST(); + } + + void sub_monomial_inplace(PolyContext& poly, C monomial_coeff, uint64_t monomial) override + { + add_monomial_inplace(poly, C::zero() - monomial_coeff, monomial); + } + + int64_t degree(PolyContext p) override { return degree_internal(p, p->get_nof_elements()); } + + // search degree starting from len, searching down (towards coeff0) + int64_t degree_internal(PolyContext p, uint64_t len) + { + // TODO: parallelize kernel? Note that typically the largest coefficient is expected in the higher half since + // memory is allocate based on #coefficients + + auto [coeff, _] = p->get_coefficients(); + + int64_t h_degree; + highest_non_zero_idx<<<1, 1, 0, m_device_context.stream>>>(coeff, len, d_degree); + CHK_STICKY( + cudaMemcpyAsync(&h_degree, d_degree, sizeof(int64_t), cudaMemcpyDeviceToHost, m_device_context.stream)); + CHK_STICKY(cudaStreamSynchronize(m_device_context.stream)); // sync to make sure return value is copied to host + + return h_degree; + } + + public: + void evaluate(PolyContext p, const D* x, I* eval) override + { + // TODO Yuval: maybe use Horner's rule and just evaluate each domain point per thread. Alternatively Need to + // reduce in parallel. + + auto [coeff, nof_coeff] = p->get_coefficients(); + + const bool is_x_on_host = is_host_ptr(x, m_device_context.device_id); + const bool is_eval_on_host = is_host_ptr(eval, m_device_context.device_id); + + const D* d_x = x; + D* allocated_x = nullptr; + if (is_x_on_host) { + CHK_STICKY(cudaMallocAsync(&allocated_x, sizeof(I), m_device_context.stream)); + CHK_STICKY(cudaMemcpyAsync(allocated_x, x, sizeof(I), cudaMemcpyHostToDevice, m_device_context.stream)); + d_x = allocated_x; + } + I* d_eval = eval; + if (is_eval_on_host) { CHK_STICKY(cudaMallocAsync(&d_eval, sizeof(I), m_device_context.stream)); } + + // TODO Yuval: other methods can avoid this allocation. Also for eval_on_domain() no need to reallocate every time + I* d_tmp = nullptr; + CHK_STICKY(cudaMallocAsync(&d_tmp, sizeof(I) * nof_coeff, m_device_context.stream)); + const int NOF_THREADS = 32; + const int NOF_BLOCKS = (nof_coeff + NOF_THREADS - 1) / NOF_THREADS; + evaluate_polynomial_without_reduction<<>>( + d_x, coeff, nof_coeff, d_tmp); // TODO Yuval: parallelize kernel + dummy_reduce<<<1, 1, 0, m_device_context.stream>>>(d_tmp, nof_coeff, d_eval); + + if (is_eval_on_host) { + CHK_STICKY(cudaMemcpyAsync(eval, d_eval, sizeof(I), cudaMemcpyDeviceToHost, m_device_context.stream)); + CHK_STICKY(cudaStreamSynchronize(m_device_context.stream)); // sync to make sure return value is copied to host + CHK_STICKY(cudaFreeAsync(d_eval, m_device_context.stream)); + } + if (allocated_x) { CHK_STICKY(cudaFreeAsync(allocated_x, m_device_context.stream)); } + CHK_STICKY(cudaFreeAsync(d_tmp, m_device_context.stream)); + } + + void evaluate_on_domain(PolyContext p, const D* domain, uint64_t size, I* evaluations /*OUT*/) override + { + // TODO Yuval: implement more efficiently ?? + for (uint64_t i = 0; i < size; ++i) { + evaluate(p, &domain[i], &evaluations[i]); + } + } + + uint64_t copy_coeffs(PolyContext op, C* out_coeffs, uint64_t start_idx, uint64_t end_idx) override + { + const uint64_t nof_coeffs = op->get_nof_elements(); + if (nullptr == out_coeffs) { return nof_coeffs; } // no allocated memory + + const bool is_valid_start_idx = start_idx < nof_coeffs; + const bool is_valid_end_idx = end_idx < nof_coeffs && end_idx >= start_idx; + const bool is_valid_indices = is_valid_start_idx && is_valid_end_idx; + if (!is_valid_indices) { + // return -1 instead? I could but 'get_coeff()' cannot with its current declaration + THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, "copy_coeffs() invalid indices"); + } + + op->transform_to_coefficients(); + auto [device_coeffs, _] = op->get_coefficients(); + const size_t nof_coeffs_to_copy = end_idx - start_idx + 1; + const bool is_copy_to_host = is_host_ptr(out_coeffs, m_device_context.device_id); + CHK_STICKY(cudaMemcpyAsync( + out_coeffs, device_coeffs + start_idx, nof_coeffs_to_copy * sizeof(C), + is_copy_to_host ? cudaMemcpyDeviceToHost : cudaMemcpyDeviceToDevice, m_device_context.stream)); + CHK_STICKY(cudaStreamSynchronize(m_device_context.stream)); // sync to make sure return value is copied + + return nof_coeffs_to_copy; + } + + // read coefficients to host + C get_coeff(PolyContext op, uint64_t coeff_idx) override + { + C host_coeff; + copy_coeffs(op, &host_coeff, coeff_idx, coeff_idx); + return host_coeff; + } + + std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> + get_coefficients_view(PolyContext p) override + { + return p->get_coefficients_view(); + } + + std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> + get_rou_evaluations_view(PolyContext p, uint64_t nof_evaluations, bool is_reversed) override + { + return p->get_rou_evaluations_view(nof_evaluations, is_reversed); + } + + inline void assert_device_compatability(PolyContext a, PolyContext b) const + { + CUDAPolynomialContext* a_cuda = static_cast*>(a.get()); + CUDAPolynomialContext* b_cuda = static_cast*>(b.get()); + + const bool is_same_device = a_cuda->m_device_context.device_id == b_cuda->m_device_context.device_id; + if (!is_same_device) { + THROW_ICICLE_ERR( + IcicleError_t::InvalidArgument, "CUDA backend: incompatible polynomials, on different devices"); + } + } + }; + + /*============================== Polynomial CUDA-factory ==============================*/ + template + CUDAPolynomialFactory::CUDAPolynomialFactory() + { + int nof_cuda_devices = -1; + CHK_STICKY(cudaGetDeviceCount(&nof_cuda_devices)); + int orig_device = -1; + + CHK_STICKY(cudaGetDevice(&orig_device)); + m_device_streams.resize(nof_cuda_devices, nullptr); + + for (int dev_id = 0; dev_id < nof_cuda_devices; ++dev_id) { + CHK_STICKY(cudaSetDevice(dev_id)); + CHK_STICKY(cudaStreamCreate(&m_device_streams[dev_id])); + DeviceContext context = {m_device_streams[dev_id], (size_t)dev_id, 0x0 /*mempool*/}; + m_device_contexts.push_back(context); + } + CHK_STICKY(cudaSetDevice(orig_device)); // setting back original device + } + + template + CUDAPolynomialFactory::~CUDAPolynomialFactory() + { + for (auto stream_it : m_device_streams) { + CHK_STICKY(cudaStreamDestroy(stream_it)); // TODO Yuval: why does it fail? + } + } + + template + std::shared_ptr> CUDAPolynomialFactory::create_context() + { + int cuda_device_id = -1; + CHK_STICKY(cudaGetDevice(&cuda_device_id)); + return std::make_shared>(m_device_contexts[cuda_device_id]); + } + + template + std::shared_ptr> CUDAPolynomialFactory::create_backend() + { + int cuda_device_id = -1; + CHK_STICKY(cudaGetDevice(&cuda_device_id)); + return std::make_shared>(m_device_contexts[cuda_device_id]); + } + + // explicit instantiation for default type (scalar) + template class CUDAPolynomialContext<>; + template class CUDAPolynomialBackend<>; + template class CUDAPolynomialFactory<>; + +} // namespace polynomials \ No newline at end of file diff --git a/icicle/src/polynomials/polynomials.cu b/icicle/src/polynomials/polynomials.cu new file mode 100644 index 00000000..b204bb00 --- /dev/null +++ b/icicle/src/polynomials/polynomials.cu @@ -0,0 +1,204 @@ +#include "polynomials/polynomials.h" + +namespace polynomials { + + template + Polynomial::Polynomial() + { + if (nullptr == s_factory) { + throw std::runtime_error("Polynomial factory not initialized. Must call Polynomial::initialize(factory)"); + } + m_context = s_factory->create_context(); + m_backend = s_factory->create_backend(); + } + + template + Polynomial Polynomial::from_coefficients(const C* coefficients, uint64_t nof_coefficients) + { + Polynomial P = {}; + P.m_backend->from_coefficients(P.m_context, nof_coefficients, coefficients); + return P; + } + + template + Polynomial Polynomial::from_rou_evaluations(const I* evaluations, uint64_t nof_evaluations) + { + Polynomial P = {}; + P.m_backend->from_rou_evaluations(P.m_context, nof_evaluations, evaluations); + return P; + } + + template + Polynomial Polynomial::clone() const + { + Polynomial P = {}; + m_backend->clone(P.m_context, m_context); + return P; + } + + template + Polynomial Polynomial::slice(uint64_t offset, uint64_t stride, uint64_t size) + { + Polynomial res = {}; + m_backend->slice(res.m_context, this->m_context, offset, stride, size); + return res; + } + template + Polynomial Polynomial::even() + { + return slice(0, 2, 0 /*all elements*/); + } + template + Polynomial Polynomial::odd() + { + return slice(1, 2, 0 /*all elements*/); + } + + template + Polynomial Polynomial::operator+(const Polynomial& rhs) const + { + Polynomial res = {}; + m_backend->add(res.m_context, m_context, rhs.m_context); + return res; + } + + template + Polynomial Polynomial::operator-(const Polynomial& rhs) const + { + Polynomial res = {}; + m_backend->subtract(res.m_context, m_context, rhs.m_context); + return res; + } + + template + Polynomial Polynomial::operator*(const Polynomial& rhs) const + { + Polynomial res = {}; + m_backend->multiply(res.m_context, m_context, rhs.m_context); + return res; + } + + template + Polynomial Polynomial::operator*(const D& scalar) const + { + Polynomial res = {}; + m_backend->multiply(res.m_context, m_context, scalar); + return res; + } + + template + Polynomial operator*(const D& scalar, const Polynomial& rhs) + { + return rhs * scalar; + } + + template + std::pair, Polynomial> Polynomial::divide(const Polynomial& rhs) const + { + Polynomial Q = {}, R = {}; + m_backend->divide(Q.m_context, R.m_context, m_context, rhs.m_context); + return std::make_pair(std::move(Q), std::move(R)); + } + + template + Polynomial Polynomial::operator/(const Polynomial& rhs) const + { + Polynomial res = {}; + m_backend->quotient(res.m_context, m_context, rhs.m_context); + return res; + } + + template + Polynomial Polynomial::operator%(const Polynomial& rhs) const + { + Polynomial res = {}; + m_backend->remainder(res.m_context, m_context, rhs.m_context); + return res; + } + + template + Polynomial Polynomial::divide_by_vanishing_polynomial(uint64_t vanishing_polynomial_degree) const + { + Polynomial res = {}; + m_backend->divide_by_vanishing_polynomial(res.m_context, m_context, vanishing_polynomial_degree); + return res; + } + + template + Polynomial& Polynomial::operator+=(const Polynomial& rhs) + { + m_backend->add(m_context, m_context, rhs.m_context); + return *this; + } + + template + Polynomial& Polynomial::add_monomial_inplace(C monomial_coeff, uint64_t monomial) + { + m_backend->add_monomial_inplace(m_context, monomial_coeff, monomial); + return *this; + } + + template + Polynomial& Polynomial::sub_monomial_inplace(C monomial_coeff, uint64_t monomial) + { + m_backend->sub_monomial_inplace(m_context, monomial_coeff, monomial); + return *this; + } + + template + I Polynomial::operator()(const D& x) const + { + I eval = {}; + evaluate(&x, &eval); + return eval; + } + + template + void Polynomial::evaluate(const D* x, I* eval) const + { + m_backend->evaluate(m_context, x, eval); + } + + template + void Polynomial::evaluate_on_domain(D* domain, uint64_t size, I* evals /*OUT*/) const + { + return m_backend->evaluate_on_domain(m_context, domain, size, evals); + } + + template + int64_t Polynomial::degree() + { + return m_backend->degree(m_context); + } + + template + C Polynomial::get_coeff(uint64_t idx) const + { + return m_backend->get_coeff(m_context, idx); + } + + template + uint64_t Polynomial::copy_coeffs(C* host_coeffs, uint64_t start_idx, uint64_t end_idx) const + { + return m_backend->copy_coeffs(m_context, host_coeffs, start_idx, end_idx); + } + + template + std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> + Polynomial::get_coefficients_view() + { + return m_backend->get_coefficients_view(m_context); + } + + template + std::tuple, uint64_t /*size*/, uint64_t /*device_id*/> + Polynomial::get_rou_evaluations_view(uint64_t nof_evaluations, bool is_reversed) + { + return m_backend->get_rou_evaluations_view(m_context, nof_evaluations, is_reversed); + } + + // explicit instantiation for default type (scalar field) + template class Polynomial; + template Polynomial operator*(const scalar_t& c, const Polynomial& rhs); + +} // namespace polynomials \ No newline at end of file diff --git a/icicle/src/polynomials/polynomials_c_api.cu b/icicle/src/polynomials/polynomials_c_api.cu new file mode 100644 index 00000000..6bf16439 --- /dev/null +++ b/icicle/src/polynomials/polynomials_c_api.cu @@ -0,0 +1,283 @@ +#include "polynomials/polynomials.h" +#include "fields/field_config.cuh" +#include "utils/utils.h" +#include "utils/integrity_pointer.h" +#include "polynomials/cuda_backend/polynomial_cuda_backend.cuh" + +namespace polynomials { + extern "C" { + + // Defines a polynomial instance based on the scalar type from the FIELD configuration. + typedef Polynomial PolynomialInst; + + bool CONCAT_EXPAND(FIELD, polynomial_init_cuda_backend)() + { + static auto cuda_factory = std::make_shared>(); + PolynomialInst::initialize(cuda_factory); + return cuda_factory != nullptr; + } + + // Constructs a polynomial from a set of coefficients. + // coeffs: Array of coefficients. + // size: Number of coefficients in the array. + // Returns a pointer to the newly created polynomial instance. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_create_from_coefficients)(scalar_t* coeffs, size_t size) + { + auto result = new PolynomialInst(PolynomialInst::from_coefficients(coeffs, size)); + return result; + } + + // Constructs a polynomial from evaluations at the roots of unity. + // evals: Array of evaluations. + // size: Number of evaluations in the array. + // Returns a pointer to the newly created polynomial instance. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_create_from_rou_evaluations)(scalar_t* evals, size_t size) + { + auto result = new PolynomialInst(PolynomialInst::from_rou_evaluations(evals, size)); + return result; + } + + // Clones an existing polynomial instance. + // p: Pointer to the polynomial instance to clone. + // Returns a pointer to the cloned polynomial instance. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_clone)(const PolynomialInst* p) + { + auto result = new PolynomialInst(p->clone()); + return result; + } + + // Deletes a polynomial instance, freeing its memory. + // instance: Pointer to the polynomial instance to delete. + void CONCAT_EXPAND(FIELD, polynomial_delete)(PolynomialInst* instance) { delete instance; } + + // Prints a polynomial to stdout + void CONCAT_EXPAND(FIELD, polynomial_print(PolynomialInst* p)) { std::cout << *p << std::endl; } + + // Adds two polynomials. + // a, b: Pointers to the polynomial instances to add. + // Returns a pointer to the resulting polynomial instance. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_add)(const PolynomialInst* a, const PolynomialInst* b) + { + auto result = new PolynomialInst(std::move(*a + *b)); + return result; + } + + // Adds a polynomial to another in place. + // a: Pointer to the polynomial to add to. + // b: Pointer to the polynomial to add. + void CONCAT_EXPAND(FIELD, polynomial_add_inplace)(PolynomialInst* a, const PolynomialInst* b) { *a += *b; } + + // Subtracts one polynomial from another. + // a, b: Pointers to the polynomial instances (minuend and subtrahend, respectively). + // Returns a pointer to the resulting polynomial instance. + + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_subtract)(const PolynomialInst* a, const PolynomialInst* b) + { + auto result = new PolynomialInst(std::move(*a - *b)); + return result; + } + + // Multiplies two polynomials. + // a, b: Pointers to the polynomial instances to multiply. + // Returns a pointer to the resulting polynomial instance. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_multiply)(const PolynomialInst* a, const PolynomialInst* b) + { + auto result = new PolynomialInst(std::move(*a * *b)); + return result; + } + + // Multiplies a polynomial by scalar. + // a: Pointer to the polynomial instance. + // scalar: Scalar to multiply by. + // Returns a pointer to the resulting polynomial instance. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_multiply_by_scalar)(const PolynomialInst* a, const scalar_t& scalar) + { + auto result = new PolynomialInst(std::move(*a * scalar)); + return result; + } + + // Divides one polynomial by another, returning both quotient and remainder. + // a, b: Pointers to the polynomial instances (dividend and divisor, respectively). + // q: Output parameter for the quotient. + // r: Output parameter for the remainder. + void CONCAT_EXPAND(FIELD, polynomial_division)( + const PolynomialInst* a, const PolynomialInst* b, PolynomialInst** q /*OUT*/, PolynomialInst** r /*OUT*/) + { + auto [_q, _r] = a->divide(*b); + *q = new PolynomialInst(std::move(_q)); + *r = new PolynomialInst(std::move(_r)); + } + + // Calculates the quotient of dividing one polynomial by another. + // a, b: Pointers to the polynomial instances (dividend and divisor, respectively). + // Returns a pointer to the resulting quotient polynomial instance. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_quotient)(const PolynomialInst* a, const PolynomialInst* b) + { + auto result = new PolynomialInst(std::move(*a / *b)); + return result; + } + + // Calculates the remainder of dividing one polynomial by another. + // a, b: Pointers to the polynomial instances (dividend and divisor, respectively). + // Returns a pointer to the resulting remainder polynomial instance. + + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_remainder)(const PolynomialInst* a, const PolynomialInst* b) + { + auto result = new PolynomialInst(std::move(*a % *b)); + return result; + } + + // Divides a polynomial by a vanishing polynomial of a given degree, over rou domain. + // p: Pointer to the polynomial instance. + // vanishing_poly_degree: Degree of the vanishing polynomial. + // Returns a pointer to the resulting polynomial instance. + PolynomialInst* + CONCAT_EXPAND(FIELD, polynomial_divide_by_vanishing)(const PolynomialInst* p, uint64_t vanishing_poly_degree) + { + auto result = new PolynomialInst(std::move(p->divide_by_vanishing_polynomial(vanishing_poly_degree))); + return result; + } + + // Adds a monomial to a polynomial in place. + // p: Pointer to the polynomial instance. + // monomial_coeff: Coefficient of the monomial to add. + // monomial: Degree of the monomial to add. + void CONCAT_EXPAND(FIELD, polynomial_add_monomial_inplace)( + PolynomialInst* p, const scalar_t& monomial_coeff, uint64_t monomial) + { + p->add_monomial_inplace(monomial_coeff, monomial); + } + + // Subtracts a monomial from a polynomial in place. + // p: Pointer to the polynomial instance. + // monomial_coeff: Coefficient of the monomial to subtract. + // monomial: Degree of the monomial to subtract. + void CONCAT_EXPAND(FIELD, polynomial_sub_monomial_inplace)( + PolynomialInst* p, const scalar_t& monomial_coeff, uint64_t monomial) + { + p->sub_monomial_inplace(monomial_coeff, monomial); + } + + // Creates a new polynomial instance by slicing an existing polynomial. + // p: Pointer to the original polynomial instance to be sliced. + // offset: Starting index for the slice. + // stride: Interval between elements in the slice. + // size: Number of elements in the slice. + // Returns: Pointer to the new polynomial instance containing the slice. + PolynomialInst* + CONCAT_EXPAND(FIELD, polynomial_slice)(PolynomialInst* p, uint64_t offset, uint64_t stride, uint64_t size) + { + auto result = new PolynomialInst(std::move(p->slice(offset, stride, size))); + return result; + } + + // Creates a new polynomial instance containing only the even-powered terms of the original polynomial. + // p: Pointer to the original polynomial instance. + // Returns: Pointer to the new polynomial instance containing only even-powered terms. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_even)(PolynomialInst* p) + { + auto result = new PolynomialInst(std::move(p->even())); + return result; + } + + // Creates a new polynomial instance containing only the odd-powered terms of the original polynomial. + // p: Pointer to the original polynomial instance. + // Returns: Pointer to the new polynomial instance containing only odd-powered terms. + PolynomialInst* CONCAT_EXPAND(FIELD, polynomial_odd)(PolynomialInst* p) + { + auto result = new PolynomialInst(std::move(p->odd())); + return result; + } + + // Evaluates a polynomial on a domain of points. + // p: Pointer to the polynomial instance. + // domain: Array of points constituting the domain. + // domain_size: Number of points in the domain. + // evals: Output array for the evaluations. + void CONCAT_EXPAND(FIELD, polynomial_evaluate_on_domain)( + const PolynomialInst* p, scalar_t* domain, uint64_t domain_size, scalar_t* evals /*OUT*/) + { + return p->evaluate_on_domain(domain, domain_size, evals); + } + + // Returns the degree of a polynomial. + // p: Pointer to the polynomial instance. + // Returns the degree of the polynomial. + int64_t CONCAT_EXPAND(FIELD, polynomial_degree)(PolynomialInst* p) { return p->degree(); } + + // Copies a range of polynomial coefficients to host/device memory. + // p: Pointer to the polynomial instance. + // host_memory: Array to copy the coefficients into. If NULL, not copying. + // start_idx: Start index of the range to copy. + // end_idx: End index of the range to copy. + // Returns the number of coefficients copied. if memory is NULL, returns number of coefficients. + uint64_t CONCAT_EXPAND(FIELD, polynomial_copy_coeffs_range)( + PolynomialInst* p, scalar_t* memory, uint64_t start_idx, uint64_t end_idx) + { + return p->copy_coeffs(memory, start_idx, end_idx); + } + + // Retrieves a device-memory raw-ptr of the polynomial coefficients. + // p: Pointer to the polynomial instance. + // size: Output parameter for the size of the view. + // device_id: Output parameter for the device ID. + // Returns a raw mutable pointer to the coefficients. + scalar_t* CONCAT_EXPAND(FIELD, polynomial_get_coeffs_raw_ptr)( + PolynomialInst* p, uint64_t* size /*OUT*/, uint64_t* device_id /*OUT*/) + { + auto [coeffs, _size, _device_id] = p->get_coefficients_view(); + *size = _size; + *device_id = _device_id; + return const_cast(coeffs.get()); + } + + // Retrieves a device-memory view of the polynomial coefficients. + // p: Pointer to the polynomial instance. + // size: Output parameter for the size of the view. + // device_id: Output parameter for the device ID. + // Returns a pointer to an integrity pointer encapsulating the coefficients view. + IntegrityPointer* CONCAT_EXPAND(FIELD, polynomial_get_coeff_view)( + PolynomialInst* p, uint64_t* size /*OUT*/, uint64_t* device_id /*OUT*/) + { + auto [coeffs, _size, _device_id] = p->get_coefficients_view(); + *size = _size; + *device_id = _device_id; + return new IntegrityPointer(std::move(coeffs)); + } + + // Retrieves a device-memory view of the polynomial's evaluations on the roots of unity. + // p: Pointer to the polynomial instance. + // nof_evals: Number of evaluations. + // is_reversed: Whether the evaluations are in reversed order. + // size: Output parameter for the size of the view. + // device_id: Output parameter for the device ID. + // Returns a pointer to an integrity pointer encapsulating the evaluations view. + IntegrityPointer* CONCAT_EXPAND(FIELD, polynomial_get_rou_evaluations_view)( + PolynomialInst* p, uint64_t nof_evals, bool is_reversed, uint64_t* size /*OUT*/, uint64_t* device_id /*OUT*/) + { + auto [rou_evals, _size, _device_id] = p->get_rou_evaluations_view(nof_evals, is_reversed); + *size = _size; + *device_id = _device_id; + return new IntegrityPointer(std::move(rou_evals)); + } + + // Reads the pointer from an integrity pointer. + // p: Pointer to the integrity pointer. + // Returns the raw pointer if still valid, otherwise NULL. + const scalar_t* CONCAT_EXPAND(FIELD, polynomial_intergrity_ptr_get)(IntegrityPointer* p) + { + return p->get(); + } + + // Checks if an integrity pointer is still valid. + // p: Pointer to the integrity pointer. + // Returns true if the pointer is valid, false otherwise. + bool CONCAT_EXPAND(FIELD, polynomial_intergrity_ptr_is_valid)(IntegrityPointer* p) { return p->isValid(); } + + // Destroys an integrity pointer, freeing its resources. + // p: Pointer to the integrity pointer to destroy. + void CONCAT_EXPAND(FIELD, polynomial_intergrity_ptr_destroy)(IntegrityPointer* p) { delete p; } + + } // extern "C" + +} // namespace polynomials diff --git a/icicle/appUtils/poseidon/.gitignore b/icicle/src/poseidon/.gitignore similarity index 100% rename from icicle/appUtils/poseidon/.gitignore rename to icicle/src/poseidon/.gitignore diff --git a/icicle/appUtils/poseidon/Makefile b/icicle/src/poseidon/Makefile similarity index 51% rename from icicle/appUtils/poseidon/Makefile rename to icicle/src/poseidon/Makefile index 53364bfb..5f08b54a 100644 --- a/icicle/appUtils/poseidon/Makefile +++ b/icicle/src/poseidon/Makefile @@ -1,3 +1,3 @@ test_poseidon: test.cu poseidon.cu kernels.cu constants.cu - nvcc -o test_poseidon -I. -I../.. test.cu + nvcc -o test_poseidon -I../../include -DFIELD_ID=2 -DCURVE_ID=2 test.cu ./test_poseidon diff --git a/icicle/appUtils/poseidon/constants.cu b/icicle/src/poseidon/constants.cu similarity index 78% rename from icicle/appUtils/poseidon/constants.cu rename to icicle/src/poseidon/constants.cu index 8cbd5c10..c1ab9aaf 100644 --- a/icicle/appUtils/poseidon/constants.cu +++ b/icicle/src/poseidon/constants.cu @@ -1,20 +1,21 @@ -#include "poseidon.cuh" +#include "poseidon/poseidon.cuh" /// These are pre-calculated constants for different curves -#if CURVE_ID == BN254 -#include "appUtils/poseidon/constants/bn254_poseidon.h" +#include "fields/id.h" +#if FIELD_ID == BN254 +#include "poseidon/constants/bn254_poseidon.h" using namespace poseidon_constants_bn254; -#elif CURVE_ID == BLS12_381 -#include "appUtils/poseidon/constants/bls12_381_poseidon.h" +#elif FIELD_ID == BLS12_381 +#include "poseidon/constants/bls12_381_poseidon.h" using namespace poseidon_constants_bls12_381; -#elif CURVE_ID == BLS12_377 -#include "appUtils/poseidon/constants/bls12_377_poseidon.h" +#elif FIELD_ID == BLS12_377 +#include "poseidon/constants/bls12_377_poseidon.h" using namespace poseidon_constants_bls12_377; -#elif CURVE_ID == BW6_761 -#include "appUtils/poseidon/constants/bw6_761_poseidon.h" +#elif FIELD_ID == BW6_761 +#include "poseidon/constants/bw6_761_poseidon.h" using namespace poseidon_constants_bw6_761; -#elif CURVE_ID == GRUMPKIN -#include "appUtils/poseidon/constants/grumpkin_poseidon.h" +#elif FIELD_ID == GRUMPKIN +#include "poseidon/constants/grumpkin_poseidon.h" using namespace poseidon_constants_grumpkin; #endif @@ -98,21 +99,21 @@ namespace poseidon { return CHK_LAST(); } - extern "C" cudaError_t CONCAT_EXPAND(CURVE, CreateOptimizedPoseidonConstants)( + extern "C" cudaError_t CONCAT_EXPAND(FIELD, create_optimized_poseidon_constants_cuda)( int arity, int full_rounds_half, int partial_rounds, - const curve_config::scalar_t* constants, + const scalar_t* constants, device_context::DeviceContext& ctx, - PoseidonConstants* poseidon_constants) + PoseidonConstants* poseidon_constants) { - return create_optimized_poseidon_constants( + return create_optimized_poseidon_constants( arity, full_rounds_half, partial_rounds, constants, ctx, poseidon_constants); } - extern "C" cudaError_t CONCAT_EXPAND(CURVE, InitOptimizedPoseidonConstants)( - int arity, device_context::DeviceContext& ctx, PoseidonConstants* constants) + extern "C" cudaError_t CONCAT_EXPAND(FIELD, init_optimized_poseidon_constants_cuda)( + int arity, device_context::DeviceContext& ctx, PoseidonConstants* constants) { - return init_optimized_poseidon_constants(arity, ctx, constants); + return init_optimized_poseidon_constants(arity, ctx, constants); } } // namespace poseidon \ No newline at end of file diff --git a/icicle/appUtils/poseidon/kernels.cu b/icicle/src/poseidon/kernels.cu similarity index 98% rename from icicle/appUtils/poseidon/kernels.cu rename to icicle/src/poseidon/kernels.cu index a4c169c1..85359292 100644 --- a/icicle/appUtils/poseidon/kernels.cu +++ b/icicle/src/poseidon/kernels.cu @@ -1,4 +1,5 @@ -#include "poseidon.cuh" +#include "poseidon/poseidon.cuh" +#include "gpu-utils/modifiers.cuh" namespace poseidon { template diff --git a/icicle/appUtils/poseidon/poseidon.cu b/icicle/src/poseidon/poseidon.cu similarity index 85% rename from icicle/appUtils/poseidon/poseidon.cu rename to icicle/src/poseidon/poseidon.cu index 336db193..6c247013 100644 --- a/icicle/appUtils/poseidon/poseidon.cu +++ b/icicle/src/poseidon/poseidon.cu @@ -1,4 +1,8 @@ -#include "poseidon.cuh" +#include "fields/field_config.cuh" + +using namespace field_config; + +#include "poseidon/poseidon.cuh" #include "constants.cu" #include "kernels.cu" @@ -85,23 +89,23 @@ namespace poseidon { return CHK_LAST(); } - extern "C" cudaError_t CONCAT_EXPAND(CURVE, PoseidonHash)( - curve_config::scalar_t* input, - curve_config::scalar_t* output, + extern "C" cudaError_t CONCAT_EXPAND(FIELD, poseidon_hash_cuda)( + scalar_t* input, + scalar_t* output, int number_of_states, int arity, - const PoseidonConstants& constants, + const PoseidonConstants& constants, PoseidonConfig& config) { switch (arity) { case 2: - return poseidon_hash(input, output, number_of_states, constants, config); + return poseidon_hash(input, output, number_of_states, constants, config); case 4: - return poseidon_hash(input, output, number_of_states, constants, config); + return poseidon_hash(input, output, number_of_states, constants, config); case 8: - return poseidon_hash(input, output, number_of_states, constants, config); + return poseidon_hash(input, output, number_of_states, constants, config); case 11: - return poseidon_hash(input, output, number_of_states, constants, config); + return poseidon_hash(input, output, number_of_states, constants, config); default: THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, "PoseidonHash: #arity must be one of [2, 4, 8, 11]"); } diff --git a/icicle/appUtils/poseidon/test.cu b/icicle/src/poseidon/test.cu similarity index 99% rename from icicle/appUtils/poseidon/test.cu rename to icicle/src/poseidon/test.cu index 3981a1c9..52998415 100644 --- a/icicle/appUtils/poseidon/test.cu +++ b/icicle/src/poseidon/test.cu @@ -1,8 +1,9 @@ // #define DEBUG -#define CURVE_ID 2 #include "curves/curve_config.cuh" -#include "utils/device_context.cuh" +using namespace curve_config; + +#include "gpu-utils/device_context.cuh" #include "poseidon.cu" #ifndef __CUDA_ARCH__ @@ -12,7 +13,6 @@ #include using namespace poseidon; -using namespace curve_config; #define A 2 #define T (A + 1) @@ -47,7 +47,7 @@ int main(int argc, char* argv[]) scalar_t* out_ptr = static_cast(malloc(number_of_blocks * sizeof(scalar_t))); START_TIMER(poseidon_timer); - PoseidonConfig config = default_poseidon_config(T); + PoseidonConfig config = default_poseidon_config(T); poseidon_hash(in_ptr, out_ptr, number_of_blocks, constants, config); END_TIMER(poseidon_timer, "Poseidon") diff --git a/icicle/appUtils/tree/.gitignore b/icicle/src/poseidon/tree/.gitignore similarity index 100% rename from icicle/appUtils/tree/.gitignore rename to icicle/src/poseidon/tree/.gitignore diff --git a/icicle/src/poseidon/tree/Makefile b/icicle/src/poseidon/tree/Makefile new file mode 100644 index 00000000..9b1ebd5b --- /dev/null +++ b/icicle/src/poseidon/tree/Makefile @@ -0,0 +1,3 @@ +test_merkle: + nvcc -o test_merkle -I../../../include -DFIELD_ID=2 -DCURVE_ID=2 test.cu + ./test_merkle \ No newline at end of file diff --git a/icicle/appUtils/tree/merkle.cu b/icicle/src/poseidon/tree/merkle.cu similarity index 93% rename from icicle/appUtils/tree/merkle.cu rename to icicle/src/poseidon/tree/merkle.cu index 61fe4dc4..076da24e 100644 --- a/icicle/appUtils/tree/merkle.cu +++ b/icicle/src/poseidon/tree/merkle.cu @@ -1,4 +1,8 @@ -#include "merkle.cuh" +#include "fields/field_config.cuh" + +using namespace field_config; + +#include "poseidon/tree/merkle.cuh" namespace merkle { /// Flattens the tree digests and sum them up to get @@ -66,7 +70,7 @@ namespace merkle { { int arity = T - 1; - PoseidonConfig config = default_poseidon_config(T); + PoseidonConfig config = default_poseidon_config(T); config.are_inputs_on_device = true; config.are_outputs_on_device = true; config.input_is_a_state = true; @@ -255,23 +259,23 @@ namespace merkle { return CHK_LAST(); } - extern "C" cudaError_t CONCAT_EXPAND(CURVE, BuildPoseidonMerkleTree)( - const curve_config::scalar_t* leaves, - curve_config::scalar_t* digests, + extern "C" cudaError_t CONCAT_EXPAND(FIELD, build_poseidon_merkle_tree)( + const scalar_t* leaves, + scalar_t* digests, uint32_t height, int arity, - PoseidonConstants& constants, + PoseidonConstants& constants, TreeBuilderConfig& config) { switch (arity) { case 2: - return build_merkle_tree(leaves, digests, height, constants, config); + return build_merkle_tree(leaves, digests, height, constants, config); case 4: - return build_merkle_tree(leaves, digests, height, constants, config); + return build_merkle_tree(leaves, digests, height, constants, config); case 8: - return build_merkle_tree(leaves, digests, height, constants, config); + return build_merkle_tree(leaves, digests, height, constants, config); case 11: - return build_merkle_tree(leaves, digests, height, constants, config); + return build_merkle_tree(leaves, digests, height, constants, config); default: THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, "BuildPoseidonMerkleTree: #arity must be one of [2, 4, 8, 11]"); } diff --git a/icicle/appUtils/tree/test.cu b/icicle/src/poseidon/tree/test.cu similarity index 99% rename from icicle/appUtils/tree/test.cu rename to icicle/src/poseidon/tree/test.cu index db07695f..e487d56b 100644 --- a/icicle/appUtils/tree/test.cu +++ b/icicle/src/poseidon/tree/test.cu @@ -1,9 +1,8 @@ // #define DEBUG #define MERKLE_DEBUG -#define CURVE_ID 2 #include "curves/curve_config.cuh" -#include "appUtils/poseidon/poseidon.cu" +#include "../poseidon.cu" #include "merkle.cu" #ifndef __CUDA_ARCH__ @@ -70,7 +69,7 @@ int main(int argc, char* argv[]) std::cout << "Total RAM consumption = " << (digests_mem + leaves_mem) / 1024 / 1024 << " MB; " << (digests_mem + leaves_mem) / 1024 / 1024 / 1024 << " GB" << std::endl; - TreeBuilderConfig config = default_merkle_config(); + TreeBuilderConfig config = default_merkle_config(); config.keep_rows = keep_rows; START_TIMER(timer_merkle); build_merkle_tree(leaves, digests, tree_height, constants, config); diff --git a/icicle/src/vec_ops/extern.cu b/icicle/src/vec_ops/extern.cu new file mode 100644 index 00000000..00be401a --- /dev/null +++ b/icicle/src/vec_ops/extern.cu @@ -0,0 +1,62 @@ +#include "fields/field_config.cuh" + +using namespace field_config; + +#include "utils/utils.h" +#include "vec_ops.cu" + +namespace vec_ops { + /** + * Extern version of [Mul](@ref Mul) function with the template parameters + * `S` and `E` being the [field](@ref scalar_t) (either scalar field of the curve given by `-DCURVE` + * or standalone "STARK field" given by `-DFIELD`). + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t + CONCAT_EXPAND(FIELD, mul_cuda)(scalar_t* vec_a, scalar_t* vec_b, int n, VecOpsConfig& config, scalar_t* result) + { + return mul(vec_a, vec_b, n, config, result); + } + + /** + * Extern version of [Add](@ref Add) function with the template parameter + * `E` being the [field](@ref scalar_t) (either scalar field of the curve given by `-DCURVE` + * or standalone "STARK field" given by `-DFIELD`). + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t + CONCAT_EXPAND(FIELD, add_cuda)(scalar_t* vec_a, scalar_t* vec_b, int n, VecOpsConfig& config, scalar_t* result) + { + return add(vec_a, vec_b, n, config, result); + } + + /** + * Extern version of [Sub](@ref Sub) function with the template parameter + * `E` being the [field](@ref scalar_t) (either scalar field of the curve given by `-DCURVE` + * or standalone "STARK field" given by `-DFIELD`). + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t + CONCAT_EXPAND(FIELD, sub_cuda)(scalar_t* vec_a, scalar_t* vec_b, int n, VecOpsConfig& config, scalar_t* result) + { + return sub(vec_a, vec_b, n, config, result); + } + + /** + * Extern version of transpose_batch function with the template parameter + * `E` being the [field](@ref scalar_t) (either scalar field of the curve given by `-DCURVE` + * or standalone "STARK field" given by `-DFIELD`). + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(FIELD, transpose_matrix_cuda)( + const scalar_t* input, + uint32_t row_size, + uint32_t column_size, + scalar_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async) + { + return transpose_matrix(input, output, row_size, column_size, ctx, on_device, is_async); + } +} // namespace vec_ops \ No newline at end of file diff --git a/icicle/src/vec_ops/extern_extension.cu b/icicle/src/vec_ops/extern_extension.cu new file mode 100644 index 00000000..df63ab34 --- /dev/null +++ b/icicle/src/vec_ops/extern_extension.cu @@ -0,0 +1,59 @@ +#include "fields/field_config.cuh" + +using namespace field_config; + +#include "utils/utils.h" +#include "vec_ops.cu" + +namespace vec_ops { + /** + * Extern version of [Mul](@ref Mul) function with the template parameters + * `S` and `E` being the [extension field](@ref extension_t) of the base field given by `-DFIELD` env variable + * during build. + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(FIELD, extension_mul_cuda)( + extension_t* vec_a, extension_t* vec_b, int n, VecOpsConfig& config, extension_t* result) + { + return mul(vec_a, vec_b, n, config, result); + } + + /** + * Extern version of [Add](@ref Add) function with the template parameter + * `E` being the [extension field](@ref extension_t) of the base field given by `-DFIELD` env variable during build. + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(FIELD, extension_add_cuda)( + extension_t* vec_a, extension_t* vec_b, int n, VecOpsConfig& config, extension_t* result) + { + return add(vec_a, vec_b, n, config, result); + } + + /** + * Extern version of [Sub](@ref Sub) function with the template parameter + * `E` being the [extension field](@ref extension_t) of the base field given by `-DFIELD` env variable during build. + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(FIELD, extension_sub_cuda)( + extension_t* vec_a, extension_t* vec_b, int n, VecOpsConfig& config, extension_t* result) + { + return sub(vec_a, vec_b, n, config, result); + } + + /** + * Extern version of transpose_batch function with the template parameter + * `E` being the [extension field](@ref extension_t) of the base field given by `-DFIELD` env variable during build. + * @return `cudaSuccess` if the execution was successful and an error code otherwise. + */ + extern "C" cudaError_t CONCAT_EXPAND(FIELD, extension_transpose_matrix_cuda)( + const extension_t* input, + uint32_t row_size, + uint32_t column_size, + extension_t* output, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async) + { + return transpose_matrix(input, output, row_size, column_size, ctx, on_device, is_async); + } +} // namespace vec_ops \ No newline at end of file diff --git a/icicle/src/vec_ops/vec_ops.cu b/icicle/src/vec_ops/vec_ops.cu new file mode 100644 index 00000000..d87dc2a8 --- /dev/null +++ b/icicle/src/vec_ops/vec_ops.cu @@ -0,0 +1,167 @@ +#include +#include + +#include "vec_ops/vec_ops.cuh" +#include "gpu-utils/device_context.cuh" +#include "utils/mont.cuh" + +namespace vec_ops { + + namespace { + +#define MAX_THREADS_PER_BLOCK 256 + + template + __global__ void mul_kernel(const E* scalar_vec, const E* element_vec, int n, E* result) + { + int tid = blockDim.x * blockIdx.x + threadIdx.x; + if (tid < n) { result[tid] = scalar_vec[tid] * element_vec[tid]; } + } + + template + __global__ void mul_scalar_kernel(const E* element_vec, const S scalar, int n, E* result) + { + int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid < n) { result[tid] = element_vec[tid] * (scalar); } + } + + template + __global__ void div_element_wise_kernel(const E* element_vec1, const E* element_vec2, int n, E* result) + { + // TODO:implement better based on https://eprint.iacr.org/2008/199 + int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid < n) { result[tid] = element_vec1[tid] * E::inverse(element_vec2[tid]); } + } + + template + __global__ void add_kernel(const E* element_vec1, const E* element_vec2, int n, E* result) + { + int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid < n) { result[tid] = element_vec1[tid] + element_vec2[tid]; } + } + + template + __global__ void sub_kernel(const E* element_vec1, const E* element_vec2, int n, E* result) + { + int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid < n) { result[tid] = element_vec1[tid] - element_vec2[tid]; } + } + + template + __global__ void transpose_kernel(const E* in, E* out, uint32_t row_size, uint32_t column_size) + { + int tid = blockDim.x * blockIdx.x + threadIdx.x; + if (tid >= row_size * column_size) return; + out[(tid % row_size) * column_size + (tid / row_size)] = in[tid]; + } + } // namespace + + template + cudaError_t vec_op(const E* vec_a, const E* vec_b, int n, VecOpsConfig& config, E* result) + { + CHK_INIT_IF_RETURN(); + + // Set the grid and block dimensions + int num_threads = MAX_THREADS_PER_BLOCK; + int num_blocks = (n + num_threads - 1) / num_threads; + + E *d_result, *d_alloc_vec_a, *d_alloc_vec_b; + const E *d_vec_a, *d_vec_b; + if (!config.is_a_on_device) { + CHK_IF_RETURN(cudaMallocAsync(&d_alloc_vec_a, n * sizeof(E), config.ctx.stream)); + CHK_IF_RETURN(cudaMemcpyAsync(d_alloc_vec_a, vec_a, n * sizeof(E), cudaMemcpyHostToDevice, config.ctx.stream)); + d_vec_a = d_alloc_vec_a; + } else { + d_vec_a = vec_a; + } + + if (!config.is_b_on_device) { + CHK_IF_RETURN(cudaMallocAsync(&d_alloc_vec_b, n * sizeof(E), config.ctx.stream)); + CHK_IF_RETURN(cudaMemcpyAsync(d_alloc_vec_b, vec_b, n * sizeof(E), cudaMemcpyHostToDevice, config.ctx.stream)); + d_vec_b = d_alloc_vec_b; + } else { + d_vec_b = vec_b; + } + + if (!config.is_result_on_device) { + CHK_IF_RETURN(cudaMallocAsync(&d_result, n * sizeof(E), config.ctx.stream)); + } else { + d_result = result; + } + + // Call the kernel to perform element-wise operation + Kernel<<>>(d_vec_a, d_vec_b, n, d_result); + + if (!config.is_a_on_device) { CHK_IF_RETURN(cudaFreeAsync(d_alloc_vec_a, config.ctx.stream)); } + if (!config.is_b_on_device) { CHK_IF_RETURN(cudaFreeAsync(d_alloc_vec_b, config.ctx.stream)); } + + if (!config.is_result_on_device) { + CHK_IF_RETURN(cudaMemcpyAsync(result, d_result, n * sizeof(E), cudaMemcpyDeviceToHost, config.ctx.stream)); + CHK_IF_RETURN(cudaFreeAsync(d_result, config.ctx.stream)); + } + + if (!config.is_async) return CHK_STICKY(cudaStreamSynchronize(config.ctx.stream)); + + return CHK_LAST(); + } + + template + cudaError_t mul(const E* vec_a, const E* vec_b, int n, VecOpsConfig& config, E* result) + { + return vec_op(vec_a, vec_b, n, config, result); + } + + template + cudaError_t add(const E* vec_a, const E* vec_b, int n, VecOpsConfig& config, E* result) + { + return vec_op(vec_a, vec_b, n, config, result); + } + + template + cudaError_t sub(const E* vec_a, const E* vec_b, int n, VecOpsConfig& config, E* result) + { + return vec_op(vec_a, vec_b, n, config, result); + } + + template + cudaError_t transpose_matrix( + const E* mat_in, + E* mat_out, + uint32_t row_size, + uint32_t column_size, + device_context::DeviceContext& ctx, + bool on_device, + bool is_async) + { + int number_of_threads = MAX_THREADS_PER_BLOCK; + int number_of_blocks = (row_size * column_size + number_of_threads - 1) / number_of_threads; + cudaStream_t stream = ctx.stream; + + const E* d_mat_in; + E* d_allocated_input = nullptr; + E* d_mat_out; + if (!on_device) { + CHK_IF_RETURN(cudaMallocAsync(&d_allocated_input, row_size * column_size * sizeof(E), ctx.stream)); + CHK_IF_RETURN(cudaMemcpyAsync( + d_allocated_input, mat_in, row_size * column_size * sizeof(E), cudaMemcpyHostToDevice, ctx.stream)); + + CHK_IF_RETURN(cudaMallocAsync(&d_mat_out, row_size * column_size * sizeof(E), ctx.stream)); + d_mat_in = d_allocated_input; + } else { + d_mat_in = mat_in; + d_mat_out = mat_out; + } + + transpose_kernel<<>>(d_mat_in, d_mat_out, row_size, column_size); + + if (!on_device) { + CHK_IF_RETURN( + cudaMemcpyAsync(mat_out, d_mat_out, row_size * column_size * sizeof(E), cudaMemcpyDeviceToHost, ctx.stream)); + CHK_IF_RETURN(cudaFreeAsync(d_mat_out, ctx.stream)); + CHK_IF_RETURN(cudaFreeAsync(d_allocated_input, ctx.stream)); + } + if (!is_async) return CHK_STICKY(cudaStreamSynchronize(ctx.stream)); + + return CHK_LAST(); + } +} // namespace vec_ops \ No newline at end of file diff --git a/icicle/tests/CMakeLists.txt b/icicle/tests/CMakeLists.txt new file mode 100644 index 00000000..ed44020c --- /dev/null +++ b/icicle/tests/CMakeLists.txt @@ -0,0 +1,32 @@ + +include(GoogleTest) +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip +) +# For Windows: Prevent overriding the parent project's compiler/linker settings + +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + + +add_executable(runner runner.cu) # TODO link to icicle rather than including source files +target_include_directories(runner PUBLIC ${CMAKE_SOURCE_DIR}/include/) +target_link_libraries(runner GTest::gtest_main) +set_target_properties(runner PROPERTIES CUDA_SEPARABLE_COMPILATION ON) + +# polynomial test-bench +if (FIELD OR CURVE) + add_executable(polynomial_tb polynomial_test.cu) + target_link_libraries(polynomial_tb GTest::gtest_main icicle_field pthread) + if (CURVE) + target_link_libraries(polynomial_tb icicle_curve) + endif() + set_target_properties(polynomial_tb PROPERTIES CUDA_SEPARABLE_COMPILATION ON) + gtest_discover_tests(polynomial_tb) +endif() + +enable_testing() + +gtest_discover_tests(runner) diff --git a/icicle/tests/curve_test.cu b/icicle/tests/curve_test.cu new file mode 100644 index 00000000..087b3e4b --- /dev/null +++ b/icicle/tests/curve_test.cu @@ -0,0 +1,194 @@ +#include "utils/test_functions.cuh" +#include "curves/curve_config.cuh" +#include +#include +#include + +using namespace curve_config; + +template +class CurveTest : public ::testing::Test +{ +protected: + using TestP = P; + + static const unsigned n = 1 << 4; + + P* points1{}; + P* points2{}; + typename P::Scalar* scalars1{}; + typename P::Scalar* scalars2{}; + P* zero_points{}; + typename P::Scalar* one_scalars{}; + typename P::Aff* aff_points{}; + P* res_points1{}; + P* res_points2{}; + typename P::Scalar* res_scalars{}; + + CurveTest() + { + assert(!cudaDeviceReset()); + assert(!cudaMallocManaged(&points1, n * sizeof(P))); + assert(!cudaMallocManaged(&points2, n * sizeof(P))); + assert(!cudaMallocManaged(&scalars1, n * sizeof(typename P::Scalar))); + assert(!cudaMallocManaged(&scalars2, n * sizeof(typename P::Scalar))); + assert(!cudaMallocManaged(&zero_points, n * sizeof(P))); + assert(!cudaMallocManaged(&one_scalars, n * sizeof(typename P::Scalar))); + assert(!cudaMallocManaged(&aff_points, n * sizeof(typename P::Aff))); + assert(!cudaMallocManaged(&res_points1, n * sizeof(P))); + assert(!cudaMallocManaged(&res_points2, n * sizeof(P))); + assert(!cudaMallocManaged(&res_scalars, n * sizeof(typename P::Scalar))); + } + + ~CurveTest() override + { + cudaFree(points1); + cudaFree(points2); + cudaFree(scalars1); + cudaFree(scalars2); + cudaFree(zero_points); + cudaFree(one_scalars); + cudaFree(aff_points); + cudaFree(res_points1); + cudaFree(res_points2); + cudaFree(res_scalars); + + cudaDeviceReset(); + } + + void SetUp() override + { + ASSERT_EQ(device_populate_random

(points1, n), cudaSuccess); + ASSERT_EQ(device_populate_random

(points2, n), cudaSuccess); + ASSERT_EQ(device_populate_random(scalars1, n), cudaSuccess); + ASSERT_EQ(device_populate_random(scalars2, n), cudaSuccess); + ASSERT_EQ(device_set

(zero_points, P::zero(), n), cudaSuccess); + ASSERT_EQ(device_set(one_scalars, P::Scalar::one(), n), cudaSuccess); + ASSERT_EQ(cudaMemset(aff_points, 0, n * sizeof(typename P::Aff)), cudaSuccess); + ASSERT_EQ(cudaMemset(res_points1, 0, n * sizeof(P)), cudaSuccess); + ASSERT_EQ(cudaMemset(res_points2, 0, n * sizeof(P)), cudaSuccess); + ASSERT_EQ(cudaMemset(res_scalars, 0, n * sizeof(typename P::Scalar)), cudaSuccess); + } +}; + +#ifdef G2 +typedef testing::Types CTImplementations; +#else +typedef testing::Types CTImplementations; +#endif + +TYPED_TEST_SUITE(CurveTest, CTImplementations); + +TYPED_TEST(CurveTest, ECRandomPointsAreOnCurve) +{ + using TestP = typename TestFixture::TestP; + for (unsigned i = 0; i < this->n; i++) + ASSERT_PRED1(TestP::is_on_curve, this->points1[i]); +} + +TYPED_TEST(CurveTest, ECPointAdditionSubtractionCancel) +{ + ASSERT_EQ(vec_add(this->points1, this->points2, this->res_points1, this->n), cudaSuccess); + ASSERT_EQ(vec_sub(this->res_points1, this->points2, this->res_points2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->points1[i], this->res_points2[i]); +} + +TYPED_TEST(CurveTest, ECPointZeroAddition) +{ + ASSERT_EQ(vec_add(this->points1, this->zero_points, this->res_points1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->points1[i], this->res_points1[i]); +} + +TYPED_TEST(CurveTest, ECPointAdditionHostDeviceEq) +{ + ASSERT_EQ(vec_add(this->points1, this->points2, this->res_points1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->points1[i] + this->points2[i], this->res_points1[i]); +} + +TYPED_TEST(CurveTest, ECScalarMultiplicationHostDeviceEq) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->points1, this->res_points1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->scalars1[i] * this->points1[i], this->res_points1[i]); +} + +TYPED_TEST(CurveTest, ECScalarMultiplicationByOne) +{ + ASSERT_EQ(vec_mul(this->one_scalars, this->points1, this->res_points1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->points1[i], this->res_points1[i]); +} + +TYPED_TEST(CurveTest, ECScalarMultiplicationByMinusOne) +{ + ASSERT_EQ(vec_neg(this->one_scalars, this->res_scalars, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->res_scalars, this->points1, this->res_points1, this->n), cudaSuccess); + ASSERT_EQ(vec_neg(this->points1, this->res_points2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->res_points1[i], this->res_points2[i]); +} + +TYPED_TEST(CurveTest, ECScalarMultiplicationByTwo) +{ + ASSERT_EQ(vec_add(this->one_scalars, this->one_scalars, this->res_scalars, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->res_scalars, this->points1, this->res_points1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ((this->one_scalars[i] + this->one_scalars[i]) * this->points1[i], this->res_points1[i]); +} + +TYPED_TEST(CurveTest, ECScalarMultiplicationInverseCancel) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->points1, this->res_points1, this->n), cudaSuccess); + ASSERT_EQ(field_vec_inv(this->scalars1, this->res_scalars, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->res_scalars, this->res_points1, this->res_points2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->points1[i], this->res_points2[i]); +} + +TYPED_TEST(CurveTest, ECScalarMultiplicationIsDistributiveOverMultiplication) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->points1, this->res_points1, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->scalars2, this->res_points1, this->res_points2, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->scalars1, this->scalars2, this->res_scalars, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->res_scalars, this->points1, this->res_points1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->res_points1[i], this->res_points2[i]); +} + +TYPED_TEST(CurveTest, ECScalarMultiplicationIsDistributiveOverAddition) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->points1, this->res_points1, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->scalars2, this->points1, this->res_points2, this->n), cudaSuccess); + ASSERT_EQ(vec_add(this->scalars1, this->scalars2, this->res_scalars, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->res_scalars[i] * this->points1[i], this->res_points1[i] + this->res_points2[i]); +} + +TYPED_TEST(CurveTest, ECProjectiveToAffine) +{ + using TestP = typename TestFixture::TestP; + ASSERT_EQ(point_vec_to_affine(this->points1, this->aff_points, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->points1[i], TestP::from_affine(this->aff_points[i])); +} + +TYPED_TEST(CurveTest, ECMixedPointAddition) +{ + ASSERT_EQ(point_vec_to_affine(this->points2, this->aff_points, this->n), cudaSuccess); + ASSERT_EQ(vec_add(this->points1, this->aff_points, this->res_points1, this->n), cudaSuccess); + ASSERT_EQ(vec_add(this->points1, this->points2, this->res_points2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->res_points1[i], this->res_points2[i]); +} + +TYPED_TEST(CurveTest, ECMixedAdditionOfNegatedPointEqSubtraction) +{ + ASSERT_EQ(point_vec_to_affine(this->points2, this->aff_points, this->n), cudaSuccess); + ASSERT_EQ(vec_sub(this->points1, this->aff_points, this->res_points1, this->n), cudaSuccess); + ASSERT_EQ(vec_neg(this->points2, this->res_points2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->res_points1[i], this->points1[i] + this->res_points2[i]); +} diff --git a/icicle/tests/device_error_test.cu b/icicle/tests/device_error_test.cu index a2ad5cf2..9071f883 100644 --- a/icicle/tests/device_error_test.cu +++ b/icicle/tests/device_error_test.cu @@ -1,4 +1,4 @@ -#include "utils/error_handler.cuh" // Include your error handling header file +#include "gpu-utils/error_handler.cuh" // Include your error handling header file #include __global__ void a_kernel_with_conditional_sticky_error(bool is_failing) diff --git a/icicle/tests/error_handler_test.cu b/icicle/tests/error_handler_test.cu index c863e3ba..64023694 100644 --- a/icicle/tests/error_handler_test.cu +++ b/icicle/tests/error_handler_test.cu @@ -1,4 +1,4 @@ -#include "utils/error_handler.cuh" // Include your error handling header file +#include "gpu-utils/error_handler.cuh" // Include your error handling header file #include class IcicleErrorTest : public ::testing::Test diff --git a/icicle/tests/field_test.cu b/icicle/tests/field_test.cu new file mode 100644 index 00000000..704c3986 --- /dev/null +++ b/icicle/tests/field_test.cu @@ -0,0 +1,145 @@ +#include "utils/test_functions.cuh" +#include "fields/field_config.cuh" +#include +#include +#include + +using namespace field_config; + +template +class FieldTest : public ::testing::Test +{ +protected: + static const unsigned n = 1 << 4; + + T* scalars1{}; + T* scalars2{}; + T* zero_scalars{}; + T* one_scalars{}; + T* res_scalars1{}; + T* res_scalars2{}; + + FieldTest() + { + assert(!cudaDeviceReset()); + assert(!cudaMallocManaged(&scalars1, n * sizeof(T))); + assert(!cudaMallocManaged(&scalars2, n * sizeof(T))); + assert(!cudaMallocManaged(&zero_scalars, n * sizeof(T))); + assert(!cudaMallocManaged(&one_scalars, n * sizeof(T))); + assert(!cudaMallocManaged(&res_scalars1, n * sizeof(T))); + assert(!cudaMallocManaged(&res_scalars2, n * sizeof(T))); + } + + ~FieldTest() override + { + cudaFree(scalars1); + cudaFree(scalars2); + cudaFree(zero_scalars); + cudaFree(one_scalars); + cudaFree(res_scalars1); + cudaFree(res_scalars2); + + cudaDeviceReset(); + } + + void SetUp() override + { + ASSERT_EQ(device_populate_random(scalars1, n), cudaSuccess); + ASSERT_EQ(device_populate_random(scalars2, n), cudaSuccess); + ASSERT_EQ(device_set(zero_scalars, T::zero(), n), cudaSuccess); + ASSERT_EQ(device_set(one_scalars, T::one(), n), cudaSuccess); + ASSERT_EQ(cudaMemset(res_scalars1, 0, n * sizeof(T)), cudaSuccess); + ASSERT_EQ(cudaMemset(res_scalars2, 0, n * sizeof(T)), cudaSuccess); + } +}; + +#ifdef EXT_FIELD +typedef testing::Types FTImplementations; +#else +typedef testing::Types FTImplementations; +#endif + +TYPED_TEST_SUITE(FieldTest, FTImplementations); + +TYPED_TEST(FieldTest, FieldAdditionSubtractionCancel) +{ + ASSERT_EQ(vec_add(this->scalars1, this->scalars2, this->res_scalars1, this->n), cudaSuccess); + ASSERT_EQ(vec_sub(this->res_scalars1, this->scalars2, this->res_scalars2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->scalars1[i], this->res_scalars2[i]); +} + +TYPED_TEST(FieldTest, FieldZeroAddition) +{ + ASSERT_EQ(vec_add(this->scalars1, this->zero_scalars, this->res_scalars1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->scalars1[i], this->res_scalars1[i]); +} + +TYPED_TEST(FieldTest, FieldAdditionHostDeviceEq) +{ + ASSERT_EQ(vec_add(this->scalars1, this->scalars2, this->res_scalars1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->scalars1[i] + this->scalars2[i], this->res_scalars1[i]); +} + +TYPED_TEST(FieldTest, FieldMultiplicationByOne) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->one_scalars, this->res_scalars1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->scalars1[i], this->res_scalars1[i]); +} + +TYPED_TEST(FieldTest, FieldMultiplicationByMinusOne) +{ + ASSERT_EQ(vec_neg(this->one_scalars, this->res_scalars1, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->scalars1, this->res_scalars1, this->res_scalars2, this->n), cudaSuccess); + ASSERT_EQ(vec_add(this->scalars1, this->res_scalars2, this->res_scalars1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->res_scalars1[i], this->zero_scalars[i]); +} + +TYPED_TEST(FieldTest, FieldMultiplicationByZero) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->zero_scalars, this->res_scalars1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->zero_scalars[i], this->res_scalars1[i]); +} + +TYPED_TEST(FieldTest, FieldMultiplicationInverseCancel) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->scalars2, this->res_scalars1, this->n), cudaSuccess); + ASSERT_EQ(field_vec_inv(this->scalars2, this->res_scalars2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->scalars1[i], this->res_scalars1[i] * this->res_scalars2[i]); +} + +TYPED_TEST(FieldTest, FieldMultiplicationHostDeviceEq) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->scalars2, this->res_scalars1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->scalars1[i] * this->scalars2[i], this->res_scalars1[i]); +} + +TYPED_TEST(FieldTest, FieldMultiplicationByTwoEqSum) +{ + ASSERT_EQ(vec_add(this->one_scalars, this->one_scalars, this->res_scalars1, this->n), cudaSuccess); + ASSERT_EQ(vec_mul(this->res_scalars1, this->scalars1, this->res_scalars2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->res_scalars2[i], this->scalars1[i] + this->scalars1[i]); +} + +TYPED_TEST(FieldTest, FieldSqrHostDeviceEq) +{ + ASSERT_EQ(field_vec_sqr(this->scalars1, this->res_scalars1, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->scalars1[i] * this->scalars1[i], this->res_scalars1[i]); +} + +TYPED_TEST(FieldTest, FieldMultiplicationSqrEq) +{ + ASSERT_EQ(vec_mul(this->scalars1, this->scalars1, this->res_scalars1, this->n), cudaSuccess); + ASSERT_EQ(field_vec_sqr(this->scalars1, this->res_scalars2, this->n), cudaSuccess); + for (unsigned i = 0; i < this->n; i++) + ASSERT_EQ(this->res_scalars1[i], this->res_scalars2[i]); +} diff --git a/icicle/tests/polynomial_test.cu b/icicle/tests/polynomial_test.cu new file mode 100644 index 00000000..c3ce623c --- /dev/null +++ b/icicle/tests/polynomial_test.cu @@ -0,0 +1,982 @@ +#include +#include +#include +#include +#include + +#include "polynomials/polynomials.h" +#include "polynomials/cuda_backend/polynomial_cuda_backend.cuh" + +#include "ntt/ntt.cuh" +#include "gpu-utils/device_context.cuh" + +/*******************************************/ + +using FpMicroseconds = std::chrono::duration; +#define START_TIMER(timer) auto timer##_start = std::chrono::high_resolution_clock::now(); +#define END_TIMER(timer, msg, enable) \ + if (enable) \ + printf( \ + "%s: %.3f ms\n", msg, FpMicroseconds(std::chrono::high_resolution_clock::now() - timer##_start).count() / 1000); + +using namespace polynomials; + +typedef Polynomial Polynomial_t; + +static uint64_t ceil_to_power_of_two(uint64_t x) { return 1ULL << uint64_t(ceil(log2(x))); } + +class PolynomialTest : public ::testing::Test +{ +public: + static inline const int MAX_NTT_LOG_SIZE = 24; + static inline const bool MEASURE = true; + + // SetUpTestSuite/TearDownTestSuite are called once for the entire test suite + static void SetUpTestSuite() + { + // init NTT domain + auto ntt_config = ntt::default_ntt_config(); + const scalar_t basic_root = scalar_t::omega(MAX_NTT_LOG_SIZE); + ntt::init_domain(basic_root, ntt_config.ctx); + // initializing polynoimals factory for CUDA backend + Polynomial_t::initialize(std::make_unique>()); + } + + static void TearDownTestSuite() {} + + void SetUp() override + { + // code that executes before each test + } + + void TearDown() override + { + // code that executes after each test + } + + static Polynomial_t randomize_polynomial(uint32_t size, bool random = true) + { + auto coeff = std::make_unique(size); + if (random) { + random_samples(coeff.get(), size); + } else { + incremental_values(coeff.get(), size); + } + return Polynomial_t::from_coefficients(coeff.get(), size); + } + + static void random_samples(scalar_t* res, uint32_t count) + { + for (int i = 0; i < count; i++) + res[i] = scalar_t::rand_host(); + } + + static void incremental_values(scalar_t* res, uint32_t count) + { + for (int i = 0; i < count; i++) { + res[i] = i ? res[i - 1] + scalar_t::one() : scalar_t::one(); + } + } + + template + static void compute_powers_of_tau(P g, scalar_t tau, A* res, uint32_t count) + { + res[0] = P::to_affine(g); + for (int i = 1; i < count; i++) { + g = tau * g; + res[i] = P::to_affine(g); + } + } + + static void assert_equal(Polynomial_t& lhs, Polynomial_t& rhs) + { + const int deg_lhs = lhs.degree(); + const int deg_rhs = rhs.degree(); + ASSERT_EQ(deg_lhs, deg_rhs); + + auto lhs_coeffs = std::make_unique(deg_lhs); + auto rhs_coeffs = std::make_unique(deg_rhs); + lhs.copy_coeffs(lhs_coeffs.get(), 1, deg_lhs - 1); + rhs.copy_coeffs(rhs_coeffs.get(), 1, deg_rhs - 1); + + ASSERT_EQ(0, memcmp(lhs_coeffs.get(), rhs_coeffs.get(), deg_lhs * sizeof(scalar_t))); + } + + static Polynomial_t vanishing_polynomial(int degree) + { + scalar_t coeffs_v[degree + 1] = {0}; + coeffs_v[0] = minus_one; // -1 + coeffs_v[degree] = one; // +x^n + auto v = Polynomial_t::from_coefficients(coeffs_v, degree + 1); + return v; + } + + const static inline auto zero = scalar_t::zero(); + const static inline auto one = scalar_t::one(); + const static inline auto two = scalar_t::from(2); + const static inline auto three = scalar_t::from(3); + const static inline auto four = scalar_t::from(4); + const static inline auto five = scalar_t::from(5); + const static inline auto minus_one = zero - one; +}; + +TEST_F(PolynomialTest, evaluation) +{ + const scalar_t coeffs[3] = {one, two, three}; + auto f = Polynomial_t::from_coefficients(coeffs, 3); + scalar_t x = scalar_t::rand_host(); + + auto f_x = f(x); // evaluation + + auto expected_f_x = one + two * x + three * x * x; + + EXPECT_EQ(f_x, expected_f_x); +} + +TEST_F(PolynomialTest, evaluationOnDomain) +{ + int size = 1 << 5; + auto f = PolynomialTest::randomize_polynomial(size); + + size *= 2; // evaluating on a larger domain + auto default_device_context = device_context::get_default_device_context(); + const auto w = ntt::get_root_of_unity_from_domain((int)log2(size), default_device_context); + + // construct domain as rou + scalar_t x = one; + auto domain = std::make_unique(size); + for (int i = 0; i < size; ++i) { + domain[i] = x; + x = x * w; + } + + // evaluate f on the domain (equivalent to NTT) + auto evaluations = std::make_unique(size); + f.evaluate_on_domain(domain.get(), size, evaluations.get()); + + // construct g from the evaluations of f + auto g = Polynomial_t::from_rou_evaluations(evaluations.get(), size); + + // check that f==g + ASSERT_EQ((f - g).degree(), -1); +} + +TEST_F(PolynomialTest, fromEvaluations) +{ + const int size = 100; + const int log_size = (int)ceil(log2(size)); + const int nof_evals = 1 << log_size; + auto f = randomize_polynomial(size); + + // evaluate f on roots of unity + scalar_t omega = scalar_t::omega(log_size); + scalar_t evals[nof_evals] = {0}; + scalar_t x = one; + for (int i = 0; i < nof_evals; ++i) { + evals[i] = f(x); + x = x * omega; + } + + // construct g from f's evaluations + auto g = Polynomial_t::from_rou_evaluations(evals, nof_evals); + + // make sure they are equal, that is f-g=0 + auto h = f - g; + EXPECT_EQ(h.degree(), -1); // degree -1 is the zero polynomial +} + +TEST_F(PolynomialTest, fromEvaluationsNotPowerOfTwo) +{ + const int size = 100; + const int log_size = (int)ceil(log2(size)); + const int nof_evals = size; + auto f = randomize_polynomial(size); + + // evaluate f on roots of unity + scalar_t omega = scalar_t::omega(log_size); + scalar_t evals[nof_evals] = {0}; + scalar_t x = one; + for (int i = 0; i < nof_evals; ++i) { + evals[i] = f(x); + x = x * omega; + } + + // construct g from f's evaluations + auto g = Polynomial_t::from_rou_evaluations(evals, nof_evals); + + // since NTT works on a power of two (therefore the extra elements are arbitrary), f!=g but they should evaluate to + // the same values on the roots of unity due to construction. + x = one; + for (int i = 0; i < nof_evals; ++i) { + EXPECT_EQ(f(x), g(x)); + x = x * omega; + } +} + +TEST_F(PolynomialTest, addition) +{ + const int size_0 = 12, size_1 = 17; + auto f = randomize_polynomial(size_0); + auto g = randomize_polynomial(size_1); + + scalar_t x = scalar_t::rand_host(); + auto f_x = f(x); + auto g_x = g(x); + auto fx_plus_gx = f_x + g_x; + + auto s = f + g; + auto s_x = s(x); + + EXPECT_EQ(fx_plus_gx, s_x); +} + +TEST_F(PolynomialTest, addition_inplace) +{ + const int size_0 = 2, size_1 = 2; + auto f = randomize_polynomial(size_0); + auto g = randomize_polynomial(size_1); + + scalar_t x = scalar_t::rand_host(); + auto f_x = f(x); + auto g_x = g(x); + auto fx_plus_gx = f_x + g_x; + + f += g; + auto s_x = f(x); + + EXPECT_EQ(fx_plus_gx, s_x); +} + +TEST_F(PolynomialTest, multiplication) +{ + const int size_0 = 1 << 15, size_1 = 1 << 12; + auto f = randomize_polynomial(size_0); + auto g = randomize_polynomial(size_1); + + scalar_t x = scalar_t::rand_host(); + auto f_x = f(x); + auto g_x = g(x); + auto fx_mul_gx = f_x * g_x; + + START_TIMER(poly_mult_start); + auto m = f * g; + END_TIMER(poly_mult_start, "Polynomial multiplication took", MEASURE); + + auto m_x = m(x); + + EXPECT_EQ(fx_mul_gx, m_x); +} + +TEST_F(PolynomialTest, multiplicationScalar) +{ + const int size = 1 << 15; + auto f = randomize_polynomial(size); + + auto g = two * f; + auto h = f * three; + + scalar_t x = scalar_t::rand_host(); + auto f_x = f(x); + auto g_x = g(x); + auto h_x = h(x); + + EXPECT_EQ(g_x, f_x * two); + EXPECT_EQ(h_x, f_x * three); + + EXPECT_EQ(g.degree(), f.degree()); + EXPECT_EQ(h.degree(), f.degree()); +} + +TEST_F(PolynomialTest, monomials) +{ + const scalar_t coeffs[3] = {one, zero, two}; // 1+2x^2 + auto f = Polynomial_t::from_coefficients(coeffs, 3); + const auto x = three; + const auto expected_f_x = one + two * x * x; + auto f_x = f(x); + + EXPECT_EQ(f_x, expected_f_x); + + f.add_monomial_inplace(three, 1); // add 3x + const auto expected_addmonmon_f_x = f_x + three * x; + const auto addmonom_f_x = f(x); + + EXPECT_EQ(addmonom_f_x, expected_addmonmon_f_x); + + f.sub_monomial_inplace(one); // subtract 1. equivalent to 'f-1' + const auto expected_submonom_f_x = addmonom_f_x - one; + const auto submonom_f_x = f(x); + + EXPECT_EQ(submonom_f_x, expected_submonom_f_x); +} + +TEST_F(PolynomialTest, ReadCoeffsToHost) +{ + const scalar_t coeffs_f[3] = {zero, one, two}; // x+2x^2 + auto f = Polynomial_t::from_coefficients(coeffs_f, 3); + const scalar_t coeffs_g[3] = {one, one, one}; // 1+x+x^2 + auto g = Polynomial_t::from_coefficients(coeffs_g, 3); + + auto h = f + g; // 1+2x+3x^3 + const auto h0 = h.get_coeff(0); + const auto h1 = h.get_coeff(1); + const auto h2 = h.get_coeff(2); + EXPECT_EQ(h0, one); + EXPECT_EQ(h1, two); + EXPECT_EQ(h2, three); + + scalar_t h_coeffs[3] = {0}; + auto nof_coeffs = h.copy_coeffs(h_coeffs, 0, 2); // read the coefficients + EXPECT_EQ(nof_coeffs, 3); // expecting 3 due to specified indices + + scalar_t expected_h_coeffs[nof_coeffs] = {one, two, three}; + for (int i = 0; i < nof_coeffs; ++i) { + EXPECT_EQ(expected_h_coeffs[i], h_coeffs[i]); + } +} + +TEST_F(PolynomialTest, divisionSimple) +{ + const scalar_t coeffs_a[4] = {five, zero, four, three}; // 3x^3+4x^2+5 + const scalar_t coeffs_b[3] = {minus_one, zero, one}; // x^2-1 + auto a = Polynomial_t::from_coefficients(coeffs_a, 4); + auto b = Polynomial_t::from_coefficients(coeffs_b, 3); + + auto [q, r] = a.divide(b); + scalar_t q_coeffs[2] = {0}; // 3x+4 + scalar_t r_coeffs[2] = {0}; // 3x+9 + const auto q_nof_coeffs = q.copy_coeffs(q_coeffs, 0, 1); + const auto r_nof_coeffs = r.copy_coeffs(r_coeffs, 0, 1); + + ASSERT_EQ(q_nof_coeffs, 2); + ASSERT_EQ(r_nof_coeffs, 2); + ASSERT_EQ(q_coeffs[0], scalar_t::from(4)); + ASSERT_EQ(q_coeffs[1], scalar_t::from(3)); + ASSERT_EQ(r_coeffs[0], scalar_t::from(9)); + ASSERT_EQ(r_coeffs[1], scalar_t::from(3)); +} + +TEST_F(PolynomialTest, divisionLarge) +{ + const int size_0 = 1 << 12, size_1 = 1 << 2; + auto a = randomize_polynomial(size_0); + auto b = randomize_polynomial(size_1); + + START_TIMER(poly_mult_start); + auto [q, r] = a.divide(b); + END_TIMER(poly_mult_start, "Polynomial division took", MEASURE); + + scalar_t x = scalar_t::rand_host(); + auto a_x = a(x); + auto b_x = b(x); + auto q_x = q(x); + auto r_x = r(x); + + // a(x) = b(x)*q(x)+r(x) + EXPECT_EQ(a_x, b_x * q_x + r_x); +} + +TEST_F(PolynomialTest, divideByVanishingPolynomial) +{ + const scalar_t coeffs_v[5] = {minus_one, zero, zero, zero, one}; // x^4-1 vanishes on 4th roots of unity + auto v = Polynomial_t::from_coefficients(coeffs_v, 5); + auto h = randomize_polynomial(1 << 11, false); + auto hv = h * v; + + START_TIMER(poly_div_vanishing); + auto h_div_by_vanishing = hv.divide_by_vanishing_polynomial(4); + END_TIMER(poly_div_vanishing, "Polynomial division by vanishing (fast) took", MEASURE); + assert_equal(h_div_by_vanishing, h); + + START_TIMER(poly_div_long); + auto [h_div, R] = hv.divide(v); + END_TIMER(poly_div_long, "Polynomial division by vanishing (long division) took", MEASURE); + assert_equal(h_div, h); +} + +TEST_F(PolynomialTest, clone) +{ + const int size = 1 << 10; + auto f = randomize_polynomial(size); + + const auto x = scalar_t::rand_host(); + const auto f_x = f(x); + + Polynomial_t g; + g = f.clone(); // operator=(&&) + g += f; + + auto h = g.clone(); // move constructor + + ASSERT_EQ(g(x), two * f_x); + ASSERT_EQ(h(x), g(x)); +} + +TEST_F(PolynomialTest, View) +{ + const int size = 1 << 6; + + auto f = randomize_polynomial(size); + auto [d_coeff, N, device_id] = f.get_coefficients_view(); + + EXPECT_EQ(d_coeff.isValid(), true); + auto g = f + f; + // expecting the view to remain valid in that case + EXPECT_EQ(d_coeff.isValid(), true); + + f += f; + // expecting view to be invalidated since f is modified + EXPECT_EQ(d_coeff.isValid(), false); +} + +TEST_F(PolynomialTest, interpolation) +{ + const int size = 1 << 4; + const int interpolation_size = 1 << 6; + + const auto x = scalar_t::rand_host(); + + auto f = randomize_polynomial(size); + auto [evals, N, device_id] = f.get_rou_evaluations_view(interpolation_size); // interpolate from 16 to 64 evaluations + + auto g = Polynomial_t::from_rou_evaluations(evals.get(), N); // note the evals is a view to f + const auto fx = f(x); + ASSERT_EQ(evals.isValid(), false); // invaidated since f(x) transforms f to coefficients + + const auto gx = g(x); // evaluating g which was constructed from interpolation of f + ASSERT_EQ(fx, gx); +} + +TEST_F(PolynomialTest, slicing) +{ + auto body = [](int size) { + const bool is_odd_size = size % 2 == 1; + auto f = randomize_polynomial(size); + const auto x = scalar_t::rand_host(); + + // computing e(x) and o(x) directly + auto expected_even = scalar_t::zero(); + auto expected_odd = scalar_t::zero(); + for (int i = size - 1; i >= 0; --i) { + if (i % 2 == 0) + expected_even = expected_even * x + f.get_coeff(i); + else + expected_odd = expected_odd * x + f.get_coeff(i); + } + + auto e = f.even(); + auto o = f.odd(); + + ASSERT_EQ(o.degree(), size / 2 - 1); + ASSERT_EQ(e.degree(), is_odd_size ? size / 2 : size / 2 - 1); + + // compute even and odd polynomials and compute them at x + ASSERT_EQ(f.even()(x), expected_even); + ASSERT_EQ(f.odd()(x), expected_odd); + }; + + body(1 << 10); // test even size + body((1 << 10) - 1); // test odd size +} + +#ifdef CURVE +#include "msm/msm.cuh" +#include "curves/curve_config.cuh" +using curve_config::affine_t; +using curve_config::projective_t; + +// using the MSM C-API directly since msm::MSM() symbol is hidden in icicle lib and I cannot understand why +namespace msm { + extern "C" cudaError_t CONCAT_EXPAND(CURVE, msm_cuda)( + const scalar_t* scalars, const affine_t* points, int msm_size, MSMConfig& config, projective_t* out); + cudaError_t _msm(const scalar_t* scalars, const affine_t* points, int msm_size, MSMConfig& config, projective_t* out) + { + return CONCAT_EXPAND(CURVE, msm_cuda)(scalars, points, msm_size, config, out); + } +} // namespace msm + +#ifdef G2 +using curve_config::g2_affine_t; +using curve_config::g2_projective_t; + +namespace msm { + extern "C" cudaError_t CONCAT_EXPAND(CURVE, g2_msm_cuda)( + const scalar_t* scalars, const g2_affine_t* points, int msm_size, MSMConfig& config, g2_projective_t* out); + + cudaError_t + _g2_msm(const scalar_t* scalars, const g2_affine_t* points, int msm_size, MSMConfig& config, g2_projective_t* out) + { + return CONCAT_EXPAND(CURVE, g2_msm_cuda)(scalars, points, msm_size, config, out); + } +} // namespace msm +#endif // G2 + +class dummy_g2_t : public scalar_t +{ +public: + static constexpr __host__ __device__ dummy_g2_t to_affine(const dummy_g2_t& point) { return point; } + + static constexpr __host__ __device__ dummy_g2_t from_affine(const dummy_g2_t& point) { return point; } + + static constexpr __host__ __device__ dummy_g2_t generator() { return dummy_g2_t{scalar_t::one()}; } + + static __host__ __device__ dummy_g2_t zero() { return dummy_g2_t{scalar_t::zero()}; } + + friend __host__ __device__ dummy_g2_t operator*(const scalar_t& xs, const dummy_g2_t& ys) + { + return dummy_g2_t{scalar_t::reduce(scalar_t::mul_wide(xs, ys))}; + } + + friend __host__ __device__ dummy_g2_t operator+(const dummy_g2_t& xs, const dummy_g2_t& ys) + { + scalar_t rs = {}; + scalar_t::add_limbs(xs.limbs_storage, ys.limbs_storage, rs.limbs_storage); + return dummy_g2_t{scalar_t::sub_modulus<1>(rs)}; + } +}; +namespace msm { + cudaError_t + _g2_msm(const scalar_t* scalars, const dummy_g2_t* points, int msm_size, MSMConfig& config, dummy_g2_t* out) + { + scalar_t* scalars_host = static_cast(malloc(msm_size * sizeof(scalar_t))); + cudaMemcpyAsync(scalars_host, scalars, msm_size * sizeof(scalar_t), cudaMemcpyDeviceToHost, config.ctx.stream); + *out = dummy_g2_t::zero(); + for (int i = 0; i < msm_size; i++) + *out = *out + scalars_host[i] * points[i]; + free(scalars_host); + return cudaSuccess; + } + +} // namespace msm +#endif // CURVE + +// Following examples are randomizing N private numbers and proving that I know N numbers such that their product is +// equal to 'out'. +// +// Circuit: +// +// in0 in1 +// \ / +// \ / in2 +// (X) / +// t0\ / in3 +// (X) / +// t1\ / +// (X) +// . +// . +// . +// | +// out +// +// simple construction: t0=in0*in1, t1=t0*in2, t2=t1*in3 and so on to simplify the example +template +class Groth16Example +{ + // based on https://www.rareskills.io/post/groth16 +public: + /******** QAP construction *********/ + + Groth16Example(int N) + : nof_inputs(N), nof_outputs(1), nof_intermediates(nof_inputs - 2), + witness_size(1 + nof_outputs + nof_inputs + nof_intermediates), input_offset(1 + nof_outputs), + intermediate_offset(input_offset + nof_inputs), nof_constraints(nof_inputs - 1) + { + construct_QAP(); + } + + void construct_QAP() + { + // (1) construct matrices A,B,C (based on the circuit) + // allocating such that columns are consecutive in memory for more efficient polynomial construction from + // consecutive evaluations + const int nof_cols = witness_size; + const int nof_rows = ceil_to_power_of_two(nof_constraints); + std::vector L(nof_cols * nof_rows, S::zero()); + std::vector R(nof_cols * nof_rows, S::zero()); + std::vector O(nof_cols * nof_rows, S::zero()); + + S* L_data = L.data(); + S* R_data = R.data(); + S* O_data = O.data(); + + // filling the R1CS matrices (where cols are consecutive, not rows) + for (int row = 0; row < nof_constraints; ++row) { + const int L_col = row == 0 ? input_offset : intermediate_offset + row - 1; + *(L_data + L_col * nof_rows + row) = S::one(); + + const int R_col = input_offset + row + 1; + *(R_data + R_col * nof_rows + row) = S::one(); + + const int O_col = row == nof_constraints - 1 ? 1 : intermediate_offset + row; + *(O_data + O_col * nof_rows + row) = S::one(); + } + + // (2) interpolate the columns of L,R,O to build the polynomials + L_QAP.reserve(nof_cols); + R_QAP.reserve(nof_cols); + O_QAP.reserve(nof_cols); + for (int col = 0; col < nof_cols; ++col) { // #polynomials is equal to witness_size + L_QAP.push_back(std::move(Polynomial_t::from_rou_evaluations(L_data + col * nof_rows, nof_rows))); + R_QAP.push_back(std::move(Polynomial_t::from_rou_evaluations(R_data + col * nof_rows, nof_rows))); + O_QAP.push_back(std::move(Polynomial_t::from_rou_evaluations(O_data + col * nof_rows, nof_rows))); + } + } + + std::vector random_witness_inputs() + { + std::vector witness(witness_size, S::zero()); + witness[0] = S::one(); + PolynomialTest::random_samples(witness.data() + input_offset, nof_inputs); // randomize inputs + + return witness; + } + + void compute_witness(std::vector& witness) + { + if (witness_size != witness.size()) { throw std::runtime_error("invalid witness size"); } + // compute intermediate values (based on the circuit above) + for (int i = 0; i < nof_intermediates; ++i) { + const auto& left_input = i == 0 ? witness[input_offset] : witness[intermediate_offset + i - 1]; + const auto& right_input = witness[input_offset + i + 1]; + witness[intermediate_offset + i] = left_input * right_input; + } + // compute output as last_input * last_intermediate + witness[1] = witness[input_offset + nof_inputs - 1] * witness[intermediate_offset + nof_intermediates - 1]; + } + + const int nof_inputs; + const int nof_outputs; + const int nof_intermediates; + const int witness_size; + const int input_offset; + const int intermediate_offset; + const int nof_constraints; + std::vector L_QAP, R_QAP, O_QAP; + +#ifdef CURVE + /******** SETUP *********/ + // https://static.wixstatic.com/media/935a00_cd68860dafbb4ebe8f166de5cc8cc50c~mv2.png + struct ToxicWaste { + S alpha; + S beta; + S gamma; + S delta; + S tau; + S gamma_inv; + S delta_inv; + + ToxicWaste() + { + alpha = S::rand_host(); + beta = S::rand_host(); + gamma = S::rand_host(); + delta = S::rand_host(); + tau = S::rand_host(); + gamma_inv = S::inverse(gamma); + delta_inv = S::inverse(delta); + } + }; + + struct ProvingKey { + struct _G1 { + G1A alpha; + G1A beta; + G1A delta; + std::vector powers_of_tau; // {X^i} @[0..n-1] + std::vector private_witness_points; // {(beta_Ui+alpha_Vi+Wi) / delta} @[l+1..m] + std::vector vanishing_poly_points; // {x^it(x) / delta} @[0..,n-2] + }; + struct _G2 { + G2A beta; + G2A gamma; + G2A delta; + std::vector powers_of_tau; // {X^i} @[0..n-1] + }; + + _G1 g1; + _G2 g2; + }; + + struct VerifyingKey { + struct _G1 { + G1A alpha; + std::vector public_witness_points; // {(beta_Ui+alpha_Vi+Wi) / delta} @[0..l] + }; + struct _G2 { + G2A beta; + G2A gamma; + G2A delta; + }; + + _G1 g1; + _G2 g2; + }; + + void setup() + { + // randomize alpha, beta, gamma, delta, tau + ToxicWaste toxic_waste; + + G1P g1 = G1P::generator(); + G2P g2 = G2P::generator(); + + // Note: n,m,l are from the groth16 paper + const int m = witness_size - 1; + const int l = nof_outputs; // public part of the witness + const int n = ceil_to_power_of_two(nof_constraints); + + // compute the proving and verifying keys + pk.g1.alpha = G1P::to_affine(toxic_waste.alpha * g1); + vk.g1.alpha = pk.g1.alpha; + pk.g1.beta = G1P::to_affine(toxic_waste.beta * g1); + pk.g1.delta = G1P::to_affine(toxic_waste.delta * g1); + + pk.g1.powers_of_tau.resize(n, G1A::zero()); + PolynomialTest::compute_powers_of_tau(g1, toxic_waste.tau, pk.g1.powers_of_tau.data(), n); + + // { (beta*Ui(tau) + alpha*Vi(tau) + Wi) / delta} + pk.g1.private_witness_points.reserve(m - l); + vk.g1.public_witness_points.reserve(l + 1); + for (int i = 0; i <= m; ++i) { + auto p = toxic_waste.beta * L_QAP[i] + toxic_waste.alpha * R_QAP[i] + O_QAP[i]; + p = p * (i < l + 1 ? toxic_waste.gamma_inv : toxic_waste.delta_inv); + if (i < l + 1) + vk.g1.public_witness_points.push_back(G1P::to_affine(p(toxic_waste.tau) * g1)); + else + pk.g1.private_witness_points.push_back(G1P::to_affine(p(toxic_waste.tau) * g1)); + } + + // {tau^i(t(tau) / delta} + const int vanishing_poly_deg = n; + auto t = PolynomialTest::vanishing_polynomial(vanishing_poly_deg); + pk.g1.vanishing_poly_points.reserve(n - 1); + auto x = S::one(); + for (int i = 0; i <= n - 2; ++i) { + pk.g1.vanishing_poly_points.push_back(G1P::to_affine(x * t(toxic_waste.tau) * toxic_waste.delta_inv * g1)); + x = x * toxic_waste.tau; + } + + pk.g2.beta = G2P::to_affine(toxic_waste.beta * g2); + vk.g2.beta = pk.g2.beta; + pk.g2.gamma = G2P::to_affine(toxic_waste.gamma * g2); + vk.g2.gamma = pk.g2.gamma; + pk.g2.delta = G2P::to_affine(toxic_waste.delta * g2); + vk.g2.delta = pk.g2.delta; + + pk.g2.powers_of_tau.resize(n, G2A::zero()); + PolynomialTest::compute_powers_of_tau(g2, toxic_waste.tau, pk.g2.powers_of_tau.data(), n); + } + + /******** PROVE *********/ + // https://static.wixstatic.com/media/935a00_432ca182820540df8d67b5c3d5d0d3e1~mv2.png + struct G16proof { + G1A A; + G2A B; + G1A C; + }; + + G16proof prove(const std::vector& witness) const + { + G16proof proof = {}; + const auto r = S::rand_host(); + const auto s = S::rand_host(); + + // Note: n,m,l are from the groth16 paper + const int m = witness_size - 1; + const int l = nof_outputs; // public part of the witness + const int n = ceil_to_power_of_two(nof_constraints); + + // construct U,V,W from the QAP and witness + Polynomial_t U = L_QAP[0].clone(); + Polynomial_t V = R_QAP[0].clone(); + Polynomial_t W = O_QAP[0].clone(); + for (int col = 1; col <= m; ++col) { + U += witness[col] * L_QAP[col]; + V += witness[col] * R_QAP[col]; + W += witness[col] * O_QAP[col]; + } + + // compute h(x) = (U(x)*V(x)-W(x)) / t(x) + const int vanishing_poly_deg = n; + Polynomial_t h = (U * V - W).divide_by_vanishing_polynomial(vanishing_poly_deg); + + auto msm_config = msm::default_msm_config(); + msm_config.are_scalars_on_device = true; + + // compute [A]1 + { + G1P U_commited; + auto [U_coeff, N, device_id] = U.get_coefficients_view(); + CHK_STICKY(msm::_msm(U_coeff.get(), pk.g1.powers_of_tau.data(), n, msm_config, &U_commited)); + proof.A = G1P::to_affine(U_commited + G1P::from_affine(pk.g1.alpha) + r * G1P::from_affine(pk.g1.delta)); + } + + // compute [B]2 and [B]1 (required to compute C) + G1P B1; + { + G2P V_commited_g2; + auto [V_coeff, N, device_id] = V.get_coefficients_view(); + CHK_STICKY(msm::_g2_msm(V_coeff.get(), pk.g2.powers_of_tau.data(), n, msm_config, &V_commited_g2)); + proof.B = G2P::to_affine(V_commited_g2 + pk.g2.beta + s * G2P::from_affine(pk.g2.delta)); + + G1P V_commited_g1; + CHK_STICKY(msm::_msm(V_coeff.get(), pk.g1.powers_of_tau.data(), n, msm_config, &V_commited_g1)); + B1 = V_commited_g1 + pk.g1.beta + G1P::from_affine(pk.g1.delta) * s; + } + + // compute [C]1 + { + auto [H_coeff, N, device_id] = h.get_coefficients_view(); + + G1P HT_commited; + CHK_STICKY(msm::_msm(H_coeff.get(), pk.g1.vanishing_poly_points.data(), n - 1, msm_config, &HT_commited)); + + G1P private_inputs_commited; + msm_config.are_scalars_on_device = false; + CHK_STICKY(msm::_msm( + witness.data() + l + 1, pk.g1.private_witness_points.data(), m - l, msm_config, &private_inputs_commited)); + + proof.C = G1P::to_affine( + private_inputs_commited + HT_commited + G1P::from_affine(proof.A) * s + B1 * r - + r * s * G1P::from_affine(pk.g1.delta)); + } + + return proof; + } + + bool verify(const G16proof& proof, const std::vector& public_witness) const + { + throw std::runtime_error("pairing not implemented"); + return false; + } + + // Dummy verification function where pairings are changed to scalar multiplications + // Suitable for verifying correctness with G1 and/or G2 swapped for scalar types + bool dummy_verify(const G16proof& proof, const std::vector& public_witness) const + { + G1P lhs = proof.B * G1P::from_affine(proof.A); + G1P rhs = G1P::zero(); + for (int i = 0; i <= nof_outputs; ++i) + rhs = rhs + public_witness.data()[i] * G1P::from_affine(vk.g1.public_witness_points[i]); + rhs = vk.g2.gamma * rhs + vk.g2.beta * G1P::from_affine(vk.g1.alpha) + vk.g2.delta * G1P::from_affine(proof.C); + return (rhs == lhs); + } + + ProvingKey pk; + VerifyingKey vk; +#endif // CURVE +}; + +TEST_F(PolynomialTest, QAP) +{ + // (1) construct R1CS and QAP for circuit with N inputs + Groth16Example QAP(300 /*=N*/); + + // (2) compute witness: randomize inputs and compute other entries [1,out,...N inputs..., ... intermediate values...] + auto witness = QAP.random_witness_inputs(); + QAP.compute_witness(witness); + + // (3) compute L(x),R(x),O(x) using the witness + const int nof_cols = QAP.witness_size; + + Polynomial_t Lx = QAP.L_QAP[0].clone(); + Polynomial_t Rx = QAP.R_QAP[0].clone(); + Polynomial_t Ox = QAP.O_QAP[0].clone(); + for (int col = 1; col < nof_cols; ++col) { + Lx += witness[col] * QAP.L_QAP[col]; + Rx += witness[col] * QAP.R_QAP[col]; + Ox += witness[col] * QAP.O_QAP[col]; + } + + const int nof_constraints = + QAP.nof_inputs - 1; // multiplying N numbers yields N-1 constraints for this circuit construction + const int vanishing_poly_deg = ceil_to_power_of_two(nof_constraints); + // (4) sanity check: verify AB=C at the evaluation points + { + auto default_device_context = device_context::get_default_device_context(); + const auto w = + ntt::get_root_of_unity_from_domain((int)ceil(log2(nof_constraints)), default_device_context); + auto x = scalar_t::one(); + for (int i = 0; i < vanishing_poly_deg; ++i) { + ASSERT_EQ(Lx(x) * Rx(x), Ox(x)); + x = x * w; + } + } + + // (5) compute h(x) as '(L(x)R(x)-O(x)) / t(x)' + Polynomial_t h = (Lx * Rx - Ox).divide_by_vanishing_polynomial(vanishing_poly_deg); + + // (6) sanity check: vanishing-polynomial divides (LR-O) without remainder + { + auto [h_long_div, r] = (Lx * Rx - Ox).divide(vanishing_polynomial(vanishing_poly_deg)); + EXPECT_EQ(r.degree(), -1); // zero polynomial (expecting division without remainder) + assert_equal(h, h_long_div); + } +} + +#ifdef CURVE +TEST_F(PolynomialTest, commitMSM) +{ + const int size = 1 << 6; + auto f = randomize_polynomial(size); + + auto [d_coeff, N, device_id] = f.get_coefficients_view(); + auto msm_config = msm::default_msm_config(); + msm_config.are_scalars_on_device = true; + + auto points = std::make_unique(size); + projective_t result; + + auto tau = scalar_t::rand_host(); + projective_t g = projective_t::rand_host(); + compute_powers_of_tau(g, tau, points.get(), size); + + EXPECT_EQ(d_coeff.isValid(), true); + CHK_STICKY(msm::_msm(d_coeff.get(), points.get(), size, msm_config, &result)); + + EXPECT_EQ(result, f(tau) * g); + + f += f; // this is invalidating the d_coeff integrity-pointer + + EXPECT_EQ(d_coeff.isValid(), false); +} + +TEST_F(PolynomialTest, DummyGroth16) +{ + // (1) construct R1CS and QAP for circuit with N inputs + Groth16Example groth16_example(30 /*=N*/); + + // (2) compute witness: randomize inputs and compute other entries [1,out,...N inputs..., ... intermediate values...] + auto witness = groth16_example.random_witness_inputs(); + groth16_example.compute_witness(witness); + + groth16_example.setup(); + auto proof = groth16_example.prove(witness); + ASSERT_EQ(groth16_example.dummy_verify(proof, witness), true); +} + +#ifdef G2 +TEST_F(PolynomialTest, Groth16) +{ + // (1) construct R1CS and QAP for circuit with N inputs + Groth16Example groth16_example(30 /*=N*/); + + // (2) compute witness: randomize inputs and compute other entries [1,out,...N inputs..., ... intermediate + // values...] + auto witness = groth16_example.random_witness_inputs(); + groth16_example.compute_witness(witness); + + groth16_example.setup(); + auto proof = groth16_example.prove(witness); + // groth16_example.verify(proof); // cannot implement without pairing +} +#endif // G2 + +#endif // CURVE + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/icicle/tests/primitives_test.cu b/icicle/tests/primitives_test.cu deleted file mode 100644 index 427c4ea5..00000000 --- a/icicle/tests/primitives_test.cu +++ /dev/null @@ -1,425 +0,0 @@ -#include "primitives/test_kernels.cuh" -#include -#include -#include - -template -int device_populate_random(T* d_elements, unsigned n) -{ - T h_elements[n]; - for (unsigned i = 0; i < n; i++) - h_elements[i] = T::rand_host(); - return cudaMemcpy(d_elements, h_elements, sizeof(T) * n, cudaMemcpyHostToDevice); -} - -template -int device_set(T* d_elements, T el, unsigned n) -{ - T h_elements[n]; - for (unsigned i = 0; i < n; i++) - h_elements[i] = el; - return cudaMemcpy(d_elements, h_elements, sizeof(T) * n, cudaMemcpyHostToDevice); -} - -class PrimitivesTest : public ::testing::Test -{ -protected: - static const unsigned n = 1 << 4; - - projective_t* points1{}; - projective_t* points2{}; - g2_projective_t* g2_points1{}; - g2_projective_t* g2_points2{}; - scalar_t* scalars1{}; - scalar_t* scalars2{}; - projective_t* zero_points{}; - g2_projective_t* g2_zero_points{}; - scalar_t* zero_scalars{}; - scalar_t* one_scalars{}; - affine_t* aff_points{}; - g2_affine_t* g2_aff_points{}; - projective_t* res_points1{}; - projective_t* res_points2{}; - g2_projective_t* g2_res_points1{}; - g2_projective_t* g2_res_points2{}; - scalar_t* res_scalars1{}; - scalar_t* res_scalars2{}; - scalar_t::Wide* res_scalars_wide{}; - scalar_t::Wide* res_scalars_wide_full{}; - - PrimitivesTest() - { - assert(!cudaDeviceReset()); - assert(!cudaMallocManaged(&points1, n * sizeof(projective_t))); - assert(!cudaMallocManaged(&points2, n * sizeof(projective_t))); - assert(!cudaMallocManaged(&g2_points1, n * sizeof(g2_projective_t))); - assert(!cudaMallocManaged(&g2_points2, n * sizeof(g2_projective_t))); - assert(!cudaMallocManaged(&scalars1, n * sizeof(scalar_t))); - assert(!cudaMallocManaged(&scalars2, n * sizeof(scalar_t))); - assert(!cudaMallocManaged(&zero_points, n * sizeof(projective_t))); - assert(!cudaMallocManaged(&g2_zero_points, n * sizeof(g2_projective_t))); - assert(!cudaMallocManaged(&zero_scalars, n * sizeof(scalar_t))); - assert(!cudaMallocManaged(&one_scalars, n * sizeof(scalar_t))); - assert(!cudaMallocManaged(&aff_points, n * sizeof(affine_t))); - assert(!cudaMallocManaged(&g2_aff_points, n * sizeof(g2_affine_t))); - assert(!cudaMallocManaged(&res_points1, n * sizeof(projective_t))); - assert(!cudaMallocManaged(&res_points2, n * sizeof(projective_t))); - assert(!cudaMallocManaged(&g2_res_points1, n * sizeof(g2_projective_t))); - assert(!cudaMallocManaged(&g2_res_points2, n * sizeof(g2_projective_t))); - assert(!cudaMallocManaged(&res_scalars1, n * sizeof(scalar_t))); - assert(!cudaMallocManaged(&res_scalars2, n * sizeof(scalar_t))); - } - - ~PrimitivesTest() override - { - cudaFree(points1); - cudaFree(points2); - cudaFree(g2_points1); - cudaFree(g2_points2); - cudaFree(scalars1); - cudaFree(scalars2); - cudaFree(zero_points); - cudaFree(g2_zero_points); - cudaFree(zero_scalars); - cudaFree(one_scalars); - cudaFree(aff_points); - cudaFree(g2_aff_points); - cudaFree(res_points1); - cudaFree(res_points2); - cudaFree(g2_res_points1); - cudaFree(g2_res_points2); - cudaFree(res_scalars1); - cudaFree(res_scalars2); - - cudaDeviceReset(); - } - - void SetUp() override - { - ASSERT_EQ(device_populate_random(points1, n), cudaSuccess); - ASSERT_EQ(device_populate_random(points2, n), cudaSuccess); - ASSERT_EQ(device_populate_random(g2_points1, n), cudaSuccess); - ASSERT_EQ(device_populate_random(g2_points2, n), cudaSuccess); - ASSERT_EQ(device_populate_random(scalars1, n), cudaSuccess); - ASSERT_EQ(device_populate_random(scalars2, n), cudaSuccess); - ASSERT_EQ(device_set(zero_points, projective_t::zero(), n), cudaSuccess); - ASSERT_EQ(device_set(g2_zero_points, g2_projective_t::zero(), n), cudaSuccess); - ASSERT_EQ(device_set(zero_scalars, scalar_t::zero(), n), cudaSuccess); - ASSERT_EQ(device_set(one_scalars, scalar_t::one(), n), cudaSuccess); - ASSERT_EQ(cudaMemset(aff_points, 0, n * sizeof(affine_t)), cudaSuccess); - ASSERT_EQ(cudaMemset(g2_aff_points, 0, n * sizeof(g2_affine_t)), cudaSuccess); - ASSERT_EQ(cudaMemset(res_points1, 0, n * sizeof(projective_t)), cudaSuccess); - ASSERT_EQ(cudaMemset(res_points2, 0, n * sizeof(projective_t)), cudaSuccess); - ASSERT_EQ(cudaMemset(g2_res_points1, 0, n * sizeof(g2_projective_t)), cudaSuccess); - ASSERT_EQ(cudaMemset(g2_res_points2, 0, n * sizeof(g2_projective_t)), cudaSuccess); - ASSERT_EQ(cudaMemset(res_scalars1, 0, n * sizeof(scalar_t)), cudaSuccess); - ASSERT_EQ(cudaMemset(res_scalars2, 0, n * sizeof(scalar_t)), cudaSuccess); - } -}; - -TEST_F(PrimitivesTest, FieldAdditionSubtractionCancel) -{ - ASSERT_EQ(vec_add(scalars1, scalars2, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_sub(res_scalars1, scalars2, res_scalars2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i], res_scalars2[i]); -} - -TEST_F(PrimitivesTest, FieldZeroAddition) -{ - ASSERT_EQ(vec_add(scalars1, zero_scalars, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i], res_scalars1[i]); -} - -TEST_F(PrimitivesTest, FieldAdditionHostDeviceEq) -{ - ASSERT_EQ(vec_add(scalars1, scalars2, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i] + scalars2[i], res_scalars1[i]); -} - -TEST_F(PrimitivesTest, FieldMultiplicationByOne) -{ - ASSERT_EQ(vec_mul(scalars1, one_scalars, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i], res_scalars1[i]); -} - -TEST_F(PrimitivesTest, FieldMultiplicationByMinusOne) -{ - ASSERT_EQ(vec_neg(one_scalars, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(scalars1, res_scalars1, res_scalars2, n), cudaSuccess); - ASSERT_EQ(vec_add(scalars1, res_scalars2, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_scalars1[i], zero_scalars[i]); -} - -TEST_F(PrimitivesTest, FieldMultiplicationByZero) -{ - ASSERT_EQ(vec_mul(scalars1, zero_scalars, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(zero_scalars[i], res_scalars1[i]); -} - -TEST_F(PrimitivesTest, FieldMultiplicationInverseCancel) -{ - ASSERT_EQ(vec_mul(scalars1, scalars2, res_scalars1, n), cudaSuccess); - ASSERT_EQ(field_vec_inv(scalars2, res_scalars2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i], res_scalars1[i] * res_scalars2[i]); -} - -TEST_F(PrimitivesTest, FieldMultiplicationHostDeviceEq) -{ - ASSERT_EQ(vec_mul(scalars1, scalars2, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i] * scalars2[i], res_scalars1[i]); -} - -TEST_F(PrimitivesTest, FieldMultiplicationByTwoEqSum) -{ - ASSERT_EQ(vec_add(one_scalars, one_scalars, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, scalars1, res_scalars2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_scalars2[i], scalars1[i] + scalars1[i]); -} - -TEST_F(PrimitivesTest, FieldSqrHostDeviceEq) -{ - ASSERT_EQ(field_vec_sqr(scalars1, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i] * scalars1[i], res_scalars1[i]); -} - -TEST_F(PrimitivesTest, FieldMultiplicationSqrEq) -{ - ASSERT_EQ(vec_mul(scalars1, scalars1, res_scalars1, n), cudaSuccess); - ASSERT_EQ(field_vec_sqr(scalars1, res_scalars2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_scalars1[i], res_scalars2[i]); -} - -TEST_F(PrimitivesTest, ECRandomPointsAreOnCurve) -{ - for (unsigned i = 0; i < n; i++) - ASSERT_PRED1(projective_t::is_on_curve, points1[i]); -} - -TEST_F(PrimitivesTest, ECPointAdditionSubtractionCancel) -{ - ASSERT_EQ(vec_add(points1, points2, res_points1, n), cudaSuccess); - ASSERT_EQ(vec_sub(res_points1, points2, res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(points1[i], res_points2[i]); -} - -TEST_F(PrimitivesTest, ECPointZeroAddition) -{ - ASSERT_EQ(vec_add(points1, zero_points, res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(points1[i], res_points1[i]); -} - -TEST_F(PrimitivesTest, ECPointAdditionHostDeviceEq) -{ - ASSERT_EQ(vec_add(points1, points2, res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(points1[i] + points2[i], res_points1[i]); -} - -TEST_F(PrimitivesTest, ECScalarMultiplicationHostDeviceEq) -{ - ASSERT_EQ(vec_mul(scalars1, points1, res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i] * points1[i], res_points1[i]); -} - -TEST_F(PrimitivesTest, ECScalarMultiplicationByOne) -{ - ASSERT_EQ(vec_mul(one_scalars, points1, res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(points1[i], res_points1[i]); -} - -TEST_F(PrimitivesTest, ECScalarMultiplicationByMinusOne) -{ - ASSERT_EQ(vec_neg(one_scalars, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, points1, res_points1, n), cudaSuccess); - ASSERT_EQ(vec_neg(points1, res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_points1[i], res_points2[i]); -} - -TEST_F(PrimitivesTest, ECScalarMultiplicationByTwo) -{ - ASSERT_EQ(vec_add(one_scalars, one_scalars, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, points1, res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ((one_scalars[i] + one_scalars[i]) * points1[i], res_points1[i]); -} - -TEST_F(PrimitivesTest, ECScalarMultiplicationInverseCancel) -{ - ASSERT_EQ(vec_mul(scalars1, points1, res_points1, n), cudaSuccess); - ASSERT_EQ(field_vec_inv(scalars1, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, res_points1, res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(points1[i], res_points2[i]); -} - -TEST_F(PrimitivesTest, ECScalarMultiplicationIsDistributiveOverMultiplication) -{ - ASSERT_EQ(vec_mul(scalars1, points1, res_points1, n), cudaSuccess); - ASSERT_EQ(vec_mul(scalars2, res_points1, res_points2, n), cudaSuccess); - ASSERT_EQ(vec_mul(scalars1, scalars2, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, points1, res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_points1[i], res_points2[i]); -} - -TEST_F(PrimitivesTest, ECScalarMultiplicationIsDistributiveOverAddition) -{ - ASSERT_EQ(vec_mul(scalars1, points1, res_points1, n), cudaSuccess); - ASSERT_EQ(vec_mul(scalars2, points1, res_points2, n), cudaSuccess); - ASSERT_EQ(vec_add(scalars1, scalars2, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_scalars1[i] * points1[i], res_points1[i] + res_points2[i]); -} - -TEST_F(PrimitivesTest, ECProjectiveToAffine) -{ - ASSERT_EQ(point_vec_to_affine(points1, aff_points, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(points1[i], projective_t::from_affine(aff_points[i])); -} - -TEST_F(PrimitivesTest, ECMixedPointAddition) -{ - ASSERT_EQ(point_vec_to_affine(points2, aff_points, n), cudaSuccess); - ASSERT_EQ(vec_add(points1, aff_points, res_points1, n), cudaSuccess); - ASSERT_EQ(vec_add(points1, points2, res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_points1[i], res_points2[i]); -} - -TEST_F(PrimitivesTest, ECMixedAdditionOfNegatedPointEqSubtraction) -{ - ASSERT_EQ(point_vec_to_affine(points2, aff_points, n), cudaSuccess); - ASSERT_EQ(vec_sub(points1, aff_points, res_points1, n), cudaSuccess); - ASSERT_EQ(vec_neg(points2, res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_points1[i], points1[i] + res_points2[i]); -} - -TEST_F(PrimitivesTest, G2ECRandomPointsAreOnCurve) -{ - for (unsigned i = 0; i < n; i++) - ASSERT_PRED1(g2_projective_t::is_on_curve, g2_points1[i]); -} - -TEST_F(PrimitivesTest, G2ECPointAdditionSubtractionCancel) -{ - ASSERT_EQ(vec_add(g2_points1, g2_points2, g2_res_points1, n), cudaSuccess); - ASSERT_EQ(vec_sub(g2_res_points1, g2_points2, g2_res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_points1[i], g2_res_points2[i]); -} - -TEST_F(PrimitivesTest, G2ECPointZeroAddition) -{ - ASSERT_EQ(vec_add(g2_points1, g2_zero_points, g2_res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_points1[i], g2_res_points1[i]); -} - -TEST_F(PrimitivesTest, G2ECPointAdditionHostDeviceEq) -{ - ASSERT_EQ(vec_add(g2_points1, g2_points2, g2_res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_points1[i] + g2_points2[i], g2_res_points1[i]); -} - -TEST_F(PrimitivesTest, G2ECScalarMultiplicationHostDeviceEq) -{ - ASSERT_EQ(vec_mul(scalars1, g2_points1, g2_res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(scalars1[i] * g2_points1[i], g2_res_points1[i]); -} - -TEST_F(PrimitivesTest, G2ECScalarMultiplicationByOne) -{ - ASSERT_EQ(vec_mul(one_scalars, points1, res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_points1[i], g2_res_points1[i]); -} - -TEST_F(PrimitivesTest, G2ECScalarMultiplicationByMinusOne) -{ - ASSERT_EQ(vec_neg(one_scalars, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, g2_points1, g2_res_points1, n), cudaSuccess); - ASSERT_EQ(vec_neg(g2_points1, g2_res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_res_points1[i], g2_res_points2[i]); -} - -TEST_F(PrimitivesTest, G2ECScalarMultiplicationByTwo) -{ - ASSERT_EQ(vec_add(one_scalars, one_scalars, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, g2_points1, g2_res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ((one_scalars[i] + one_scalars[i]) * g2_points1[i], g2_res_points1[i]); -} - -TEST_F(PrimitivesTest, G2ECScalarMultiplicationInverseCancel) -{ - ASSERT_EQ(vec_mul(scalars1, g2_points1, g2_res_points1, n), cudaSuccess); - ASSERT_EQ(field_vec_inv(scalars1, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, g2_res_points1, g2_res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_points1[i], g2_res_points2[i]); -} - -TEST_F(PrimitivesTest, G2ECScalarMultiplicationIsDistributiveOverMultiplication) -{ - ASSERT_EQ(vec_mul(scalars1, g2_points1, g2_res_points1, n), cudaSuccess); - ASSERT_EQ(vec_mul(scalars2, g2_res_points1, g2_res_points2, n), cudaSuccess); - ASSERT_EQ(vec_mul(scalars1, scalars2, res_scalars1, n), cudaSuccess); - ASSERT_EQ(vec_mul(res_scalars1, g2_points1, g2_res_points1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_res_points1[i], g2_res_points2[i]); -} - -TEST_F(PrimitivesTest, G2ECScalarMultiplicationIsDistributiveOverAddition) -{ - ASSERT_EQ(vec_mul(scalars1, g2_points1, g2_res_points1, n), cudaSuccess); - ASSERT_EQ(vec_mul(scalars2, g2_points1, g2_res_points2, n), cudaSuccess); - ASSERT_EQ(vec_add(scalars1, scalars2, res_scalars1, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(res_scalars1[i] * g2_points1[i], g2_res_points1[i] + g2_res_points2[i]); -} - -TEST_F(PrimitivesTest, G2ECProjectiveToAffine) -{ - ASSERT_EQ(point_vec_to_affine(g2_points1, g2_aff_points, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_points1[i], g2_projective_t::from_affine(g2_aff_points[i])); -} - -TEST_F(PrimitivesTest, G2ECMixedPointAddition) -{ - ASSERT_EQ(point_vec_to_affine(g2_points2, g2_aff_points, n), cudaSuccess); - ASSERT_EQ(vec_add(g2_points1, g2_aff_points, g2_res_points1, n), cudaSuccess); - ASSERT_EQ(vec_add(g2_points1, g2_points2, g2_res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_res_points1[i], g2_res_points2[i]); -} - -TEST_F(PrimitivesTest, G2ECMixedAdditionOfNegatedPointEqSubtraction) -{ - ASSERT_EQ(point_vec_to_affine(g2_points2, g2_aff_points, n), cudaSuccess); - ASSERT_EQ(vec_sub(g2_points1, g2_aff_points, g2_res_points1, n), cudaSuccess); - ASSERT_EQ(vec_neg(g2_points2, g2_res_points2, n), cudaSuccess); - for (unsigned i = 0; i < n; i++) - ASSERT_EQ(g2_res_points1[i], g2_points1[i] + g2_res_points2[i]); -} \ No newline at end of file diff --git a/icicle/tests/runner.cu b/icicle/tests/runner.cu index 0939b059..6be2b2a5 100644 --- a/icicle/tests/runner.cu +++ b/icicle/tests/runner.cu @@ -4,7 +4,10 @@ // include list of test files // Ensure the device_error_test.cu is last to prevent aborting mid-test run -#include "primitives_test.cu" +#include "field_test.cu" +#ifdef CURVE_ID +#include "curve_test.cu" +#endif #include "error_handler_test.cu" #include "device_error_test.cu" diff --git a/icicle/utils/device_context.cu b/icicle/utils/device_context.cu deleted file mode 100644 index 9ccbec34..00000000 --- a/icicle/utils/device_context.cu +++ /dev/null @@ -1,7 +0,0 @@ -#include "device_context.cuh" - -namespace device_context { - - extern "C" DeviceContext GetDefaultDeviceContext() { return get_default_device_context(); } - -} // namespace device_context diff --git a/icicle/utils/mont.cu b/icicle/utils/mont.cu deleted file mode 100644 index 1ec0dfa2..00000000 --- a/icicle/utils/mont.cu +++ /dev/null @@ -1,60 +0,0 @@ -#include "curves/curve_config.cuh" -#include "device_context.cuh" -#include "mont.cuh" -#include "utils/utils.h" - -namespace mont { - extern "C" cudaError_t CONCAT_EXPAND(CURVE, ScalarConvertMontgomery)( - curve_config::scalar_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) - { - if (is_into) { - return ToMontgomery(d_inout, n, ctx.stream, d_inout); - } else { - return FromMontgomery(d_inout, n, ctx.stream, d_inout); - } - } - - extern "C" cudaError_t CONCAT_EXPAND(CURVE, AffineConvertMontgomery)( - curve_config::affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) - { - if (is_into) { - return ToMontgomery(d_inout, n, ctx.stream, d_inout); - } else { - return FromMontgomery(d_inout, n, ctx.stream, d_inout); - } - } - - extern "C" cudaError_t CONCAT_EXPAND(CURVE, ProjectiveConvertMontgomery)( - curve_config::projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) - { - if (is_into) { - return ToMontgomery(d_inout, n, ctx.stream, d_inout); - } else { - return FromMontgomery(d_inout, n, ctx.stream, d_inout); - } - } - -#if defined(G2_DEFINED) - - extern "C" cudaError_t CONCAT_EXPAND(CURVE, G2AffineConvertMontgomery)( - curve_config::g2_affine_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) - { - if (is_into) { - return ToMontgomery(d_inout, n, ctx.stream, d_inout); - } else { - return FromMontgomery(d_inout, n, ctx.stream, d_inout); - } - } - - extern "C" cudaError_t CONCAT_EXPAND(CURVE, G2ProjectiveConvertMontgomery)( - curve_config::g2_projective_t* d_inout, size_t n, bool is_into, device_context::DeviceContext& ctx) - { - if (is_into) { - return ToMontgomery(d_inout, n, ctx.stream, d_inout); - } else { - return FromMontgomery(d_inout, n, ctx.stream, d_inout); - } - } - -#endif -} // namespace mont \ No newline at end of file diff --git a/icicle/utils/vec_ops.cu b/icicle/utils/vec_ops.cu deleted file mode 100644 index 145737f2..00000000 --- a/icicle/utils/vec_ops.cu +++ /dev/null @@ -1,216 +0,0 @@ -#include -#include - -#include "vec_ops.cuh" -#include "curves/curve_config.cuh" -#include "device_context.cuh" -#include "mont.cuh" -#include "utils/utils.h" - -namespace vec_ops { - - namespace { - -#define MAX_THREADS_PER_BLOCK 256 - - template - __global__ void MulKernel(E* scalar_vec, E* element_vec, int n, E* result) - { - int tid = blockDim.x * blockIdx.x + threadIdx.x; - if (tid < n) { result[tid] = scalar_vec[tid] * element_vec[tid]; } - } - - template - __global__ void AddKernel(E* element_vec1, E* element_vec2, int n, E* result) - { - int tid = blockIdx.x * blockDim.x + threadIdx.x; - if (tid < n) { result[tid] = element_vec1[tid] + element_vec2[tid]; } - } - - template - __global__ void SubKernel(E* element_vec1, E* element_vec2, int n, E* result) - { - int tid = blockIdx.x * blockDim.x + threadIdx.x; - if (tid < n) { result[tid] = element_vec1[tid] - element_vec2[tid]; } - } - - template - __global__ void transpose_kernel(const E* in, E* out, uint32_t row_size, uint32_t column_size) - { - int tid = blockDim.x * blockIdx.x + threadIdx.x; - if (tid >= row_size * column_size) return; - out[(tid % row_size) * column_size + (tid / row_size)] = in[tid]; - } - } // namespace - - template - cudaError_t VecOp(E* vec_a, E* vec_b, int n, VecOpsConfig& config, E* result) - { - CHK_INIT_IF_RETURN(); - - // Set the grid and block dimensions - int num_threads = MAX_THREADS_PER_BLOCK; - int num_blocks = (n + num_threads - 1) / num_threads; - - E *d_vec_a, *d_vec_b, *d_result; - if (!config.is_a_on_device) { - CHK_IF_RETURN(cudaMallocAsync(&d_vec_a, n * sizeof(E), config.ctx.stream)); - CHK_IF_RETURN(cudaMemcpyAsync(d_vec_a, vec_a, n * sizeof(E), cudaMemcpyHostToDevice, config.ctx.stream)); - } else { - d_vec_a = vec_a; - } - - if (!config.is_b_on_device) { - CHK_IF_RETURN(cudaMallocAsync(&d_vec_b, n * sizeof(E), config.ctx.stream)); - CHK_IF_RETURN(cudaMemcpyAsync(d_vec_b, vec_b, n * sizeof(E), cudaMemcpyHostToDevice, config.ctx.stream)); - } else { - d_vec_b = vec_b; - } - - if (!config.is_result_on_device) { - CHK_IF_RETURN(cudaMallocAsync(&d_result, n * sizeof(E), config.ctx.stream)); - } else { - d_result = result; - } - - // Call the kernel to perform element-wise operation - Kernel<<>>(d_vec_a, d_vec_b, n, d_result); - if (config.is_result_montgomery_form) CHK_IF_RETURN(mont::FromMontgomery(d_result, n, config.ctx.stream, d_result)); - - if (!config.is_a_on_device) { CHK_IF_RETURN(cudaFreeAsync(d_vec_a, config.ctx.stream)); } - - if (!config.is_b_on_device) { CHK_IF_RETURN(cudaFreeAsync(d_vec_b, config.ctx.stream)); } - - if (!config.is_result_on_device) { - CHK_IF_RETURN(cudaMemcpyAsync(result, d_result, n * sizeof(E), cudaMemcpyDeviceToHost, config.ctx.stream)); - CHK_IF_RETURN(cudaFreeAsync(d_result, config.ctx.stream)); - } - - if (!config.is_async) return CHK_STICKY(cudaStreamSynchronize(config.ctx.stream)); - - return CHK_LAST(); - } - - template - cudaError_t Mul(E* vec_a, E* vec_b, int n, VecOpsConfig& config, E* result) - { - return VecOp(vec_a, vec_b, n, config, result); - } - - template - cudaError_t Add(E* vec_a, E* vec_b, int n, VecOpsConfig& config, E* result) - { - return VecOp(vec_a, vec_b, n, config, result); - } - - template - cudaError_t Sub(E* vec_a, E* vec_b, int n, VecOpsConfig& config, E* result) - { - return VecOp(vec_a, vec_b, n, config, result); - } - - template - cudaError_t transpose_matrix( - const E* mat_in, - E* mat_out, - uint32_t row_size, - uint32_t column_size, - device_context::DeviceContext& ctx, - bool on_device, - bool is_async) - { - int number_of_threads = MAX_THREADS_PER_BLOCK; - int number_of_blocks = (row_size * column_size + number_of_threads - 1) / number_of_threads; - cudaStream_t stream = ctx.stream; - - const E* d_mat_in; - E* d_allocated_input = nullptr; - E* d_mat_out; - if (!on_device) { - CHK_IF_RETURN(cudaMallocAsync(&d_allocated_input, row_size * column_size * sizeof(E), ctx.stream)); - CHK_IF_RETURN(cudaMemcpyAsync( - d_allocated_input, mat_in, row_size * column_size * sizeof(E), cudaMemcpyHostToDevice, ctx.stream)); - - CHK_IF_RETURN(cudaMallocAsync(&d_mat_out, row_size * column_size * sizeof(E), ctx.stream)); - d_mat_in = d_allocated_input; - } else { - d_mat_in = mat_in; - d_mat_out = mat_out; - } - - transpose_kernel<<>>(d_mat_in, d_mat_out, row_size, column_size); - - if (!on_device) { - CHK_IF_RETURN( - cudaMemcpyAsync(mat_out, d_mat_out, row_size * column_size * sizeof(E), cudaMemcpyDeviceToHost, ctx.stream)); - CHK_IF_RETURN(cudaFreeAsync(d_mat_out, ctx.stream)); - CHK_IF_RETURN(cudaFreeAsync(d_allocated_input, ctx.stream)); - } - if (!is_async) return CHK_STICKY(cudaStreamSynchronize(ctx.stream)); - - return CHK_LAST(); - } - - /** - * Extern version of [Mul](@ref Mul) function with the template parameters - * `S` and `E` being the [scalar field](@ref scalar_t) of the curve given by `-DCURVE` env variable during build. - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, MulCuda)( - curve_config::scalar_t* vec_a, - curve_config::scalar_t* vec_b, - int n, - VecOpsConfig& config, - curve_config::scalar_t* result) - { - return Mul(vec_a, vec_b, n, config, result); - } - - /** - * Extern version of [Add](@ref Add) function with the template parameter - * `E` being the [scalar field](@ref scalar_t) of the curve given by `-DCURVE` env variable during build. - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, AddCuda)( - curve_config::scalar_t* vec_a, - curve_config::scalar_t* vec_b, - int n, - VecOpsConfig& config, - curve_config::scalar_t* result) - { - return Add(vec_a, vec_b, n, config, result); - } - - /** - * Extern version of [Sub](@ref Sub) function with the template parameter - * `E` being the [scalar field](@ref scalar_t) of the curve given by `-DCURVE` env variable during build. - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, SubCuda)( - curve_config::scalar_t* vec_a, - curve_config::scalar_t* vec_b, - int n, - VecOpsConfig& config, - curve_config::scalar_t* result) - { - return Sub(vec_a, vec_b, n, config, result); - } - - /** - * Extern version of transpose_batch function with the template parameter - * `E` being the [scalar field](@ref scalar_t) of the curve given by `-DCURVE` env variable during build. - * @return `cudaSuccess` if the execution was successful and an error code otherwise. - */ - extern "C" cudaError_t CONCAT_EXPAND(CURVE, TransposeMatrix)( - const curve_config::scalar_t* input, - uint32_t row_size, - uint32_t column_size, - curve_config::scalar_t* output, - device_context::DeviceContext& ctx, - bool on_device, - bool is_async) - { - return transpose_matrix(input, output, row_size, column_size, ctx, on_device, is_async); - } - -} // namespace vec_ops \ No newline at end of file diff --git a/scripts/gen_c_api.py b/scripts/gen_c_api.py new file mode 100755 index 00000000..b6d2b7a8 --- /dev/null +++ b/scripts/gen_c_api.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +""" +This script generates extern declarators for every curve/field +""" + +from itertools import chain +from pathlib import Path +from string import Template + +API_PATH = Path(__file__).resolve().parent.parent.joinpath("icicle").joinpath("include").joinpath("api") +TEMPLATES_PATH = API_PATH.joinpath("templates") + +""" +Defines a set of curves to generate API for. +A set corresponding to each curve contains headers that shouldn't be included. +""" +CURVES_CONFIG = { + "bn254": [ + "field_ext.h", + "vec_ops_ext.h", + "ntt_ext.h", + ], + "bls12_381": [ + "field_ext.h", + "vec_ops_ext.h", + "ntt_ext.h", + ], + "bls12_377": [ + "field_ext.h", + "vec_ops_ext.h", + "ntt_ext.h", + ], + "bw6_761": [ + "field_ext.h", + "vec_ops_ext.h", + "ntt_ext.h", + ], + "grumpkin": { + "curve_g2.h", + "msm_g2.h", + "ecntt.h", + "ntt.h", + "vec_ops_ext.h", + "field_ext.h", + "ntt_ext.h", + }, +} + +""" +Defines a set of fields to generate API for. +A set corresponding to each field contains headers that shouldn't be included. +""" +FIELDS_CONFIG = { + "babybear": { + "poseidon.h", + } +} + +# For cudaError_t and device_context +COMMON_INCLUDES = [ + '#include ', + '#include "gpu-utils/device_context.cuh"', +] + +WARN_TEXT = """\ +// WARNING: This file is auto-generated by a script. +// Any changes made to this file may be overwritten. +// Please modify the code generation script instead. +// Path to the code generation script: scripts/gen_c_api.py + +""" + +INCLUDE_ONCE = """\ +#pragma once +#ifndef {0}_API_H +#define {0}_API_H + +""" + +CURVE_HEADERS = list(TEMPLATES_PATH.joinpath("curves").iterdir()) +FIELD_HEADERS = list(TEMPLATES_PATH.joinpath("fields").iterdir()) + +if __name__ == "__main__": + + # Generate API for ingo_curve + for curve, skip in CURVES_CONFIG.items(): + curve_api = API_PATH.joinpath(f"{curve}.h") + + headers = [header for header in chain(CURVE_HEADERS, FIELD_HEADERS) if header.name not in skip] + + # Collect includes + includes = COMMON_INCLUDES.copy() + includes.append(f'#include "curves/params/{curve}.cuh"') + if any(header.name.startswith("ntt") for header in headers): + includes.append('#include "ntt/ntt.cuh"') + if any(header.name.startswith("msm") for header in headers): + includes.append('#include "msm/msm.cuh"') + if any(header.name.startswith("vec_ops") for header in headers): + includes.append('#include "vec_ops/vec_ops.cuh"') + if any(header.name.startswith("poseidon") for header in headers): + includes.append('#include "poseidon/poseidon.cuh"') + includes.append('#include "poseidon/tree/merkle.cuh"') + + contents = WARN_TEXT + INCLUDE_ONCE.format(curve.upper()) + "\n".join(includes) + "\n\n" + for header in headers: + with open(header) as f: + template = Template(f.read()) + contents += template.safe_substitute({ + "CURVE": curve, + "FIELD": curve, + }) + contents += "\n\n" + contents += "#endif" + + with open(curve_api, "w") as f: + f.write(contents) + + + # Generate API for ingo_field + for field, skip in FIELDS_CONFIG.items(): + field_api = API_PATH.joinpath(f"{field}.h") + + headers = [header for header in FIELD_HEADERS if header.name not in skip] + + # Collect includes + includes = COMMON_INCLUDES.copy() + includes.append(f'#include "fields/stark_fields/{field}.cuh"') + if any(header.name.startswith("ntt") for header in headers): + includes.append('#include "ntt/ntt.cuh"') + if any(header.name.startswith("vec_ops") for header in headers): + includes.append('#include "vec_ops/vec_ops.cuh"') + if any(header.name.startswith("poseidon") for header in headers): + includes.append('#include "poseidon/poseidon.cuh"') + includes.append('#include "poseidon/tree/merkle.cuh"') + + contents = WARN_TEXT + INCLUDE_ONCE.format(field.upper()) + "\n".join(includes) + "\n\n" + for header in headers: + with open(header) as f: + template = Template(f.read()) + contents += template.safe_substitute({ + "FIELD": field, + }) + contents += "\n\n" + contents += "#endif" + + with open(field_api, "w") as f: + f.write(contents) \ No newline at end of file diff --git a/wrappers/golang/README.md b/wrappers/golang/README.md index e71206c6..46d486f4 100644 --- a/wrappers/golang/README.md +++ b/wrappers/golang/README.md @@ -4,57 +4,60 @@ In order to build the underlying ICICLE libraries you should run the build scrip Build script USAGE -``` -./build [G2_enabled] +```bash +./build.sh [-curve= | -field=] [-cuda_version=] [-g2] [-ecntt] [-devmode] curve - The name of the curve to build or "all" to build all curves -G2_enabled - Optional - To build with G2 enabled +field - The name of the field to build or "all" to build all fields +-g2 - Optional - build with G2 enabled +-ecntt - Optional - build with ECNTT enabled +-devmode - Optional - build in devmode ``` -To build ICICLE libraries for all supported curves with G2 enabled. +To build ICICLE libraries for all supported curves with G2 and ECNTT enabled. ``` -./build.sh all ON +./build.sh all -g2 -ecntt ``` -If you wish to build for a specific curve, for example bn254, without G2 enabled. +If you wish to build for a specific curve, for example bn254, without G2 or ECNTT enabled. ``` ./build.sh bn254 ``` >[!NOTE] ->Current supported curves are `bn254`, `bls12_381`, `bls12_377` and `bw6_671` +>Current supported curves are `bn254`, `bls12_381`, `bls12_377`, `bw6_671` and `grumpkin` +>Current supported fields are `babybear` >[!NOTE] ->G2 is enabled by building your golang project with the build tag `g2` ->Make sure to add it to your build tags if you want it enabled +>G2 and ECNTT are located in nested packages ## Running golang tests To run the tests for curve bn254. -``` +```bash go test ./wrappers/golang/curves/bn254 -count=1 ``` To run all the tests in the golang bindings -``` -go test --tags=g2 ./... -count=1 +```bash +go test ./... -count=1 ``` ## How do Golang bindings work? The libraries produced from the CUDA code compilation are used to bind Golang to ICICLE's CUDA code. -1. These libraries (named `libingo_.a`) can be imported in your Go project to leverage the GPU accelerated functionalities provided by ICICLE. +1. These libraries (named `libingo_curve_.a` and `libingo_field_.a`) can be imported in your Go project to leverage the GPU accelerated functionalities provided by ICICLE. 2. In your Go project, you can use `cgo` to link these libraries. Here's a basic example on how you can use `cgo` to link these libraries: ```go /* -#cgo LDFLAGS: -L/path/to/shared/libs -lingo_bn254 +#cgo LDFLAGS: -L$/path/to/shared/libs -lingo_curve_bn254 -L$/path/to/shared/libs -lingo_field_bn254 -lstdc++ -lm #include "icicle.h" // make sure you use the correct header file(s) */ import "C" diff --git a/wrappers/golang/build.sh b/wrappers/golang/build.sh index 68d2a3c1..b858ac72 100755 --- a/wrappers/golang/build.sh +++ b/wrappers/golang/build.sh @@ -2,32 +2,106 @@ G2_DEFINED=OFF ECNTT_DEFINED=OFF +CUDA_COMPILER_PATH=/usr/local/cuda/bin/nvcc +DEVMODE=OFF +EXT_FIELD=OFF +BUILD_CURVES=( ) +BUILD_FIELDS=( ) -if [[ $2 == "ON" ]] -then - G2_DEFINED=ON +SUPPORTED_CURVES=("bn254" "bls12_377" "bls12_381" "bw6_761", "grumpkin") +SUPPORTED_FIELDS=("babybear") + +if [[ $1 == "-help" ]]; then + echo "Build script for building ICICLE cpp libraries" + echo "" + echo "If more than one curve or more than one field is supplied, the last one supplied will be built" + echo "" + echo "USAGE: ./build.sh [OPTION...]" + echo "" + echo "OPTIONS:" + echo " -curve= The curve that should be built. If \"all\" is supplied," + echo " all curves will be built with any other supplied curve options" + echo " -g2 Builds the curve lib with G2 enabled" + echo " -ecntt Builds the curve lib with ECNTT enabled" + echo " -field= The field that should be built. If \"all\" is supplied," + echo " all fields will be built with any other supplied field options" + echo " -field-ext Builds the field lib with the extension field enabled" + echo " -devmode Enables devmode debugging and fast build times" + echo " -cuda_version= The version of cuda to use for compiling" + echo "" + exit 0 fi -if [[ $3 ]] -then - ECNTT_DEFINED=ON -fi +for arg in "$@" +do + arg_lower=$(echo "$arg" | tr '[:upper:]' '[:lower:]') + case "$arg_lower" in + -cuda_version=*) + cuda_version=$(echo "$arg" | cut -d'=' -f2) + CUDA_COMPILER_PATH=/usr/local/cuda-$cuda_version/bin/nvcc + ;; + -ecntt) + ECNTT_DEFINED=ON + ;; + -g2) + G2_DEFINED=ON + ;; + -curve=*) + curve=$(echo "$arg_lower" | cut -d'=' -f2) + if [[ $curve == "all" ]] + then + BUILD_CURVES=("${SUPPORTED_CURVES[@]}") + else + BUILD_CURVES=( $curve ) + fi + ;; + -field=*) + field=$(echo "$arg_lower" | cut -d'=' -f2) + if [[ $field == "all" ]] + then + BUILD_FIELDS=("${SUPPORTED_FIELDS[@]}") + else + BUILD_FIELDS=( $field ) + fi + ;; + -field-ext) + EXT_FIELD=ON + ;; + -devmode) + DEVMODE=ON + ;; + *) + echo "Unknown argument: $arg" + exit 1 + ;; + esac +done BUILD_DIR=$(realpath "$PWD/../../icicle/build") -SUPPORTED_CURVES=("bn254" "bls12_377" "bls12_381" "bw6_761") - -if [[ $1 == "all" ]] -then - BUILD_CURVES=("${SUPPORTED_CURVES[@]}") -else - BUILD_CURVES=( $1 ) -fi cd ../../icicle mkdir -p build +rm -f "$BUILD_DIR/CMakeCache.txt" for CURVE in "${BUILD_CURVES[@]}" do - cmake -DCURVE=$CURVE -DG2_DEFINED=$G2_DEFINED -DECNTT_DEFINED=$ECNTT_DEFINED -DCMAKE_BUILD_TYPE=Release -S . -B build - cmake --build build -j8 -done \ No newline at end of file + echo "CURVE=${CURVE}" > build_config.txt + echo "ECNTT=${ECNTT_DEFINED}" >> build_config.txt + echo "G2=${G2_DEFINED}" >> build_config.txt + echo "DEVMODE=${DEVMODE}" >> build_config.txt + cmake -DCMAKE_CUDA_COMPILER=$CUDA_COMPILER_PATH -DCURVE=$CURVE -DG2=$G2_DEFINED -DECNTT=$ECNTT_DEFINED -DDEVMODE=$DEVMODE -DCMAKE_BUILD_TYPE=Release -S . -B build + cmake --build build -j8 && rm build_config.txt +done + +# Needs to remove the CMakeCache.txt file to allow building fields after curves +# have been built since CURVE and FIELD cannot both be defined +rm -f "$BUILD_DIR/CMakeCache.txt" + +for FIELD in "${BUILD_FIELDS[@]}" +do + echo "FIELD=${FIELD}" > build_config.txt + echo "DEVMODE=${DEVMODE}" >> build_config.txt + cmake -DCMAKE_CUDA_COMPILER=$CUDA_COMPILER_PATH -DFIELD=$FIELD -DEXT_FIELD=$EXT_FIELD -DDEVMODE=$DEVMODE -DCMAKE_BUILD_TYPE=Release -S . -B build + cmake --build build -j8 && rm build_config.txt +done + diff --git a/wrappers/golang/core/internal/curve_test.go b/wrappers/golang/core/internal/curve_test.go deleted file mode 100644 index 0314a770..00000000 --- a/wrappers/golang/core/internal/curve_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package internal - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestMockAffineZero(t *testing.T) { - var fieldZero = MockField{} - - var affineZero MockAffine - assert.Equal(t, affineZero.X, fieldZero) - assert.Equal(t, affineZero.Y, fieldZero) - - x := generateRandomLimb(int(BASE_LIMBS)) - y := generateRandomLimb(int(BASE_LIMBS)) - var affine MockAffine - affine.FromLimbs(x, y) - - affine.Zero() - assert.Equal(t, affine.X, fieldZero) - assert.Equal(t, affine.Y, fieldZero) -} - -func TestMockAffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - - var affine MockAffine - affine.FromLimbs(randLimbs, randLimbs2) - - assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) - assert.ElementsMatch(t, randLimbs2, affine.Y.GetLimbs()) -} - -func TestMockAffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne MockField - fieldOne.One() - - var expected MockProjective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) - - var affine MockAffine - affine.FromLimbs(randLimbs, randLimbs2) - - projectivePoint := affine.ToProjective() - assert.Equal(t, expected, projectivePoint) -} - -func TestMockProjectiveZero(t *testing.T) { - var projectiveZero MockProjective - projectiveZero.Zero() - var fieldZero = MockField{} - var fieldOne MockField - fieldOne.One() - - assert.Equal(t, projectiveZero.X, fieldZero) - assert.Equal(t, projectiveZero.Y, fieldOne) - assert.Equal(t, projectiveZero.Z, fieldZero) - - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - var projective MockProjective - projective.FromLimbs(randLimbs, randLimbs, randLimbs) - - projective.Zero() - assert.Equal(t, projective.X, fieldZero) - assert.Equal(t, projective.Y, fieldOne) - assert.Equal(t, projective.Z, fieldZero) -} - -func TestMockProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(BASE_LIMBS)) - - var projective MockProjective - projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) - - assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) - assert.ElementsMatch(t, randLimbs2, projective.Y.GetLimbs()) - assert.ElementsMatch(t, randLimbs3, projective.Z.GetLimbs()) -} - -func TestMockProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne MockField - fieldOne.One() - - var expected MockProjective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) - - var affine MockAffine - affine.FromLimbs(randLimbs, randLimbs2) - - var projectivePoint MockProjective - projectivePoint.FromAffine(affine) - assert.Equal(t, expected, projectivePoint) -} diff --git a/wrappers/golang/core/internal/field_test.go b/wrappers/golang/core/internal/field_test.go deleted file mode 100644 index c9a2b0f3..00000000 --- a/wrappers/golang/core/internal/field_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package internal - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestMockFieldFromLimbs(t *testing.T) { - emptyField := MockField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the MockField's limbs") - randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) -} - -func TestMockFieldGetLimbs(t *testing.T) { - emptyField := MockField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the MockField's limbs") -} - -func TestMockFieldOne(t *testing.T) { - var emptyField MockField - emptyField.One() - limbOne := generateLimbOne(int(BASE_LIMBS)) - assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - emptyField.One() - assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "MockField with limbs to field one did not work") -} - -func TestMockFieldZero(t *testing.T) { - var emptyField MockField - emptyField.Zero() - limbsZero := make([]uint32, BASE_LIMBS) - assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - emptyField.Zero() - assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "MockField with limbs to field zero failed") -} - -func TestMockFieldSize(t *testing.T) { - var emptyField MockField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") -} - -func TestMockFieldAsPointer(t *testing.T) { - var emptyField MockField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") -} - -func TestMockFieldFromBytes(t *testing.T) { - var emptyField MockField - bytes, expected := generateBytesArray(int(BASE_LIMBS)) - - emptyField.FromBytesLittleEndian(bytes) - - assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") -} - -func TestMockFieldToBytes(t *testing.T) { - var emptyField MockField - expected, limbs := generateBytesArray(int(BASE_LIMBS)) - emptyField.FromLimbs(limbs) - - assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") -} diff --git a/wrappers/golang/core/internal/curve.go b/wrappers/golang/core/internal/mock_curve.go similarity index 92% rename from wrappers/golang/core/internal/curve.go rename to wrappers/golang/core/internal/mock_curve.go index b5a4185e..0a6fe8be 100644 --- a/wrappers/golang/core/internal/curve.go +++ b/wrappers/golang/core/internal/mock_curve.go @@ -1,7 +1,7 @@ package internal type MockProjective struct { - X, Y, Z MockField + X, Y, Z MockBaseField } func (p MockProjective) Size() int { @@ -29,7 +29,7 @@ func (p *MockProjective) FromLimbs(x, y, z []uint32) MockProjective { } func (p *MockProjective) FromAffine(a MockAffine) MockProjective { - z := MockField{} + z := MockBaseField{} z.One() p.X = a.X @@ -40,7 +40,7 @@ func (p *MockProjective) FromAffine(a MockAffine) MockProjective { } type MockAffine struct { - X, Y MockField + X, Y MockBaseField } func (a MockAffine) Size() int { @@ -66,7 +66,7 @@ func (a *MockAffine) FromLimbs(x, y []uint32) MockAffine { } func (a MockAffine) ToProjective() MockProjective { - var z MockField + var z MockBaseField return MockProjective{ X: a.X, diff --git a/wrappers/golang/core/internal/mock_field.go b/wrappers/golang/core/internal/mock_field.go new file mode 100644 index 00000000..ae5ce68a --- /dev/null +++ b/wrappers/golang/core/internal/mock_field.go @@ -0,0 +1,84 @@ +package internal + +import ( + "encoding/binary" + "fmt" +) + +const ( + MOCKBASE_LIMBS int = 4 +) + +type MockBaseField struct { + limbs [MOCKBASE_LIMBS]uint32 +} + +func (f MockBaseField) Len() int { + return int(MOCKBASE_LIMBS) +} + +func (f MockBaseField) Size() int { + return int(MOCKBASE_LIMBS * 4) +} + +func (f MockBaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +func (f MockBaseField) AsPointer() *uint32 { + return &f.limbs[0] +} + +func (f *MockBaseField) FromUint32(v uint32) MockBaseField { + f.limbs[0] = v + return *f +} + +func (f *MockBaseField) FromLimbs(limbs []uint32) MockBaseField { + if len(limbs) != f.Len() { + panic("Called FromLimbs with limbs of different length than field") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *MockBaseField) Zero() MockBaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *MockBaseField) One() MockBaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *MockBaseField) FromBytesLittleEndian(bytes []byte) MockBaseField { + if len(bytes)/4 != f.Len() { + panic(fmt.Sprintf("Called FromBytesLittleEndian with incorrect bytes length; expected %d - got %d", f.Len()*4, len(bytes))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f MockBaseField) ToBytesLittleEndian() []byte { + bytes := make([]byte, f.Len()*4) + for i, v := range f.limbs { + binary.LittleEndian.PutUint32(bytes[i*4:], v) + } + + return bytes +} diff --git a/wrappers/golang/core/msm.go b/wrappers/golang/core/msm.go index 6229cf80..f55f7408 100644 --- a/wrappers/golang/core/msm.go +++ b/wrappers/golang/core/msm.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "unsafe" cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" ) @@ -75,7 +76,7 @@ func GetDefaultMSMConfig() MSMConfig { } } -func MsmCheck(scalars HostOrDeviceSlice, points HostOrDeviceSlice, cfg *MSMConfig, results HostOrDeviceSlice) { +func MsmCheck(scalars HostOrDeviceSlice, points HostOrDeviceSlice, cfg *MSMConfig, results HostOrDeviceSlice) (unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int, unsafe.Pointer) { scalarsLength, pointsLength, resultsLength := scalars.Len(), points.Len()/int(cfg.PrecomputeFactor), results.Len() if scalarsLength%pointsLength != 0 { errorString := fmt.Sprintf( @@ -98,9 +99,24 @@ func MsmCheck(scalars HostOrDeviceSlice, points HostOrDeviceSlice, cfg *MSMConfi cfg.areScalarsOnDevice = scalars.IsOnDevice() cfg.arePointsOnDevice = points.IsOnDevice() cfg.areResultsOnDevice = results.IsOnDevice() + + if scalars.IsOnDevice() { + scalars.(DeviceSlice).CheckDevice() + } + + if points.IsOnDevice() { + points.(DeviceSlice).CheckDevice() + } + + if results.IsOnDevice() { + results.(DeviceSlice).CheckDevice() + } + + size := scalars.Len() / results.Len() + return scalars.AsUnsafePointer(), points.AsUnsafePointer(), results.AsUnsafePointer(), size, unsafe.Pointer(cfg) } -func PrecomputeBasesCheck(points HostOrDeviceSlice, precomputeFactor int32, outputBases DeviceSlice) { +func PrecomputeBasesCheck(points HostOrDeviceSlice, precomputeFactor int32, outputBases DeviceSlice) (unsafe.Pointer, unsafe.Pointer) { outputBasesLength, pointsLength := outputBases.Len(), points.Len() if outputBasesLength != pointsLength*int(precomputeFactor) { errorString := fmt.Sprintf( @@ -110,4 +126,10 @@ func PrecomputeBasesCheck(points HostOrDeviceSlice, precomputeFactor int32, outp ) panic(errorString) } + + if points.IsOnDevice() { + points.(DeviceSlice).CheckDevice() + } + + return points.AsUnsafePointer(), outputBases.AsUnsafePointer() } diff --git a/wrappers/golang/core/msm_test.go b/wrappers/golang/core/msm_test.go index 9aa773be..cc85ba92 100644 --- a/wrappers/golang/core/msm_test.go +++ b/wrappers/golang/core/msm_test.go @@ -36,19 +36,19 @@ func TestMSMDefaultConfig(t *testing.T) { func TestMSMCheckHostSlices(t *testing.T) { cfg := GetDefaultMSMConfig() - randLimbs := []uint32{1, 2, 3, 4, 5, 6, 7, 8} - rawScalars := make([]internal.MockField, 10) + rawScalars := make([]internal.MockBaseField, 10) for i := range rawScalars { - var emptyField internal.MockField - emptyField.FromLimbs(randLimbs) + var emptyField internal.MockBaseField + emptyField.One() rawScalars[i] = emptyField } - scalars := HostSliceFromElements[internal.MockField](rawScalars) + scalars := HostSliceFromElements[internal.MockBaseField](rawScalars) affine := internal.MockAffine{} - limbs := []uint32{1, 2, 3, 4, 5, 6, 7, 8} - affine.FromLimbs(limbs, limbs) + var emptyField internal.MockBaseField + emptyField.One() + affine.FromLimbs(emptyField.GetLimbs(), emptyField.GetLimbs()) rawAffinePoints := make([]internal.MockAffine, 10) for i := range rawAffinePoints { rawAffinePoints[i] = affine @@ -69,21 +69,21 @@ func TestMSMCheckHostSlices(t *testing.T) { func TestMSMCheckDeviceSlices(t *testing.T) { cfg := GetDefaultMSMConfig() - randLimbs := []uint32{1, 2, 3, 4, 5, 6, 7, 8} - rawScalars := make([]internal.MockField, 10) + rawScalars := make([]internal.MockBaseField, 10) for i := range rawScalars { - var emptyField internal.MockField - emptyField.FromLimbs(randLimbs) + var emptyField internal.MockBaseField + emptyField.One() rawScalars[i] = emptyField } - scalars := HostSliceFromElements[internal.MockField](rawScalars) + scalars := HostSliceFromElements[internal.MockBaseField](rawScalars) var scalarsOnDevice DeviceSlice scalars.CopyToDevice(&scalarsOnDevice, true) affine := internal.MockAffine{} - limbs := []uint32{1, 2, 3, 4, 5, 6, 7, 8} - affine.FromLimbs(limbs, limbs) + var emptyField internal.MockBaseField + emptyField.One() + affine.FromLimbs(emptyField.GetLimbs(), emptyField.GetLimbs()) rawAffinePoints := make([]internal.MockAffine, 10) for i := range rawAffinePoints { rawAffinePoints[i] = affine diff --git a/wrappers/golang/core/ntt.go b/wrappers/golang/core/ntt.go index 6f77db32..586774fe 100644 --- a/wrappers/golang/core/ntt.go +++ b/wrappers/golang/core/ntt.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "unsafe" cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" ) @@ -67,7 +68,7 @@ func GetDefaultNTTConfig[T any](cosetGen T) NTTConfig[T] { } } -func NttCheck[T any](input HostOrDeviceSlice, cfg *NTTConfig[T], output HostOrDeviceSlice) { +func NttCheck[T any](input HostOrDeviceSlice, cfg *NTTConfig[T], output HostOrDeviceSlice) (unsafe.Pointer, unsafe.Pointer, int, unsafe.Pointer) { inputLen, outputLen := input.Len(), output.Len() if inputLen != outputLen { errorString := fmt.Sprintf( @@ -79,4 +80,17 @@ func NttCheck[T any](input HostOrDeviceSlice, cfg *NTTConfig[T], output HostOrDe } cfg.areInputsOnDevice = input.IsOnDevice() cfg.areOutputsOnDevice = output.IsOnDevice() + + if input.IsOnDevice() { + input.(DeviceSlice).CheckDevice() + } + + if output.IsOnDevice() { + output.(DeviceSlice).CheckDevice() + } + + size := input.Len() / int(cfg.BatchSize) + cfgPointer := unsafe.Pointer(cfg) + + return input.AsUnsafePointer(), output.AsUnsafePointer(), size, cfgPointer } diff --git a/wrappers/golang/core/ntt_test.go b/wrappers/golang/core/ntt_test.go index 338ae4dc..b9866e45 100644 --- a/wrappers/golang/core/ntt_test.go +++ b/wrappers/golang/core/ntt_test.go @@ -1,7 +1,6 @@ package core import ( - // "unsafe" "testing" "github.com/ingonyama-zk/icicle/wrappers/golang/core/internal" @@ -10,7 +9,7 @@ import ( ) func TestNTTDefaultConfig(t *testing.T) { - var cosetGenField internal.MockField + var cosetGenField internal.MockBaseField cosetGenField.One() var cosetGen [1]uint32 copy(cosetGen[:], cosetGenField.GetLimbs()) @@ -33,56 +32,52 @@ func TestNTTDefaultConfig(t *testing.T) { } func TestNTTCheckHostScalars(t *testing.T) { - randLimbs := []uint32{1, 2, 3, 4, 5, 6, 7, 8} - - var cosetGen internal.MockField - cosetGen.FromLimbs(randLimbs) + var cosetGen internal.MockBaseField + cosetGen.One() cfg := GetDefaultNTTConfig(&cosetGen) - rawInput := make([]internal.MockField, 10) - var emptyField internal.MockField - emptyField.FromLimbs(randLimbs) + rawInput := make([]internal.MockBaseField, 10) + var emptyField internal.MockBaseField + emptyField.One() for i := range rawInput { rawInput[i] = emptyField } - input := HostSliceFromElements[internal.MockField](rawInput) - output := HostSliceFromElements[internal.MockField](rawInput) + input := HostSliceFromElements[internal.MockBaseField](rawInput) + output := HostSliceFromElements[internal.MockBaseField](rawInput) assert.NotPanics(t, func() { NttCheck(input, &cfg, output) }) assert.False(t, cfg.areInputsOnDevice) assert.False(t, cfg.areOutputsOnDevice) - rawInputLarger := make([]internal.MockField, 11) + rawInputLarger := make([]internal.MockBaseField, 11) for i := range rawInputLarger { rawInputLarger[i] = emptyField } - output2 := HostSliceFromElements[internal.MockField](rawInputLarger) + output2 := HostSliceFromElements[internal.MockBaseField](rawInputLarger) assert.Panics(t, func() { NttCheck(input, &cfg, output2) }) } func TestNTTCheckDeviceScalars(t *testing.T) { - randLimbs := []uint32{1, 2, 3, 4, 5, 6, 7, 8} - - var cosetGen internal.MockField - cosetGen.FromLimbs(randLimbs) + var cosetGen internal.MockBaseField + cosetGen.One() cfg := GetDefaultNTTConfig(cosetGen) - fieldBytesSize := 16 numFields := 10 - rawInput := make([]internal.MockField, numFields) + rawInput := make([]internal.MockBaseField, numFields) for i := range rawInput { - var emptyField internal.MockField - emptyField.FromLimbs(randLimbs) + var emptyField internal.MockBaseField + emptyField.One() rawInput[i] = emptyField } - hostElements := HostSliceFromElements[internal.MockField](rawInput) + hostElements := HostSliceFromElements[internal.MockBaseField](rawInput) var input DeviceSlice hostElements.CopyToDevice(&input, true) + fieldBytesSize := hostElements.SizeOfElement() var output DeviceSlice output.Malloc(numFields*fieldBytesSize, fieldBytesSize) diff --git a/wrappers/golang/core/slice.go b/wrappers/golang/core/slice.go index 56ee8206..bcf3ace6 100644 --- a/wrappers/golang/core/slice.go +++ b/wrappers/golang/core/slice.go @@ -11,6 +11,7 @@ type HostOrDeviceSlice interface { Cap() int IsEmpty() bool IsOnDevice() bool + AsUnsafePointer() unsafe.Pointer } type DevicePointer = unsafe.Pointer @@ -35,7 +36,7 @@ func (d DeviceSlice) IsEmpty() bool { return d.length == 0 } -func (d DeviceSlice) AsPointer() unsafe.Pointer { +func (d DeviceSlice) AsUnsafePointer() unsafe.Pointer { return d.inner } @@ -188,6 +189,14 @@ func (h HostSlice[T]) SizeOfElement() int { return int(unsafe.Sizeof(h[0])) } +func (h HostSlice[T]) AsPointer() *T { + return &h[0] +} + +func (h HostSlice[T]) AsUnsafePointer() unsafe.Pointer { + return unsafe.Pointer(&h[0]) +} + func (h HostSlice[T]) CopyToDevice(dst *DeviceSlice, shouldAllocate bool) *DeviceSlice { size := h.Len() * h.SizeOfElement() if shouldAllocate { @@ -198,8 +207,7 @@ func (h HostSlice[T]) CopyToDevice(dst *DeviceSlice, shouldAllocate bool) *Devic panic("Number of bytes to copy is too large for destination") } - hostSrc := unsafe.Pointer(&h[0]) - cr.CopyToDevice(dst.inner, hostSrc, uint(size)) + cr.CopyToDevice(dst.inner, h.AsUnsafePointer(), uint(size)) dst.length = h.Len() return dst } @@ -214,8 +222,7 @@ func (h HostSlice[T]) CopyToDeviceAsync(dst *DeviceSlice, stream cr.CudaStream, panic("Number of bytes to copy is too large for destination") } - hostSrc := unsafe.Pointer(&h[0]) - cr.CopyToDeviceAsync(dst.inner, hostSrc, uint(size), stream) + cr.CopyToDeviceAsync(dst.inner, h.AsUnsafePointer(), uint(size), stream) dst.length = h.Len() return dst } @@ -226,7 +233,7 @@ func (h HostSlice[T]) CopyFromDevice(src *DeviceSlice) { panic("destination and source slices have different lengths") } bytesSize := src.Len() * h.SizeOfElement() - cr.CopyFromDevice(unsafe.Pointer(&h[0]), src.inner, uint(bytesSize)) + cr.CopyFromDevice(h.AsUnsafePointer(), src.inner, uint(bytesSize)) } func (h HostSlice[T]) CopyFromDeviceAsync(src *DeviceSlice, stream cr.Stream) { @@ -235,5 +242,5 @@ func (h HostSlice[T]) CopyFromDeviceAsync(src *DeviceSlice, stream cr.Stream) { panic("destination and source slices have different lengths") } bytesSize := src.Len() * h.SizeOfElement() - cr.CopyFromDeviceAsync(unsafe.Pointer(&h[0]), src.inner, uint(bytesSize), stream) + cr.CopyFromDeviceAsync(h.AsUnsafePointer(), src.inner, uint(bytesSize), stream) } diff --git a/wrappers/golang/core/slice_test.go b/wrappers/golang/core/slice_test.go index 6f9709cc..46642cee 100644 --- a/wrappers/golang/core/slice_test.go +++ b/wrappers/golang/core/slice_test.go @@ -9,20 +9,20 @@ import ( "github.com/stretchr/testify/assert" ) -func randomField(size int) internal.MockField { +func randomField(size int) internal.MockBaseField { limbs := make([]uint32, size) for i := range limbs { limbs[i] = rand.Uint32() } - var field internal.MockField + var field internal.MockBaseField field.FromLimbs(limbs) return field } -func randomFields(numFields, fieldSize int) []internal.MockField { - var randFields []internal.MockField +func randomFields(numFields, fieldSize int) []internal.MockBaseField { + var randFields []internal.MockBaseField for i := 0; i < numFields; i++ { randFields = append(randFields, randomField(fieldSize)) @@ -67,12 +67,12 @@ func randomAffinePoints(numPoints, fieldSize int) []internal.MockAffine { const ( numPoints = 4 numFields = 4 - fieldSize = 8 + fieldSize = 4 fieldBytesSize = fieldSize * 4 ) func TestHostSlice(t *testing.T) { - var emptyHostSlice HostSlice[internal.MockField] + var emptyHostSlice HostSlice[internal.MockBaseField] assert.Equal(t, emptyHostSlice.Len(), 0) assert.Equal(t, emptyHostSlice.Cap(), 0) @@ -82,13 +82,13 @@ func TestHostSlice(t *testing.T) { assert.Equal(t, hostSlice.Len(), 4) assert.Equal(t, hostSlice.Cap(), 4) - hostSliceCasted := (HostSlice[internal.MockField])(randFields) + hostSliceCasted := (HostSlice[internal.MockBaseField])(randFields) assert.Equal(t, hostSliceCasted.Len(), 4) assert.Equal(t, hostSliceCasted.Cap(), 4) } func TestHostSliceIsEmpty(t *testing.T) { - var emptyHostSlice HostSlice[*internal.MockField] + var emptyHostSlice HostSlice[*internal.MockBaseField] assert.True(t, emptyHostSlice.IsEmpty()) randFields := randomFields(numFields, fieldSize) @@ -98,7 +98,7 @@ func TestHostSliceIsEmpty(t *testing.T) { } func TestHostSliceIsOnDevice(t *testing.T) { - var emptyHostSlice HostSlice[*internal.MockField] + var emptyHostSlice HostSlice[*internal.MockBaseField] assert.False(t, emptyHostSlice.IsOnDevice()) } @@ -112,17 +112,17 @@ func TestDeviceSlice(t *testing.T) { var emptyDeviceSlice DeviceSlice assert.Equal(t, 0, emptyDeviceSlice.Len()) assert.Equal(t, 0, emptyDeviceSlice.Cap()) - assert.Equal(t, unsafe.Pointer(nil), emptyDeviceSlice.AsPointer()) + assert.Equal(t, unsafe.Pointer(nil), emptyDeviceSlice.AsUnsafePointer()) emptyDeviceSlice.Malloc(numFields*fieldBytesSize, fieldBytesSize) assert.Equal(t, numFields, emptyDeviceSlice.Len()) assert.Equal(t, numFields*fieldBytesSize, emptyDeviceSlice.Cap()) - assert.NotEqual(t, unsafe.Pointer(nil), emptyDeviceSlice.AsPointer()) + assert.NotEqual(t, unsafe.Pointer(nil), emptyDeviceSlice.AsUnsafePointer()) emptyDeviceSlice.Free() assert.Equal(t, 0, emptyDeviceSlice.Len()) assert.Equal(t, 0, emptyDeviceSlice.Cap()) - assert.Equal(t, unsafe.Pointer(nil), emptyDeviceSlice.AsPointer()) + assert.Equal(t, unsafe.Pointer(nil), emptyDeviceSlice.AsUnsafePointer()) } func TestDeviceSliceIsEmpty(t *testing.T) { diff --git a/wrappers/golang/core/vec_ops.go b/wrappers/golang/core/vec_ops.go index 6b758c6c..adae9756 100644 --- a/wrappers/golang/core/vec_ops.go +++ b/wrappers/golang/core/vec_ops.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "unsafe" cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" ) @@ -23,8 +24,6 @@ type VecOpsConfig struct { isBOnDevice bool /* If true, output is preserved on device, otherwise on host. Default value: false. */ isResultOnDevice bool - /* True if `result` vector should be in Montgomery form and false otherwise. Default value: false. */ - IsResultMontgomeryForm bool /* Whether to run the vector operations asynchronously. If set to `true`, the function will be * non-blocking and you'll need to synchronize it explicitly by calling * `SynchronizeStream`. If set to false, the function will block the current CPU thread. */ @@ -42,14 +41,13 @@ func DefaultVecOpsConfig() VecOpsConfig { false, // isAOnDevice false, // isBOnDevice false, // isResultOnDevice - false, // IsResultMontgomeryForm false, // IsAsync } return config } -func VecOpCheck(a, b, out HostOrDeviceSlice, cfg *VecOpsConfig) { +func VecOpCheck(a, b, out HostOrDeviceSlice, cfg *VecOpsConfig) (unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int) { aLen, bLen, outLen := a.Len(), b.Len(), out.Len() if aLen != bLen { errorString := fmt.Sprintf( @@ -68,7 +66,45 @@ func VecOpCheck(a, b, out HostOrDeviceSlice, cfg *VecOpsConfig) { panic(errorString) } + if a.IsOnDevice() { + a.(DeviceSlice).CheckDevice() + } + if b.IsOnDevice() { + b.(DeviceSlice).CheckDevice() + } + if out.IsOnDevice() { + out.(DeviceSlice).CheckDevice() + } + cfg.isAOnDevice = a.IsOnDevice() cfg.isBOnDevice = b.IsOnDevice() cfg.isResultOnDevice = out.IsOnDevice() + + return a.AsUnsafePointer(), b.AsUnsafePointer(), out.AsUnsafePointer(), unsafe.Pointer(cfg), a.Len() +} + +func TransposeCheck(in, out HostOrDeviceSlice, onDevice bool) { + inLen, outLen := in.Len(), out.Len() + + if inLen != outLen { + errorString := fmt.Sprintf( + "in and out vector lengths %d; %d are not equal", + inLen, + outLen, + ) + panic(errorString) + } + if (onDevice != in.IsOnDevice()) || (onDevice != out.IsOnDevice()) { + errorString := fmt.Sprintf( + "onDevice is set to %t, but in.IsOnDevice():%t and out.IsOnDevice():%t", + onDevice, + in.IsOnDevice(), + out.IsOnDevice(), + ) + panic(errorString) + } + if onDevice { + in.(DeviceSlice).CheckDevice() + out.(DeviceSlice).CheckDevice() + } } diff --git a/wrappers/golang/core/vec_ops_test.go b/wrappers/golang/core/vec_ops_test.go index 49a35bb5..7c608dd4 100644 --- a/wrappers/golang/core/vec_ops_test.go +++ b/wrappers/golang/core/vec_ops_test.go @@ -1,9 +1,10 @@ package core import ( + "testing" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" "github.com/stretchr/testify/assert" - "testing" ) func TestVecOpsDefaultConfig(t *testing.T) { @@ -13,7 +14,6 @@ func TestVecOpsDefaultConfig(t *testing.T) { false, // isAOnDevice false, // isBOnDevice false, // isResultOnDevice - false, // IsResultMontgomeryForm false, // IsAsync } diff --git a/wrappers/golang/curves/bls12377/base_field.go b/wrappers/golang/curves/bls12377/base_field.go index 9a2c1e45..24748211 100644 --- a/wrappers/golang/curves/bls12377/base_field.go +++ b/wrappers/golang/curves/bls12377/base_field.go @@ -6,7 +6,7 @@ import ( ) const ( - BASE_LIMBS int8 = 12 + BASE_LIMBS int = 12 ) type BaseField struct { @@ -29,6 +29,11 @@ func (f BaseField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *BaseField) FromUint32(v uint32) BaseField { + f.limbs[0] = v + return *f +} + func (f *BaseField) FromLimbs(limbs []uint32) BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") diff --git a/wrappers/golang/curves/bls12377/bls12_377.go b/wrappers/golang/curves/bls12377/bls12_377.go deleted file mode 100644 index 504ff58c..00000000 --- a/wrappers/golang/curves/bls12377/bls12_377.go +++ /dev/null @@ -1,4 +0,0 @@ -package bls12377 - -// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build -lingo_bls12_377 -lstdc++ -lm -import "C" diff --git a/wrappers/golang/curves/bls12377/curve.go b/wrappers/golang/curves/bls12377/curve.go index 15624fbb..ec129044 100644 --- a/wrappers/golang/curves/bls12377/curve.go +++ b/wrappers/golang/curves/bls12377/curve.go @@ -53,7 +53,7 @@ func (p *Projective) FromAffine(a Affine) Projective { func (p Projective) ProjectiveEq(p2 *Projective) bool { cP := (*C.projective_t)(unsafe.Pointer(&p)) cP2 := (*C.projective_t)(unsafe.Pointer(&p2)) - __ret := C.bls12_377Eq(cP, cP2) + __ret := C.bls12_377_eq(cP, cP2) return __ret == (C._Bool)(true) } @@ -62,7 +62,7 @@ func (p *Projective) ProjectiveToAffine() Affine { cA := (*C.affine_t)(unsafe.Pointer(&a)) cP := (*C.projective_t)(unsafe.Pointer(&p)) - C.bls12_377ToAffine(cP, cA) + C.bls12_377_to_affine(cP, cA) return a } @@ -75,7 +75,7 @@ func GenerateProjectivePoints(size int) core.HostSlice[Projective] { pointsSlice := core.HostSliceFromElements[Projective](points) pPoints := (*C.projective_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bls12_377GenerateProjectivePoints(pPoints, cSize) + C.bls12_377_generate_projective_points(pPoints, cSize) return pointsSlice } @@ -129,18 +129,18 @@ func GenerateAffinePoints(size int) core.HostSlice[Affine] { pointsSlice := core.HostSliceFromElements[Affine](points) cPoints := (*C.affine_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bls12_377GenerateAffinePoints(cPoints, cSize) + C.bls12_377_generate_affine_points(cPoints, cSize) return pointsSlice } func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.affine_t)(points.AsPointer()) + cValues := (*C.affine_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_377AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_377_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } @@ -156,12 +156,12 @@ func AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { } func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.projective_t)(points.AsPointer()) + cValues := (*C.projective_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_377ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_377_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bls12377/ecntt/ecntt.go b/wrappers/golang/curves/bls12377/ecntt/ecntt.go new file mode 100644 index 00000000..aea8f5ba --- /dev/null +++ b/wrappers/golang/curves/bls12377/ecntt/ecntt.go @@ -0,0 +1,24 @@ +package ecntt + +// #cgo CFLAGS: -I./include/ +// #include "ecntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + pointsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](points, cfg, results) + + cPoints := (*C.projective_t)(pointsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.projective_t)(resultsPointer) + + __ret := C.bls12_377_ecntt_cuda(cPoints, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bls12377/ecntt/include/ecntt.h b/wrappers/golang/curves/bls12377/ecntt/include/ecntt.h new file mode 100644 index 00000000..2561e260 --- /dev/null +++ b/wrappers/golang/curves/bls12377/ecntt/include/ecntt.h @@ -0,0 +1,19 @@ +#include + +#ifndef _BLS12_377_ECNTT_H +#define _BLS12_377_ECNTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct NTTConfig NTTConfig; +typedef struct projective_t projective_t; + +cudaError_t bls12_377_ecntt_cuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bls12381/g2_curve.go b/wrappers/golang/curves/bls12377/g2/curve.go similarity index 88% rename from wrappers/golang/curves/bls12381/g2_curve.go rename to wrappers/golang/curves/bls12377/g2/curve.go index 68697b37..8dd9ce5e 100644 --- a/wrappers/golang/curves/bls12381/g2_curve.go +++ b/wrappers/golang/curves/bls12377/g2/curve.go @@ -1,9 +1,7 @@ -//go:build g2 - -package bls12381 +package g2 // #cgo CFLAGS: -I./include/ -// #include "g2_curve.h" +// #include "curve.h" import "C" import ( @@ -55,7 +53,7 @@ func (p *G2Projective) FromAffine(a G2Affine) G2Projective { func (p G2Projective) ProjectiveEq(p2 *G2Projective) bool { cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) cP2 := (*C.g2_projective_t)(unsafe.Pointer(&p2)) - __ret := C.bls12_381G2Eq(cP, cP2) + __ret := C.bls12_377_g2_eq(cP, cP2) return __ret == (C._Bool)(true) } @@ -64,7 +62,7 @@ func (p *G2Projective) ProjectiveToAffine() G2Affine { cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) - C.bls12_381G2ToAffine(cP, cA) + C.bls12_377_g2_to_affine(cP, cA) return a } @@ -77,7 +75,7 @@ func G2GenerateProjectivePoints(size int) core.HostSlice[G2Projective] { pointsSlice := core.HostSliceFromElements[G2Projective](points) pPoints := (*C.g2_projective_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bls12_381G2GenerateProjectivePoints(pPoints, cSize) + C.bls12_377_g2_generate_projective_points(pPoints, cSize) return pointsSlice } @@ -131,18 +129,18 @@ func G2GenerateAffinePoints(size int) core.HostSlice[G2Affine] { pointsSlice := core.HostSliceFromElements[G2Affine](points) cPoints := (*C.g2_affine_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bls12_381G2GenerateAffinePoints(cPoints, cSize) + C.bls12_377_g2_generate_affine_points(cPoints, cSize) return pointsSlice } func convertG2AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.g2_affine_t)(points.AsPointer()) + cValues := (*C.g2_affine_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_381G2AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_377_g2_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } @@ -158,12 +156,12 @@ func G2AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { } func convertG2ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.g2_projective_t)(points.AsPointer()) + cValues := (*C.g2_projective_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_381G2ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_377_g2_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bn254/g2_base_field.go b/wrappers/golang/curves/bls12377/g2/g2base_field.go similarity index 86% rename from wrappers/golang/curves/bn254/g2_base_field.go rename to wrappers/golang/curves/bls12377/g2/g2base_field.go index bcd7832b..c4073af9 100644 --- a/wrappers/golang/curves/bn254/g2_base_field.go +++ b/wrappers/golang/curves/bls12377/g2/g2base_field.go @@ -1,6 +1,4 @@ -//go:build g2 - -package bn254 +package g2 import ( "encoding/binary" @@ -8,19 +6,19 @@ import ( ) const ( - G2_BASE_LIMBS int8 = 16 + G2BASE_LIMBS int = 24 ) type G2BaseField struct { - limbs [G2_BASE_LIMBS]uint32 + limbs [G2BASE_LIMBS]uint32 } func (f G2BaseField) Len() int { - return int(G2_BASE_LIMBS) + return int(G2BASE_LIMBS) } func (f G2BaseField) Size() int { - return int(G2_BASE_LIMBS * 4) + return int(G2BASE_LIMBS * 4) } func (f G2BaseField) GetLimbs() []uint32 { @@ -31,6 +29,11 @@ func (f G2BaseField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *G2BaseField) FromUint32(v uint32) G2BaseField { + f.limbs[0] = v + return *f +} + func (f *G2BaseField) FromLimbs(limbs []uint32) G2BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") diff --git a/wrappers/golang/curves/bls12377/g2/include/curve.h b/wrappers/golang/curves/bls12377/g2/include/curve.h new file mode 100644 index 00000000..b136de9a --- /dev/null +++ b/wrappers/golang/curves/bls12377/g2/include/curve.h @@ -0,0 +1,26 @@ +#include +#include + +#ifndef _BLS12_377_G2CURVE_H +#define _BLS12_377_G2CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct g2_projective_t g2_projective_t; +typedef struct g2_affine_t g2_affine_t; +typedef struct DeviceContext DeviceContext; + +bool bls12_377_g2_eq(g2_projective_t* point1, g2_projective_t* point2); +void bls12_377_g2_to_affine(g2_projective_t* point, g2_affine_t* point_out); +void bls12_377_g2_generate_projective_points(g2_projective_t* points, int size); +void bls12_377_g2_generate_affine_points(g2_affine_t* points, int size); +cudaError_t bls12_377_g2_affine_convert_montgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t bls12_377_g2_projective_convert_montgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bls12377/g2/include/msm.h b/wrappers/golang/curves/bls12377/g2/include/msm.h new file mode 100644 index 00000000..772c70ed --- /dev/null +++ b/wrappers/golang/curves/bls12377/g2/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _BLS12_377_G2MSM_H +#define _BLS12_377_G2MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct g2_projective_t g2_projective_t; +typedef struct g2_affine_t g2_affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bls12_377_g2_msm_cuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); +cudaError_t bls12_377_g2_precompute_msm_bases_cuda(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bls12377/g2/include/scalar_field.h b/wrappers/golang/curves/bls12377/g2/include/scalar_field.h new file mode 100644 index 00000000..09b1bfb7 --- /dev/null +++ b/wrappers/golang/curves/bls12377/g2/include/scalar_field.h @@ -0,0 +1,21 @@ +#include +#include + +#ifndef _BLS12_377_FIELD_H +#define _BLS12_377_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void bls12_377_generate_scalars(scalar_t* scalars, int size); +cudaError_t bls12_377_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bls12377/g2/msm.go b/wrappers/golang/curves/bls12377/g2/msm.go new file mode 100644 index 00000000..66ef66b6 --- /dev/null +++ b/wrappers/golang/curves/bls12377/g2/msm.go @@ -0,0 +1,45 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func G2GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.g2_affine_t)(pointsPointer) + cResults := (*C.g2_projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.bls12_377_g2_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.g2_affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.g2_affine_t)(outputBasesPointer) + + __ret := C.bls12_377_g2_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/bls12377/g2_msm.go b/wrappers/golang/curves/bls12377/g2_msm.go deleted file mode 100644 index d12d988b..00000000 --- a/wrappers/golang/curves/bls12377/g2_msm.go +++ /dev/null @@ -1,82 +0,0 @@ -//go:build g2 - -package bls12377 - -// #cgo CFLAGS: -I./include/ -// #include "g2_msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func G2GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[G2Affine])[0]) - } - cPoints := (*C.g2_affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[G2Projective])[0]) - } - cResults := (*C.g2_projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.bls12_377G2MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[G2Affine])[0]) - } - cPoints := (*C.g2_affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.g2_affine_t)(outputBasesPointer) - - __ret := C.bls12_377G2PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/curves/bls12377/helpers_test.go b/wrappers/golang/curves/bls12377/helpers_test.go deleted file mode 100644 index ead8b7c1..00000000 --- a/wrappers/golang/curves/bls12377/helpers_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package bls12377 - -import ( - "math/rand" -) - -func generateRandomLimb(size int) []uint32 { - limbs := make([]uint32, size) - for i := range limbs { - limbs[i] = rand.Uint32() - } - return limbs -} - -func generateLimbOne(size int) []uint32 { - limbs := make([]uint32, size) - limbs[0] = 1 - return limbs -} - -func generateBytesArray(size int) ([]byte, []uint32) { - baseBytes := []byte{1, 2, 3, 4} - var bytes []byte - var limbs []uint32 - for i := 0; i < size; i++ { - bytes = append(bytes, baseBytes...) - limbs = append(limbs, 67305985) - } - - return bytes, limbs -} diff --git a/wrappers/golang/curves/bls12377/include/curve.h b/wrappers/golang/curves/bls12377/include/curve.h index 0d7e9d38..87a0229b 100644 --- a/wrappers/golang/curves/bls12377/include/curve.h +++ b/wrappers/golang/curves/bls12377/include/curve.h @@ -1,5 +1,4 @@ #include -#include "../../include/types.h" #include #ifndef _BLS12_377_CURVE_H @@ -9,12 +8,16 @@ extern "C" { #endif -bool bls12_377Eq(projective_t* point1, projective_t* point2); -void bls12_377ToAffine(projective_t* point, affine_t* point_out); -void bls12_377GenerateProjectivePoints(projective_t* points, int size); -void bls12_377GenerateAffinePoints(affine_t* points, int size); -cudaError_t bls12_377AffineConvertMontgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t bls12_377ProjectiveConvertMontgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct DeviceContext DeviceContext; + +bool bls12_377_eq(projective_t* point1, projective_t* point2); +void bls12_377_to_affine(projective_t* point, affine_t* point_out); +void bls12_377_generate_projective_points(projective_t* points, int size); +void bls12_377_generate_affine_points(affine_t* points, int size); +cudaError_t bls12_377_affine_convert_montgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t bls12_377_projective_convert_montgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); #ifdef __cplusplus } diff --git a/wrappers/golang/curves/bls12377/include/g2_curve.h b/wrappers/golang/curves/bls12377/include/g2_curve.h deleted file mode 100644 index a8409e72..00000000 --- a/wrappers/golang/curves/bls12377/include/g2_curve.h +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BLS12_377_G2CURVE_H -#define _BLS12_377_G2CURVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -bool bls12_377G2Eq(g2_projective_t* point1, g2_projective_t* point2); -void bls12_377G2ToAffine(g2_projective_t* point, g2_affine_t* point_out); -void bls12_377G2GenerateProjectivePoints(g2_projective_t* points, int size); -void bls12_377G2GenerateAffinePoints(g2_affine_t* points, int size); -cudaError_t bls12_377G2AffineConvertMontgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t bls12_377G2ProjectiveConvertMontgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bls12377/include/g2_msm.h b/wrappers/golang/curves/bls12377/include/g2_msm.h deleted file mode 100644 index f156f4e2..00000000 --- a/wrappers/golang/curves/bls12377/include/g2_msm.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BLS12_377_G2MSM_H -#define _BLS12_377_G2MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bls12_377G2MSMCuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); -cudaError_t bls12_377G2PrecomputeMSMBases(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bls12377/include/msm.h b/wrappers/golang/curves/bls12377/include/msm.h deleted file mode 100644 index 6351db1b..00000000 --- a/wrappers/golang/curves/bls12377/include/msm.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BLS12_377_MSM_H -#define _BLS12_377_MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bls12_377MSMCuda(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); -cudaError_t bls12_377PrecomputeMSMBases(affine_t* points, int bases_size, int precompute_factor, int _c, bool are_bases_on_device, DeviceContext* ctx, affine_t* output_bases); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bls12377/include/ntt.h b/wrappers/golang/curves/bls12377/include/ntt.h deleted file mode 100644 index c8de11b2..00000000 --- a/wrappers/golang/curves/bls12377/include/ntt.h +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BLS12_377_NTT_H -#define _BLS12_377_NTT_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bls12_377NTTCuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); -cudaError_t bls12_377ECNTTCuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); -cudaError_t bls12_377InitializeDomain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); -cudaError_t bls12_377ReleaseDomain(DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bls12377/include/scalar_field.h b/wrappers/golang/curves/bls12377/include/scalar_field.h index 01dc5366..09b1bfb7 100644 --- a/wrappers/golang/curves/bls12377/include/scalar_field.h +++ b/wrappers/golang/curves/bls12377/include/scalar_field.h @@ -1,5 +1,4 @@ #include -#include "../../include/types.h" #include #ifndef _BLS12_377_FIELD_H @@ -9,8 +8,11 @@ extern "C" { #endif -void bls12_377GenerateScalars(scalar_t* scalars, int size); -cudaError_t bls12_377ScalarConvertMontgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void bls12_377_generate_scalars(scalar_t* scalars, int size); +cudaError_t bls12_377_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); #ifdef __cplusplus } diff --git a/wrappers/golang/curves/bls12377/main.go b/wrappers/golang/curves/bls12377/main.go new file mode 100644 index 00000000..fa60eb5f --- /dev/null +++ b/wrappers/golang/curves/bls12377/main.go @@ -0,0 +1,4 @@ +package bls12377 + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build/lib -lingo_curve_bls12_377 -lingo_field_bls12_377 -lstdc++ -lm +import "C" diff --git a/wrappers/golang/curves/bls12377/msm.go b/wrappers/golang/curves/bls12377/msm.go deleted file mode 100644 index 24eba750..00000000 --- a/wrappers/golang/curves/bls12377/msm.go +++ /dev/null @@ -1,80 +0,0 @@ -package bls12377 - -// #cgo CFLAGS: -I./include/ -// #include "msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Affine])[0]) - } - cPoints := (*C.affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.bls12_377MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Affine])[0]) - } - cPoints := (*C.affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.affine_t)(outputBasesPointer) - - __ret := C.bls12_377PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/curves/bls12377/msm/include/msm.h b/wrappers/golang/curves/bls12377/msm/include/msm.h new file mode 100644 index 00000000..63d18294 --- /dev/null +++ b/wrappers/golang/curves/bls12377/msm/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _BLS12_377_MSM_H +#define _BLS12_377_MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bls12_377_msm_cuda(const scalar_t* scalars,const affine_t* points, int count, MSMConfig* config, projective_t* out); +cudaError_t bls12_377_precompute_msm_bases_cuda(affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bls12377/msm/msm.go b/wrappers/golang/curves/bls12377/msm/msm.go new file mode 100644 index 00000000..a56bdd76 --- /dev/null +++ b/wrappers/golang/curves/bls12377/msm/msm.go @@ -0,0 +1,45 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.affine_t)(pointsPointer) + cResults := (*C.projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.bls12_377_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.bls12_377_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/bls12377/ntt.go b/wrappers/golang/curves/bls12377/ntt.go deleted file mode 100644 index 3602735a..00000000 --- a/wrappers/golang/curves/bls12377/ntt.go +++ /dev/null @@ -1,97 +0,0 @@ -package bls12377 - -// #cgo CFLAGS: -I./include/ -// #include "ntt.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func GetDefaultNttConfig() core.NTTConfig[[SCALAR_LIMBS]uint32] { - cosetGenField := ScalarField{} - cosetGenField.One() - var cosetGen [SCALAR_LIMBS]uint32 - for i, v := range cosetGenField.GetLimbs() { - cosetGen[i] = v - } - - return core.GetDefaultNTTConfig(cosetGen) -} - -func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](scalars, cfg, results) - - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - cSize := (C.int)(scalars.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[ScalarField])[0]) - } - cResults := (*C.scalar_t)(resultsPointer) - - __ret := C.bls12_377NTTCuda(cScalars, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](points, cfg, results) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Projective])[0]) - } - cPoints := (*C.projective_t)(pointsPointer) - cSize := (C.int)(points.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsPointer = results.(core.DeviceSlice).AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - __ret := C.bls12_377ECNTTCuda(cPoints, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func InitDomain(primitiveRoot ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { - cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - cFastTwiddles := (C._Bool)(fastTwiddles) - __ret := C.bls12_377InitializeDomain(cPrimitiveRoot, cCtx, cFastTwiddles) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - __ret := C.bls12_377ReleaseDomain(cCtx) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} diff --git a/wrappers/golang/curves/bls12377/ntt/include/ntt.h b/wrappers/golang/curves/bls12377/ntt/include/ntt.h new file mode 100644 index 00000000..d933f5ba --- /dev/null +++ b/wrappers/golang/curves/bls12377/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#include +#include + +#ifndef _BLS12_377_NTT_H +#define _BLS12_377_NTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct NTTConfig NTTConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bls12_377_ntt_cuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +cudaError_t bls12_377_initialize_domain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); +cudaError_t bls12_377_release_domain(DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bls12377/ntt/ntt.go b/wrappers/golang/curves/bls12377/ntt/ntt.go new file mode 100644 index 00000000..bcb104b6 --- /dev/null +++ b/wrappers/golang/curves/bls12377/ntt/ntt.go @@ -0,0 +1,56 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" +) + +import ( + "unsafe" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + scalarsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](scalars, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.scalar_t)(resultsPointer) + + __ret := C.bls12_377_ntt_cuda(cScalars, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func GetDefaultNttConfig() core.NTTConfig[[bls12_377.SCALAR_LIMBS]uint32] { + cosetGenField := bls12_377.ScalarField{} + cosetGenField.One() + var cosetGen [bls12_377.SCALAR_LIMBS]uint32 + for i, v := range cosetGenField.GetLimbs() { + cosetGen[i] = v + } + + return core.GetDefaultNTTConfig(cosetGen) +} + +func InitDomain(primitiveRoot bls12_377.ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cFastTwiddles := (C._Bool)(fastTwiddles) + __ret := C.bls12_377_initialize_domain(cPrimitiveRoot, cCtx, cFastTwiddles) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + __ret := C.bls12_377_release_domain(cCtx) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bls12377/polynomial/include/polynomial.h b/wrappers/golang/curves/bls12377/polynomial/include/polynomial.h new file mode 100644 index 00000000..6aa6e3bf --- /dev/null +++ b/wrappers/golang/curves/bls12377/polynomial/include/polynomial.h @@ -0,0 +1,51 @@ +#include +#include + +#ifndef _BLS12_377_POLY_H +#define _BLS12_377_POLY_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct PolynomialInst PolynomialInst; +typedef struct IntegrityPointer IntegrityPointer; + +bool bls12_377_polynomial_init_cuda_backend(); +PolynomialInst* bls12_377_polynomial_create_from_coefficients(scalar_t* coeffs, size_t size); +PolynomialInst* bls12_377_polynomial_create_from_rou_evaluations(scalar_t* evals, size_t size); +PolynomialInst* bls12_377_polynomial_clone(const PolynomialInst* p); +void bls12_377_polynomial_print(PolynomialInst* p); +void bls12_377_polynomial_delete(PolynomialInst* instance); +PolynomialInst* bls12_377_polynomial_add(const PolynomialInst* a, const PolynomialInst* b); +void bls12_377_polynomial_add_inplace(PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_377_polynomial_subtract(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_377_polynomial_multiply(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_377_polynomial_multiply_by_scalar(const PolynomialInst* a, const scalar_t* scalar); +void bls12_377_polynomial_division(const PolynomialInst* a, const PolynomialInst* b, PolynomialInst** q /*OUT*/, PolynomialInst** r /*OUT*/); +PolynomialInst* bls12_377_polynomial_quotient(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_377_polynomial_remainder(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_377_polynomial_divide_by_vanishing(const PolynomialInst* p, size_t vanishing_poly_degree); +void bls12_377_polynomial_add_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void bls12_377_polynomial_sub_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void bls12_377_polynomial_evaluate_on_domain(const PolynomialInst* p, scalar_t* domain, size_t domain_size, scalar_t* evals /*OUT*/); +size_t bls12_377_polynomial_degree(PolynomialInst* p); +size_t bls12_377_polynomial_copy_coeffs_range(PolynomialInst* p, scalar_t* memory, size_t start_idx, size_t end_idx); +PolynomialInst* bls12_377_polynomial_even(PolynomialInst* p); +PolynomialInst* bls12_377_polynomial_odd(PolynomialInst* p); + +// scalar_t* bls12_377_polynomial_get_coeffs_raw_ptr(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// PolynomialInst* bls12_377_polynomial_slice(PolynomialInst* p, size_t offset, size_t stride, size_t size); +// IntegrityPointer* bls12_377_polynomial_get_coeff_view(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// IntegrityPointer* bls12_377_polynomial_get_rou_evaluations_view(PolynomialInst* p, size_t nof_evals, bool is_reversed, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// const scalar_t* bls12_377_polynomial_intergrity_ptr_get(IntegrityPointer* p); +// bool bls12_377_polynomial_intergrity_ptr_is_valid(IntegrityPointer* p); +// void bls12_377_polynomial_intergrity_ptr_destroy(IntegrityPointer* p); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/wrappers/golang/curves/bls12377/polynomial/polynomial.go b/wrappers/golang/curves/bls12377/polynomial/polynomial.go new file mode 100644 index 00000000..417f86d5 --- /dev/null +++ b/wrappers/golang/curves/bls12377/polynomial/polynomial.go @@ -0,0 +1,176 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +func InitPolyBackend() bool { + return (bool)(C.bls12_377_polynomial_init_cuda_backend()) +} + +func (up *DensePolynomial) Print() { + C.bls12_377_polynomial_print(up.handle) +} + +func (up *DensePolynomial) CreateFromCoeffecitients(coeffs core.HostOrDeviceSlice) DensePolynomial { + if coeffs.IsOnDevice() { + coeffs.(core.DeviceSlice).CheckDevice() + } + coeffsPointer := (*C.scalar_t)(coeffs.AsUnsafePointer()) + cSize := (C.size_t)(coeffs.Len()) + up.handle = C.bls12_377_polynomial_create_from_coefficients(coeffsPointer, cSize) + return *up +} + +func (up *DensePolynomial) CreateFromROUEvaluations(evals core.HostOrDeviceSlice) DensePolynomial { + evalsPointer := (*C.scalar_t)(evals.AsUnsafePointer()) + cSize := (C.size_t)(evals.Len()) + up.handle = C.bls12_377_polynomial_create_from_coefficients(evalsPointer, cSize) + return *up +} + +func (up *DensePolynomial) Clone() DensePolynomial { + return DensePolynomial{ + handle: C.bls12_377_polynomial_clone(up.handle), + } +} + +// TODO @jeremyfelder: Maybe this should be in a SetFinalizer that is set on Create functions? +func (up *DensePolynomial) Delete() { + C.bls12_377_polynomial_delete(up.handle) +} + +func (up *DensePolynomial) Add(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_377_polynomial_add(up.handle, b.handle), + } +} + +func (up *DensePolynomial) AddInplace(b *DensePolynomial) { + C.bls12_377_polynomial_add_inplace(up.handle, b.handle) +} + +func (up *DensePolynomial) Subtract(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_377_polynomial_subtract(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Multiply(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_377_polynomial_multiply(up.handle, b.handle), + } +} + +func (up *DensePolynomial) MultiplyByScalar(scalar bls12_377.ScalarField) DensePolynomial { + cScalar := (*C.scalar_t)(unsafe.Pointer(scalar.AsPointer())) + return DensePolynomial{ + handle: C.bls12_377_polynomial_multiply_by_scalar(up.handle, cScalar), + } +} + +func (up *DensePolynomial) Divide(b *DensePolynomial) (DensePolynomial, DensePolynomial) { + var q, r *PolynomialHandle + C.bls12_377_polynomial_division(up.handle, b.handle, &q, &r) + return DensePolynomial{ + handle: q, + }, DensePolynomial{ + handle: r, + } +} + +func (up *DensePolynomial) Quotient(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_377_polynomial_quotient(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Remainder(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_377_polynomial_remainder(up.handle, b.handle), + } +} + +func (up *DensePolynomial) DivideByVanishing(vanishing_degree uint64) DensePolynomial { + cVanishingDegree := (C.ulong)(vanishing_degree) + return DensePolynomial{ + handle: C.bls12_377_polynomial_divide_by_vanishing(up.handle, cVanishingDegree), + } +} + +func (up *DensePolynomial) AddMonomial(monomialCoeff bls12_377.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]bls12_377.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.bls12_377_polynomial_add_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) SubMonomial(monomialCoeff bls12_377.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]bls12_377.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.bls12_377_polynomial_sub_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) Eval(x bls12_377.ScalarField) bls12_377.ScalarField { + domains := make(core.HostSlice[bls12_377.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[bls12_377.ScalarField], 1) + up.EvalOnDomain(domains, evals) + return evals[0] +} + +func (up *DensePolynomial) EvalOnDomain(domain, evals core.HostOrDeviceSlice) core.HostOrDeviceSlice { + cDomain := (*C.scalar_t)(domain.AsUnsafePointer()) + cDomainSize := (C.size_t)(domain.Len()) + cEvals := (*C.scalar_t)(evals.AsUnsafePointer()) + C.bls12_377_polynomial_evaluate_on_domain(up.handle, cDomain, cDomainSize, cEvals) + return evals +} + +func (up *DensePolynomial) Degree() int { + return int(C.bls12_377_polynomial_degree(up.handle)) +} + +func (up *DensePolynomial) CopyCoeffsRange(start, end int, out core.HostOrDeviceSlice) (int, core.HostOrDeviceSlice) { + cStart := (C.size_t)(start) + cEnd := (C.size_t)(end) + cScalarOut := (*C.scalar_t)(out.AsUnsafePointer()) + __cNumCoeffsRead := C.bls12_377_polynomial_copy_coeffs_range(up.handle, cScalarOut, cStart, cEnd) + return int(__cNumCoeffsRead), out +} + +func (up *DensePolynomial) GetCoeff(idx int) bls12_377.ScalarField { + out := make(core.HostSlice[bls12_377.ScalarField], 1) + up.CopyCoeffsRange(idx, idx, out) + return out[0] +} + +func (up *DensePolynomial) Even() DensePolynomial { + evenPoly := C.bls12_377_polynomial_even(up.handle) + return DensePolynomial{ + handle: evenPoly, + } +} + +func (up *DensePolynomial) Odd() DensePolynomial { + oddPoly := C.bls12_377_polynomial_odd(up.handle) + return DensePolynomial{ + handle: oddPoly, + } +} diff --git a/wrappers/golang/curves/bls12377/scalar_field.go b/wrappers/golang/curves/bls12377/scalar_field.go index 6725428b..d7126220 100644 --- a/wrappers/golang/curves/bls12377/scalar_field.go +++ b/wrappers/golang/curves/bls12377/scalar_field.go @@ -12,7 +12,7 @@ import ( ) const ( - SCALAR_LIMBS int8 = 8 + SCALAR_LIMBS int = 8 ) type ScalarField struct { @@ -35,6 +35,11 @@ func (f ScalarField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *ScalarField) FromUint32(v uint32) ScalarField { + f.limbs[0] = v + return *f +} + func (f *ScalarField) FromLimbs(limbs []uint32) ScalarField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") @@ -89,18 +94,18 @@ func GenerateScalars(size int) core.HostSlice[ScalarField] { cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) cSize := (C.int)(size) - C.bls12_377GenerateScalars(cScalars, cSize) + C.bls12_377_generate_scalars(cScalars, cSize) return scalarSlice } func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.scalar_t)(scalars.AsPointer()) + cValues := (*C.scalar_t)(scalars.AsUnsafePointer()) cSize := (C.size_t)(scalars.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_377ScalarConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_377_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bls12381/base_field_test.go b/wrappers/golang/curves/bls12377/tests/base_field_test.go similarity index 58% rename from wrappers/golang/curves/bls12381/base_field_test.go rename to wrappers/golang/curves/bls12377/tests/base_field_test.go index 30a04f90..8fd37dc8 100644 --- a/wrappers/golang/curves/bls12381/base_field_test.go +++ b/wrappers/golang/curves/bls12377/tests/base_field_test.go @@ -1,34 +1,40 @@ -package bls12381 +package tests import ( + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + BASE_LIMBS = bls12_377.BASE_LIMBS +) + func TestBaseFieldFromLimbs(t *testing.T) { - emptyField := BaseField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + emptyField := bls12_377.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the BaseField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestBaseFieldGetLimbs(t *testing.T) { - emptyField := BaseField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + emptyField := bls12_377.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") } func TestBaseFieldOne(t *testing.T) { - var emptyField BaseField + var emptyField bls12_377.BaseField emptyField.One() - limbOne := generateLimbOne(int(BASE_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(BASE_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -36,12 +42,12 @@ func TestBaseFieldOne(t *testing.T) { } func TestBaseFieldZero(t *testing.T) { - var emptyField BaseField + var emptyField bls12_377.BaseField emptyField.Zero() limbsZero := make([]uint32, BASE_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -49,24 +55,24 @@ func TestBaseFieldZero(t *testing.T) { } func TestBaseFieldSize(t *testing.T) { - var emptyField BaseField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + var emptyField bls12_377.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestBaseFieldAsPointer(t *testing.T) { - var emptyField BaseField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + var emptyField bls12_377.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestBaseFieldFromBytes(t *testing.T) { - var emptyField BaseField - bytes, expected := generateBytesArray(int(BASE_LIMBS)) + var emptyField bls12_377.BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -74,8 +80,8 @@ func TestBaseFieldFromBytes(t *testing.T) { } func TestBaseFieldToBytes(t *testing.T) { - var emptyField BaseField - expected, limbs := generateBytesArray(int(BASE_LIMBS)) + var emptyField bls12_377.BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") diff --git a/wrappers/golang/curves/bn254/curve_test.go b/wrappers/golang/curves/bls12377/tests/curve_test.go similarity index 50% rename from wrappers/golang/curves/bn254/curve_test.go rename to wrappers/golang/curves/bls12377/tests/curve_test.go index 8d1bfda6..2c918622 100644 --- a/wrappers/golang/curves/bn254/curve_test.go +++ b/wrappers/golang/curves/bls12377/tests/curve_test.go @@ -1,20 +1,22 @@ -package bn254 +package tests import ( + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) func TestAffineZero(t *testing.T) { - var fieldZero = BaseField{} + var fieldZero = bls12_377.BaseField{} - var affineZero Affine + var affineZero bls12_377.Affine assert.Equal(t, affineZero.X, fieldZero) assert.Equal(t, affineZero.Y, fieldZero) - x := generateRandomLimb(int(BASE_LIMBS)) - y := generateRandomLimb(int(BASE_LIMBS)) - var affine Affine + x := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var affine bls12_377.Affine affine.FromLimbs(x, y) affine.Zero() @@ -23,10 +25,10 @@ func TestAffineZero(t *testing.T) { } func TestAffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) - var affine Affine + var affine bls12_377.Affine affine.FromLimbs(randLimbs, randLimbs2) assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) @@ -34,15 +36,15 @@ func TestAffineFromLimbs(t *testing.T) { } func TestAffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne bls12_377.BaseField fieldOne.One() - var expected Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected bls12_377.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine Affine + var affine bls12_377.Affine affine.FromLimbs(randLimbs, randLimbs2) projectivePoint := affine.ToProjective() @@ -50,18 +52,18 @@ func TestAffineToProjective(t *testing.T) { } func TestProjectiveZero(t *testing.T) { - var projectiveZero Projective + var projectiveZero bls12_377.Projective projectiveZero.Zero() - var fieldZero = BaseField{} - var fieldOne BaseField + var fieldZero = bls12_377.BaseField{} + var fieldOne bls12_377.BaseField fieldOne.One() assert.Equal(t, projectiveZero.X, fieldZero) assert.Equal(t, projectiveZero.Y, fieldOne) assert.Equal(t, projectiveZero.Z, fieldZero) - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - var projective Projective + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective bls12_377.Projective projective.FromLimbs(randLimbs, randLimbs, randLimbs) projective.Zero() @@ -71,11 +73,11 @@ func TestProjectiveZero(t *testing.T) { } func TestProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) - var projective Projective + var projective bls12_377.Projective projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) @@ -84,18 +86,18 @@ func TestProjectiveFromLimbs(t *testing.T) { } func TestProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne bls12_377.BaseField fieldOne.One() - var expected Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected bls12_377.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine Affine + var affine bls12_377.Affine affine.FromLimbs(randLimbs, randLimbs2) - var projectivePoint Projective + var projectivePoint bls12_377.Projective projectivePoint.FromAffine(affine) assert.Equal(t, expected, projectivePoint) } diff --git a/wrappers/golang/curves/bls12377/tests/ecntt_test.go b/wrappers/golang/curves/bls12377/tests/ecntt_test.go new file mode 100644 index 00000000..9c435f0e --- /dev/null +++ b/wrappers/golang/curves/bls12377/tests/ecntt_test.go @@ -0,0 +1,30 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + ecntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/ecntt" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/ntt" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + points := bls12_377.GenerateProjectivePoints(1 << largestTestSize) + + for _, size := range []int{4, 5, 6, 7, 8} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[bls12_377.Projective](points[:testSize]) + cfg.Ordering = v + cfg.NttAlgorithm = core.Radix2 + + output := make(core.HostSlice[bls12_377.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + } + } +} diff --git a/wrappers/golang/curves/bw6761/g2_curve_test.go b/wrappers/golang/curves/bls12377/tests/g2_curve_test.go similarity index 51% rename from wrappers/golang/curves/bw6761/g2_curve_test.go rename to wrappers/golang/curves/bls12377/tests/g2_curve_test.go index c1426c7d..a7691d2b 100644 --- a/wrappers/golang/curves/bw6761/g2_curve_test.go +++ b/wrappers/golang/curves/bls12377/tests/g2_curve_test.go @@ -1,22 +1,22 @@ -//go:build g2 - -package bw6761 +package tests import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/g2" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) func TestG2AffineZero(t *testing.T) { - var fieldZero = G2BaseField{} + var fieldZero = g2.G2BaseField{} - var affineZero G2Affine + var affineZero g2.G2Affine assert.Equal(t, affineZero.X, fieldZero) assert.Equal(t, affineZero.Y, fieldZero) - x := generateRandomLimb(int(G2_BASE_LIMBS)) - y := generateRandomLimb(int(G2_BASE_LIMBS)) - var affine G2Affine + x := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var affine g2.G2Affine affine.FromLimbs(x, y) affine.Zero() @@ -25,10 +25,10 @@ func TestG2AffineZero(t *testing.T) { } func TestG2AffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) @@ -36,15 +36,15 @@ func TestG2AffineFromLimbs(t *testing.T) { } func TestG2AffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - var fieldOne G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField fieldOne.One() - var expected G2Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) projectivePoint := affine.ToProjective() @@ -52,18 +52,18 @@ func TestG2AffineToProjective(t *testing.T) { } func TestG2ProjectiveZero(t *testing.T) { - var projectiveZero G2Projective + var projectiveZero g2.G2Projective projectiveZero.Zero() - var fieldZero = G2BaseField{} - var fieldOne G2BaseField + var fieldZero = g2.G2BaseField{} + var fieldOne g2.G2BaseField fieldOne.One() assert.Equal(t, projectiveZero.X, fieldZero) assert.Equal(t, projectiveZero.Y, fieldOne) assert.Equal(t, projectiveZero.Z, fieldZero) - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - var projective G2Projective + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var projective g2.G2Projective projective.FromLimbs(randLimbs, randLimbs, randLimbs) projective.Zero() @@ -73,11 +73,11 @@ func TestG2ProjectiveZero(t *testing.T) { } func TestG2ProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) - var projective G2Projective + var projective g2.G2Projective projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) @@ -86,18 +86,18 @@ func TestG2ProjectiveFromLimbs(t *testing.T) { } func TestG2ProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - var fieldOne G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField fieldOne.One() - var expected G2Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) - var projectivePoint G2Projective + var projectivePoint g2.G2Projective projectivePoint.FromAffine(affine) assert.Equal(t, expected, projectivePoint) } diff --git a/wrappers/golang/curves/bn254/g2_base_field_test.go b/wrappers/golang/curves/bls12377/tests/g2_g2base_field_test.go similarity index 57% rename from wrappers/golang/curves/bn254/g2_base_field_test.go rename to wrappers/golang/curves/bls12377/tests/g2_g2base_field_test.go index 2e4faf1c..83c7d8ce 100644 --- a/wrappers/golang/curves/bn254/g2_base_field_test.go +++ b/wrappers/golang/curves/bls12377/tests/g2_g2base_field_test.go @@ -1,36 +1,40 @@ -//go:build g2 - -package bn254 +package tests import ( + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/g2" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + G2BASE_LIMBS = bls12_377.G2BASE_LIMBS +) + func TestG2BaseFieldFromLimbs(t *testing.T) { - emptyField := G2BaseField{} - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + emptyField := bls12_377.G2BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the G2BaseField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the G2BaseField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestG2BaseFieldGetLimbs(t *testing.T) { - emptyField := G2BaseField{} - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + emptyField := bls12_377.G2BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the G2BaseField's limbs") } func TestG2BaseFieldOne(t *testing.T) { - var emptyField G2BaseField + var emptyField bls12_377.G2BaseField emptyField.One() - limbOne := generateLimbOne(int(G2_BASE_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(G2BASE_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -38,12 +42,12 @@ func TestG2BaseFieldOne(t *testing.T) { } func TestG2BaseFieldZero(t *testing.T) { - var emptyField G2BaseField + var emptyField bls12_377.G2BaseField emptyField.Zero() - limbsZero := make([]uint32, G2_BASE_LIMBS) + limbsZero := make([]uint32, G2BASE_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -51,24 +55,24 @@ func TestG2BaseFieldZero(t *testing.T) { } func TestG2BaseFieldSize(t *testing.T) { - var emptyField G2BaseField - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + var emptyField bls12_377.G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestG2BaseFieldAsPointer(t *testing.T) { - var emptyField G2BaseField - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + var emptyField bls12_377.G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestG2BaseFieldFromBytes(t *testing.T) { - var emptyField G2BaseField - bytes, expected := generateBytesArray(int(G2_BASE_LIMBS)) + var emptyField bls12_377.G2BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -76,8 +80,8 @@ func TestG2BaseFieldFromBytes(t *testing.T) { } func TestG2BaseFieldToBytes(t *testing.T) { - var emptyField G2BaseField - expected, limbs := generateBytesArray(int(G2_BASE_LIMBS)) + var emptyField bls12_377.G2BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") diff --git a/wrappers/golang/curves/bls12377/g2_msm_test.go b/wrappers/golang/curves/bls12377/tests/g2_msm_test.go similarity index 60% rename from wrappers/golang/curves/bls12377/g2_msm_test.go rename to wrappers/golang/curves/bls12377/tests/g2_msm_test.go index ec81d329..2ccd5008 100644 --- a/wrappers/golang/curves/bls12377/g2_msm_test.go +++ b/wrappers/golang/curves/bls12377/tests/g2_msm_test.go @@ -1,6 +1,4 @@ -//go:build g2 - -package bls12377 +package tests import ( "fmt" @@ -9,16 +7,18 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-377" "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleBls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/g2" ) -func projectiveToGnarkAffineG2(p G2Projective) bls12377.G2Affine { +func projectiveToGnarkAffineG2(p g2.G2Projective) bls12377.G2Affine { pxBytes := p.X.ToBytesLittleEndian() pxA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[:fp.Bytes])) pxA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[fp.Bytes:])) @@ -62,7 +62,7 @@ func projectiveToGnarkAffineG2(p G2Projective) bls12377.G2Affine { return *g2Affine.FromJacobian(&g2Jac) } -func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points core.HostSlice[G2Affine], out G2Projective) bool { +func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[icicleBls12_377.ScalarField], points core.HostSlice[g2.G2Affine], out g2.G2Projective) bool { scalarsFr := make([]fr.Element, len(scalars)) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -73,6 +73,11 @@ func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points cor for i, v := range points { pointsFp[i] = projectiveToGnarkAffineG2(v.ToProjective()) } + + return testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bls12377.G2Affine], out g2.G2Projective) bool { var msmRes bls12377.G2Jac msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) @@ -83,54 +88,117 @@ func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points cor return msmRes.Equal(&icicleResAsJac) } +func convertIcicleG2AffineToG2Affine(iciclePoints []g2.G2Affine) []bls12377.G2Affine { + points := make([]bls12377.G2Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes * 2]byte)(iciclePoint.X.ToBytesLittleEndian()) + xA0Bytes := ([fp.Bytes]byte)(xBytes[:fp.Bytes]) + xA1Bytes := ([fp.Bytes]byte)(xBytes[fp.Bytes:]) + xA0Elem, _ := fp.LittleEndian.Element(&xA0Bytes) + xA1Elem, _ := fp.LittleEndian.Element(&xA1Bytes) + + yBytes := ([fp.Bytes * 2]byte)(iciclePoint.Y.ToBytesLittleEndian()) + yA0Bytes := ([fp.Bytes]byte)(yBytes[:fp.Bytes]) + yA1Bytes := ([fp.Bytes]byte)(yBytes[fp.Bytes:]) + yA0Elem, _ := fp.LittleEndian.Element(&yA0Bytes) + yA1Elem, _ := fp.LittleEndian.Element(&yA1Bytes) + + points[index] = bls12377.G2Affine{ + X: bls12377.E2{ + A0: xA0Elem, + A1: xA1Elem, + }, + Y: bls12377.E2{ + A0: yA0Elem, + A1: yA1Elem, + }, + } + } + + return points +} + func TestMSMG2(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := G2GenerateAffinePoints(size) + scalars := icicleBls12_377.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) cr.SynchronizeStream(&stream) // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) + + } +} +func TestMSMG2GnarkCryptoTypes(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := g2.G2GenerateAffinePoints(size) + pointsGnark := convertIcicleG2AffineToG2Affine(points) + pointsHost := (core.HostSlice[bls12377.G2Affine])(pointsGnark) + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = g2.G2Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) } } func TestMSMG2Batch(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := G2GenerateAffinePoints(totalSize) + scalars := icicleBls12_377.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], batchSize) + outHost := make(core.HostSlice[g2.G2Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -143,36 +211,35 @@ func TestMSMG2Batch(t *testing.T) { } func TestPrecomputeBaseG2(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() const precomputeFactor = 8 for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := G2GenerateAffinePoints(totalSize) + scalars := icicleBls12_377.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) var precomputeOut core.DeviceSlice _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - e = G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + e = g2.G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e = out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.PrecomputeFactor = precomputeFactor - e = G2Msm(scalars, precomputeOut, &cfg, out) + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], batchSize) + outHost := make(core.HostSlice[g2.G2Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() precomputeOut.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -185,30 +252,29 @@ func TestPrecomputeBaseG2(t *testing.T) { } func TestMSMG2SkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) + scalars := icicleBls12_377.GenerateScalars(size) for i := size / 4; i < size; i++ { scalars[i].One() } - points := G2GenerateAffinePoints(size) + points := g2.G2GenerateAffinePoints(size) for i := 0; i < size/4; i++ { points[i].Zero() } - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.Malloc(p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) } @@ -225,23 +291,23 @@ func TestMSMG2MultiDevice(t *testing.T) { wg.Add(1) cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := G2GenerateAffinePoints(size) + scalars := icicleBls12_377.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) diff --git a/wrappers/golang/curves/bls12377/tests/main_test.go b/wrappers/golang/curves/bls12377/tests/main_test.go new file mode 100644 index 00000000..5a8d9091 --- /dev/null +++ b/wrappers/golang/curves/bls12377/tests/main_test.go @@ -0,0 +1,48 @@ +package tests + +import ( + "os" + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/ntt" + poly "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/polynomial" + + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft" +) + +const ( + largestTestSize = 20 +) + +func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { + rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) + rou := rouMont.Bits() + rouIcicle := bls12_377.ScalarField{} + limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) + + rouIcicle.FromLimbs(limbs) + e := ntt.InitDomain(rouIcicle, cfg.Ctx, false) + return e +} + +func TestMain(m *testing.M) { + poly.InitPolyBackend() + + // setup domain + cfg := ntt.GetDefaultNttConfig() + e := initDomain(largestTestSize, cfg) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("initDomain failed") + } + + // execute tests + os.Exit(m.Run()) + + // release domain + e = ntt.ReleaseDomain(cfg.Ctx) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("ReleaseDomain failed") + } +} diff --git a/wrappers/golang/curves/bls12377/msm_test.go b/wrappers/golang/curves/bls12377/tests/msm_test.go similarity index 58% rename from wrappers/golang/curves/bls12377/msm_test.go rename to wrappers/golang/curves/bls12377/tests/msm_test.go index 74c9744d..b3237db7 100644 --- a/wrappers/golang/curves/bls12377/msm_test.go +++ b/wrappers/golang/curves/bls12377/tests/msm_test.go @@ -1,4 +1,4 @@ -package bls12377 +package tests import ( "fmt" @@ -7,16 +7,18 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-377" "github.com/consensys/gnark-crypto/ecc/bls12-377/fp" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleBls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/msm" ) -func projectiveToGnarkAffine(p Projective) bls12377.G1Affine { +func projectiveToGnarkAffine(p icicleBls12_377.Projective) bls12377.G1Affine { px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.X).ToBytesLittleEndian())) py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Y).ToBytesLittleEndian())) pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Z).ToBytesLittleEndian())) @@ -33,7 +35,7 @@ func projectiveToGnarkAffine(p Projective) bls12377.G1Affine { return bls12377.G1Affine{X: *x, Y: *y} } -func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core.HostSlice[Affine], out Projective) bool { +func testAgainstGnarkCryptoMsm(scalars core.HostSlice[icicleBls12_377.ScalarField], points core.HostSlice[icicleBls12_377.Affine], out icicleBls12_377.Projective) bool { scalarsFr := make([]fr.Element, len(scalars)) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -44,6 +46,11 @@ func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core. for i, v := range points { pointsFp[i] = projectiveToGnarkAffine(v.ToProjective()) } + + return testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bls12377.G1Affine], out icicleBls12_377.Projective) bool { var msmRes bls12377.G1Jac msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) @@ -54,54 +61,104 @@ func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core. return msmRes.Equal(&icicleResAsJac) } +func convertIcicleAffineToG1Affine(iciclePoints []icicleBls12_377.Affine) []bls12377.G1Affine { + points := make([]bls12377.G1Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes]byte)(iciclePoint.X.ToBytesLittleEndian()) + fpXElem, _ := fp.LittleEndian.Element(&xBytes) + + yBytes := ([fp.Bytes]byte)(iciclePoint.Y.ToBytesLittleEndian()) + fpYElem, _ := fp.LittleEndian.Element(&yBytes) + points[index] = bls12377.G1Affine{ + X: fpXElem, + Y: fpYElem, + } + } + + return points +} + func TestMSM(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + scalars := icicleBls12_377.GenerateScalars(size) + points := icicleBls12_377.GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p Projective + var p icicleBls12_377.Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) cr.SynchronizeStream(&stream) // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) + + } +} +func TestMSMGnarkCryptoTypes(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := icicleBls12_377.GenerateAffinePoints(size) + pointsGnark := convertIcicleAffineToG1Affine(points) + pointsHost := (core.HostSlice[bls12377.G1Affine])(pointsGnark) + + var p icicleBls12_377.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = msm.Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) } } func TestMSMBatch(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := GenerateAffinePoints(totalSize) + scalars := icicleBls12_377.GenerateScalars(totalSize) + points := icicleBls12_377.GenerateAffinePoints(totalSize) - var p Projective + var p icicleBls12_377.Projective var out core.DeviceSlice _, e := out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], batchSize) + outHost := make(core.HostSlice[icicleBls12_377.Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -114,36 +171,35 @@ func TestMSMBatch(t *testing.T) { } func TestPrecomputeBase(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() const precomputeFactor = 8 for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := GenerateAffinePoints(totalSize) + scalars := icicleBls12_377.GenerateScalars(totalSize) + points := icicleBls12_377.GenerateAffinePoints(totalSize) var precomputeOut core.DeviceSlice _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - e = PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + e = msm.PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - var p Projective + var p icicleBls12_377.Projective var out core.DeviceSlice _, e = out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.PrecomputeFactor = precomputeFactor - e = Msm(scalars, precomputeOut, &cfg, out) + e = msm.Msm(scalars, precomputeOut, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], batchSize) + outHost := make(core.HostSlice[icicleBls12_377.Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() precomputeOut.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -156,30 +212,29 @@ func TestPrecomputeBase(t *testing.T) { } func TestMSMSkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) + scalars := icicleBls12_377.GenerateScalars(size) for i := size / 4; i < size; i++ { scalars[i].One() } - points := GenerateAffinePoints(size) + points := icicleBls12_377.GenerateAffinePoints(size) for i := 0; i < size/4; i++ { points[i].Zero() } - var p Projective + var p icicleBls12_377.Projective var out core.DeviceSlice _, e := out.Malloc(p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) } @@ -196,23 +251,23 @@ func TestMSMMultiDevice(t *testing.T) { wg.Add(1) cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + scalars := icicleBls12_377.GenerateScalars(size) + points := icicleBls12_377.GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p Projective + var p icicleBls12_377.Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) diff --git a/wrappers/golang/curves/bls12377/ntt_test.go b/wrappers/golang/curves/bls12377/tests/ntt_test.go similarity index 71% rename from wrappers/golang/curves/bls12377/ntt_test.go rename to wrappers/golang/curves/bls12377/tests/ntt_test.go index 5b9465f1..5bdb5013 100644 --- a/wrappers/golang/curves/bls12377/ntt_test.go +++ b/wrappers/golang/curves/bls12377/tests/ntt_test.go @@ -1,40 +1,20 @@ -package bls12377 +package tests import ( - "os" "reflect" "testing" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" ) -const ( - largestTestSize = 17 -) - -func init() { - cfg := GetDefaultNttConfig() - initDomain(largestTestSize, cfg) -} - -func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { - rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) - rou := rouMont.Bits() - rouIcicle := ScalarField{} - limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) - - rouIcicle.FromLimbs(limbs) - e := InitDomain(rouIcicle, cfg.Ctx, false) - return e -} - -func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], output core.HostSlice[ScalarField], order core.Ordering, direction core.NTTDir) bool { - domainWithPrecompute := fft.NewDomain(uint64(size)) +func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[bls12_377.ScalarField], output core.HostSlice[bls12_377.ScalarField], order core.Ordering, direction core.NTTDir) bool { scalarsFr := make([]fr.Element, size) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -46,6 +26,11 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou outputAsFr[i] = slice64 } + return testAgainstGnarkCryptoNttGnarkTypes(size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) bool { + domainWithPrecompute := fft.NewDomain(uint64(size)) // DIT + BitReverse == Ordering.kRR // DIT == Ordering.kRN // DIF + BitReverse == Ordering.kNN @@ -68,72 +53,77 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou } return reflect.DeepEqual(scalarsFr, outputAsFr) } - func TestNTTGetDefaultConfig(t *testing.T) { - actual := GetDefaultNttConfig() - expected := generateLimbOne(int(SCALAR_LIMBS)) + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(bls12_377.SCALAR_LIMBS)) assert.Equal(t, expected, actual.CosetGen[:]) - cosetGenField := ScalarField{} + cosetGenField := bls12_377.ScalarField{} cosetGenField.One() assert.ElementsMatch(t, cosetGenField.GetLimbs(), actual.CosetGen) } func TestInitDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) } func TestNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := bls12_377.GenerateScalars(1 << largestTestSize) for _, size := range []int{4, largestTestSize} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[bls12_377.ScalarField](scalars[:testSize]) cfg.Ordering = v // run ntt - output := make(core.HostSlice[ScalarField], testSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[bls12_377.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, core.KForward)) } } } +func TestNttFrElement(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := make([]fr.Element, 4) + var x fr.Element + for i := 0; i < 4; i++ { + x.SetRandom() + scalars[i] = x + } -func TestECNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - points := GenerateProjectivePoints(1 << largestTestSize) + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + testSize := size - for _, size := range []int{4, 5, 6, 7, 8} { - for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { - testSize := 1 << size - - pointsCopy := core.HostSliceFromElements[Projective](points[:testSize]) + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) cfg.Ordering = v - cfg.NttAlgorithm = core.Radix2 - output := make(core.HostSlice[Projective], testSize) - e := ECNtt(pointsCopy, core.KForward, &cfg, output) - assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + assert.True(t, testAgainstGnarkCryptoNttGnarkTypes(testSize, scalarsCopy, output, v, core.KForward)) } } } func TestNttDeviceAsync(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := bls12_377.GenerateScalars(1 << largestTestSize) for _, size := range []int{1, 10, largestTestSize} { for _, direction := range []core.NTTDir{core.KForward, core.KInverse} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[bls12_377.ScalarField](scalars[:testSize]) stream, _ := cr.CreateStream() @@ -147,12 +137,11 @@ func TestNttDeviceAsync(t *testing.T) { deviceOutput.MallocAsync(testSize*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) // run ntt - Ntt(deviceInput, direction, &cfg, deviceOutput) - output := make(core.HostSlice[ScalarField], testSize) + ntt.Ntt(deviceInput, direction, &cfg, deviceOutput) + output := make(core.HostSlice[bls12_377.ScalarField], testSize) output.CopyFromDeviceAsync(&deviceOutput, stream) cr.SynchronizeStream(&stream) - // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, direction)) } @@ -161,22 +150,22 @@ func TestNttDeviceAsync(t *testing.T) { } func TestNttBatch(t *testing.T) { - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() largestBatchSize := 100 - scalars := GenerateScalars(1 << largestTestSize * largestBatchSize) + scalars := bls12_377.GenerateScalars(1 << largestTestSize * largestBatchSize) for _, size := range []int{4, largestTestSize} { for _, batchSize := range []int{1, 16, largestBatchSize} { testSize := 1 << size totalSize := testSize * batchSize - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:totalSize]) + scalarsCopy := core.HostSliceFromElements[bls12_377.ScalarField](scalars[:totalSize]) cfg.Ordering = core.KNN cfg.BatchSize = int32(batchSize) // run ntt - output := make(core.HostSlice[ScalarField], totalSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[bls12_377.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) // Compare with gnark-crypto domainWithPrecompute := fft.NewDomain(uint64(testSize)) @@ -205,36 +194,18 @@ func TestNttBatch(t *testing.T) { func TestReleaseDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() - e := ReleaseDomain(cfg.Ctx) + cfg := ntt.GetDefaultNttConfig() + e := ntt.ReleaseDomain(cfg.Ctx) assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ReleasDomain failed") } -func TestMain(m *testing.M) { - // setup domain - cfg := GetDefaultNttConfig() - e := initDomain(largestTestSize, cfg) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("initDomain failed") - } - - // execute tests - os.Exit(m.Run()) - - // release domain - e = ReleaseDomain(cfg.Ctx) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("ReleaseDomain failed") - } -} - // func TestNttArbitraryCoset(t *testing.T) { // for _, size := range []int{20} { // for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { // testSize := 1 << size // scalars := GenerateScalars(testSize) -// cfg := GetDefaultNttConfig() +// cfg := ntt.GetDefaultNttConfig() // var scalarsCopy core.HostSlice[ScalarField] // for _, v := range scalars { diff --git a/wrappers/golang/curves/bls12377/tests/polynomial_test.go b/wrappers/golang/curves/bls12377/tests/polynomial_test.go new file mode 100644 index 00000000..82164cfa --- /dev/null +++ b/wrappers/golang/curves/bls12377/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + // "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/polynomial" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/vecOps" + "github.com/stretchr/testify/assert" +) + +var one, two, three, four, five bls12_377.ScalarField + +func init() { + one.One() + two.FromUint32(2) + three.FromUint32(3) + four.FromUint32(4) + five.FromUint32(5) +} + +func rand() bls12_377.ScalarField { + return bls12_377.GenerateScalars(1)[0] +} + +func randomPoly(size int) (f polynomial.DensePolynomial) { + f.CreateFromCoeffecitients(core.HostSliceFromElements(bls12_377.GenerateScalars(size))) + return f +} + +func vecOp(a, b bls12_377.ScalarField, op core.VecOps) bls12_377.ScalarField { + ahost := core.HostSliceWithValue(a, 1) + bhost := core.HostSliceWithValue(b, 1) + out := make(core.HostSlice[bls12_377.ScalarField], 1) + + cfg := core.DefaultVecOpsConfig() + vecOps.VecOp(ahost, bhost, out, cfg, op) + return out[0] +} + +func TestPolyCreateFromCoefficients(t *testing.T) { + scalars := bls12_377.GenerateScalars(33) + var uniPoly polynomial.DensePolynomial + + poly := uniPoly.CreateFromCoeffecitients(scalars) + poly.Print() +} + +func TestPolyEval(t *testing.T) { + // testing correct evaluation of f(8) for f(x)=4x^2+2x+5 + coeffs := core.HostSliceFromElements([]bls12_377.ScalarField{five, two, four}) + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(coeffs) + + var x bls12_377.ScalarField + x.FromUint32(8) + domains := make(core.HostSlice[bls12_377.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[bls12_377.ScalarField], 1) + fEvaled := f.EvalOnDomain(domains, evals) + var expected bls12_377.ScalarField + assert.Equal(t, expected.FromUint32(277), fEvaled.(core.HostSlice[bls12_377.ScalarField])[0]) +} + +func TestPolyClone(t *testing.T) { + f := randomPoly(8) + x := rand() + fx := f.Eval(x) + + g := f.Clone() + fg := f.Add(&g) + + gx := g.Eval(x) + fgx := fg.Eval(x) + + assert.Equal(t, fx, gx) + assert.Equal(t, vecOp(fx, gx, core.Add), fgx) +} + +func TestPolyAddSubMul(t *testing.T) { + testSize := 1 << 10 + f := randomPoly(testSize) + g := randomPoly(testSize) + x := rand() + + fx := f.Eval(x) + gx := g.Eval(x) + + polyAdd := f.Add(&g) + fxAddgx := vecOp(fx, gx, core.Add) + assert.Equal(t, polyAdd.Eval(x), fxAddgx) + + polySub := f.Subtract(&g) + fxSubgx := vecOp(fx, gx, core.Sub) + assert.Equal(t, polySub.Eval(x), fxSubgx) + + polyMul := f.Multiply(&g) + fxMulgx := vecOp(fx, gx, core.Mul) + assert.Equal(t, polyMul.Eval(x), fxMulgx) + + s1 := rand() + polMulS1 := f.MultiplyByScalar(s1) + assert.Equal(t, polMulS1.Eval(x), vecOp(fx, s1, core.Mul)) + + s2 := rand() + polMulS2 := f.MultiplyByScalar(s2) + assert.Equal(t, polMulS2.Eval(x), vecOp(fx, s2, core.Mul)) +} + +func TestPolyMonomials(t *testing.T) { + var zero bls12_377.ScalarField + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements([]bls12_377.ScalarField{one, zero, two})) + x := rand() + + fx := f.Eval(x) + f.AddMonomial(three, 1) + fxAdded := f.Eval(x) + assert.Equal(t, fxAdded, vecOp(fx, vecOp(three, x, core.Mul), core.Add)) + + f.SubMonomial(one, 0) + fxSub := f.Eval(x) + assert.Equal(t, fxSub, vecOp(fxAdded, one, core.Sub)) +} + +func TestPolyReadCoeffs(t *testing.T) { + var f polynomial.DensePolynomial + coeffs := core.HostSliceFromElements([]bls12_377.ScalarField{one, two, three, four}) + f.CreateFromCoeffecitients(coeffs) + coeffsCopied := make(core.HostSlice[bls12_377.ScalarField], coeffs.Len()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsCopied) + assert.ElementsMatch(t, coeffs, coeffsCopied) + + var coeffsDevice core.DeviceSlice + coeffsDevice.Malloc(coeffs.Len()*one.Size(), one.Size()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsDevice) + coeffsHost := make(core.HostSlice[bls12_377.ScalarField], coeffs.Len()) + coeffsHost.CopyFromDevice(&coeffsDevice) + + assert.ElementsMatch(t, coeffs, coeffsHost) +} + +func TestPolyOddEvenSlicing(t *testing.T) { + size := 1<<10 - 3 + f := randomPoly(size) + + even := f.Even() + odd := f.Odd() + assert.Equal(t, f.Degree(), even.Degree()+odd.Degree()+1) + + x := rand() + var evenExpected, oddExpected bls12_377.ScalarField + for i := size; i >= 0; i-- { + if i%2 == 0 { + mul := vecOp(evenExpected, x, core.Mul) + evenExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } else { + mul := vecOp(oddExpected, x, core.Mul) + oddExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } + } + + evenEvaled := even.Eval(x) + assert.Equal(t, evenExpected, evenEvaled) + + oddEvaled := odd.Eval(x) + assert.Equal(t, oddExpected, oddEvaled) +} + +func TestPolynomialDivision(t *testing.T) { + // divide f(x)/g(x), compute q(x), r(x) and check f(x)=q(x)*g(x)+r(x) + var f, g polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements(bls12_377.GenerateScalars(1 << 4))) + g.CreateFromCoeffecitients(core.HostSliceFromElements(bls12_377.GenerateScalars(1 << 2))) + + q, r := f.Divide(&g) + + qMulG := q.Multiply(&g) + fRecon := qMulG.Add(&r) + + x := bls12_377.GenerateScalars(1)[0] + fEval := f.Eval(x) + fReconEval := fRecon.Eval(x) + assert.Equal(t, fEval, fReconEval) +} + +func TestDivideByVanishing(t *testing.T) { + // poly of x^4-1 vanishes ad 4th rou + var zero bls12_377.ScalarField + minus_one := vecOp(zero, one, core.Sub) + coeffs := core.HostSliceFromElements([]bls12_377.ScalarField{minus_one, zero, zero, zero, one}) // x^4-1 + var v polynomial.DensePolynomial + v.CreateFromCoeffecitients(coeffs) + + f := randomPoly(1 << 3) + + fv := f.Multiply(&v) + fDegree := f.Degree() + fvDegree := fv.Degree() + assert.Equal(t, fDegree+4, fvDegree) + + fReconstructed := fv.DivideByVanishing(4) + assert.Equal(t, fDegree, fReconstructed.Degree()) + + x := rand() + assert.Equal(t, f.Eval(x), fReconstructed.Eval(x)) +} + +// func TestPolySlice(t *testing.T) { +// size := 4 +// coeffs := bls12_377.GenerateScalars(size) +// var f DensePolynomial +// f.CreateFromCoeffecitients(coeffs) +// fSlice := f.AsSlice() +// assert.True(t, fSlice.IsOnDevice()) +// assert.Equal(t, size, fSlice.Len()) + +// hostSlice := make(core.HostSlice[bls12_377.ScalarField], size) +// hostSlice.CopyFromDevice(fSlice) +// assert.Equal(t, coeffs, hostSlice) + +// cfg := ntt.GetDefaultNttConfig() +// res := make(core.HostSlice[bls12_377.ScalarField], size) +// ntt.Ntt(fSlice, core.KForward, cfg, res) + +// assert.Equal(t, f.Eval(one), res[0]) +// } diff --git a/wrappers/golang/curves/bls12377/tests/scalar_field_test.go b/wrappers/golang/curves/bls12377/tests/scalar_field_test.go new file mode 100644 index 00000000..208df7e5 --- /dev/null +++ b/wrappers/golang/curves/bls12377/tests/scalar_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + SCALAR_LIMBS = bls12_377.SCALAR_LIMBS +) + +func TestScalarFieldFromLimbs(t *testing.T) { + emptyField := bls12_377.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestScalarFieldGetLimbs(t *testing.T) { + emptyField := bls12_377.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") +} + +func TestScalarFieldOne(t *testing.T) { + var emptyField bls12_377.ScalarField + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int(SCALAR_LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "ScalarField with limbs to field one did not work") +} + +func TestScalarFieldZero(t *testing.T) { + var emptyField bls12_377.ScalarField + emptyField.Zero() + limbsZero := make([]uint32, SCALAR_LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "ScalarField with limbs to field zero failed") +} + +func TestScalarFieldSize(t *testing.T) { + var emptyField bls12_377.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") +} + +func TestScalarFieldAsPointer(t *testing.T) { + var emptyField bls12_377.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") +} + +func TestScalarFieldFromBytes(t *testing.T) { + var emptyField bls12_377.ScalarField + bytes, expected := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func TestScalarFieldToBytes(t *testing.T) { + var emptyField bls12_377.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) + emptyField.FromLimbs(limbs) + + assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") +} + +func TestBls12_377GenerateScalars(t *testing.T) { + const numScalars = 8 + scalars := bls12_377.GenerateScalars(numScalars) + + assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) + + assert.Equal(t, numScalars, scalars.Len()) + zeroScalar := bls12_377.ScalarField{} + assert.NotContains(t, scalars, zeroScalar) +} + +func TestBls12_377MongtomeryConversion(t *testing.T) { + size := 1 << 15 + scalars := bls12_377.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + bls12_377.ToMontgomery(&deviceScalars) + + scalarsMontHost := bls12_377.GenerateScalars(size) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.NotEqual(t, scalars, scalarsMontHost) + + bls12_377.FromMontgomery(&deviceScalars) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.Equal(t, scalars, scalarsMontHost) +} diff --git a/wrappers/golang/curves/bls12377/tests/vec_ops_test.go b/wrappers/golang/curves/bls12377/tests/vec_ops_test.go new file mode 100644 index 00000000..a7c82174 --- /dev/null +++ b/wrappers/golang/curves/bls12377/tests/vec_ops_test.go @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bls12_377 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12377/vecOps" + "github.com/stretchr/testify/assert" +) + +func TestBls12_377VecOps(t *testing.T) { + testSize := 1 << 14 + + a := bls12_377.GenerateScalars(testSize) + b := bls12_377.GenerateScalars(testSize) + var scalar bls12_377.ScalarField + scalar.One() + ones := core.HostSliceWithValue(scalar, testSize) + + out := make(core.HostSlice[bls12_377.ScalarField], testSize) + out2 := make(core.HostSlice[bls12_377.ScalarField], testSize) + out3 := make(core.HostSlice[bls12_377.ScalarField], testSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.VecOp(a, b, out, cfg, core.Add) + vecOps.VecOp(out, b, out2, cfg, core.Sub) + + assert.Equal(t, a, out2) + + vecOps.VecOp(a, ones, out3, cfg, core.Mul) + + assert.Equal(t, a, out3) +} + +func TestBls12_377Transpose(t *testing.T) { + rowSize := 1 << 6 + columnSize := 1 << 8 + onDevice := false + isAsync := false + + matrix := bls12_377.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[bls12_377.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[bls12_377.ScalarField], rowSize*columnSize) + + ctx, _ := cr.GetDefaultDeviceContext() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, ctx, onDevice, isAsync) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + onDevice = true + + matrix.CopyToDevice(&dMatrix, true) + dOut.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + dOut2.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + + vecOps.TransposeMatrix(dMatrix, dOut, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, ctx, onDevice, isAsync) + output := make(core.HostSlice[bls12_377.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang/curves/bls12377/include/vec_ops.h b/wrappers/golang/curves/bls12377/vecOps/include/vec_ops.h similarity index 51% rename from wrappers/golang/curves/bls12377/include/vec_ops.h rename to wrappers/golang/curves/bls12377/vecOps/include/vec_ops.h index d13ae7da..54a4968e 100644 --- a/wrappers/golang/curves/bls12377/include/vec_ops.h +++ b/wrappers/golang/curves/bls12377/vecOps/include/vec_ops.h @@ -1,5 +1,5 @@ #include -#include "../../include/types.h" +#include #ifndef _BLS12_377_VEC_OPS_H #define _BLS12_377_VEC_OPS_H @@ -8,7 +8,11 @@ extern "C" { #endif -cudaError_t bls12_377MulCuda( +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bls12_377_mul_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -16,7 +20,7 @@ cudaError_t bls12_377MulCuda( scalar_t* result ); -cudaError_t bls12_377AddCuda( +cudaError_t bls12_377_add_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -24,7 +28,7 @@ cudaError_t bls12_377AddCuda( scalar_t* result ); -cudaError_t bls12_377SubCuda( +cudaError_t bls12_377_sub_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -32,6 +36,16 @@ cudaError_t bls12_377SubCuda( scalar_t* result ); +cudaError_t bls12_377_transpose_matrix_cuda( + scalar_t* mat_in, + int row_size, + int column_size, + scalar_t* mat_out, + DeviceContext* ctx, + bool on_device, + bool is_async +); + #ifdef __cplusplus } #endif diff --git a/wrappers/golang/curves/bls12377/vecOps/vec_ops.go b/wrappers/golang/curves/bls12377/vecOps/vec_ops.go new file mode 100644 index 00000000..5e41c4f2 --- /dev/null +++ b/wrappers/golang/curves/bls12377/vecOps/vec_ops.go @@ -0,0 +1,48 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + + "unsafe" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { + aPointer, bPointer, outPointer, cfgPointer, size := core.VecOpCheck(a, b, out, &config) + + cA := (*C.scalar_t)(aPointer) + cB := (*C.scalar_t)(bPointer) + cOut := (*C.scalar_t)(outPointer) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cSize := (C.int)(size) + + switch op { + case core.Sub: + ret = (cr.CudaError)(C.bls12_377_sub_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (cr.CudaError)(C.bls12_377_add_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (cr.CudaError)(C.bls12_377_mul_cuda(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError) { + core.TransposeCheck(in, out, onDevice) + + cIn := (*C.scalar_t)(in.AsUnsafePointer()) + cOut := (*C.scalar_t)(out.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cOnDevice := (C._Bool)(onDevice) + cIsAsync := (C._Bool)(isAsync) + + err := (cr.CudaError)(C.bls12_377_transpose_matrix_cuda(cIn, cRowSize, cColumnSize, cOut, cCtx, cOnDevice, cIsAsync)) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bls12377/vec_ops.go b/wrappers/golang/curves/bls12377/vec_ops.go deleted file mode 100644 index 35c1367f..00000000 --- a/wrappers/golang/curves/bls12377/vec_ops.go +++ /dev/null @@ -1,55 +0,0 @@ -package bls12377 - -// #cgo CFLAGS: -I./include/ -// #include "vec_ops.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { - core.VecOpCheck(a, b, out, &config) - var cA, cB, cOut *C.scalar_t - - if a.IsOnDevice() { - aDevice := a.(core.DeviceSlice) - aDevice.CheckDevice() - cA = (*C.scalar_t)(aDevice.AsPointer()) - } else { - cA = (*C.scalar_t)(unsafe.Pointer(&a.(core.HostSlice[ScalarField])[0])) - } - - if b.IsOnDevice() { - bDevice := b.(core.DeviceSlice) - bDevice.CheckDevice() - cB = (*C.scalar_t)(bDevice.AsPointer()) - } else { - cB = (*C.scalar_t)(unsafe.Pointer(&b.(core.HostSlice[ScalarField])[0])) - } - - if out.IsOnDevice() { - outDevice := out.(core.DeviceSlice) - outDevice.CheckDevice() - cOut = (*C.scalar_t)(outDevice.AsPointer()) - } else { - cOut = (*C.scalar_t)(unsafe.Pointer(&out.(core.HostSlice[ScalarField])[0])) - } - - cConfig := (*C.VecOpsConfig)(unsafe.Pointer(&config)) - cSize := (C.int)(a.Len()) - - switch op { - case core.Sub: - ret = (cr.CudaError)(C.bls12_377SubCuda(cA, cB, cSize, cConfig, cOut)) - case core.Add: - ret = (cr.CudaError)(C.bls12_377AddCuda(cA, cB, cSize, cConfig, cOut)) - case core.Mul: - ret = (cr.CudaError)(C.bls12_377MulCuda(cA, cB, cSize, cConfig, cOut)) - } - - return ret -} diff --git a/wrappers/golang/curves/bls12377/vec_ops_test.go b/wrappers/golang/curves/bls12377/vec_ops_test.go deleted file mode 100644 index 9a29b020..00000000 --- a/wrappers/golang/curves/bls12377/vec_ops_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package bls12377 - -import ( - "testing" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - "github.com/stretchr/testify/assert" -) - -func TestVecOps(t *testing.T) { - testSize := 1 << 14 - - a := GenerateScalars(testSize) - b := GenerateScalars(testSize) - var scalar ScalarField - scalar.One() - ones := core.HostSliceWithValue(scalar, testSize) - - out := make(core.HostSlice[ScalarField], testSize) - out2 := make(core.HostSlice[ScalarField], testSize) - out3 := make(core.HostSlice[ScalarField], testSize) - - cfg := core.DefaultVecOpsConfig() - - VecOp(a, b, out, cfg, core.Add) - VecOp(out, b, out2, cfg, core.Sub) - - assert.Equal(t, a, out2) - - VecOp(a, ones, out3, cfg, core.Mul) - - assert.Equal(t, a, out3) -} diff --git a/wrappers/golang/curves/bls12381/base_field.go b/wrappers/golang/curves/bls12381/base_field.go index fce5a84e..724c8bca 100644 --- a/wrappers/golang/curves/bls12381/base_field.go +++ b/wrappers/golang/curves/bls12381/base_field.go @@ -6,7 +6,7 @@ import ( ) const ( - BASE_LIMBS int8 = 12 + BASE_LIMBS int = 12 ) type BaseField struct { @@ -29,6 +29,11 @@ func (f BaseField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *BaseField) FromUint32(v uint32) BaseField { + f.limbs[0] = v + return *f +} + func (f *BaseField) FromLimbs(limbs []uint32) BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") diff --git a/wrappers/golang/curves/bls12381/bls12_381.go b/wrappers/golang/curves/bls12381/bls12_381.go deleted file mode 100644 index c7797bbc..00000000 --- a/wrappers/golang/curves/bls12381/bls12_381.go +++ /dev/null @@ -1,4 +0,0 @@ -package bls12381 - -// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build -lingo_bls12_381 -lstdc++ -lm -import "C" diff --git a/wrappers/golang/curves/bls12381/curve.go b/wrappers/golang/curves/bls12381/curve.go index 97bd5b11..0b946c25 100644 --- a/wrappers/golang/curves/bls12381/curve.go +++ b/wrappers/golang/curves/bls12381/curve.go @@ -53,7 +53,7 @@ func (p *Projective) FromAffine(a Affine) Projective { func (p Projective) ProjectiveEq(p2 *Projective) bool { cP := (*C.projective_t)(unsafe.Pointer(&p)) cP2 := (*C.projective_t)(unsafe.Pointer(&p2)) - __ret := C.bls12_381Eq(cP, cP2) + __ret := C.bls12_381_eq(cP, cP2) return __ret == (C._Bool)(true) } @@ -62,7 +62,7 @@ func (p *Projective) ProjectiveToAffine() Affine { cA := (*C.affine_t)(unsafe.Pointer(&a)) cP := (*C.projective_t)(unsafe.Pointer(&p)) - C.bls12_381ToAffine(cP, cA) + C.bls12_381_to_affine(cP, cA) return a } @@ -75,7 +75,7 @@ func GenerateProjectivePoints(size int) core.HostSlice[Projective] { pointsSlice := core.HostSliceFromElements[Projective](points) pPoints := (*C.projective_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bls12_381GenerateProjectivePoints(pPoints, cSize) + C.bls12_381_generate_projective_points(pPoints, cSize) return pointsSlice } @@ -129,18 +129,18 @@ func GenerateAffinePoints(size int) core.HostSlice[Affine] { pointsSlice := core.HostSliceFromElements[Affine](points) cPoints := (*C.affine_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bls12_381GenerateAffinePoints(cPoints, cSize) + C.bls12_381_generate_affine_points(cPoints, cSize) return pointsSlice } func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.affine_t)(points.AsPointer()) + cValues := (*C.affine_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_381AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_381_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } @@ -156,12 +156,12 @@ func AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { } func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.projective_t)(points.AsPointer()) + cValues := (*C.projective_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_381ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_381_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bls12381/ecntt/ecntt.go b/wrappers/golang/curves/bls12381/ecntt/ecntt.go new file mode 100644 index 00000000..9b9ebf44 --- /dev/null +++ b/wrappers/golang/curves/bls12381/ecntt/ecntt.go @@ -0,0 +1,24 @@ +package ecntt + +// #cgo CFLAGS: -I./include/ +// #include "ecntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + pointsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](points, cfg, results) + + cPoints := (*C.projective_t)(pointsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.projective_t)(resultsPointer) + + __ret := C.bls12_381_ecntt_cuda(cPoints, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bls12381/ecntt/include/ecntt.h b/wrappers/golang/curves/bls12381/ecntt/include/ecntt.h new file mode 100644 index 00000000..033af466 --- /dev/null +++ b/wrappers/golang/curves/bls12381/ecntt/include/ecntt.h @@ -0,0 +1,19 @@ +#include + +#ifndef _BLS12_381_ECNTT_H +#define _BLS12_381_ECNTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct NTTConfig NTTConfig; +typedef struct projective_t projective_t; + +cudaError_t bls12_381_ecntt_cuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bn254/g2_curve.go b/wrappers/golang/curves/bls12381/g2/curve.go similarity index 87% rename from wrappers/golang/curves/bn254/g2_curve.go rename to wrappers/golang/curves/bls12381/g2/curve.go index 63e793d8..92bd2537 100644 --- a/wrappers/golang/curves/bn254/g2_curve.go +++ b/wrappers/golang/curves/bls12381/g2/curve.go @@ -1,9 +1,7 @@ -//go:build g2 - -package bn254 +package g2 // #cgo CFLAGS: -I./include/ -// #include "g2_curve.h" +// #include "curve.h" import "C" import ( @@ -55,7 +53,7 @@ func (p *G2Projective) FromAffine(a G2Affine) G2Projective { func (p G2Projective) ProjectiveEq(p2 *G2Projective) bool { cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) cP2 := (*C.g2_projective_t)(unsafe.Pointer(&p2)) - __ret := C.bn254G2Eq(cP, cP2) + __ret := C.bls12_381_g2_eq(cP, cP2) return __ret == (C._Bool)(true) } @@ -64,7 +62,7 @@ func (p *G2Projective) ProjectiveToAffine() G2Affine { cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) - C.bn254G2ToAffine(cP, cA) + C.bls12_381_g2_to_affine(cP, cA) return a } @@ -77,7 +75,7 @@ func G2GenerateProjectivePoints(size int) core.HostSlice[G2Projective] { pointsSlice := core.HostSliceFromElements[G2Projective](points) pPoints := (*C.g2_projective_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bn254G2GenerateProjectivePoints(pPoints, cSize) + C.bls12_381_g2_generate_projective_points(pPoints, cSize) return pointsSlice } @@ -131,18 +129,18 @@ func G2GenerateAffinePoints(size int) core.HostSlice[G2Affine] { pointsSlice := core.HostSliceFromElements[G2Affine](points) cPoints := (*C.g2_affine_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bn254G2GenerateAffinePoints(cPoints, cSize) + C.bls12_381_g2_generate_affine_points(cPoints, cSize) return pointsSlice } func convertG2AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.g2_affine_t)(points.AsPointer()) + cValues := (*C.g2_affine_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bn254G2AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_381_g2_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } @@ -158,12 +156,12 @@ func G2AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { } func convertG2ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.g2_projective_t)(points.AsPointer()) + cValues := (*C.g2_projective_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bn254G2ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_381_g2_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bw6761/g2_base_field.go b/wrappers/golang/curves/bls12381/g2/g2base_field.go similarity index 86% rename from wrappers/golang/curves/bw6761/g2_base_field.go rename to wrappers/golang/curves/bls12381/g2/g2base_field.go index d74de530..c4073af9 100644 --- a/wrappers/golang/curves/bw6761/g2_base_field.go +++ b/wrappers/golang/curves/bls12381/g2/g2base_field.go @@ -1,6 +1,4 @@ -//go:build g2 - -package bw6761 +package g2 import ( "encoding/binary" @@ -8,19 +6,19 @@ import ( ) const ( - G2_BASE_LIMBS int8 = 24 + G2BASE_LIMBS int = 24 ) type G2BaseField struct { - limbs [G2_BASE_LIMBS]uint32 + limbs [G2BASE_LIMBS]uint32 } func (f G2BaseField) Len() int { - return int(G2_BASE_LIMBS) + return int(G2BASE_LIMBS) } func (f G2BaseField) Size() int { - return int(G2_BASE_LIMBS * 4) + return int(G2BASE_LIMBS * 4) } func (f G2BaseField) GetLimbs() []uint32 { @@ -31,6 +29,11 @@ func (f G2BaseField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *G2BaseField) FromUint32(v uint32) G2BaseField { + f.limbs[0] = v + return *f +} + func (f *G2BaseField) FromLimbs(limbs []uint32) G2BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") diff --git a/wrappers/golang/curves/bls12381/g2/include/curve.h b/wrappers/golang/curves/bls12381/g2/include/curve.h new file mode 100644 index 00000000..b7710244 --- /dev/null +++ b/wrappers/golang/curves/bls12381/g2/include/curve.h @@ -0,0 +1,26 @@ +#include +#include + +#ifndef _BLS12_381_G2CURVE_H +#define _BLS12_381_G2CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct g2_projective_t g2_projective_t; +typedef struct g2_affine_t g2_affine_t; +typedef struct DeviceContext DeviceContext; + +bool bls12_381_g2_eq(g2_projective_t* point1, g2_projective_t* point2); +void bls12_381_g2_to_affine(g2_projective_t* point, g2_affine_t* point_out); +void bls12_381_g2_generate_projective_points(g2_projective_t* points, int size); +void bls12_381_g2_generate_affine_points(g2_affine_t* points, int size); +cudaError_t bls12_381_g2_affine_convert_montgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t bls12_381_g2_projective_convert_montgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bls12381/g2/include/msm.h b/wrappers/golang/curves/bls12381/g2/include/msm.h new file mode 100644 index 00000000..b2265419 --- /dev/null +++ b/wrappers/golang/curves/bls12381/g2/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _BLS12_381_G2MSM_H +#define _BLS12_381_G2MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct g2_projective_t g2_projective_t; +typedef struct g2_affine_t g2_affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bls12_381_g2_msm_cuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); +cudaError_t bls12_381_g2_precompute_msm_bases_cuda(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bls12381/g2/include/scalar_field.h b/wrappers/golang/curves/bls12381/g2/include/scalar_field.h new file mode 100644 index 00000000..842a6e10 --- /dev/null +++ b/wrappers/golang/curves/bls12381/g2/include/scalar_field.h @@ -0,0 +1,21 @@ +#include +#include + +#ifndef _BLS12_381_FIELD_H +#define _BLS12_381_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void bls12_381_generate_scalars(scalar_t* scalars, int size); +cudaError_t bls12_381_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bls12381/g2/msm.go b/wrappers/golang/curves/bls12381/g2/msm.go new file mode 100644 index 00000000..2f459f82 --- /dev/null +++ b/wrappers/golang/curves/bls12381/g2/msm.go @@ -0,0 +1,45 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func G2GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.g2_affine_t)(pointsPointer) + cResults := (*C.g2_projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.bls12_381_g2_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.g2_affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.g2_affine_t)(outputBasesPointer) + + __ret := C.bls12_381_g2_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/bls12381/g2_msm.go b/wrappers/golang/curves/bls12381/g2_msm.go deleted file mode 100644 index 8dadacbf..00000000 --- a/wrappers/golang/curves/bls12381/g2_msm.go +++ /dev/null @@ -1,82 +0,0 @@ -//go:build g2 - -package bls12381 - -// #cgo CFLAGS: -I./include/ -// #include "g2_msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func G2GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[G2Affine])[0]) - } - cPoints := (*C.g2_affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[G2Projective])[0]) - } - cResults := (*C.g2_projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.bls12_381G2MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[G2Affine])[0]) - } - cPoints := (*C.g2_affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.g2_affine_t)(outputBasesPointer) - - __ret := C.bls12_381G2PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/curves/bls12381/helpers_test.go b/wrappers/golang/curves/bls12381/helpers_test.go deleted file mode 100644 index 924a92ba..00000000 --- a/wrappers/golang/curves/bls12381/helpers_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package bls12381 - -import ( - "math/rand" -) - -func generateRandomLimb(size int) []uint32 { - limbs := make([]uint32, size) - for i := range limbs { - limbs[i] = rand.Uint32() - } - return limbs -} - -func generateLimbOne(size int) []uint32 { - limbs := make([]uint32, size) - limbs[0] = 1 - return limbs -} - -func generateBytesArray(size int) ([]byte, []uint32) { - baseBytes := []byte{1, 2, 3, 4} - var bytes []byte - var limbs []uint32 - for i := 0; i < size; i++ { - bytes = append(bytes, baseBytes...) - limbs = append(limbs, 67305985) - } - - return bytes, limbs -} diff --git a/wrappers/golang/curves/bls12381/include/curve.h b/wrappers/golang/curves/bls12381/include/curve.h index d20f48f9..1cb3bd61 100644 --- a/wrappers/golang/curves/bls12381/include/curve.h +++ b/wrappers/golang/curves/bls12381/include/curve.h @@ -1,5 +1,4 @@ #include -#include "../../include/types.h" #include #ifndef _BLS12_381_CURVE_H @@ -9,12 +8,16 @@ extern "C" { #endif -bool bls12_381Eq(projective_t* point1, projective_t* point2); -void bls12_381ToAffine(projective_t* point, affine_t* point_out); -void bls12_381GenerateProjectivePoints(projective_t* points, int size); -void bls12_381GenerateAffinePoints(affine_t* points, int size); -cudaError_t bls12_381AffineConvertMontgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t bls12_381ProjectiveConvertMontgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct DeviceContext DeviceContext; + +bool bls12_381_eq(projective_t* point1, projective_t* point2); +void bls12_381_to_affine(projective_t* point, affine_t* point_out); +void bls12_381_generate_projective_points(projective_t* points, int size); +void bls12_381_generate_affine_points(affine_t* points, int size); +cudaError_t bls12_381_affine_convert_montgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t bls12_381_projective_convert_montgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); #ifdef __cplusplus } diff --git a/wrappers/golang/curves/bls12381/include/g2_curve.h b/wrappers/golang/curves/bls12381/include/g2_curve.h deleted file mode 100644 index 23ae11d4..00000000 --- a/wrappers/golang/curves/bls12381/include/g2_curve.h +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BLS12_381_G2CURVE_H -#define _BLS12_381_G2CURVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -bool bls12_381G2Eq(g2_projective_t* point1, g2_projective_t* point2); -void bls12_381G2ToAffine(g2_projective_t* point, g2_affine_t* point_out); -void bls12_381G2GenerateProjectivePoints(g2_projective_t* points, int size); -void bls12_381G2GenerateAffinePoints(g2_affine_t* points, int size); -cudaError_t bls12_381G2AffineConvertMontgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t bls12_381G2ProjectiveConvertMontgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bls12381/include/g2_msm.h b/wrappers/golang/curves/bls12381/include/g2_msm.h deleted file mode 100644 index 983136b8..00000000 --- a/wrappers/golang/curves/bls12381/include/g2_msm.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BLS12_381_G2MSM_H -#define _BLS12_381_G2MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bls12_381G2MSMCuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); -cudaError_t bls12_381G2PrecomputeMSMBases(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bls12381/include/msm.h b/wrappers/golang/curves/bls12381/include/msm.h deleted file mode 100644 index cb8093b8..00000000 --- a/wrappers/golang/curves/bls12381/include/msm.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BLS12_381_MSM_H -#define _BLS12_381_MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bls12_381MSMCuda(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); -cudaError_t bls12_381PrecomputeMSMBases(affine_t* points, int bases_size, int precompute_factor, int _c, bool are_bases_on_device, DeviceContext* ctx, affine_t* output_bases); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bls12381/include/ntt.h b/wrappers/golang/curves/bls12381/include/ntt.h deleted file mode 100644 index 93d28297..00000000 --- a/wrappers/golang/curves/bls12381/include/ntt.h +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BLS12_381_NTT_H -#define _BLS12_381_NTT_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bls12_381NTTCuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); -cudaError_t bls12_381ECNTTCuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); -cudaError_t bls12_381InitializeDomain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); -cudaError_t bls12_381ReleaseDomain(DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bls12381/include/scalar_field.h b/wrappers/golang/curves/bls12381/include/scalar_field.h index 8abeef80..842a6e10 100644 --- a/wrappers/golang/curves/bls12381/include/scalar_field.h +++ b/wrappers/golang/curves/bls12381/include/scalar_field.h @@ -1,5 +1,4 @@ #include -#include "../../include/types.h" #include #ifndef _BLS12_381_FIELD_H @@ -9,8 +8,11 @@ extern "C" { #endif -void bls12_381GenerateScalars(scalar_t* scalars, int size); -cudaError_t bls12_381ScalarConvertMontgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void bls12_381_generate_scalars(scalar_t* scalars, int size); +cudaError_t bls12_381_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); #ifdef __cplusplus } diff --git a/wrappers/golang/curves/bls12381/main.go b/wrappers/golang/curves/bls12381/main.go new file mode 100644 index 00000000..3f3a2105 --- /dev/null +++ b/wrappers/golang/curves/bls12381/main.go @@ -0,0 +1,4 @@ +package bls12381 + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build/lib -lingo_curve_bls12_381 -lingo_field_bls12_381 -lstdc++ -lm +import "C" diff --git a/wrappers/golang/curves/bls12381/msm.go b/wrappers/golang/curves/bls12381/msm.go deleted file mode 100644 index 48c8ca01..00000000 --- a/wrappers/golang/curves/bls12381/msm.go +++ /dev/null @@ -1,80 +0,0 @@ -package bls12381 - -// #cgo CFLAGS: -I./include/ -// #include "msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Affine])[0]) - } - cPoints := (*C.affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.bls12_381MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Affine])[0]) - } - cPoints := (*C.affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.affine_t)(outputBasesPointer) - - __ret := C.bls12_381PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/curves/bls12381/msm/include/msm.h b/wrappers/golang/curves/bls12381/msm/include/msm.h new file mode 100644 index 00000000..0d93b861 --- /dev/null +++ b/wrappers/golang/curves/bls12381/msm/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _BLS12_381_MSM_H +#define _BLS12_381_MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bls12_381_msm_cuda(const scalar_t* scalars,const affine_t* points, int count, MSMConfig* config, projective_t* out); +cudaError_t bls12_381_precompute_msm_bases_cuda(affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bls12381/msm/msm.go b/wrappers/golang/curves/bls12381/msm/msm.go new file mode 100644 index 00000000..c205e795 --- /dev/null +++ b/wrappers/golang/curves/bls12381/msm/msm.go @@ -0,0 +1,45 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.affine_t)(pointsPointer) + cResults := (*C.projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.bls12_381_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.bls12_381_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/bls12381/ntt.go b/wrappers/golang/curves/bls12381/ntt.go deleted file mode 100644 index 4a67561c..00000000 --- a/wrappers/golang/curves/bls12381/ntt.go +++ /dev/null @@ -1,97 +0,0 @@ -package bls12381 - -// #cgo CFLAGS: -I./include/ -// #include "ntt.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func GetDefaultNttConfig() core.NTTConfig[[SCALAR_LIMBS]uint32] { - cosetGenField := ScalarField{} - cosetGenField.One() - var cosetGen [SCALAR_LIMBS]uint32 - for i, v := range cosetGenField.GetLimbs() { - cosetGen[i] = v - } - - return core.GetDefaultNTTConfig(cosetGen) -} - -func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](scalars, cfg, results) - - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - cSize := (C.int)(scalars.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[ScalarField])[0]) - } - cResults := (*C.scalar_t)(resultsPointer) - - __ret := C.bls12_381NTTCuda(cScalars, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](points, cfg, results) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Projective])[0]) - } - cPoints := (*C.projective_t)(pointsPointer) - cSize := (C.int)(points.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsPointer = results.(core.DeviceSlice).AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - __ret := C.bls12_381ECNTTCuda(cPoints, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func InitDomain(primitiveRoot ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { - cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - cFastTwiddles := (C._Bool)(fastTwiddles) - __ret := C.bls12_381InitializeDomain(cPrimitiveRoot, cCtx, cFastTwiddles) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - __ret := C.bls12_381ReleaseDomain(cCtx) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} diff --git a/wrappers/golang/curves/bls12381/ntt/include/ntt.h b/wrappers/golang/curves/bls12381/ntt/include/ntt.h new file mode 100644 index 00000000..34133442 --- /dev/null +++ b/wrappers/golang/curves/bls12381/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#include +#include + +#ifndef _BLS12_381_NTT_H +#define _BLS12_381_NTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct NTTConfig NTTConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bls12_381_ntt_cuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +cudaError_t bls12_381_initialize_domain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); +cudaError_t bls12_381_release_domain(DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bls12381/ntt/ntt.go b/wrappers/golang/curves/bls12381/ntt/ntt.go new file mode 100644 index 00000000..201737dc --- /dev/null +++ b/wrappers/golang/curves/bls12381/ntt/ntt.go @@ -0,0 +1,56 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" +) + +import ( + "unsafe" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + scalarsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](scalars, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.scalar_t)(resultsPointer) + + __ret := C.bls12_381_ntt_cuda(cScalars, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func GetDefaultNttConfig() core.NTTConfig[[bls12_381.SCALAR_LIMBS]uint32] { + cosetGenField := bls12_381.ScalarField{} + cosetGenField.One() + var cosetGen [bls12_381.SCALAR_LIMBS]uint32 + for i, v := range cosetGenField.GetLimbs() { + cosetGen[i] = v + } + + return core.GetDefaultNTTConfig(cosetGen) +} + +func InitDomain(primitiveRoot bls12_381.ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cFastTwiddles := (C._Bool)(fastTwiddles) + __ret := C.bls12_381_initialize_domain(cPrimitiveRoot, cCtx, cFastTwiddles) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + __ret := C.bls12_381_release_domain(cCtx) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bls12381/polynomial/include/polynomial.h b/wrappers/golang/curves/bls12381/polynomial/include/polynomial.h new file mode 100644 index 00000000..2a36c90a --- /dev/null +++ b/wrappers/golang/curves/bls12381/polynomial/include/polynomial.h @@ -0,0 +1,51 @@ +#include +#include + +#ifndef _BLS12_381_POLY_H +#define _BLS12_381_POLY_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct PolynomialInst PolynomialInst; +typedef struct IntegrityPointer IntegrityPointer; + +bool bls12_381_polynomial_init_cuda_backend(); +PolynomialInst* bls12_381_polynomial_create_from_coefficients(scalar_t* coeffs, size_t size); +PolynomialInst* bls12_381_polynomial_create_from_rou_evaluations(scalar_t* evals, size_t size); +PolynomialInst* bls12_381_polynomial_clone(const PolynomialInst* p); +void bls12_381_polynomial_print(PolynomialInst* p); +void bls12_381_polynomial_delete(PolynomialInst* instance); +PolynomialInst* bls12_381_polynomial_add(const PolynomialInst* a, const PolynomialInst* b); +void bls12_381_polynomial_add_inplace(PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_381_polynomial_subtract(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_381_polynomial_multiply(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_381_polynomial_multiply_by_scalar(const PolynomialInst* a, const scalar_t* scalar); +void bls12_381_polynomial_division(const PolynomialInst* a, const PolynomialInst* b, PolynomialInst** q /*OUT*/, PolynomialInst** r /*OUT*/); +PolynomialInst* bls12_381_polynomial_quotient(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_381_polynomial_remainder(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bls12_381_polynomial_divide_by_vanishing(const PolynomialInst* p, size_t vanishing_poly_degree); +void bls12_381_polynomial_add_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void bls12_381_polynomial_sub_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void bls12_381_polynomial_evaluate_on_domain(const PolynomialInst* p, scalar_t* domain, size_t domain_size, scalar_t* evals /*OUT*/); +size_t bls12_381_polynomial_degree(PolynomialInst* p); +size_t bls12_381_polynomial_copy_coeffs_range(PolynomialInst* p, scalar_t* memory, size_t start_idx, size_t end_idx); +PolynomialInst* bls12_381_polynomial_even(PolynomialInst* p); +PolynomialInst* bls12_381_polynomial_odd(PolynomialInst* p); + +// scalar_t* bls12_381_polynomial_get_coeffs_raw_ptr(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// PolynomialInst* bls12_381_polynomial_slice(PolynomialInst* p, size_t offset, size_t stride, size_t size); +// IntegrityPointer* bls12_381_polynomial_get_coeff_view(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// IntegrityPointer* bls12_381_polynomial_get_rou_evaluations_view(PolynomialInst* p, size_t nof_evals, bool is_reversed, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// const scalar_t* bls12_381_polynomial_intergrity_ptr_get(IntegrityPointer* p); +// bool bls12_381_polynomial_intergrity_ptr_is_valid(IntegrityPointer* p); +// void bls12_381_polynomial_intergrity_ptr_destroy(IntegrityPointer* p); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/wrappers/golang/curves/bls12381/polynomial/polynomial.go b/wrappers/golang/curves/bls12381/polynomial/polynomial.go new file mode 100644 index 00000000..3add5d0b --- /dev/null +++ b/wrappers/golang/curves/bls12381/polynomial/polynomial.go @@ -0,0 +1,176 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +func InitPolyBackend() bool { + return (bool)(C.bls12_381_polynomial_init_cuda_backend()) +} + +func (up *DensePolynomial) Print() { + C.bls12_381_polynomial_print(up.handle) +} + +func (up *DensePolynomial) CreateFromCoeffecitients(coeffs core.HostOrDeviceSlice) DensePolynomial { + if coeffs.IsOnDevice() { + coeffs.(core.DeviceSlice).CheckDevice() + } + coeffsPointer := (*C.scalar_t)(coeffs.AsUnsafePointer()) + cSize := (C.size_t)(coeffs.Len()) + up.handle = C.bls12_381_polynomial_create_from_coefficients(coeffsPointer, cSize) + return *up +} + +func (up *DensePolynomial) CreateFromROUEvaluations(evals core.HostOrDeviceSlice) DensePolynomial { + evalsPointer := (*C.scalar_t)(evals.AsUnsafePointer()) + cSize := (C.size_t)(evals.Len()) + up.handle = C.bls12_381_polynomial_create_from_coefficients(evalsPointer, cSize) + return *up +} + +func (up *DensePolynomial) Clone() DensePolynomial { + return DensePolynomial{ + handle: C.bls12_381_polynomial_clone(up.handle), + } +} + +// TODO @jeremyfelder: Maybe this should be in a SetFinalizer that is set on Create functions? +func (up *DensePolynomial) Delete() { + C.bls12_381_polynomial_delete(up.handle) +} + +func (up *DensePolynomial) Add(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_381_polynomial_add(up.handle, b.handle), + } +} + +func (up *DensePolynomial) AddInplace(b *DensePolynomial) { + C.bls12_381_polynomial_add_inplace(up.handle, b.handle) +} + +func (up *DensePolynomial) Subtract(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_381_polynomial_subtract(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Multiply(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_381_polynomial_multiply(up.handle, b.handle), + } +} + +func (up *DensePolynomial) MultiplyByScalar(scalar bls12_381.ScalarField) DensePolynomial { + cScalar := (*C.scalar_t)(unsafe.Pointer(scalar.AsPointer())) + return DensePolynomial{ + handle: C.bls12_381_polynomial_multiply_by_scalar(up.handle, cScalar), + } +} + +func (up *DensePolynomial) Divide(b *DensePolynomial) (DensePolynomial, DensePolynomial) { + var q, r *PolynomialHandle + C.bls12_381_polynomial_division(up.handle, b.handle, &q, &r) + return DensePolynomial{ + handle: q, + }, DensePolynomial{ + handle: r, + } +} + +func (up *DensePolynomial) Quotient(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_381_polynomial_quotient(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Remainder(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bls12_381_polynomial_remainder(up.handle, b.handle), + } +} + +func (up *DensePolynomial) DivideByVanishing(vanishing_degree uint64) DensePolynomial { + cVanishingDegree := (C.ulong)(vanishing_degree) + return DensePolynomial{ + handle: C.bls12_381_polynomial_divide_by_vanishing(up.handle, cVanishingDegree), + } +} + +func (up *DensePolynomial) AddMonomial(monomialCoeff bls12_381.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]bls12_381.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.bls12_381_polynomial_add_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) SubMonomial(monomialCoeff bls12_381.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]bls12_381.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.bls12_381_polynomial_sub_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) Eval(x bls12_381.ScalarField) bls12_381.ScalarField { + domains := make(core.HostSlice[bls12_381.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[bls12_381.ScalarField], 1) + up.EvalOnDomain(domains, evals) + return evals[0] +} + +func (up *DensePolynomial) EvalOnDomain(domain, evals core.HostOrDeviceSlice) core.HostOrDeviceSlice { + cDomain := (*C.scalar_t)(domain.AsUnsafePointer()) + cDomainSize := (C.size_t)(domain.Len()) + cEvals := (*C.scalar_t)(evals.AsUnsafePointer()) + C.bls12_381_polynomial_evaluate_on_domain(up.handle, cDomain, cDomainSize, cEvals) + return evals +} + +func (up *DensePolynomial) Degree() int { + return int(C.bls12_381_polynomial_degree(up.handle)) +} + +func (up *DensePolynomial) CopyCoeffsRange(start, end int, out core.HostOrDeviceSlice) (int, core.HostOrDeviceSlice) { + cStart := (C.size_t)(start) + cEnd := (C.size_t)(end) + cScalarOut := (*C.scalar_t)(out.AsUnsafePointer()) + __cNumCoeffsRead := C.bls12_381_polynomial_copy_coeffs_range(up.handle, cScalarOut, cStart, cEnd) + return int(__cNumCoeffsRead), out +} + +func (up *DensePolynomial) GetCoeff(idx int) bls12_381.ScalarField { + out := make(core.HostSlice[bls12_381.ScalarField], 1) + up.CopyCoeffsRange(idx, idx, out) + return out[0] +} + +func (up *DensePolynomial) Even() DensePolynomial { + evenPoly := C.bls12_381_polynomial_even(up.handle) + return DensePolynomial{ + handle: evenPoly, + } +} + +func (up *DensePolynomial) Odd() DensePolynomial { + oddPoly := C.bls12_381_polynomial_odd(up.handle) + return DensePolynomial{ + handle: oddPoly, + } +} diff --git a/wrappers/golang/curves/bls12381/scalar_field.go b/wrappers/golang/curves/bls12381/scalar_field.go index fc395a8c..6f6bec63 100644 --- a/wrappers/golang/curves/bls12381/scalar_field.go +++ b/wrappers/golang/curves/bls12381/scalar_field.go @@ -12,7 +12,7 @@ import ( ) const ( - SCALAR_LIMBS int8 = 8 + SCALAR_LIMBS int = 8 ) type ScalarField struct { @@ -35,6 +35,11 @@ func (f ScalarField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *ScalarField) FromUint32(v uint32) ScalarField { + f.limbs[0] = v + return *f +} + func (f *ScalarField) FromLimbs(limbs []uint32) ScalarField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") @@ -89,18 +94,18 @@ func GenerateScalars(size int) core.HostSlice[ScalarField] { cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) cSize := (C.int)(size) - C.bls12_381GenerateScalars(cScalars, cSize) + C.bls12_381_generate_scalars(cScalars, cSize) return scalarSlice } func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.scalar_t)(scalars.AsPointer()) + cValues := (*C.scalar_t)(scalars.AsUnsafePointer()) cSize := (C.size_t)(scalars.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_381ScalarConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bls12_381_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bls12381/tests/base_field_test.go b/wrappers/golang/curves/bls12381/tests/base_field_test.go new file mode 100644 index 00000000..9e263e29 --- /dev/null +++ b/wrappers/golang/curves/bls12381/tests/base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + BASE_LIMBS = bls12_381.BASE_LIMBS +) + +func TestBaseFieldFromLimbs(t *testing.T) { + emptyField := bls12_381.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestBaseFieldGetLimbs(t *testing.T) { + emptyField := bls12_381.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") +} + +func TestBaseFieldOne(t *testing.T) { + var emptyField bls12_381.BaseField + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int(BASE_LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "BaseField with limbs to field one did not work") +} + +func TestBaseFieldZero(t *testing.T) { + var emptyField bls12_381.BaseField + emptyField.Zero() + limbsZero := make([]uint32, BASE_LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "BaseField with limbs to field zero failed") +} + +func TestBaseFieldSize(t *testing.T) { + var emptyField bls12_381.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") +} + +func TestBaseFieldAsPointer(t *testing.T) { + var emptyField bls12_381.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") +} + +func TestBaseFieldFromBytes(t *testing.T) { + var emptyField bls12_381.BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func TestBaseFieldToBytes(t *testing.T) { + var emptyField bls12_381.BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) + emptyField.FromLimbs(limbs) + + assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") +} diff --git a/wrappers/golang/curves/bls12381/tests/curve_test.go b/wrappers/golang/curves/bls12381/tests/curve_test.go new file mode 100644 index 00000000..be35807d --- /dev/null +++ b/wrappers/golang/curves/bls12381/tests/curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAffineZero(t *testing.T) { + var fieldZero = bls12_381.BaseField{} + + var affineZero bls12_381.Affine + assert.Equal(t, affineZero.X, fieldZero) + assert.Equal(t, affineZero.Y, fieldZero) + + x := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var affine bls12_381.Affine + affine.FromLimbs(x, y) + + affine.Zero() + assert.Equal(t, affine.X, fieldZero) + assert.Equal(t, affine.Y, fieldZero) +} + +func TestAffineFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + + var affine bls12_381.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) + assert.ElementsMatch(t, randLimbs2, affine.Y.GetLimbs()) +} + +func TestAffineToProjective(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne bls12_381.BaseField + fieldOne.One() + + var expected bls12_381.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine bls12_381.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestProjectiveZero(t *testing.T) { + var projectiveZero bls12_381.Projective + projectiveZero.Zero() + var fieldZero = bls12_381.BaseField{} + var fieldOne bls12_381.BaseField + fieldOne.One() + + assert.Equal(t, projectiveZero.X, fieldZero) + assert.Equal(t, projectiveZero.Y, fieldOne) + assert.Equal(t, projectiveZero.Z, fieldZero) + + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective bls12_381.Projective + projective.FromLimbs(randLimbs, randLimbs, randLimbs) + + projective.Zero() + assert.Equal(t, projective.X, fieldZero) + assert.Equal(t, projective.Y, fieldOne) + assert.Equal(t, projective.Z, fieldZero) +} + +func TestProjectiveFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + + var projective bls12_381.Projective + projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) + + assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) + assert.ElementsMatch(t, randLimbs2, projective.Y.GetLimbs()) + assert.ElementsMatch(t, randLimbs3, projective.Z.GetLimbs()) +} + +func TestProjectiveFromAffine(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne bls12_381.BaseField + fieldOne.One() + + var expected bls12_381.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine bls12_381.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint bls12_381.Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang/curves/bls12381/tests/ecntt_test.go b/wrappers/golang/curves/bls12381/tests/ecntt_test.go new file mode 100644 index 00000000..6180b5bd --- /dev/null +++ b/wrappers/golang/curves/bls12381/tests/ecntt_test.go @@ -0,0 +1,30 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + ecntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/ecntt" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/ntt" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + points := bls12_381.GenerateProjectivePoints(1 << largestTestSize) + + for _, size := range []int{4, 5, 6, 7, 8} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[bls12_381.Projective](points[:testSize]) + cfg.Ordering = v + cfg.NttAlgorithm = core.Radix2 + + output := make(core.HostSlice[bls12_381.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + } + } +} diff --git a/wrappers/golang/curves/bls12377/g2_curve_test.go b/wrappers/golang/curves/bls12381/tests/g2_curve_test.go similarity index 51% rename from wrappers/golang/curves/bls12377/g2_curve_test.go rename to wrappers/golang/curves/bls12381/tests/g2_curve_test.go index 2c97484e..a3d5586a 100644 --- a/wrappers/golang/curves/bls12377/g2_curve_test.go +++ b/wrappers/golang/curves/bls12381/tests/g2_curve_test.go @@ -1,22 +1,22 @@ -//go:build g2 - -package bls12377 +package tests import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/g2" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) func TestG2AffineZero(t *testing.T) { - var fieldZero = G2BaseField{} + var fieldZero = g2.G2BaseField{} - var affineZero G2Affine + var affineZero g2.G2Affine assert.Equal(t, affineZero.X, fieldZero) assert.Equal(t, affineZero.Y, fieldZero) - x := generateRandomLimb(int(G2_BASE_LIMBS)) - y := generateRandomLimb(int(G2_BASE_LIMBS)) - var affine G2Affine + x := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var affine g2.G2Affine affine.FromLimbs(x, y) affine.Zero() @@ -25,10 +25,10 @@ func TestG2AffineZero(t *testing.T) { } func TestG2AffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) @@ -36,15 +36,15 @@ func TestG2AffineFromLimbs(t *testing.T) { } func TestG2AffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - var fieldOne G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField fieldOne.One() - var expected G2Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) projectivePoint := affine.ToProjective() @@ -52,18 +52,18 @@ func TestG2AffineToProjective(t *testing.T) { } func TestG2ProjectiveZero(t *testing.T) { - var projectiveZero G2Projective + var projectiveZero g2.G2Projective projectiveZero.Zero() - var fieldZero = G2BaseField{} - var fieldOne G2BaseField + var fieldZero = g2.G2BaseField{} + var fieldOne g2.G2BaseField fieldOne.One() assert.Equal(t, projectiveZero.X, fieldZero) assert.Equal(t, projectiveZero.Y, fieldOne) assert.Equal(t, projectiveZero.Z, fieldZero) - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - var projective G2Projective + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var projective g2.G2Projective projective.FromLimbs(randLimbs, randLimbs, randLimbs) projective.Zero() @@ -73,11 +73,11 @@ func TestG2ProjectiveZero(t *testing.T) { } func TestG2ProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) - var projective G2Projective + var projective g2.G2Projective projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) @@ -86,18 +86,18 @@ func TestG2ProjectiveFromLimbs(t *testing.T) { } func TestG2ProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - var fieldOne G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField fieldOne.One() - var expected G2Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) - var projectivePoint G2Projective + var projectivePoint g2.G2Projective projectivePoint.FromAffine(affine) assert.Equal(t, expected, projectivePoint) } diff --git a/wrappers/golang/curves/bw6761/g2_base_field_test.go b/wrappers/golang/curves/bls12381/tests/g2_g2base_field_test.go similarity index 57% rename from wrappers/golang/curves/bw6761/g2_base_field_test.go rename to wrappers/golang/curves/bls12381/tests/g2_g2base_field_test.go index 9682866c..78267c7a 100644 --- a/wrappers/golang/curves/bw6761/g2_base_field_test.go +++ b/wrappers/golang/curves/bls12381/tests/g2_g2base_field_test.go @@ -1,36 +1,40 @@ -//go:build g2 - -package bw6761 +package tests import ( + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/g2" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + G2BASE_LIMBS = bls12_381.G2BASE_LIMBS +) + func TestG2BaseFieldFromLimbs(t *testing.T) { - emptyField := G2BaseField{} - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + emptyField := bls12_381.G2BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the G2BaseField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the G2BaseField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestG2BaseFieldGetLimbs(t *testing.T) { - emptyField := G2BaseField{} - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + emptyField := bls12_381.G2BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the G2BaseField's limbs") } func TestG2BaseFieldOne(t *testing.T) { - var emptyField G2BaseField + var emptyField bls12_381.G2BaseField emptyField.One() - limbOne := generateLimbOne(int(G2_BASE_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(G2BASE_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -38,12 +42,12 @@ func TestG2BaseFieldOne(t *testing.T) { } func TestG2BaseFieldZero(t *testing.T) { - var emptyField G2BaseField + var emptyField bls12_381.G2BaseField emptyField.Zero() - limbsZero := make([]uint32, G2_BASE_LIMBS) + limbsZero := make([]uint32, G2BASE_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -51,24 +55,24 @@ func TestG2BaseFieldZero(t *testing.T) { } func TestG2BaseFieldSize(t *testing.T) { - var emptyField G2BaseField - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + var emptyField bls12_381.G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestG2BaseFieldAsPointer(t *testing.T) { - var emptyField G2BaseField - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + var emptyField bls12_381.G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestG2BaseFieldFromBytes(t *testing.T) { - var emptyField G2BaseField - bytes, expected := generateBytesArray(int(G2_BASE_LIMBS)) + var emptyField bls12_381.G2BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -76,8 +80,8 @@ func TestG2BaseFieldFromBytes(t *testing.T) { } func TestG2BaseFieldToBytes(t *testing.T) { - var emptyField G2BaseField - expected, limbs := generateBytesArray(int(G2_BASE_LIMBS)) + var emptyField bls12_381.G2BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") diff --git a/wrappers/golang/curves/bls12381/g2_msm_test.go b/wrappers/golang/curves/bls12381/tests/g2_msm_test.go similarity index 60% rename from wrappers/golang/curves/bls12381/g2_msm_test.go rename to wrappers/golang/curves/bls12381/tests/g2_msm_test.go index fed99647..f8001675 100644 --- a/wrappers/golang/curves/bls12381/g2_msm_test.go +++ b/wrappers/golang/curves/bls12381/tests/g2_msm_test.go @@ -1,6 +1,4 @@ -//go:build g2 - -package bls12381 +package tests import ( "fmt" @@ -9,16 +7,18 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleBls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/g2" ) -func projectiveToGnarkAffineG2(p G2Projective) bls12381.G2Affine { +func projectiveToGnarkAffineG2(p g2.G2Projective) bls12381.G2Affine { pxBytes := p.X.ToBytesLittleEndian() pxA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[:fp.Bytes])) pxA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[fp.Bytes:])) @@ -62,7 +62,7 @@ func projectiveToGnarkAffineG2(p G2Projective) bls12381.G2Affine { return *g2Affine.FromJacobian(&g2Jac) } -func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points core.HostSlice[G2Affine], out G2Projective) bool { +func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[icicleBls12_381.ScalarField], points core.HostSlice[g2.G2Affine], out g2.G2Projective) bool { scalarsFr := make([]fr.Element, len(scalars)) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -73,6 +73,11 @@ func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points cor for i, v := range points { pointsFp[i] = projectiveToGnarkAffineG2(v.ToProjective()) } + + return testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bls12381.G2Affine], out g2.G2Projective) bool { var msmRes bls12381.G2Jac msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) @@ -83,54 +88,117 @@ func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points cor return msmRes.Equal(&icicleResAsJac) } +func convertIcicleG2AffineToG2Affine(iciclePoints []g2.G2Affine) []bls12381.G2Affine { + points := make([]bls12381.G2Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes * 2]byte)(iciclePoint.X.ToBytesLittleEndian()) + xA0Bytes := ([fp.Bytes]byte)(xBytes[:fp.Bytes]) + xA1Bytes := ([fp.Bytes]byte)(xBytes[fp.Bytes:]) + xA0Elem, _ := fp.LittleEndian.Element(&xA0Bytes) + xA1Elem, _ := fp.LittleEndian.Element(&xA1Bytes) + + yBytes := ([fp.Bytes * 2]byte)(iciclePoint.Y.ToBytesLittleEndian()) + yA0Bytes := ([fp.Bytes]byte)(yBytes[:fp.Bytes]) + yA1Bytes := ([fp.Bytes]byte)(yBytes[fp.Bytes:]) + yA0Elem, _ := fp.LittleEndian.Element(&yA0Bytes) + yA1Elem, _ := fp.LittleEndian.Element(&yA1Bytes) + + points[index] = bls12381.G2Affine{ + X: bls12381.E2{ + A0: xA0Elem, + A1: xA1Elem, + }, + Y: bls12381.E2{ + A0: yA0Elem, + A1: yA1Elem, + }, + } + } + + return points +} + func TestMSMG2(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := G2GenerateAffinePoints(size) + scalars := icicleBls12_381.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) cr.SynchronizeStream(&stream) // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) + + } +} +func TestMSMG2GnarkCryptoTypes(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := g2.G2GenerateAffinePoints(size) + pointsGnark := convertIcicleG2AffineToG2Affine(points) + pointsHost := (core.HostSlice[bls12381.G2Affine])(pointsGnark) + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = g2.G2Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) } } func TestMSMG2Batch(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := G2GenerateAffinePoints(totalSize) + scalars := icicleBls12_381.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], batchSize) + outHost := make(core.HostSlice[g2.G2Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -143,36 +211,35 @@ func TestMSMG2Batch(t *testing.T) { } func TestPrecomputeBaseG2(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() const precomputeFactor = 8 for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := G2GenerateAffinePoints(totalSize) + scalars := icicleBls12_381.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) var precomputeOut core.DeviceSlice _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - e = G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + e = g2.G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e = out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.PrecomputeFactor = precomputeFactor - e = G2Msm(scalars, precomputeOut, &cfg, out) + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], batchSize) + outHost := make(core.HostSlice[g2.G2Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() precomputeOut.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -185,30 +252,29 @@ func TestPrecomputeBaseG2(t *testing.T) { } func TestMSMG2SkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) + scalars := icicleBls12_381.GenerateScalars(size) for i := size / 4; i < size; i++ { scalars[i].One() } - points := G2GenerateAffinePoints(size) + points := g2.G2GenerateAffinePoints(size) for i := 0; i < size/4; i++ { points[i].Zero() } - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.Malloc(p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) } @@ -225,23 +291,23 @@ func TestMSMG2MultiDevice(t *testing.T) { wg.Add(1) cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := G2GenerateAffinePoints(size) + scalars := icicleBls12_381.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) diff --git a/wrappers/golang/curves/bls12381/tests/main_test.go b/wrappers/golang/curves/bls12381/tests/main_test.go new file mode 100644 index 00000000..288f7374 --- /dev/null +++ b/wrappers/golang/curves/bls12381/tests/main_test.go @@ -0,0 +1,48 @@ +package tests + +import ( + "os" + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/ntt" + poly "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/polynomial" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fft" +) + +const ( + largestTestSize = 20 +) + +func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { + rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) + rou := rouMont.Bits() + rouIcicle := bls12_381.ScalarField{} + limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) + + rouIcicle.FromLimbs(limbs) + e := ntt.InitDomain(rouIcicle, cfg.Ctx, false) + return e +} + +func TestMain(m *testing.M) { + poly.InitPolyBackend() + + // setup domain + cfg := ntt.GetDefaultNttConfig() + e := initDomain(largestTestSize, cfg) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("initDomain failed") + } + + // execute tests + os.Exit(m.Run()) + + // release domain + e = ntt.ReleaseDomain(cfg.Ctx) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("ReleaseDomain failed") + } +} diff --git a/wrappers/golang/curves/bls12381/msm_test.go b/wrappers/golang/curves/bls12381/tests/msm_test.go similarity index 58% rename from wrappers/golang/curves/bls12381/msm_test.go rename to wrappers/golang/curves/bls12381/tests/msm_test.go index f53e6d78..4f615343 100644 --- a/wrappers/golang/curves/bls12381/msm_test.go +++ b/wrappers/golang/curves/bls12381/tests/msm_test.go @@ -1,4 +1,4 @@ -package bls12381 +package tests import ( "fmt" @@ -7,16 +7,18 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bls12-381" "github.com/consensys/gnark-crypto/ecc/bls12-381/fp" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleBls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/msm" ) -func projectiveToGnarkAffine(p Projective) bls12381.G1Affine { +func projectiveToGnarkAffine(p icicleBls12_381.Projective) bls12381.G1Affine { px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.X).ToBytesLittleEndian())) py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Y).ToBytesLittleEndian())) pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Z).ToBytesLittleEndian())) @@ -33,7 +35,7 @@ func projectiveToGnarkAffine(p Projective) bls12381.G1Affine { return bls12381.G1Affine{X: *x, Y: *y} } -func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core.HostSlice[Affine], out Projective) bool { +func testAgainstGnarkCryptoMsm(scalars core.HostSlice[icicleBls12_381.ScalarField], points core.HostSlice[icicleBls12_381.Affine], out icicleBls12_381.Projective) bool { scalarsFr := make([]fr.Element, len(scalars)) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -44,6 +46,11 @@ func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core. for i, v := range points { pointsFp[i] = projectiveToGnarkAffine(v.ToProjective()) } + + return testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bls12381.G1Affine], out icicleBls12_381.Projective) bool { var msmRes bls12381.G1Jac msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) @@ -54,54 +61,104 @@ func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core. return msmRes.Equal(&icicleResAsJac) } +func convertIcicleAffineToG1Affine(iciclePoints []icicleBls12_381.Affine) []bls12381.G1Affine { + points := make([]bls12381.G1Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes]byte)(iciclePoint.X.ToBytesLittleEndian()) + fpXElem, _ := fp.LittleEndian.Element(&xBytes) + + yBytes := ([fp.Bytes]byte)(iciclePoint.Y.ToBytesLittleEndian()) + fpYElem, _ := fp.LittleEndian.Element(&yBytes) + points[index] = bls12381.G1Affine{ + X: fpXElem, + Y: fpYElem, + } + } + + return points +} + func TestMSM(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + scalars := icicleBls12_381.GenerateScalars(size) + points := icicleBls12_381.GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p Projective + var p icicleBls12_381.Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) cr.SynchronizeStream(&stream) // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) + + } +} +func TestMSMGnarkCryptoTypes(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := icicleBls12_381.GenerateAffinePoints(size) + pointsGnark := convertIcicleAffineToG1Affine(points) + pointsHost := (core.HostSlice[bls12381.G1Affine])(pointsGnark) + + var p icicleBls12_381.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = msm.Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) } } func TestMSMBatch(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := GenerateAffinePoints(totalSize) + scalars := icicleBls12_381.GenerateScalars(totalSize) + points := icicleBls12_381.GenerateAffinePoints(totalSize) - var p Projective + var p icicleBls12_381.Projective var out core.DeviceSlice _, e := out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], batchSize) + outHost := make(core.HostSlice[icicleBls12_381.Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -114,36 +171,35 @@ func TestMSMBatch(t *testing.T) { } func TestPrecomputeBase(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() const precomputeFactor = 8 for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := GenerateAffinePoints(totalSize) + scalars := icicleBls12_381.GenerateScalars(totalSize) + points := icicleBls12_381.GenerateAffinePoints(totalSize) var precomputeOut core.DeviceSlice _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - e = PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + e = msm.PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - var p Projective + var p icicleBls12_381.Projective var out core.DeviceSlice _, e = out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.PrecomputeFactor = precomputeFactor - e = Msm(scalars, precomputeOut, &cfg, out) + e = msm.Msm(scalars, precomputeOut, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], batchSize) + outHost := make(core.HostSlice[icicleBls12_381.Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() precomputeOut.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -156,30 +212,29 @@ func TestPrecomputeBase(t *testing.T) { } func TestMSMSkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) + scalars := icicleBls12_381.GenerateScalars(size) for i := size / 4; i < size; i++ { scalars[i].One() } - points := GenerateAffinePoints(size) + points := icicleBls12_381.GenerateAffinePoints(size) for i := 0; i < size/4; i++ { points[i].Zero() } - var p Projective + var p icicleBls12_381.Projective var out core.DeviceSlice _, e := out.Malloc(p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) } @@ -196,23 +251,23 @@ func TestMSMMultiDevice(t *testing.T) { wg.Add(1) cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + scalars := icicleBls12_381.GenerateScalars(size) + points := icicleBls12_381.GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p Projective + var p icicleBls12_381.Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) diff --git a/wrappers/golang/curves/bls12381/ntt_test.go b/wrappers/golang/curves/bls12381/tests/ntt_test.go similarity index 71% rename from wrappers/golang/curves/bls12381/ntt_test.go rename to wrappers/golang/curves/bls12381/tests/ntt_test.go index d2d8c66e..281cf73f 100644 --- a/wrappers/golang/curves/bls12381/ntt_test.go +++ b/wrappers/golang/curves/bls12381/tests/ntt_test.go @@ -1,40 +1,20 @@ -package bls12381 +package tests import ( - "os" "reflect" "testing" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fft" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" ) -const ( - largestTestSize = 17 -) - -func init() { - cfg := GetDefaultNttConfig() - initDomain(largestTestSize, cfg) -} - -func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { - rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) - rou := rouMont.Bits() - rouIcicle := ScalarField{} - limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) - - rouIcicle.FromLimbs(limbs) - e := InitDomain(rouIcicle, cfg.Ctx, false) - return e -} - -func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], output core.HostSlice[ScalarField], order core.Ordering, direction core.NTTDir) bool { - domainWithPrecompute := fft.NewDomain(uint64(size)) +func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[bls12_381.ScalarField], output core.HostSlice[bls12_381.ScalarField], order core.Ordering, direction core.NTTDir) bool { scalarsFr := make([]fr.Element, size) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -46,6 +26,11 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou outputAsFr[i] = slice64 } + return testAgainstGnarkCryptoNttGnarkTypes(size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) bool { + domainWithPrecompute := fft.NewDomain(uint64(size)) // DIT + BitReverse == Ordering.kRR // DIT == Ordering.kRN // DIF + BitReverse == Ordering.kNN @@ -68,72 +53,77 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou } return reflect.DeepEqual(scalarsFr, outputAsFr) } - func TestNTTGetDefaultConfig(t *testing.T) { - actual := GetDefaultNttConfig() - expected := generateLimbOne(int(SCALAR_LIMBS)) + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(bls12_381.SCALAR_LIMBS)) assert.Equal(t, expected, actual.CosetGen[:]) - cosetGenField := ScalarField{} + cosetGenField := bls12_381.ScalarField{} cosetGenField.One() assert.ElementsMatch(t, cosetGenField.GetLimbs(), actual.CosetGen) } func TestInitDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) } func TestNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := bls12_381.GenerateScalars(1 << largestTestSize) for _, size := range []int{4, largestTestSize} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[bls12_381.ScalarField](scalars[:testSize]) cfg.Ordering = v // run ntt - output := make(core.HostSlice[ScalarField], testSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[bls12_381.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, core.KForward)) } } } +func TestNttFrElement(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := make([]fr.Element, 4) + var x fr.Element + for i := 0; i < 4; i++ { + x.SetRandom() + scalars[i] = x + } -func TestECNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - points := GenerateProjectivePoints(1 << largestTestSize) + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + testSize := size - for _, size := range []int{4, 5, 6, 7, 8} { - for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { - testSize := 1 << size - - pointsCopy := core.HostSliceFromElements[Projective](points[:testSize]) + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) cfg.Ordering = v - cfg.NttAlgorithm = core.Radix2 - output := make(core.HostSlice[Projective], testSize) - e := ECNtt(pointsCopy, core.KForward, &cfg, output) - assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + assert.True(t, testAgainstGnarkCryptoNttGnarkTypes(testSize, scalarsCopy, output, v, core.KForward)) } } } func TestNttDeviceAsync(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := bls12_381.GenerateScalars(1 << largestTestSize) for _, size := range []int{1, 10, largestTestSize} { for _, direction := range []core.NTTDir{core.KForward, core.KInverse} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[bls12_381.ScalarField](scalars[:testSize]) stream, _ := cr.CreateStream() @@ -147,12 +137,11 @@ func TestNttDeviceAsync(t *testing.T) { deviceOutput.MallocAsync(testSize*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) // run ntt - Ntt(deviceInput, direction, &cfg, deviceOutput) - output := make(core.HostSlice[ScalarField], testSize) + ntt.Ntt(deviceInput, direction, &cfg, deviceOutput) + output := make(core.HostSlice[bls12_381.ScalarField], testSize) output.CopyFromDeviceAsync(&deviceOutput, stream) cr.SynchronizeStream(&stream) - // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, direction)) } @@ -161,22 +150,22 @@ func TestNttDeviceAsync(t *testing.T) { } func TestNttBatch(t *testing.T) { - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() largestBatchSize := 100 - scalars := GenerateScalars(1 << largestTestSize * largestBatchSize) + scalars := bls12_381.GenerateScalars(1 << largestTestSize * largestBatchSize) for _, size := range []int{4, largestTestSize} { for _, batchSize := range []int{1, 16, largestBatchSize} { testSize := 1 << size totalSize := testSize * batchSize - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:totalSize]) + scalarsCopy := core.HostSliceFromElements[bls12_381.ScalarField](scalars[:totalSize]) cfg.Ordering = core.KNN cfg.BatchSize = int32(batchSize) // run ntt - output := make(core.HostSlice[ScalarField], totalSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[bls12_381.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) // Compare with gnark-crypto domainWithPrecompute := fft.NewDomain(uint64(testSize)) @@ -205,36 +194,18 @@ func TestNttBatch(t *testing.T) { func TestReleaseDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() - e := ReleaseDomain(cfg.Ctx) + cfg := ntt.GetDefaultNttConfig() + e := ntt.ReleaseDomain(cfg.Ctx) assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ReleasDomain failed") } -func TestMain(m *testing.M) { - // setup domain - cfg := GetDefaultNttConfig() - e := initDomain(largestTestSize, cfg) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("initDomain failed") - } - - // execute tests - os.Exit(m.Run()) - - // release domain - e = ReleaseDomain(cfg.Ctx) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("ReleaseDomain failed") - } -} - // func TestNttArbitraryCoset(t *testing.T) { // for _, size := range []int{20} { // for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { // testSize := 1 << size // scalars := GenerateScalars(testSize) -// cfg := GetDefaultNttConfig() +// cfg := ntt.GetDefaultNttConfig() // var scalarsCopy core.HostSlice[ScalarField] // for _, v := range scalars { diff --git a/wrappers/golang/curves/bls12381/tests/polynomial_test.go b/wrappers/golang/curves/bls12381/tests/polynomial_test.go new file mode 100644 index 00000000..a8ee8ce4 --- /dev/null +++ b/wrappers/golang/curves/bls12381/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + // "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/polynomial" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/vecOps" + "github.com/stretchr/testify/assert" +) + +var one, two, three, four, five bls12_381.ScalarField + +func init() { + one.One() + two.FromUint32(2) + three.FromUint32(3) + four.FromUint32(4) + five.FromUint32(5) +} + +func rand() bls12_381.ScalarField { + return bls12_381.GenerateScalars(1)[0] +} + +func randomPoly(size int) (f polynomial.DensePolynomial) { + f.CreateFromCoeffecitients(core.HostSliceFromElements(bls12_381.GenerateScalars(size))) + return f +} + +func vecOp(a, b bls12_381.ScalarField, op core.VecOps) bls12_381.ScalarField { + ahost := core.HostSliceWithValue(a, 1) + bhost := core.HostSliceWithValue(b, 1) + out := make(core.HostSlice[bls12_381.ScalarField], 1) + + cfg := core.DefaultVecOpsConfig() + vecOps.VecOp(ahost, bhost, out, cfg, op) + return out[0] +} + +func TestPolyCreateFromCoefficients(t *testing.T) { + scalars := bls12_381.GenerateScalars(33) + var uniPoly polynomial.DensePolynomial + + poly := uniPoly.CreateFromCoeffecitients(scalars) + poly.Print() +} + +func TestPolyEval(t *testing.T) { + // testing correct evaluation of f(8) for f(x)=4x^2+2x+5 + coeffs := core.HostSliceFromElements([]bls12_381.ScalarField{five, two, four}) + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(coeffs) + + var x bls12_381.ScalarField + x.FromUint32(8) + domains := make(core.HostSlice[bls12_381.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[bls12_381.ScalarField], 1) + fEvaled := f.EvalOnDomain(domains, evals) + var expected bls12_381.ScalarField + assert.Equal(t, expected.FromUint32(277), fEvaled.(core.HostSlice[bls12_381.ScalarField])[0]) +} + +func TestPolyClone(t *testing.T) { + f := randomPoly(8) + x := rand() + fx := f.Eval(x) + + g := f.Clone() + fg := f.Add(&g) + + gx := g.Eval(x) + fgx := fg.Eval(x) + + assert.Equal(t, fx, gx) + assert.Equal(t, vecOp(fx, gx, core.Add), fgx) +} + +func TestPolyAddSubMul(t *testing.T) { + testSize := 1 << 10 + f := randomPoly(testSize) + g := randomPoly(testSize) + x := rand() + + fx := f.Eval(x) + gx := g.Eval(x) + + polyAdd := f.Add(&g) + fxAddgx := vecOp(fx, gx, core.Add) + assert.Equal(t, polyAdd.Eval(x), fxAddgx) + + polySub := f.Subtract(&g) + fxSubgx := vecOp(fx, gx, core.Sub) + assert.Equal(t, polySub.Eval(x), fxSubgx) + + polyMul := f.Multiply(&g) + fxMulgx := vecOp(fx, gx, core.Mul) + assert.Equal(t, polyMul.Eval(x), fxMulgx) + + s1 := rand() + polMulS1 := f.MultiplyByScalar(s1) + assert.Equal(t, polMulS1.Eval(x), vecOp(fx, s1, core.Mul)) + + s2 := rand() + polMulS2 := f.MultiplyByScalar(s2) + assert.Equal(t, polMulS2.Eval(x), vecOp(fx, s2, core.Mul)) +} + +func TestPolyMonomials(t *testing.T) { + var zero bls12_381.ScalarField + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements([]bls12_381.ScalarField{one, zero, two})) + x := rand() + + fx := f.Eval(x) + f.AddMonomial(three, 1) + fxAdded := f.Eval(x) + assert.Equal(t, fxAdded, vecOp(fx, vecOp(three, x, core.Mul), core.Add)) + + f.SubMonomial(one, 0) + fxSub := f.Eval(x) + assert.Equal(t, fxSub, vecOp(fxAdded, one, core.Sub)) +} + +func TestPolyReadCoeffs(t *testing.T) { + var f polynomial.DensePolynomial + coeffs := core.HostSliceFromElements([]bls12_381.ScalarField{one, two, three, four}) + f.CreateFromCoeffecitients(coeffs) + coeffsCopied := make(core.HostSlice[bls12_381.ScalarField], coeffs.Len()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsCopied) + assert.ElementsMatch(t, coeffs, coeffsCopied) + + var coeffsDevice core.DeviceSlice + coeffsDevice.Malloc(coeffs.Len()*one.Size(), one.Size()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsDevice) + coeffsHost := make(core.HostSlice[bls12_381.ScalarField], coeffs.Len()) + coeffsHost.CopyFromDevice(&coeffsDevice) + + assert.ElementsMatch(t, coeffs, coeffsHost) +} + +func TestPolyOddEvenSlicing(t *testing.T) { + size := 1<<10 - 3 + f := randomPoly(size) + + even := f.Even() + odd := f.Odd() + assert.Equal(t, f.Degree(), even.Degree()+odd.Degree()+1) + + x := rand() + var evenExpected, oddExpected bls12_381.ScalarField + for i := size; i >= 0; i-- { + if i%2 == 0 { + mul := vecOp(evenExpected, x, core.Mul) + evenExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } else { + mul := vecOp(oddExpected, x, core.Mul) + oddExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } + } + + evenEvaled := even.Eval(x) + assert.Equal(t, evenExpected, evenEvaled) + + oddEvaled := odd.Eval(x) + assert.Equal(t, oddExpected, oddEvaled) +} + +func TestPolynomialDivision(t *testing.T) { + // divide f(x)/g(x), compute q(x), r(x) and check f(x)=q(x)*g(x)+r(x) + var f, g polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements(bls12_381.GenerateScalars(1 << 4))) + g.CreateFromCoeffecitients(core.HostSliceFromElements(bls12_381.GenerateScalars(1 << 2))) + + q, r := f.Divide(&g) + + qMulG := q.Multiply(&g) + fRecon := qMulG.Add(&r) + + x := bls12_381.GenerateScalars(1)[0] + fEval := f.Eval(x) + fReconEval := fRecon.Eval(x) + assert.Equal(t, fEval, fReconEval) +} + +func TestDivideByVanishing(t *testing.T) { + // poly of x^4-1 vanishes ad 4th rou + var zero bls12_381.ScalarField + minus_one := vecOp(zero, one, core.Sub) + coeffs := core.HostSliceFromElements([]bls12_381.ScalarField{minus_one, zero, zero, zero, one}) // x^4-1 + var v polynomial.DensePolynomial + v.CreateFromCoeffecitients(coeffs) + + f := randomPoly(1 << 3) + + fv := f.Multiply(&v) + fDegree := f.Degree() + fvDegree := fv.Degree() + assert.Equal(t, fDegree+4, fvDegree) + + fReconstructed := fv.DivideByVanishing(4) + assert.Equal(t, fDegree, fReconstructed.Degree()) + + x := rand() + assert.Equal(t, f.Eval(x), fReconstructed.Eval(x)) +} + +// func TestPolySlice(t *testing.T) { +// size := 4 +// coeffs := bls12_381.GenerateScalars(size) +// var f DensePolynomial +// f.CreateFromCoeffecitients(coeffs) +// fSlice := f.AsSlice() +// assert.True(t, fSlice.IsOnDevice()) +// assert.Equal(t, size, fSlice.Len()) + +// hostSlice := make(core.HostSlice[bls12_381.ScalarField], size) +// hostSlice.CopyFromDevice(fSlice) +// assert.Equal(t, coeffs, hostSlice) + +// cfg := ntt.GetDefaultNttConfig() +// res := make(core.HostSlice[bls12_381.ScalarField], size) +// ntt.Ntt(fSlice, core.KForward, cfg, res) + +// assert.Equal(t, f.Eval(one), res[0]) +// } diff --git a/wrappers/golang/curves/bls12381/tests/scalar_field_test.go b/wrappers/golang/curves/bls12381/tests/scalar_field_test.go new file mode 100644 index 00000000..d85ed6ce --- /dev/null +++ b/wrappers/golang/curves/bls12381/tests/scalar_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + SCALAR_LIMBS = bls12_381.SCALAR_LIMBS +) + +func TestScalarFieldFromLimbs(t *testing.T) { + emptyField := bls12_381.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestScalarFieldGetLimbs(t *testing.T) { + emptyField := bls12_381.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") +} + +func TestScalarFieldOne(t *testing.T) { + var emptyField bls12_381.ScalarField + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int(SCALAR_LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "ScalarField with limbs to field one did not work") +} + +func TestScalarFieldZero(t *testing.T) { + var emptyField bls12_381.ScalarField + emptyField.Zero() + limbsZero := make([]uint32, SCALAR_LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "ScalarField with limbs to field zero failed") +} + +func TestScalarFieldSize(t *testing.T) { + var emptyField bls12_381.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") +} + +func TestScalarFieldAsPointer(t *testing.T) { + var emptyField bls12_381.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") +} + +func TestScalarFieldFromBytes(t *testing.T) { + var emptyField bls12_381.ScalarField + bytes, expected := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func TestScalarFieldToBytes(t *testing.T) { + var emptyField bls12_381.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) + emptyField.FromLimbs(limbs) + + assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") +} + +func TestBls12_381GenerateScalars(t *testing.T) { + const numScalars = 8 + scalars := bls12_381.GenerateScalars(numScalars) + + assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) + + assert.Equal(t, numScalars, scalars.Len()) + zeroScalar := bls12_381.ScalarField{} + assert.NotContains(t, scalars, zeroScalar) +} + +func TestBls12_381MongtomeryConversion(t *testing.T) { + size := 1 << 15 + scalars := bls12_381.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + bls12_381.ToMontgomery(&deviceScalars) + + scalarsMontHost := bls12_381.GenerateScalars(size) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.NotEqual(t, scalars, scalarsMontHost) + + bls12_381.FromMontgomery(&deviceScalars) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.Equal(t, scalars, scalarsMontHost) +} diff --git a/wrappers/golang/curves/bls12381/tests/vec_ops_test.go b/wrappers/golang/curves/bls12381/tests/vec_ops_test.go new file mode 100644 index 00000000..f39e93fc --- /dev/null +++ b/wrappers/golang/curves/bls12381/tests/vec_ops_test.go @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bls12_381 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bls12381/vecOps" + "github.com/stretchr/testify/assert" +) + +func TestBls12_381VecOps(t *testing.T) { + testSize := 1 << 14 + + a := bls12_381.GenerateScalars(testSize) + b := bls12_381.GenerateScalars(testSize) + var scalar bls12_381.ScalarField + scalar.One() + ones := core.HostSliceWithValue(scalar, testSize) + + out := make(core.HostSlice[bls12_381.ScalarField], testSize) + out2 := make(core.HostSlice[bls12_381.ScalarField], testSize) + out3 := make(core.HostSlice[bls12_381.ScalarField], testSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.VecOp(a, b, out, cfg, core.Add) + vecOps.VecOp(out, b, out2, cfg, core.Sub) + + assert.Equal(t, a, out2) + + vecOps.VecOp(a, ones, out3, cfg, core.Mul) + + assert.Equal(t, a, out3) +} + +func TestBls12_381Transpose(t *testing.T) { + rowSize := 1 << 6 + columnSize := 1 << 8 + onDevice := false + isAsync := false + + matrix := bls12_381.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[bls12_381.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[bls12_381.ScalarField], rowSize*columnSize) + + ctx, _ := cr.GetDefaultDeviceContext() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, ctx, onDevice, isAsync) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + onDevice = true + + matrix.CopyToDevice(&dMatrix, true) + dOut.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + dOut2.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + + vecOps.TransposeMatrix(dMatrix, dOut, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, ctx, onDevice, isAsync) + output := make(core.HostSlice[bls12_381.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang/curves/bls12381/include/vec_ops.h b/wrappers/golang/curves/bls12381/vecOps/include/vec_ops.h similarity index 51% rename from wrappers/golang/curves/bls12381/include/vec_ops.h rename to wrappers/golang/curves/bls12381/vecOps/include/vec_ops.h index 0c635fb4..65ff6178 100644 --- a/wrappers/golang/curves/bls12381/include/vec_ops.h +++ b/wrappers/golang/curves/bls12381/vecOps/include/vec_ops.h @@ -1,5 +1,5 @@ #include -#include "../../include/types.h" +#include #ifndef _BLS12_381_VEC_OPS_H #define _BLS12_381_VEC_OPS_H @@ -8,7 +8,11 @@ extern "C" { #endif -cudaError_t bls12_381MulCuda( +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bls12_381_mul_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -16,7 +20,7 @@ cudaError_t bls12_381MulCuda( scalar_t* result ); -cudaError_t bls12_381AddCuda( +cudaError_t bls12_381_add_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -24,7 +28,7 @@ cudaError_t bls12_381AddCuda( scalar_t* result ); -cudaError_t bls12_381SubCuda( +cudaError_t bls12_381_sub_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -32,6 +36,16 @@ cudaError_t bls12_381SubCuda( scalar_t* result ); +cudaError_t bls12_381_transpose_matrix_cuda( + scalar_t* mat_in, + int row_size, + int column_size, + scalar_t* mat_out, + DeviceContext* ctx, + bool on_device, + bool is_async +); + #ifdef __cplusplus } #endif diff --git a/wrappers/golang/curves/bls12381/vecOps/vec_ops.go b/wrappers/golang/curves/bls12381/vecOps/vec_ops.go new file mode 100644 index 00000000..49409685 --- /dev/null +++ b/wrappers/golang/curves/bls12381/vecOps/vec_ops.go @@ -0,0 +1,48 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + + "unsafe" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { + aPointer, bPointer, outPointer, cfgPointer, size := core.VecOpCheck(a, b, out, &config) + + cA := (*C.scalar_t)(aPointer) + cB := (*C.scalar_t)(bPointer) + cOut := (*C.scalar_t)(outPointer) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cSize := (C.int)(size) + + switch op { + case core.Sub: + ret = (cr.CudaError)(C.bls12_381_sub_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (cr.CudaError)(C.bls12_381_add_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (cr.CudaError)(C.bls12_381_mul_cuda(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError) { + core.TransposeCheck(in, out, onDevice) + + cIn := (*C.scalar_t)(in.AsUnsafePointer()) + cOut := (*C.scalar_t)(out.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cOnDevice := (C._Bool)(onDevice) + cIsAsync := (C._Bool)(isAsync) + + err := (cr.CudaError)(C.bls12_381_transpose_matrix_cuda(cIn, cRowSize, cColumnSize, cOut, cCtx, cOnDevice, cIsAsync)) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bls12381/vec_ops.go b/wrappers/golang/curves/bls12381/vec_ops.go deleted file mode 100644 index e6ed4438..00000000 --- a/wrappers/golang/curves/bls12381/vec_ops.go +++ /dev/null @@ -1,55 +0,0 @@ -package bls12381 - -// #cgo CFLAGS: -I./include/ -// #include "vec_ops.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { - core.VecOpCheck(a, b, out, &config) - var cA, cB, cOut *C.scalar_t - - if a.IsOnDevice() { - aDevice := a.(core.DeviceSlice) - aDevice.CheckDevice() - cA = (*C.scalar_t)(aDevice.AsPointer()) - } else { - cA = (*C.scalar_t)(unsafe.Pointer(&a.(core.HostSlice[ScalarField])[0])) - } - - if b.IsOnDevice() { - bDevice := b.(core.DeviceSlice) - bDevice.CheckDevice() - cB = (*C.scalar_t)(bDevice.AsPointer()) - } else { - cB = (*C.scalar_t)(unsafe.Pointer(&b.(core.HostSlice[ScalarField])[0])) - } - - if out.IsOnDevice() { - outDevice := out.(core.DeviceSlice) - outDevice.CheckDevice() - cOut = (*C.scalar_t)(outDevice.AsPointer()) - } else { - cOut = (*C.scalar_t)(unsafe.Pointer(&out.(core.HostSlice[ScalarField])[0])) - } - - cConfig := (*C.VecOpsConfig)(unsafe.Pointer(&config)) - cSize := (C.int)(a.Len()) - - switch op { - case core.Sub: - ret = (cr.CudaError)(C.bls12_381SubCuda(cA, cB, cSize, cConfig, cOut)) - case core.Add: - ret = (cr.CudaError)(C.bls12_381AddCuda(cA, cB, cSize, cConfig, cOut)) - case core.Mul: - ret = (cr.CudaError)(C.bls12_381MulCuda(cA, cB, cSize, cConfig, cOut)) - } - - return ret -} diff --git a/wrappers/golang/curves/bls12381/vec_ops_test.go b/wrappers/golang/curves/bls12381/vec_ops_test.go deleted file mode 100644 index 8cdfc575..00000000 --- a/wrappers/golang/curves/bls12381/vec_ops_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package bls12381 - -import ( - "testing" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - "github.com/stretchr/testify/assert" -) - -func TestVecOps(t *testing.T) { - testSize := 1 << 14 - - a := GenerateScalars(testSize) - b := GenerateScalars(testSize) - var scalar ScalarField - scalar.One() - ones := core.HostSliceWithValue(scalar, testSize) - - out := make(core.HostSlice[ScalarField], testSize) - out2 := make(core.HostSlice[ScalarField], testSize) - out3 := make(core.HostSlice[ScalarField], testSize) - - cfg := core.DefaultVecOpsConfig() - - VecOp(a, b, out, cfg, core.Add) - VecOp(out, b, out2, cfg, core.Sub) - - assert.Equal(t, a, out2) - - VecOp(a, ones, out3, cfg, core.Mul) - - assert.Equal(t, a, out3) -} diff --git a/wrappers/golang/curves/bn254/base_field.go b/wrappers/golang/curves/bn254/base_field.go index e920a767..0fbf3aee 100644 --- a/wrappers/golang/curves/bn254/base_field.go +++ b/wrappers/golang/curves/bn254/base_field.go @@ -6,7 +6,7 @@ import ( ) const ( - BASE_LIMBS int8 = 8 + BASE_LIMBS int = 8 ) type BaseField struct { @@ -29,6 +29,11 @@ func (f BaseField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *BaseField) FromUint32(v uint32) BaseField { + f.limbs[0] = v + return *f +} + func (f *BaseField) FromLimbs(limbs []uint32) BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") diff --git a/wrappers/golang/curves/bn254/bn254.go b/wrappers/golang/curves/bn254/bn254.go deleted file mode 100644 index 2a8214ab..00000000 --- a/wrappers/golang/curves/bn254/bn254.go +++ /dev/null @@ -1,4 +0,0 @@ -package bn254 - -// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build -lingo_bn254 -lstdc++ -lm -import "C" diff --git a/wrappers/golang/curves/bn254/curve.go b/wrappers/golang/curves/bn254/curve.go index 4430c00b..09a8bd10 100644 --- a/wrappers/golang/curves/bn254/curve.go +++ b/wrappers/golang/curves/bn254/curve.go @@ -53,7 +53,7 @@ func (p *Projective) FromAffine(a Affine) Projective { func (p Projective) ProjectiveEq(p2 *Projective) bool { cP := (*C.projective_t)(unsafe.Pointer(&p)) cP2 := (*C.projective_t)(unsafe.Pointer(&p2)) - __ret := C.bn254Eq(cP, cP2) + __ret := C.bn254_eq(cP, cP2) return __ret == (C._Bool)(true) } @@ -62,7 +62,7 @@ func (p *Projective) ProjectiveToAffine() Affine { cA := (*C.affine_t)(unsafe.Pointer(&a)) cP := (*C.projective_t)(unsafe.Pointer(&p)) - C.bn254ToAffine(cP, cA) + C.bn254_to_affine(cP, cA) return a } @@ -75,7 +75,7 @@ func GenerateProjectivePoints(size int) core.HostSlice[Projective] { pointsSlice := core.HostSliceFromElements[Projective](points) pPoints := (*C.projective_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bn254GenerateProjectivePoints(pPoints, cSize) + C.bn254_generate_projective_points(pPoints, cSize) return pointsSlice } @@ -129,18 +129,18 @@ func GenerateAffinePoints(size int) core.HostSlice[Affine] { pointsSlice := core.HostSliceFromElements[Affine](points) cPoints := (*C.affine_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bn254GenerateAffinePoints(cPoints, cSize) + C.bn254_generate_affine_points(cPoints, cSize) return pointsSlice } func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.affine_t)(points.AsPointer()) + cValues := (*C.affine_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bn254AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bn254_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } @@ -156,12 +156,12 @@ func AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { } func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.projective_t)(points.AsPointer()) + cValues := (*C.projective_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bn254ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bn254_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bn254/ecntt/ecntt.go b/wrappers/golang/curves/bn254/ecntt/ecntt.go new file mode 100644 index 00000000..c0b341df --- /dev/null +++ b/wrappers/golang/curves/bn254/ecntt/ecntt.go @@ -0,0 +1,24 @@ +package ecntt + +// #cgo CFLAGS: -I./include/ +// #include "ecntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + pointsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](points, cfg, results) + + cPoints := (*C.projective_t)(pointsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.projective_t)(resultsPointer) + + __ret := C.bn254_ecntt_cuda(cPoints, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bn254/ecntt/include/ecntt.h b/wrappers/golang/curves/bn254/ecntt/include/ecntt.h new file mode 100644 index 00000000..da61fb91 --- /dev/null +++ b/wrappers/golang/curves/bn254/ecntt/include/ecntt.h @@ -0,0 +1,19 @@ +#include + +#ifndef _BN254_ECNTT_H +#define _BN254_ECNTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct NTTConfig NTTConfig; +typedef struct projective_t projective_t; + +cudaError_t bn254_ecntt_cuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bls12377/g2_curve.go b/wrappers/golang/curves/bn254/g2/curve.go similarity index 88% rename from wrappers/golang/curves/bls12377/g2_curve.go rename to wrappers/golang/curves/bn254/g2/curve.go index 69eebb33..0e3f0ccd 100644 --- a/wrappers/golang/curves/bls12377/g2_curve.go +++ b/wrappers/golang/curves/bn254/g2/curve.go @@ -1,9 +1,7 @@ -//go:build g2 - -package bls12377 +package g2 // #cgo CFLAGS: -I./include/ -// #include "g2_curve.h" +// #include "curve.h" import "C" import ( @@ -55,7 +53,7 @@ func (p *G2Projective) FromAffine(a G2Affine) G2Projective { func (p G2Projective) ProjectiveEq(p2 *G2Projective) bool { cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) cP2 := (*C.g2_projective_t)(unsafe.Pointer(&p2)) - __ret := C.bls12_377G2Eq(cP, cP2) + __ret := C.bn254_g2_eq(cP, cP2) return __ret == (C._Bool)(true) } @@ -64,7 +62,7 @@ func (p *G2Projective) ProjectiveToAffine() G2Affine { cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) - C.bls12_377G2ToAffine(cP, cA) + C.bn254_g2_to_affine(cP, cA) return a } @@ -77,7 +75,7 @@ func G2GenerateProjectivePoints(size int) core.HostSlice[G2Projective] { pointsSlice := core.HostSliceFromElements[G2Projective](points) pPoints := (*C.g2_projective_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bls12_377G2GenerateProjectivePoints(pPoints, cSize) + C.bn254_g2_generate_projective_points(pPoints, cSize) return pointsSlice } @@ -131,18 +129,18 @@ func G2GenerateAffinePoints(size int) core.HostSlice[G2Affine] { pointsSlice := core.HostSliceFromElements[G2Affine](points) cPoints := (*C.g2_affine_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bls12_377G2GenerateAffinePoints(cPoints, cSize) + C.bn254_g2_generate_affine_points(cPoints, cSize) return pointsSlice } func convertG2AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.g2_affine_t)(points.AsPointer()) + cValues := (*C.g2_affine_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_377G2AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bn254_g2_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } @@ -158,12 +156,12 @@ func G2AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { } func convertG2ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.g2_projective_t)(points.AsPointer()) + cValues := (*C.g2_projective_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bls12_377G2ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bn254_g2_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bls12377/g2_base_field.go b/wrappers/golang/curves/bn254/g2/g2base_field.go similarity index 86% rename from wrappers/golang/curves/bls12377/g2_base_field.go rename to wrappers/golang/curves/bn254/g2/g2base_field.go index 02e7a0a7..409a7d5a 100644 --- a/wrappers/golang/curves/bls12377/g2_base_field.go +++ b/wrappers/golang/curves/bn254/g2/g2base_field.go @@ -1,6 +1,4 @@ -//go:build g2 - -package bls12377 +package g2 import ( "encoding/binary" @@ -8,19 +6,19 @@ import ( ) const ( - G2_BASE_LIMBS int8 = 24 + G2BASE_LIMBS int = 16 ) type G2BaseField struct { - limbs [G2_BASE_LIMBS]uint32 + limbs [G2BASE_LIMBS]uint32 } func (f G2BaseField) Len() int { - return int(G2_BASE_LIMBS) + return int(G2BASE_LIMBS) } func (f G2BaseField) Size() int { - return int(G2_BASE_LIMBS * 4) + return int(G2BASE_LIMBS * 4) } func (f G2BaseField) GetLimbs() []uint32 { @@ -31,6 +29,11 @@ func (f G2BaseField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *G2BaseField) FromUint32(v uint32) G2BaseField { + f.limbs[0] = v + return *f +} + func (f *G2BaseField) FromLimbs(limbs []uint32) G2BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") diff --git a/wrappers/golang/curves/bn254/g2/include/curve.h b/wrappers/golang/curves/bn254/g2/include/curve.h new file mode 100644 index 00000000..e8863f1e --- /dev/null +++ b/wrappers/golang/curves/bn254/g2/include/curve.h @@ -0,0 +1,26 @@ +#include +#include + +#ifndef _BN254_G2CURVE_H +#define _BN254_G2CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct g2_projective_t g2_projective_t; +typedef struct g2_affine_t g2_affine_t; +typedef struct DeviceContext DeviceContext; + +bool bn254_g2_eq(g2_projective_t* point1, g2_projective_t* point2); +void bn254_g2_to_affine(g2_projective_t* point, g2_affine_t* point_out); +void bn254_g2_generate_projective_points(g2_projective_t* points, int size); +void bn254_g2_generate_affine_points(g2_affine_t* points, int size); +cudaError_t bn254_g2_affine_convert_montgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t bn254_g2_projective_convert_montgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bn254/g2/include/msm.h b/wrappers/golang/curves/bn254/g2/include/msm.h new file mode 100644 index 00000000..75a80625 --- /dev/null +++ b/wrappers/golang/curves/bn254/g2/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _BN254_G2MSM_H +#define _BN254_G2MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct g2_projective_t g2_projective_t; +typedef struct g2_affine_t g2_affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bn254_g2_msm_cuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); +cudaError_t bn254_g2_precompute_msm_bases_cuda(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bn254/g2/include/scalar_field.h b/wrappers/golang/curves/bn254/g2/include/scalar_field.h new file mode 100644 index 00000000..f37e47db --- /dev/null +++ b/wrappers/golang/curves/bn254/g2/include/scalar_field.h @@ -0,0 +1,21 @@ +#include +#include + +#ifndef _BN254_FIELD_H +#define _BN254_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void bn254_generate_scalars(scalar_t* scalars, int size); +cudaError_t bn254_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bn254/g2/msm.go b/wrappers/golang/curves/bn254/g2/msm.go new file mode 100644 index 00000000..af58ca48 --- /dev/null +++ b/wrappers/golang/curves/bn254/g2/msm.go @@ -0,0 +1,45 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func G2GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.g2_affine_t)(pointsPointer) + cResults := (*C.g2_projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.bn254_g2_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.g2_affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.g2_affine_t)(outputBasesPointer) + + __ret := C.bn254_g2_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/bn254/g2_msm.go b/wrappers/golang/curves/bn254/g2_msm.go deleted file mode 100644 index e8917692..00000000 --- a/wrappers/golang/curves/bn254/g2_msm.go +++ /dev/null @@ -1,82 +0,0 @@ -//go:build g2 - -package bn254 - -// #cgo CFLAGS: -I./include/ -// #include "g2_msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func G2GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[G2Affine])[0]) - } - cPoints := (*C.g2_affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[G2Projective])[0]) - } - cResults := (*C.g2_projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.bn254G2MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[G2Affine])[0]) - } - cPoints := (*C.g2_affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.g2_affine_t)(outputBasesPointer) - - __ret := C.bn254G2PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/curves/bn254/helpers_test.go b/wrappers/golang/curves/bn254/helpers_test.go deleted file mode 100644 index 679a0049..00000000 --- a/wrappers/golang/curves/bn254/helpers_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package bn254 - -import ( - "math/rand" -) - -func generateRandomLimb(size int) []uint32 { - limbs := make([]uint32, size) - for i := range limbs { - limbs[i] = rand.Uint32() - } - return limbs -} - -func generateLimbOne(size int) []uint32 { - limbs := make([]uint32, size) - limbs[0] = 1 - return limbs -} - -func generateBytesArray(size int) ([]byte, []uint32) { - baseBytes := []byte{1, 2, 3, 4} - var bytes []byte - var limbs []uint32 - for i := 0; i < size; i++ { - bytes = append(bytes, baseBytes...) - limbs = append(limbs, 67305985) - } - - return bytes, limbs -} diff --git a/wrappers/golang/curves/bn254/include/curve.h b/wrappers/golang/curves/bn254/include/curve.h index 60893674..069600aa 100644 --- a/wrappers/golang/curves/bn254/include/curve.h +++ b/wrappers/golang/curves/bn254/include/curve.h @@ -1,5 +1,4 @@ #include -#include "../../include/types.h" #include #ifndef _BN254_CURVE_H @@ -9,12 +8,16 @@ extern "C" { #endif -bool bn254Eq(projective_t* point1, projective_t* point2); -void bn254ToAffine(projective_t* point, affine_t* point_out); -void bn254GenerateProjectivePoints(projective_t* points, int size); -void bn254GenerateAffinePoints(affine_t* points, int size); -cudaError_t bn254AffineConvertMontgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t bn254ProjectiveConvertMontgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct DeviceContext DeviceContext; + +bool bn254_eq(projective_t* point1, projective_t* point2); +void bn254_to_affine(projective_t* point, affine_t* point_out); +void bn254_generate_projective_points(projective_t* points, int size); +void bn254_generate_affine_points(affine_t* points, int size); +cudaError_t bn254_affine_convert_montgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t bn254_projective_convert_montgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); #ifdef __cplusplus } diff --git a/wrappers/golang/curves/bn254/include/g2_curve.h b/wrappers/golang/curves/bn254/include/g2_curve.h deleted file mode 100644 index aad0bd08..00000000 --- a/wrappers/golang/curves/bn254/include/g2_curve.h +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BN254_G2CURVE_H -#define _BN254_G2CURVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -bool bn254G2Eq(g2_projective_t* point1, g2_projective_t* point2); -void bn254G2ToAffine(g2_projective_t* point, g2_affine_t* point_out); -void bn254G2GenerateProjectivePoints(g2_projective_t* points, int size); -void bn254G2GenerateAffinePoints(g2_affine_t* points, int size); -cudaError_t bn254G2AffineConvertMontgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t bn254G2ProjectiveConvertMontgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bn254/include/g2_msm.h b/wrappers/golang/curves/bn254/include/g2_msm.h deleted file mode 100644 index bf562055..00000000 --- a/wrappers/golang/curves/bn254/include/g2_msm.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BN254_G2MSM_H -#define _BN254_G2MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bn254G2MSMCuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); -cudaError_t bn254G2PrecomputeMSMBases(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bn254/include/msm.h b/wrappers/golang/curves/bn254/include/msm.h deleted file mode 100644 index 3504c178..00000000 --- a/wrappers/golang/curves/bn254/include/msm.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BN254_MSM_H -#define _BN254_MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bn254MSMCuda(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); -cudaError_t bn254PrecomputeMSMBases(affine_t* points, int bases_size, int precompute_factor, int _c, bool are_bases_on_device, DeviceContext* ctx, affine_t* output_bases); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bn254/include/ntt.h b/wrappers/golang/curves/bn254/include/ntt.h deleted file mode 100644 index 2cd03958..00000000 --- a/wrappers/golang/curves/bn254/include/ntt.h +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BN254_NTT_H -#define _BN254_NTT_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bn254NTTCuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); -cudaError_t bn254ECNTTCuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); -cudaError_t bn254InitializeDomain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); -cudaError_t bn254ReleaseDomain(DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bn254/include/scalar_field.h b/wrappers/golang/curves/bn254/include/scalar_field.h index f4ef9f59..f37e47db 100644 --- a/wrappers/golang/curves/bn254/include/scalar_field.h +++ b/wrappers/golang/curves/bn254/include/scalar_field.h @@ -1,5 +1,4 @@ #include -#include "../../include/types.h" #include #ifndef _BN254_FIELD_H @@ -9,8 +8,11 @@ extern "C" { #endif -void bn254GenerateScalars(scalar_t* scalars, int size); -cudaError_t bn254ScalarConvertMontgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void bn254_generate_scalars(scalar_t* scalars, int size); +cudaError_t bn254_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); #ifdef __cplusplus } diff --git a/wrappers/golang/curves/bn254/main.go b/wrappers/golang/curves/bn254/main.go new file mode 100644 index 00000000..2d88cd5b --- /dev/null +++ b/wrappers/golang/curves/bn254/main.go @@ -0,0 +1,4 @@ +package bn254 + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build/lib -lingo_curve_bn254 -lingo_field_bn254 -lstdc++ -lm +import "C" diff --git a/wrappers/golang/curves/bn254/msm.go b/wrappers/golang/curves/bn254/msm.go deleted file mode 100644 index 48b1d382..00000000 --- a/wrappers/golang/curves/bn254/msm.go +++ /dev/null @@ -1,80 +0,0 @@ -package bn254 - -// #cgo CFLAGS: -I./include/ -// #include "msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Affine])[0]) - } - cPoints := (*C.affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.bn254MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Affine])[0]) - } - cPoints := (*C.affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.affine_t)(outputBasesPointer) - - __ret := C.bn254PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/curves/bn254/msm/include/msm.h b/wrappers/golang/curves/bn254/msm/include/msm.h new file mode 100644 index 00000000..6f7940f0 --- /dev/null +++ b/wrappers/golang/curves/bn254/msm/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _BN254_MSM_H +#define _BN254_MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bn254_msm_cuda(const scalar_t* scalars,const affine_t* points, int count, MSMConfig* config, projective_t* out); +cudaError_t bn254_precompute_msm_bases_cuda(affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bn254/msm/msm.go b/wrappers/golang/curves/bn254/msm/msm.go new file mode 100644 index 00000000..17ae582a --- /dev/null +++ b/wrappers/golang/curves/bn254/msm/msm.go @@ -0,0 +1,45 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.affine_t)(pointsPointer) + cResults := (*C.projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.bn254_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.bn254_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/bn254/ntt.go b/wrappers/golang/curves/bn254/ntt.go deleted file mode 100644 index a73e31da..00000000 --- a/wrappers/golang/curves/bn254/ntt.go +++ /dev/null @@ -1,97 +0,0 @@ -package bn254 - -// #cgo CFLAGS: -I./include/ -// #include "ntt.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func GetDefaultNttConfig() core.NTTConfig[[SCALAR_LIMBS]uint32] { - cosetGenField := ScalarField{} - cosetGenField.One() - var cosetGen [SCALAR_LIMBS]uint32 - for i, v := range cosetGenField.GetLimbs() { - cosetGen[i] = v - } - - return core.GetDefaultNTTConfig(cosetGen) -} - -func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](scalars, cfg, results) - - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - cSize := (C.int)(scalars.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[ScalarField])[0]) - } - cResults := (*C.scalar_t)(resultsPointer) - - __ret := C.bn254NTTCuda(cScalars, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](points, cfg, results) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Projective])[0]) - } - cPoints := (*C.projective_t)(pointsPointer) - cSize := (C.int)(points.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsPointer = results.(core.DeviceSlice).AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - __ret := C.bn254ECNTTCuda(cPoints, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func InitDomain(primitiveRoot ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { - cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - cFastTwiddles := (C._Bool)(fastTwiddles) - __ret := C.bn254InitializeDomain(cPrimitiveRoot, cCtx, cFastTwiddles) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - __ret := C.bn254ReleaseDomain(cCtx) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} diff --git a/wrappers/golang/curves/bn254/ntt/include/ntt.h b/wrappers/golang/curves/bn254/ntt/include/ntt.h new file mode 100644 index 00000000..48b244aa --- /dev/null +++ b/wrappers/golang/curves/bn254/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#include +#include + +#ifndef _BN254_NTT_H +#define _BN254_NTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct NTTConfig NTTConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bn254_ntt_cuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +cudaError_t bn254_initialize_domain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); +cudaError_t bn254_release_domain(DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bn254/ntt/ntt.go b/wrappers/golang/curves/bn254/ntt/ntt.go new file mode 100644 index 00000000..3082cdee --- /dev/null +++ b/wrappers/golang/curves/bn254/ntt/ntt.go @@ -0,0 +1,56 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" +) + +import ( + "unsafe" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + scalarsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](scalars, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.scalar_t)(resultsPointer) + + __ret := C.bn254_ntt_cuda(cScalars, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func GetDefaultNttConfig() core.NTTConfig[[bn254.SCALAR_LIMBS]uint32] { + cosetGenField := bn254.ScalarField{} + cosetGenField.One() + var cosetGen [bn254.SCALAR_LIMBS]uint32 + for i, v := range cosetGenField.GetLimbs() { + cosetGen[i] = v + } + + return core.GetDefaultNTTConfig(cosetGen) +} + +func InitDomain(primitiveRoot bn254.ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cFastTwiddles := (C._Bool)(fastTwiddles) + __ret := C.bn254_initialize_domain(cPrimitiveRoot, cCtx, cFastTwiddles) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + __ret := C.bn254_release_domain(cCtx) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bn254/polynomial/include/polynomial.h b/wrappers/golang/curves/bn254/polynomial/include/polynomial.h new file mode 100644 index 00000000..14cf6eeb --- /dev/null +++ b/wrappers/golang/curves/bn254/polynomial/include/polynomial.h @@ -0,0 +1,51 @@ +#include +#include + +#ifndef _BN254_POLY_H +#define _BN254_POLY_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct PolynomialInst PolynomialInst; +typedef struct IntegrityPointer IntegrityPointer; + +bool bn254_polynomial_init_cuda_backend(); +PolynomialInst* bn254_polynomial_create_from_coefficients(scalar_t* coeffs, size_t size); +PolynomialInst* bn254_polynomial_create_from_rou_evaluations(scalar_t* evals, size_t size); +PolynomialInst* bn254_polynomial_clone(const PolynomialInst* p); +void bn254_polynomial_print(PolynomialInst* p); +void bn254_polynomial_delete(PolynomialInst* instance); +PolynomialInst* bn254_polynomial_add(const PolynomialInst* a, const PolynomialInst* b); +void bn254_polynomial_add_inplace(PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bn254_polynomial_subtract(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bn254_polynomial_multiply(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bn254_polynomial_multiply_by_scalar(const PolynomialInst* a, const scalar_t* scalar); +void bn254_polynomial_division(const PolynomialInst* a, const PolynomialInst* b, PolynomialInst** q /*OUT*/, PolynomialInst** r /*OUT*/); +PolynomialInst* bn254_polynomial_quotient(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bn254_polynomial_remainder(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bn254_polynomial_divide_by_vanishing(const PolynomialInst* p, size_t vanishing_poly_degree); +void bn254_polynomial_add_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void bn254_polynomial_sub_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void bn254_polynomial_evaluate_on_domain(const PolynomialInst* p, scalar_t* domain, size_t domain_size, scalar_t* evals /*OUT*/); +size_t bn254_polynomial_degree(PolynomialInst* p); +size_t bn254_polynomial_copy_coeffs_range(PolynomialInst* p, scalar_t* memory, size_t start_idx, size_t end_idx); +PolynomialInst* bn254_polynomial_even(PolynomialInst* p); +PolynomialInst* bn254_polynomial_odd(PolynomialInst* p); + +// scalar_t* bn254_polynomial_get_coeffs_raw_ptr(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// PolynomialInst* bn254_polynomial_slice(PolynomialInst* p, size_t offset, size_t stride, size_t size); +// IntegrityPointer* bn254_polynomial_get_coeff_view(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// IntegrityPointer* bn254_polynomial_get_rou_evaluations_view(PolynomialInst* p, size_t nof_evals, bool is_reversed, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// const scalar_t* bn254_polynomial_intergrity_ptr_get(IntegrityPointer* p); +// bool bn254_polynomial_intergrity_ptr_is_valid(IntegrityPointer* p); +// void bn254_polynomial_intergrity_ptr_destroy(IntegrityPointer* p); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/wrappers/golang/curves/bn254/polynomial/polynomial.go b/wrappers/golang/curves/bn254/polynomial/polynomial.go new file mode 100644 index 00000000..1df05cac --- /dev/null +++ b/wrappers/golang/curves/bn254/polynomial/polynomial.go @@ -0,0 +1,176 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +func InitPolyBackend() bool { + return (bool)(C.bn254_polynomial_init_cuda_backend()) +} + +func (up *DensePolynomial) Print() { + C.bn254_polynomial_print(up.handle) +} + +func (up *DensePolynomial) CreateFromCoeffecitients(coeffs core.HostOrDeviceSlice) DensePolynomial { + if coeffs.IsOnDevice() { + coeffs.(core.DeviceSlice).CheckDevice() + } + coeffsPointer := (*C.scalar_t)(coeffs.AsUnsafePointer()) + cSize := (C.size_t)(coeffs.Len()) + up.handle = C.bn254_polynomial_create_from_coefficients(coeffsPointer, cSize) + return *up +} + +func (up *DensePolynomial) CreateFromROUEvaluations(evals core.HostOrDeviceSlice) DensePolynomial { + evalsPointer := (*C.scalar_t)(evals.AsUnsafePointer()) + cSize := (C.size_t)(evals.Len()) + up.handle = C.bn254_polynomial_create_from_coefficients(evalsPointer, cSize) + return *up +} + +func (up *DensePolynomial) Clone() DensePolynomial { + return DensePolynomial{ + handle: C.bn254_polynomial_clone(up.handle), + } +} + +// TODO @jeremyfelder: Maybe this should be in a SetFinalizer that is set on Create functions? +func (up *DensePolynomial) Delete() { + C.bn254_polynomial_delete(up.handle) +} + +func (up *DensePolynomial) Add(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bn254_polynomial_add(up.handle, b.handle), + } +} + +func (up *DensePolynomial) AddInplace(b *DensePolynomial) { + C.bn254_polynomial_add_inplace(up.handle, b.handle) +} + +func (up *DensePolynomial) Subtract(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bn254_polynomial_subtract(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Multiply(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bn254_polynomial_multiply(up.handle, b.handle), + } +} + +func (up *DensePolynomial) MultiplyByScalar(scalar bn254.ScalarField) DensePolynomial { + cScalar := (*C.scalar_t)(unsafe.Pointer(scalar.AsPointer())) + return DensePolynomial{ + handle: C.bn254_polynomial_multiply_by_scalar(up.handle, cScalar), + } +} + +func (up *DensePolynomial) Divide(b *DensePolynomial) (DensePolynomial, DensePolynomial) { + var q, r *PolynomialHandle + C.bn254_polynomial_division(up.handle, b.handle, &q, &r) + return DensePolynomial{ + handle: q, + }, DensePolynomial{ + handle: r, + } +} + +func (up *DensePolynomial) Quotient(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bn254_polynomial_quotient(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Remainder(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bn254_polynomial_remainder(up.handle, b.handle), + } +} + +func (up *DensePolynomial) DivideByVanishing(vanishing_degree uint64) DensePolynomial { + cVanishingDegree := (C.ulong)(vanishing_degree) + return DensePolynomial{ + handle: C.bn254_polynomial_divide_by_vanishing(up.handle, cVanishingDegree), + } +} + +func (up *DensePolynomial) AddMonomial(monomialCoeff bn254.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]bn254.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.bn254_polynomial_add_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) SubMonomial(monomialCoeff bn254.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]bn254.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.bn254_polynomial_sub_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) Eval(x bn254.ScalarField) bn254.ScalarField { + domains := make(core.HostSlice[bn254.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[bn254.ScalarField], 1) + up.EvalOnDomain(domains, evals) + return evals[0] +} + +func (up *DensePolynomial) EvalOnDomain(domain, evals core.HostOrDeviceSlice) core.HostOrDeviceSlice { + cDomain := (*C.scalar_t)(domain.AsUnsafePointer()) + cDomainSize := (C.size_t)(domain.Len()) + cEvals := (*C.scalar_t)(evals.AsUnsafePointer()) + C.bn254_polynomial_evaluate_on_domain(up.handle, cDomain, cDomainSize, cEvals) + return evals +} + +func (up *DensePolynomial) Degree() int { + return int(C.bn254_polynomial_degree(up.handle)) +} + +func (up *DensePolynomial) CopyCoeffsRange(start, end int, out core.HostOrDeviceSlice) (int, core.HostOrDeviceSlice) { + cStart := (C.size_t)(start) + cEnd := (C.size_t)(end) + cScalarOut := (*C.scalar_t)(out.AsUnsafePointer()) + __cNumCoeffsRead := C.bn254_polynomial_copy_coeffs_range(up.handle, cScalarOut, cStart, cEnd) + return int(__cNumCoeffsRead), out +} + +func (up *DensePolynomial) GetCoeff(idx int) bn254.ScalarField { + out := make(core.HostSlice[bn254.ScalarField], 1) + up.CopyCoeffsRange(idx, idx, out) + return out[0] +} + +func (up *DensePolynomial) Even() DensePolynomial { + evenPoly := C.bn254_polynomial_even(up.handle) + return DensePolynomial{ + handle: evenPoly, + } +} + +func (up *DensePolynomial) Odd() DensePolynomial { + oddPoly := C.bn254_polynomial_odd(up.handle) + return DensePolynomial{ + handle: oddPoly, + } +} diff --git a/wrappers/golang/curves/bn254/scalar_field.go b/wrappers/golang/curves/bn254/scalar_field.go index 1b21d061..021983bc 100644 --- a/wrappers/golang/curves/bn254/scalar_field.go +++ b/wrappers/golang/curves/bn254/scalar_field.go @@ -12,7 +12,7 @@ import ( ) const ( - SCALAR_LIMBS int8 = 8 + SCALAR_LIMBS int = 8 ) type ScalarField struct { @@ -35,6 +35,11 @@ func (f ScalarField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *ScalarField) FromUint32(v uint32) ScalarField { + f.limbs[0] = v + return *f +} + func (f *ScalarField) FromLimbs(limbs []uint32) ScalarField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") @@ -89,18 +94,18 @@ func GenerateScalars(size int) core.HostSlice[ScalarField] { cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) cSize := (C.int)(size) - C.bn254GenerateScalars(cScalars, cSize) + C.bn254_generate_scalars(cScalars, cSize) return scalarSlice } func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.scalar_t)(scalars.AsPointer()) + cValues := (*C.scalar_t)(scalars.AsUnsafePointer()) cSize := (C.size_t)(scalars.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bn254ScalarConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bn254_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bls12377/base_field_test.go b/wrappers/golang/curves/bn254/tests/base_field_test.go similarity index 59% rename from wrappers/golang/curves/bls12377/base_field_test.go rename to wrappers/golang/curves/bn254/tests/base_field_test.go index a52543df..92bf2732 100644 --- a/wrappers/golang/curves/bls12377/base_field_test.go +++ b/wrappers/golang/curves/bn254/tests/base_field_test.go @@ -1,34 +1,40 @@ -package bls12377 +package tests import ( + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + BASE_LIMBS = bn254.BASE_LIMBS +) + func TestBaseFieldFromLimbs(t *testing.T) { - emptyField := BaseField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + emptyField := bn254.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the BaseField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestBaseFieldGetLimbs(t *testing.T) { - emptyField := BaseField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + emptyField := bn254.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") } func TestBaseFieldOne(t *testing.T) { - var emptyField BaseField + var emptyField bn254.BaseField emptyField.One() - limbOne := generateLimbOne(int(BASE_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(BASE_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -36,12 +42,12 @@ func TestBaseFieldOne(t *testing.T) { } func TestBaseFieldZero(t *testing.T) { - var emptyField BaseField + var emptyField bn254.BaseField emptyField.Zero() limbsZero := make([]uint32, BASE_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -49,24 +55,24 @@ func TestBaseFieldZero(t *testing.T) { } func TestBaseFieldSize(t *testing.T) { - var emptyField BaseField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + var emptyField bn254.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestBaseFieldAsPointer(t *testing.T) { - var emptyField BaseField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + var emptyField bn254.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestBaseFieldFromBytes(t *testing.T) { - var emptyField BaseField - bytes, expected := generateBytesArray(int(BASE_LIMBS)) + var emptyField bn254.BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -74,8 +80,8 @@ func TestBaseFieldFromBytes(t *testing.T) { } func TestBaseFieldToBytes(t *testing.T) { - var emptyField BaseField - expected, limbs := generateBytesArray(int(BASE_LIMBS)) + var emptyField bn254.BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") diff --git a/wrappers/golang/curves/bls12377/curve_test.go b/wrappers/golang/curves/bn254/tests/curve_test.go similarity index 51% rename from wrappers/golang/curves/bls12377/curve_test.go rename to wrappers/golang/curves/bn254/tests/curve_test.go index 724a0e73..50988d8d 100644 --- a/wrappers/golang/curves/bls12377/curve_test.go +++ b/wrappers/golang/curves/bn254/tests/curve_test.go @@ -1,20 +1,22 @@ -package bls12377 +package tests import ( + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) func TestAffineZero(t *testing.T) { - var fieldZero = BaseField{} + var fieldZero = bn254.BaseField{} - var affineZero Affine + var affineZero bn254.Affine assert.Equal(t, affineZero.X, fieldZero) assert.Equal(t, affineZero.Y, fieldZero) - x := generateRandomLimb(int(BASE_LIMBS)) - y := generateRandomLimb(int(BASE_LIMBS)) - var affine Affine + x := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var affine bn254.Affine affine.FromLimbs(x, y) affine.Zero() @@ -23,10 +25,10 @@ func TestAffineZero(t *testing.T) { } func TestAffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) - var affine Affine + var affine bn254.Affine affine.FromLimbs(randLimbs, randLimbs2) assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) @@ -34,15 +36,15 @@ func TestAffineFromLimbs(t *testing.T) { } func TestAffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne bn254.BaseField fieldOne.One() - var expected Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected bn254.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine Affine + var affine bn254.Affine affine.FromLimbs(randLimbs, randLimbs2) projectivePoint := affine.ToProjective() @@ -50,18 +52,18 @@ func TestAffineToProjective(t *testing.T) { } func TestProjectiveZero(t *testing.T) { - var projectiveZero Projective + var projectiveZero bn254.Projective projectiveZero.Zero() - var fieldZero = BaseField{} - var fieldOne BaseField + var fieldZero = bn254.BaseField{} + var fieldOne bn254.BaseField fieldOne.One() assert.Equal(t, projectiveZero.X, fieldZero) assert.Equal(t, projectiveZero.Y, fieldOne) assert.Equal(t, projectiveZero.Z, fieldZero) - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - var projective Projective + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective bn254.Projective projective.FromLimbs(randLimbs, randLimbs, randLimbs) projective.Zero() @@ -71,11 +73,11 @@ func TestProjectiveZero(t *testing.T) { } func TestProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) - var projective Projective + var projective bn254.Projective projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) @@ -84,18 +86,18 @@ func TestProjectiveFromLimbs(t *testing.T) { } func TestProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne bn254.BaseField fieldOne.One() - var expected Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected bn254.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine Affine + var affine bn254.Affine affine.FromLimbs(randLimbs, randLimbs2) - var projectivePoint Projective + var projectivePoint bn254.Projective projectivePoint.FromAffine(affine) assert.Equal(t, expected, projectivePoint) } diff --git a/wrappers/golang/curves/bn254/tests/ecntt_test.go b/wrappers/golang/curves/bn254/tests/ecntt_test.go new file mode 100644 index 00000000..28f9a635 --- /dev/null +++ b/wrappers/golang/curves/bn254/tests/ecntt_test.go @@ -0,0 +1,30 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + ecntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/ecntt" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/ntt" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + points := bn254.GenerateProjectivePoints(1 << largestTestSize) + + for _, size := range []int{4, 5, 6, 7, 8} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[bn254.Projective](points[:testSize]) + cfg.Ordering = v + cfg.NttAlgorithm = core.Radix2 + + output := make(core.HostSlice[bn254.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + } + } +} diff --git a/wrappers/golang/curves/bls12381/g2_curve_test.go b/wrappers/golang/curves/bn254/tests/g2_curve_test.go similarity index 51% rename from wrappers/golang/curves/bls12381/g2_curve_test.go rename to wrappers/golang/curves/bn254/tests/g2_curve_test.go index c9fb4f87..5ae9d2e2 100644 --- a/wrappers/golang/curves/bls12381/g2_curve_test.go +++ b/wrappers/golang/curves/bn254/tests/g2_curve_test.go @@ -1,22 +1,22 @@ -//go:build g2 - -package bls12381 +package tests import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/g2" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) func TestG2AffineZero(t *testing.T) { - var fieldZero = G2BaseField{} + var fieldZero = g2.G2BaseField{} - var affineZero G2Affine + var affineZero g2.G2Affine assert.Equal(t, affineZero.X, fieldZero) assert.Equal(t, affineZero.Y, fieldZero) - x := generateRandomLimb(int(G2_BASE_LIMBS)) - y := generateRandomLimb(int(G2_BASE_LIMBS)) - var affine G2Affine + x := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var affine g2.G2Affine affine.FromLimbs(x, y) affine.Zero() @@ -25,10 +25,10 @@ func TestG2AffineZero(t *testing.T) { } func TestG2AffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) @@ -36,15 +36,15 @@ func TestG2AffineFromLimbs(t *testing.T) { } func TestG2AffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - var fieldOne G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField fieldOne.One() - var expected G2Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) projectivePoint := affine.ToProjective() @@ -52,18 +52,18 @@ func TestG2AffineToProjective(t *testing.T) { } func TestG2ProjectiveZero(t *testing.T) { - var projectiveZero G2Projective + var projectiveZero g2.G2Projective projectiveZero.Zero() - var fieldZero = G2BaseField{} - var fieldOne G2BaseField + var fieldZero = g2.G2BaseField{} + var fieldOne g2.G2BaseField fieldOne.One() assert.Equal(t, projectiveZero.X, fieldZero) assert.Equal(t, projectiveZero.Y, fieldOne) assert.Equal(t, projectiveZero.Z, fieldZero) - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - var projective G2Projective + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var projective g2.G2Projective projective.FromLimbs(randLimbs, randLimbs, randLimbs) projective.Zero() @@ -73,11 +73,11 @@ func TestG2ProjectiveZero(t *testing.T) { } func TestG2ProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) - var projective G2Projective + var projective g2.G2Projective projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) @@ -86,18 +86,18 @@ func TestG2ProjectiveFromLimbs(t *testing.T) { } func TestG2ProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - var fieldOne G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField fieldOne.One() - var expected G2Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) - var projectivePoint G2Projective + var projectivePoint g2.G2Projective projectivePoint.FromAffine(affine) assert.Equal(t, expected, projectivePoint) } diff --git a/wrappers/golang/curves/bls12381/g2_base_field_test.go b/wrappers/golang/curves/bn254/tests/g2_g2base_field_test.go similarity index 57% rename from wrappers/golang/curves/bls12381/g2_base_field_test.go rename to wrappers/golang/curves/bn254/tests/g2_g2base_field_test.go index da0e64dd..ced8e6a2 100644 --- a/wrappers/golang/curves/bls12381/g2_base_field_test.go +++ b/wrappers/golang/curves/bn254/tests/g2_g2base_field_test.go @@ -1,36 +1,40 @@ -//go:build g2 - -package bls12381 +package tests import ( + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/g2" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + G2BASE_LIMBS = bn254.G2BASE_LIMBS +) + func TestG2BaseFieldFromLimbs(t *testing.T) { - emptyField := G2BaseField{} - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + emptyField := bn254.G2BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the G2BaseField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the G2BaseField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestG2BaseFieldGetLimbs(t *testing.T) { - emptyField := G2BaseField{} - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + emptyField := bn254.G2BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the G2BaseField's limbs") } func TestG2BaseFieldOne(t *testing.T) { - var emptyField G2BaseField + var emptyField bn254.G2BaseField emptyField.One() - limbOne := generateLimbOne(int(G2_BASE_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(G2BASE_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -38,12 +42,12 @@ func TestG2BaseFieldOne(t *testing.T) { } func TestG2BaseFieldZero(t *testing.T) { - var emptyField G2BaseField + var emptyField bn254.G2BaseField emptyField.Zero() - limbsZero := make([]uint32, G2_BASE_LIMBS) + limbsZero := make([]uint32, G2BASE_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -51,24 +55,24 @@ func TestG2BaseFieldZero(t *testing.T) { } func TestG2BaseFieldSize(t *testing.T) { - var emptyField G2BaseField - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + var emptyField bn254.G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestG2BaseFieldAsPointer(t *testing.T) { - var emptyField G2BaseField - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + var emptyField bn254.G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestG2BaseFieldFromBytes(t *testing.T) { - var emptyField G2BaseField - bytes, expected := generateBytesArray(int(G2_BASE_LIMBS)) + var emptyField bn254.G2BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -76,8 +80,8 @@ func TestG2BaseFieldFromBytes(t *testing.T) { } func TestG2BaseFieldToBytes(t *testing.T) { - var emptyField G2BaseField - expected, limbs := generateBytesArray(int(G2_BASE_LIMBS)) + var emptyField bn254.G2BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") diff --git a/wrappers/golang/curves/bn254/g2_msm_test.go b/wrappers/golang/curves/bn254/tests/g2_msm_test.go similarity index 60% rename from wrappers/golang/curves/bn254/g2_msm_test.go rename to wrappers/golang/curves/bn254/tests/g2_msm_test.go index 688e9026..471d89f7 100644 --- a/wrappers/golang/curves/bn254/g2_msm_test.go +++ b/wrappers/golang/curves/bn254/tests/g2_msm_test.go @@ -1,6 +1,4 @@ -//go:build g2 - -package bn254 +package tests import ( "fmt" @@ -9,16 +7,18 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark-crypto/ecc/bn254/fr" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleBn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/g2" ) -func projectiveToGnarkAffineG2(p G2Projective) bn254.G2Affine { +func projectiveToGnarkAffineG2(p g2.G2Projective) bn254.G2Affine { pxBytes := p.X.ToBytesLittleEndian() pxA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[:fp.Bytes])) pxA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[fp.Bytes:])) @@ -62,7 +62,7 @@ func projectiveToGnarkAffineG2(p G2Projective) bn254.G2Affine { return *g2Affine.FromJacobian(&g2Jac) } -func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points core.HostSlice[G2Affine], out G2Projective) bool { +func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[icicleBn254.ScalarField], points core.HostSlice[g2.G2Affine], out g2.G2Projective) bool { scalarsFr := make([]fr.Element, len(scalars)) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -73,6 +73,11 @@ func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points cor for i, v := range points { pointsFp[i] = projectiveToGnarkAffineG2(v.ToProjective()) } + + return testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bn254.G2Affine], out g2.G2Projective) bool { var msmRes bn254.G2Jac msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) @@ -83,54 +88,117 @@ func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points cor return msmRes.Equal(&icicleResAsJac) } +func convertIcicleG2AffineToG2Affine(iciclePoints []g2.G2Affine) []bn254.G2Affine { + points := make([]bn254.G2Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes * 2]byte)(iciclePoint.X.ToBytesLittleEndian()) + xA0Bytes := ([fp.Bytes]byte)(xBytes[:fp.Bytes]) + xA1Bytes := ([fp.Bytes]byte)(xBytes[fp.Bytes:]) + xA0Elem, _ := fp.LittleEndian.Element(&xA0Bytes) + xA1Elem, _ := fp.LittleEndian.Element(&xA1Bytes) + + yBytes := ([fp.Bytes * 2]byte)(iciclePoint.Y.ToBytesLittleEndian()) + yA0Bytes := ([fp.Bytes]byte)(yBytes[:fp.Bytes]) + yA1Bytes := ([fp.Bytes]byte)(yBytes[fp.Bytes:]) + yA0Elem, _ := fp.LittleEndian.Element(&yA0Bytes) + yA1Elem, _ := fp.LittleEndian.Element(&yA1Bytes) + + points[index] = bn254.G2Affine{ + X: bn254.E2{ + A0: xA0Elem, + A1: xA1Elem, + }, + Y: bn254.E2{ + A0: yA0Elem, + A1: yA1Elem, + }, + } + } + + return points +} + func TestMSMG2(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := G2GenerateAffinePoints(size) + scalars := icicleBn254.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) cr.SynchronizeStream(&stream) // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) + + } +} +func TestMSMG2GnarkCryptoTypes(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := g2.G2GenerateAffinePoints(size) + pointsGnark := convertIcicleG2AffineToG2Affine(points) + pointsHost := (core.HostSlice[bn254.G2Affine])(pointsGnark) + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = g2.G2Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) } } func TestMSMG2Batch(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := G2GenerateAffinePoints(totalSize) + scalars := icicleBn254.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], batchSize) + outHost := make(core.HostSlice[g2.G2Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -143,36 +211,35 @@ func TestMSMG2Batch(t *testing.T) { } func TestPrecomputeBaseG2(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() const precomputeFactor = 8 for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := G2GenerateAffinePoints(totalSize) + scalars := icicleBn254.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) var precomputeOut core.DeviceSlice _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - e = G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + e = g2.G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e = out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.PrecomputeFactor = precomputeFactor - e = G2Msm(scalars, precomputeOut, &cfg, out) + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], batchSize) + outHost := make(core.HostSlice[g2.G2Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() precomputeOut.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -185,30 +252,29 @@ func TestPrecomputeBaseG2(t *testing.T) { } func TestMSMG2SkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) + scalars := icicleBn254.GenerateScalars(size) for i := size / 4; i < size; i++ { scalars[i].One() } - points := G2GenerateAffinePoints(size) + points := g2.G2GenerateAffinePoints(size) for i := 0; i < size/4; i++ { points[i].Zero() } - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.Malloc(p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) } @@ -225,23 +291,23 @@ func TestMSMG2MultiDevice(t *testing.T) { wg.Add(1) cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := G2GenerateAffinePoints(size) + scalars := icicleBn254.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) diff --git a/wrappers/golang/curves/bn254/tests/main_test.go b/wrappers/golang/curves/bn254/tests/main_test.go new file mode 100644 index 00000000..71fa2e2d --- /dev/null +++ b/wrappers/golang/curves/bn254/tests/main_test.go @@ -0,0 +1,48 @@ +package tests + +import ( + "os" + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/ntt" + poly "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/polynomial" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" +) + +const ( + largestTestSize = 20 +) + +func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { + rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) + rou := rouMont.Bits() + rouIcicle := bn254.ScalarField{} + limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) + + rouIcicle.FromLimbs(limbs) + e := ntt.InitDomain(rouIcicle, cfg.Ctx, false) + return e +} + +func TestMain(m *testing.M) { + poly.InitPolyBackend() + + // setup domain + cfg := ntt.GetDefaultNttConfig() + e := initDomain(largestTestSize, cfg) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("initDomain failed") + } + + // execute tests + os.Exit(m.Run()) + + // release domain + e = ntt.ReleaseDomain(cfg.Ctx) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("ReleaseDomain failed") + } +} diff --git a/wrappers/golang/curves/bn254/msm_test.go b/wrappers/golang/curves/bn254/tests/msm_test.go similarity index 59% rename from wrappers/golang/curves/bn254/msm_test.go rename to wrappers/golang/curves/bn254/tests/msm_test.go index 637490b0..1dfc5e19 100644 --- a/wrappers/golang/curves/bn254/msm_test.go +++ b/wrappers/golang/curves/bn254/tests/msm_test.go @@ -1,4 +1,4 @@ -package bn254 +package tests import ( "fmt" @@ -7,16 +7,18 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bn254" "github.com/consensys/gnark-crypto/ecc/bn254/fp" "github.com/consensys/gnark-crypto/ecc/bn254/fr" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleBn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/msm" ) -func projectiveToGnarkAffine(p Projective) bn254.G1Affine { +func projectiveToGnarkAffine(p icicleBn254.Projective) bn254.G1Affine { px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.X).ToBytesLittleEndian())) py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Y).ToBytesLittleEndian())) pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Z).ToBytesLittleEndian())) @@ -33,7 +35,7 @@ func projectiveToGnarkAffine(p Projective) bn254.G1Affine { return bn254.G1Affine{X: *x, Y: *y} } -func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core.HostSlice[Affine], out Projective) bool { +func testAgainstGnarkCryptoMsm(scalars core.HostSlice[icicleBn254.ScalarField], points core.HostSlice[icicleBn254.Affine], out icicleBn254.Projective) bool { scalarsFr := make([]fr.Element, len(scalars)) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -44,6 +46,11 @@ func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core. for i, v := range points { pointsFp[i] = projectiveToGnarkAffine(v.ToProjective()) } + + return testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bn254.G1Affine], out icicleBn254.Projective) bool { var msmRes bn254.G1Jac msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) @@ -54,54 +61,104 @@ func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core. return msmRes.Equal(&icicleResAsJac) } +func convertIcicleAffineToG1Affine(iciclePoints []icicleBn254.Affine) []bn254.G1Affine { + points := make([]bn254.G1Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes]byte)(iciclePoint.X.ToBytesLittleEndian()) + fpXElem, _ := fp.LittleEndian.Element(&xBytes) + + yBytes := ([fp.Bytes]byte)(iciclePoint.Y.ToBytesLittleEndian()) + fpYElem, _ := fp.LittleEndian.Element(&yBytes) + points[index] = bn254.G1Affine{ + X: fpXElem, + Y: fpYElem, + } + } + + return points +} + func TestMSM(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + scalars := icicleBn254.GenerateScalars(size) + points := icicleBn254.GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p Projective + var p icicleBn254.Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBn254.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) cr.SynchronizeStream(&stream) // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) + + } +} +func TestMSMGnarkCryptoTypes(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := icicleBn254.GenerateAffinePoints(size) + pointsGnark := convertIcicleAffineToG1Affine(points) + pointsHost := (core.HostSlice[bn254.G1Affine])(pointsGnark) + + var p icicleBn254.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = msm.Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleBn254.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) } } func TestMSMBatch(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := GenerateAffinePoints(totalSize) + scalars := icicleBn254.GenerateScalars(totalSize) + points := icicleBn254.GenerateAffinePoints(totalSize) - var p Projective + var p icicleBn254.Projective var out core.DeviceSlice _, e := out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], batchSize) + outHost := make(core.HostSlice[icicleBn254.Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -114,36 +171,35 @@ func TestMSMBatch(t *testing.T) { } func TestPrecomputeBase(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() const precomputeFactor = 8 for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := GenerateAffinePoints(totalSize) + scalars := icicleBn254.GenerateScalars(totalSize) + points := icicleBn254.GenerateAffinePoints(totalSize) var precomputeOut core.DeviceSlice _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - e = PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + e = msm.PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - var p Projective + var p icicleBn254.Projective var out core.DeviceSlice _, e = out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.PrecomputeFactor = precomputeFactor - e = Msm(scalars, precomputeOut, &cfg, out) + e = msm.Msm(scalars, precomputeOut, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], batchSize) + outHost := make(core.HostSlice[icicleBn254.Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() precomputeOut.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -156,30 +212,29 @@ func TestPrecomputeBase(t *testing.T) { } func TestMSMSkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) + scalars := icicleBn254.GenerateScalars(size) for i := size / 4; i < size; i++ { scalars[i].One() } - points := GenerateAffinePoints(size) + points := icicleBn254.GenerateAffinePoints(size) for i := 0; i < size/4; i++ { points[i].Zero() } - var p Projective + var p icicleBn254.Projective var out core.DeviceSlice _, e := out.Malloc(p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBn254.Projective], 1) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) } @@ -196,23 +251,23 @@ func TestMSMMultiDevice(t *testing.T) { wg.Add(1) cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + scalars := icicleBn254.GenerateScalars(size) + points := icicleBn254.GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p Projective + var p icicleBn254.Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBn254.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) diff --git a/wrappers/golang/curves/bn254/ntt_test.go b/wrappers/golang/curves/bn254/tests/ntt_test.go similarity index 71% rename from wrappers/golang/curves/bn254/ntt_test.go rename to wrappers/golang/curves/bn254/tests/ntt_test.go index 78e06916..49d60e63 100644 --- a/wrappers/golang/curves/bn254/ntt_test.go +++ b/wrappers/golang/curves/bn254/tests/ntt_test.go @@ -1,40 +1,20 @@ -package bn254 +package tests import ( - "os" "reflect" "testing" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc/bn254/fr" "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" ) -const ( - largestTestSize = 17 -) - -func init() { - cfg := GetDefaultNttConfig() - initDomain(largestTestSize, cfg) -} - -func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { - rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) - rou := rouMont.Bits() - rouIcicle := ScalarField{} - limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) - - rouIcicle.FromLimbs(limbs) - e := InitDomain(rouIcicle, cfg.Ctx, false) - return e -} - -func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], output core.HostSlice[ScalarField], order core.Ordering, direction core.NTTDir) bool { - domainWithPrecompute := fft.NewDomain(uint64(size)) +func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[bn254.ScalarField], output core.HostSlice[bn254.ScalarField], order core.Ordering, direction core.NTTDir) bool { scalarsFr := make([]fr.Element, size) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -46,6 +26,11 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou outputAsFr[i] = slice64 } + return testAgainstGnarkCryptoNttGnarkTypes(size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) bool { + domainWithPrecompute := fft.NewDomain(uint64(size)) // DIT + BitReverse == Ordering.kRR // DIT == Ordering.kRN // DIF + BitReverse == Ordering.kNN @@ -68,72 +53,77 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou } return reflect.DeepEqual(scalarsFr, outputAsFr) } - func TestNTTGetDefaultConfig(t *testing.T) { - actual := GetDefaultNttConfig() - expected := generateLimbOne(int(SCALAR_LIMBS)) + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(bn254.SCALAR_LIMBS)) assert.Equal(t, expected, actual.CosetGen[:]) - cosetGenField := ScalarField{} + cosetGenField := bn254.ScalarField{} cosetGenField.One() assert.ElementsMatch(t, cosetGenField.GetLimbs(), actual.CosetGen) } func TestInitDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) } func TestNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := bn254.GenerateScalars(1 << largestTestSize) for _, size := range []int{4, largestTestSize} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[bn254.ScalarField](scalars[:testSize]) cfg.Ordering = v // run ntt - output := make(core.HostSlice[ScalarField], testSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[bn254.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, core.KForward)) } } } +func TestNttFrElement(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := make([]fr.Element, 4) + var x fr.Element + for i := 0; i < 4; i++ { + x.SetRandom() + scalars[i] = x + } -func TestECNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - points := GenerateProjectivePoints(1 << largestTestSize) + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + testSize := size - for _, size := range []int{4, 5, 6, 7, 8} { - for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { - testSize := 1 << size - - pointsCopy := core.HostSliceFromElements[Projective](points[:testSize]) + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) cfg.Ordering = v - cfg.NttAlgorithm = core.Radix2 - output := make(core.HostSlice[Projective], testSize) - e := ECNtt(pointsCopy, core.KForward, &cfg, output) - assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + assert.True(t, testAgainstGnarkCryptoNttGnarkTypes(testSize, scalarsCopy, output, v, core.KForward)) } } } func TestNttDeviceAsync(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := bn254.GenerateScalars(1 << largestTestSize) for _, size := range []int{1, 10, largestTestSize} { for _, direction := range []core.NTTDir{core.KForward, core.KInverse} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[bn254.ScalarField](scalars[:testSize]) stream, _ := cr.CreateStream() @@ -147,12 +137,11 @@ func TestNttDeviceAsync(t *testing.T) { deviceOutput.MallocAsync(testSize*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) // run ntt - Ntt(deviceInput, direction, &cfg, deviceOutput) - output := make(core.HostSlice[ScalarField], testSize) + ntt.Ntt(deviceInput, direction, &cfg, deviceOutput) + output := make(core.HostSlice[bn254.ScalarField], testSize) output.CopyFromDeviceAsync(&deviceOutput, stream) cr.SynchronizeStream(&stream) - // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, direction)) } @@ -161,22 +150,22 @@ func TestNttDeviceAsync(t *testing.T) { } func TestNttBatch(t *testing.T) { - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() largestBatchSize := 100 - scalars := GenerateScalars(1 << largestTestSize * largestBatchSize) + scalars := bn254.GenerateScalars(1 << largestTestSize * largestBatchSize) for _, size := range []int{4, largestTestSize} { for _, batchSize := range []int{1, 16, largestBatchSize} { testSize := 1 << size totalSize := testSize * batchSize - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:totalSize]) + scalarsCopy := core.HostSliceFromElements[bn254.ScalarField](scalars[:totalSize]) cfg.Ordering = core.KNN cfg.BatchSize = int32(batchSize) // run ntt - output := make(core.HostSlice[ScalarField], totalSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[bn254.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) // Compare with gnark-crypto domainWithPrecompute := fft.NewDomain(uint64(testSize)) @@ -205,36 +194,18 @@ func TestNttBatch(t *testing.T) { func TestReleaseDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() - e := ReleaseDomain(cfg.Ctx) + cfg := ntt.GetDefaultNttConfig() + e := ntt.ReleaseDomain(cfg.Ctx) assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ReleasDomain failed") } -func TestMain(m *testing.M) { - // setup domain - cfg := GetDefaultNttConfig() - e := initDomain(largestTestSize, cfg) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("initDomain failed") - } - - // execute tests - os.Exit(m.Run()) - - // release domain - e = ReleaseDomain(cfg.Ctx) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("ReleaseDomain failed") - } -} - // func TestNttArbitraryCoset(t *testing.T) { // for _, size := range []int{20} { // for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { // testSize := 1 << size // scalars := GenerateScalars(testSize) -// cfg := GetDefaultNttConfig() +// cfg := ntt.GetDefaultNttConfig() // var scalarsCopy core.HostSlice[ScalarField] // for _, v := range scalars { diff --git a/wrappers/golang/curves/bn254/tests/polynomial_test.go b/wrappers/golang/curves/bn254/tests/polynomial_test.go new file mode 100644 index 00000000..2861f2d6 --- /dev/null +++ b/wrappers/golang/curves/bn254/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + // "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/polynomial" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/vecOps" + "github.com/stretchr/testify/assert" +) + +var one, two, three, four, five bn254.ScalarField + +func init() { + one.One() + two.FromUint32(2) + three.FromUint32(3) + four.FromUint32(4) + five.FromUint32(5) +} + +func rand() bn254.ScalarField { + return bn254.GenerateScalars(1)[0] +} + +func randomPoly(size int) (f polynomial.DensePolynomial) { + f.CreateFromCoeffecitients(core.HostSliceFromElements(bn254.GenerateScalars(size))) + return f +} + +func vecOp(a, b bn254.ScalarField, op core.VecOps) bn254.ScalarField { + ahost := core.HostSliceWithValue(a, 1) + bhost := core.HostSliceWithValue(b, 1) + out := make(core.HostSlice[bn254.ScalarField], 1) + + cfg := core.DefaultVecOpsConfig() + vecOps.VecOp(ahost, bhost, out, cfg, op) + return out[0] +} + +func TestPolyCreateFromCoefficients(t *testing.T) { + scalars := bn254.GenerateScalars(33) + var uniPoly polynomial.DensePolynomial + + poly := uniPoly.CreateFromCoeffecitients(scalars) + poly.Print() +} + +func TestPolyEval(t *testing.T) { + // testing correct evaluation of f(8) for f(x)=4x^2+2x+5 + coeffs := core.HostSliceFromElements([]bn254.ScalarField{five, two, four}) + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(coeffs) + + var x bn254.ScalarField + x.FromUint32(8) + domains := make(core.HostSlice[bn254.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[bn254.ScalarField], 1) + fEvaled := f.EvalOnDomain(domains, evals) + var expected bn254.ScalarField + assert.Equal(t, expected.FromUint32(277), fEvaled.(core.HostSlice[bn254.ScalarField])[0]) +} + +func TestPolyClone(t *testing.T) { + f := randomPoly(8) + x := rand() + fx := f.Eval(x) + + g := f.Clone() + fg := f.Add(&g) + + gx := g.Eval(x) + fgx := fg.Eval(x) + + assert.Equal(t, fx, gx) + assert.Equal(t, vecOp(fx, gx, core.Add), fgx) +} + +func TestPolyAddSubMul(t *testing.T) { + testSize := 1 << 10 + f := randomPoly(testSize) + g := randomPoly(testSize) + x := rand() + + fx := f.Eval(x) + gx := g.Eval(x) + + polyAdd := f.Add(&g) + fxAddgx := vecOp(fx, gx, core.Add) + assert.Equal(t, polyAdd.Eval(x), fxAddgx) + + polySub := f.Subtract(&g) + fxSubgx := vecOp(fx, gx, core.Sub) + assert.Equal(t, polySub.Eval(x), fxSubgx) + + polyMul := f.Multiply(&g) + fxMulgx := vecOp(fx, gx, core.Mul) + assert.Equal(t, polyMul.Eval(x), fxMulgx) + + s1 := rand() + polMulS1 := f.MultiplyByScalar(s1) + assert.Equal(t, polMulS1.Eval(x), vecOp(fx, s1, core.Mul)) + + s2 := rand() + polMulS2 := f.MultiplyByScalar(s2) + assert.Equal(t, polMulS2.Eval(x), vecOp(fx, s2, core.Mul)) +} + +func TestPolyMonomials(t *testing.T) { + var zero bn254.ScalarField + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements([]bn254.ScalarField{one, zero, two})) + x := rand() + + fx := f.Eval(x) + f.AddMonomial(three, 1) + fxAdded := f.Eval(x) + assert.Equal(t, fxAdded, vecOp(fx, vecOp(three, x, core.Mul), core.Add)) + + f.SubMonomial(one, 0) + fxSub := f.Eval(x) + assert.Equal(t, fxSub, vecOp(fxAdded, one, core.Sub)) +} + +func TestPolyReadCoeffs(t *testing.T) { + var f polynomial.DensePolynomial + coeffs := core.HostSliceFromElements([]bn254.ScalarField{one, two, three, four}) + f.CreateFromCoeffecitients(coeffs) + coeffsCopied := make(core.HostSlice[bn254.ScalarField], coeffs.Len()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsCopied) + assert.ElementsMatch(t, coeffs, coeffsCopied) + + var coeffsDevice core.DeviceSlice + coeffsDevice.Malloc(coeffs.Len()*one.Size(), one.Size()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsDevice) + coeffsHost := make(core.HostSlice[bn254.ScalarField], coeffs.Len()) + coeffsHost.CopyFromDevice(&coeffsDevice) + + assert.ElementsMatch(t, coeffs, coeffsHost) +} + +func TestPolyOddEvenSlicing(t *testing.T) { + size := 1<<10 - 3 + f := randomPoly(size) + + even := f.Even() + odd := f.Odd() + assert.Equal(t, f.Degree(), even.Degree()+odd.Degree()+1) + + x := rand() + var evenExpected, oddExpected bn254.ScalarField + for i := size; i >= 0; i-- { + if i%2 == 0 { + mul := vecOp(evenExpected, x, core.Mul) + evenExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } else { + mul := vecOp(oddExpected, x, core.Mul) + oddExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } + } + + evenEvaled := even.Eval(x) + assert.Equal(t, evenExpected, evenEvaled) + + oddEvaled := odd.Eval(x) + assert.Equal(t, oddExpected, oddEvaled) +} + +func TestPolynomialDivision(t *testing.T) { + // divide f(x)/g(x), compute q(x), r(x) and check f(x)=q(x)*g(x)+r(x) + var f, g polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements(bn254.GenerateScalars(1 << 4))) + g.CreateFromCoeffecitients(core.HostSliceFromElements(bn254.GenerateScalars(1 << 2))) + + q, r := f.Divide(&g) + + qMulG := q.Multiply(&g) + fRecon := qMulG.Add(&r) + + x := bn254.GenerateScalars(1)[0] + fEval := f.Eval(x) + fReconEval := fRecon.Eval(x) + assert.Equal(t, fEval, fReconEval) +} + +func TestDivideByVanishing(t *testing.T) { + // poly of x^4-1 vanishes ad 4th rou + var zero bn254.ScalarField + minus_one := vecOp(zero, one, core.Sub) + coeffs := core.HostSliceFromElements([]bn254.ScalarField{minus_one, zero, zero, zero, one}) // x^4-1 + var v polynomial.DensePolynomial + v.CreateFromCoeffecitients(coeffs) + + f := randomPoly(1 << 3) + + fv := f.Multiply(&v) + fDegree := f.Degree() + fvDegree := fv.Degree() + assert.Equal(t, fDegree+4, fvDegree) + + fReconstructed := fv.DivideByVanishing(4) + assert.Equal(t, fDegree, fReconstructed.Degree()) + + x := rand() + assert.Equal(t, f.Eval(x), fReconstructed.Eval(x)) +} + +// func TestPolySlice(t *testing.T) { +// size := 4 +// coeffs := bn254.GenerateScalars(size) +// var f DensePolynomial +// f.CreateFromCoeffecitients(coeffs) +// fSlice := f.AsSlice() +// assert.True(t, fSlice.IsOnDevice()) +// assert.Equal(t, size, fSlice.Len()) + +// hostSlice := make(core.HostSlice[bn254.ScalarField], size) +// hostSlice.CopyFromDevice(fSlice) +// assert.Equal(t, coeffs, hostSlice) + +// cfg := ntt.GetDefaultNttConfig() +// res := make(core.HostSlice[bn254.ScalarField], size) +// ntt.Ntt(fSlice, core.KForward, cfg, res) + +// assert.Equal(t, f.Eval(one), res[0]) +// } diff --git a/wrappers/golang/curves/bn254/scalar_field_test.go b/wrappers/golang/curves/bn254/tests/scalar_field_test.go similarity index 59% rename from wrappers/golang/curves/bn254/scalar_field_test.go rename to wrappers/golang/curves/bn254/tests/scalar_field_test.go index 0b3dcca4..04a1e6d2 100644 --- a/wrappers/golang/curves/bn254/scalar_field_test.go +++ b/wrappers/golang/curves/bn254/tests/scalar_field_test.go @@ -1,35 +1,41 @@ -package bn254 +package tests import ( "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + SCALAR_LIMBS = bn254.SCALAR_LIMBS +) + func TestScalarFieldFromLimbs(t *testing.T) { - emptyField := ScalarField{} - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + emptyField := bn254.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the ScalarField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestScalarFieldGetLimbs(t *testing.T) { - emptyField := ScalarField{} - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + emptyField := bn254.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") } func TestScalarFieldOne(t *testing.T) { - var emptyField ScalarField + var emptyField bn254.ScalarField emptyField.One() - limbOne := generateLimbOne(int(SCALAR_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(SCALAR_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -37,12 +43,12 @@ func TestScalarFieldOne(t *testing.T) { } func TestScalarFieldZero(t *testing.T) { - var emptyField ScalarField + var emptyField bn254.ScalarField emptyField.Zero() limbsZero := make([]uint32, SCALAR_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -50,24 +56,24 @@ func TestScalarFieldZero(t *testing.T) { } func TestScalarFieldSize(t *testing.T) { - var emptyField ScalarField - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + var emptyField bn254.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestScalarFieldAsPointer(t *testing.T) { - var emptyField ScalarField - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + var emptyField bn254.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestScalarFieldFromBytes(t *testing.T) { - var emptyField ScalarField - bytes, expected := generateBytesArray(int(SCALAR_LIMBS)) + var emptyField bn254.ScalarField + bytes, expected := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -75,39 +81,39 @@ func TestScalarFieldFromBytes(t *testing.T) { } func TestScalarFieldToBytes(t *testing.T) { - var emptyField ScalarField - expected, limbs := generateBytesArray(int(SCALAR_LIMBS)) + var emptyField bn254.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") } -func TestGenerateScalars(t *testing.T) { +func TestBn254GenerateScalars(t *testing.T) { const numScalars = 8 - scalars := GenerateScalars(numScalars) + scalars := bn254.GenerateScalars(numScalars) assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) assert.Equal(t, numScalars, scalars.Len()) - zeroScalar := ScalarField{} + zeroScalar := bn254.ScalarField{} assert.NotContains(t, scalars, zeroScalar) } -func TestMongtomeryConversion(t *testing.T) { +func TestBn254MongtomeryConversion(t *testing.T) { size := 1 << 15 - scalars := GenerateScalars(size) + scalars := bn254.GenerateScalars(size) var deviceScalars core.DeviceSlice scalars.CopyToDevice(&deviceScalars, true) - ToMontgomery(&deviceScalars) + bn254.ToMontgomery(&deviceScalars) - scalarsMontHost := GenerateScalars(size) + scalarsMontHost := bn254.GenerateScalars(size) scalarsMontHost.CopyFromDevice(&deviceScalars) assert.NotEqual(t, scalars, scalarsMontHost) - FromMontgomery(&deviceScalars) + bn254.FromMontgomery(&deviceScalars) scalarsMontHost.CopyFromDevice(&deviceScalars) assert.Equal(t, scalars, scalarsMontHost) diff --git a/wrappers/golang/curves/bn254/tests/vec_ops_test.go b/wrappers/golang/curves/bn254/tests/vec_ops_test.go new file mode 100644 index 00000000..ac121cd8 --- /dev/null +++ b/wrappers/golang/curves/bn254/tests/vec_ops_test.go @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bn254 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bn254/vecOps" + "github.com/stretchr/testify/assert" +) + +func TestBn254VecOps(t *testing.T) { + testSize := 1 << 14 + + a := bn254.GenerateScalars(testSize) + b := bn254.GenerateScalars(testSize) + var scalar bn254.ScalarField + scalar.One() + ones := core.HostSliceWithValue(scalar, testSize) + + out := make(core.HostSlice[bn254.ScalarField], testSize) + out2 := make(core.HostSlice[bn254.ScalarField], testSize) + out3 := make(core.HostSlice[bn254.ScalarField], testSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.VecOp(a, b, out, cfg, core.Add) + vecOps.VecOp(out, b, out2, cfg, core.Sub) + + assert.Equal(t, a, out2) + + vecOps.VecOp(a, ones, out3, cfg, core.Mul) + + assert.Equal(t, a, out3) +} + +func TestBn254Transpose(t *testing.T) { + rowSize := 1 << 6 + columnSize := 1 << 8 + onDevice := false + isAsync := false + + matrix := bn254.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[bn254.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[bn254.ScalarField], rowSize*columnSize) + + ctx, _ := cr.GetDefaultDeviceContext() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, ctx, onDevice, isAsync) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + onDevice = true + + matrix.CopyToDevice(&dMatrix, true) + dOut.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + dOut2.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + + vecOps.TransposeMatrix(dMatrix, dOut, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, ctx, onDevice, isAsync) + output := make(core.HostSlice[bn254.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang/curves/bn254/include/vec_ops.h b/wrappers/golang/curves/bn254/vecOps/include/vec_ops.h similarity index 51% rename from wrappers/golang/curves/bn254/include/vec_ops.h rename to wrappers/golang/curves/bn254/vecOps/include/vec_ops.h index 88efcc56..5735fce5 100644 --- a/wrappers/golang/curves/bn254/include/vec_ops.h +++ b/wrappers/golang/curves/bn254/vecOps/include/vec_ops.h @@ -1,5 +1,5 @@ #include -#include "../../include/types.h" +#include #ifndef _BN254_VEC_OPS_H #define _BN254_VEC_OPS_H @@ -8,7 +8,11 @@ extern "C" { #endif -cudaError_t bn254MulCuda( +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bn254_mul_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -16,7 +20,7 @@ cudaError_t bn254MulCuda( scalar_t* result ); -cudaError_t bn254AddCuda( +cudaError_t bn254_add_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -24,7 +28,7 @@ cudaError_t bn254AddCuda( scalar_t* result ); -cudaError_t bn254SubCuda( +cudaError_t bn254_sub_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -32,6 +36,16 @@ cudaError_t bn254SubCuda( scalar_t* result ); +cudaError_t bn254_transpose_matrix_cuda( + scalar_t* mat_in, + int row_size, + int column_size, + scalar_t* mat_out, + DeviceContext* ctx, + bool on_device, + bool is_async +); + #ifdef __cplusplus } #endif diff --git a/wrappers/golang/curves/bn254/vecOps/vec_ops.go b/wrappers/golang/curves/bn254/vecOps/vec_ops.go new file mode 100644 index 00000000..bf7f0efd --- /dev/null +++ b/wrappers/golang/curves/bn254/vecOps/vec_ops.go @@ -0,0 +1,48 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + + "unsafe" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { + aPointer, bPointer, outPointer, cfgPointer, size := core.VecOpCheck(a, b, out, &config) + + cA := (*C.scalar_t)(aPointer) + cB := (*C.scalar_t)(bPointer) + cOut := (*C.scalar_t)(outPointer) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cSize := (C.int)(size) + + switch op { + case core.Sub: + ret = (cr.CudaError)(C.bn254_sub_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (cr.CudaError)(C.bn254_add_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (cr.CudaError)(C.bn254_mul_cuda(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError) { + core.TransposeCheck(in, out, onDevice) + + cIn := (*C.scalar_t)(in.AsUnsafePointer()) + cOut := (*C.scalar_t)(out.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cOnDevice := (C._Bool)(onDevice) + cIsAsync := (C._Bool)(isAsync) + + err := (cr.CudaError)(C.bn254_transpose_matrix_cuda(cIn, cRowSize, cColumnSize, cOut, cCtx, cOnDevice, cIsAsync)) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bn254/vec_ops.go b/wrappers/golang/curves/bn254/vec_ops.go deleted file mode 100644 index 78f9b917..00000000 --- a/wrappers/golang/curves/bn254/vec_ops.go +++ /dev/null @@ -1,55 +0,0 @@ -package bn254 - -// #cgo CFLAGS: -I./include/ -// #include "vec_ops.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { - core.VecOpCheck(a, b, out, &config) - var cA, cB, cOut *C.scalar_t - - if a.IsOnDevice() { - aDevice := a.(core.DeviceSlice) - aDevice.CheckDevice() - cA = (*C.scalar_t)(aDevice.AsPointer()) - } else { - cA = (*C.scalar_t)(unsafe.Pointer(&a.(core.HostSlice[ScalarField])[0])) - } - - if b.IsOnDevice() { - bDevice := b.(core.DeviceSlice) - bDevice.CheckDevice() - cB = (*C.scalar_t)(bDevice.AsPointer()) - } else { - cB = (*C.scalar_t)(unsafe.Pointer(&b.(core.HostSlice[ScalarField])[0])) - } - - if out.IsOnDevice() { - outDevice := out.(core.DeviceSlice) - outDevice.CheckDevice() - cOut = (*C.scalar_t)(outDevice.AsPointer()) - } else { - cOut = (*C.scalar_t)(unsafe.Pointer(&out.(core.HostSlice[ScalarField])[0])) - } - - cConfig := (*C.VecOpsConfig)(unsafe.Pointer(&config)) - cSize := (C.int)(a.Len()) - - switch op { - case core.Sub: - ret = (cr.CudaError)(C.bn254SubCuda(cA, cB, cSize, cConfig, cOut)) - case core.Add: - ret = (cr.CudaError)(C.bn254AddCuda(cA, cB, cSize, cConfig, cOut)) - case core.Mul: - ret = (cr.CudaError)(C.bn254MulCuda(cA, cB, cSize, cConfig, cOut)) - } - - return ret -} diff --git a/wrappers/golang/curves/bn254/vec_ops_test.go b/wrappers/golang/curves/bn254/vec_ops_test.go deleted file mode 100644 index 022ecf99..00000000 --- a/wrappers/golang/curves/bn254/vec_ops_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package bn254 - -import ( - "testing" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - "github.com/stretchr/testify/assert" -) - -func TestVecOps(t *testing.T) { - testSize := 1 << 14 - - a := GenerateScalars(testSize) - b := GenerateScalars(testSize) - var scalar ScalarField - scalar.One() - ones := core.HostSliceWithValue(scalar, testSize) - - out := make(core.HostSlice[ScalarField], testSize) - out2 := make(core.HostSlice[ScalarField], testSize) - out3 := make(core.HostSlice[ScalarField], testSize) - - cfg := core.DefaultVecOpsConfig() - - VecOp(a, b, out, cfg, core.Add) - VecOp(out, b, out2, cfg, core.Sub) - - assert.Equal(t, a, out2) - - VecOp(a, ones, out3, cfg, core.Mul) - - assert.Equal(t, a, out3) -} diff --git a/wrappers/golang/curves/bw6761/base_field.go b/wrappers/golang/curves/bw6761/base_field.go index ff2700a0..b401e49d 100644 --- a/wrappers/golang/curves/bw6761/base_field.go +++ b/wrappers/golang/curves/bw6761/base_field.go @@ -6,7 +6,7 @@ import ( ) const ( - BASE_LIMBS int8 = 24 + BASE_LIMBS int = 24 ) type BaseField struct { @@ -29,6 +29,11 @@ func (f BaseField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *BaseField) FromUint32(v uint32) BaseField { + f.limbs[0] = v + return *f +} + func (f *BaseField) FromLimbs(limbs []uint32) BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") diff --git a/wrappers/golang/curves/bw6761/bw6_761.go b/wrappers/golang/curves/bw6761/bw6_761.go deleted file mode 100644 index 4e679ffe..00000000 --- a/wrappers/golang/curves/bw6761/bw6_761.go +++ /dev/null @@ -1,4 +0,0 @@ -package bw6761 - -// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build -lingo_bw6_761 -lstdc++ -lm -import "C" diff --git a/wrappers/golang/curves/bw6761/curve.go b/wrappers/golang/curves/bw6761/curve.go index 3c492e66..7fc3e2a3 100644 --- a/wrappers/golang/curves/bw6761/curve.go +++ b/wrappers/golang/curves/bw6761/curve.go @@ -53,7 +53,7 @@ func (p *Projective) FromAffine(a Affine) Projective { func (p Projective) ProjectiveEq(p2 *Projective) bool { cP := (*C.projective_t)(unsafe.Pointer(&p)) cP2 := (*C.projective_t)(unsafe.Pointer(&p2)) - __ret := C.bw6_761Eq(cP, cP2) + __ret := C.bw6_761_eq(cP, cP2) return __ret == (C._Bool)(true) } @@ -62,7 +62,7 @@ func (p *Projective) ProjectiveToAffine() Affine { cA := (*C.affine_t)(unsafe.Pointer(&a)) cP := (*C.projective_t)(unsafe.Pointer(&p)) - C.bw6_761ToAffine(cP, cA) + C.bw6_761_to_affine(cP, cA) return a } @@ -75,7 +75,7 @@ func GenerateProjectivePoints(size int) core.HostSlice[Projective] { pointsSlice := core.HostSliceFromElements[Projective](points) pPoints := (*C.projective_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bw6_761GenerateProjectivePoints(pPoints, cSize) + C.bw6_761_generate_projective_points(pPoints, cSize) return pointsSlice } @@ -129,18 +129,18 @@ func GenerateAffinePoints(size int) core.HostSlice[Affine] { pointsSlice := core.HostSliceFromElements[Affine](points) cPoints := (*C.affine_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bw6_761GenerateAffinePoints(cPoints, cSize) + C.bw6_761_generate_affine_points(cPoints, cSize) return pointsSlice } func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.affine_t)(points.AsPointer()) + cValues := (*C.affine_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bw6_761AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bw6_761_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } @@ -156,12 +156,12 @@ func AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { } func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.projective_t)(points.AsPointer()) + cValues := (*C.projective_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bw6_761ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bw6_761_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bw6761/ecntt/ecntt.go b/wrappers/golang/curves/bw6761/ecntt/ecntt.go new file mode 100644 index 00000000..29b2a539 --- /dev/null +++ b/wrappers/golang/curves/bw6761/ecntt/ecntt.go @@ -0,0 +1,24 @@ +package ecntt + +// #cgo CFLAGS: -I./include/ +// #include "ecntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + pointsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](points, cfg, results) + + cPoints := (*C.projective_t)(pointsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.projective_t)(resultsPointer) + + __ret := C.bw6_761_ecntt_cuda(cPoints, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bw6761/ecntt/include/ecntt.h b/wrappers/golang/curves/bw6761/ecntt/include/ecntt.h new file mode 100644 index 00000000..9c0cf0ef --- /dev/null +++ b/wrappers/golang/curves/bw6761/ecntt/include/ecntt.h @@ -0,0 +1,19 @@ +#include + +#ifndef _BW6_761_ECNTT_H +#define _BW6_761_ECNTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct NTTConfig NTTConfig; +typedef struct projective_t projective_t; + +cudaError_t bw6_761_ecntt_cuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bw6761/g2_curve.go b/wrappers/golang/curves/bw6761/g2/curve.go similarity index 88% rename from wrappers/golang/curves/bw6761/g2_curve.go rename to wrappers/golang/curves/bw6761/g2/curve.go index 035bbd94..ccffe49f 100644 --- a/wrappers/golang/curves/bw6761/g2_curve.go +++ b/wrappers/golang/curves/bw6761/g2/curve.go @@ -1,9 +1,7 @@ -//go:build g2 - -package bw6761 +package g2 // #cgo CFLAGS: -I./include/ -// #include "g2_curve.h" +// #include "curve.h" import "C" import ( @@ -55,7 +53,7 @@ func (p *G2Projective) FromAffine(a G2Affine) G2Projective { func (p G2Projective) ProjectiveEq(p2 *G2Projective) bool { cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) cP2 := (*C.g2_projective_t)(unsafe.Pointer(&p2)) - __ret := C.bw6_761G2Eq(cP, cP2) + __ret := C.bw6_761_g2_eq(cP, cP2) return __ret == (C._Bool)(true) } @@ -64,7 +62,7 @@ func (p *G2Projective) ProjectiveToAffine() G2Affine { cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) - C.bw6_761G2ToAffine(cP, cA) + C.bw6_761_g2_to_affine(cP, cA) return a } @@ -77,7 +75,7 @@ func G2GenerateProjectivePoints(size int) core.HostSlice[G2Projective] { pointsSlice := core.HostSliceFromElements[G2Projective](points) pPoints := (*C.g2_projective_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bw6_761G2GenerateProjectivePoints(pPoints, cSize) + C.bw6_761_g2_generate_projective_points(pPoints, cSize) return pointsSlice } @@ -131,18 +129,18 @@ func G2GenerateAffinePoints(size int) core.HostSlice[G2Affine] { pointsSlice := core.HostSliceFromElements[G2Affine](points) cPoints := (*C.g2_affine_t)(unsafe.Pointer(&pointsSlice[0])) cSize := (C.int)(size) - C.bw6_761G2GenerateAffinePoints(cPoints, cSize) + C.bw6_761_g2_generate_affine_points(cPoints, cSize) return pointsSlice } func convertG2AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.g2_affine_t)(points.AsPointer()) + cValues := (*C.g2_affine_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bw6_761G2AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bw6_761_g2_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } @@ -158,12 +156,12 @@ func G2AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { } func convertG2ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.g2_projective_t)(points.AsPointer()) + cValues := (*C.g2_projective_t)(points.AsUnsafePointer()) cSize := (C.size_t)(points.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bw6_761G2ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bw6_761_g2_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bls12381/g2_base_field.go b/wrappers/golang/curves/bw6761/g2/g2base_field.go similarity index 86% rename from wrappers/golang/curves/bls12381/g2_base_field.go rename to wrappers/golang/curves/bw6761/g2/g2base_field.go index e0b15d1f..c4073af9 100644 --- a/wrappers/golang/curves/bls12381/g2_base_field.go +++ b/wrappers/golang/curves/bw6761/g2/g2base_field.go @@ -1,6 +1,4 @@ -//go:build g2 - -package bls12381 +package g2 import ( "encoding/binary" @@ -8,19 +6,19 @@ import ( ) const ( - G2_BASE_LIMBS int8 = 24 + G2BASE_LIMBS int = 24 ) type G2BaseField struct { - limbs [G2_BASE_LIMBS]uint32 + limbs [G2BASE_LIMBS]uint32 } func (f G2BaseField) Len() int { - return int(G2_BASE_LIMBS) + return int(G2BASE_LIMBS) } func (f G2BaseField) Size() int { - return int(G2_BASE_LIMBS * 4) + return int(G2BASE_LIMBS * 4) } func (f G2BaseField) GetLimbs() []uint32 { @@ -31,6 +29,11 @@ func (f G2BaseField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *G2BaseField) FromUint32(v uint32) G2BaseField { + f.limbs[0] = v + return *f +} + func (f *G2BaseField) FromLimbs(limbs []uint32) G2BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") diff --git a/wrappers/golang/curves/bw6761/g2/include/curve.h b/wrappers/golang/curves/bw6761/g2/include/curve.h new file mode 100644 index 00000000..b57b55cf --- /dev/null +++ b/wrappers/golang/curves/bw6761/g2/include/curve.h @@ -0,0 +1,26 @@ +#include +#include + +#ifndef _BW6_761_G2CURVE_H +#define _BW6_761_G2CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct g2_projective_t g2_projective_t; +typedef struct g2_affine_t g2_affine_t; +typedef struct DeviceContext DeviceContext; + +bool bw6_761_g2_eq(g2_projective_t* point1, g2_projective_t* point2); +void bw6_761_g2_to_affine(g2_projective_t* point, g2_affine_t* point_out); +void bw6_761_g2_generate_projective_points(g2_projective_t* points, int size); +void bw6_761_g2_generate_affine_points(g2_affine_t* points, int size); +cudaError_t bw6_761_g2_affine_convert_montgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t bw6_761_g2_projective_convert_montgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bw6761/g2/include/msm.h b/wrappers/golang/curves/bw6761/g2/include/msm.h new file mode 100644 index 00000000..1340217b --- /dev/null +++ b/wrappers/golang/curves/bw6761/g2/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _BW6_761_G2MSM_H +#define _BW6_761_G2MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct g2_projective_t g2_projective_t; +typedef struct g2_affine_t g2_affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bw6_761_g2_msm_cuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); +cudaError_t bw6_761_g2_precompute_msm_bases_cuda(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bw6761/g2/include/scalar_field.h b/wrappers/golang/curves/bw6761/g2/include/scalar_field.h new file mode 100644 index 00000000..4a480078 --- /dev/null +++ b/wrappers/golang/curves/bw6761/g2/include/scalar_field.h @@ -0,0 +1,21 @@ +#include +#include + +#ifndef _BW6_761_FIELD_H +#define _BW6_761_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void bw6_761_generate_scalars(scalar_t* scalars, int size); +cudaError_t bw6_761_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bw6761/g2/msm.go b/wrappers/golang/curves/bw6761/g2/msm.go new file mode 100644 index 00000000..98ac3af3 --- /dev/null +++ b/wrappers/golang/curves/bw6761/g2/msm.go @@ -0,0 +1,45 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func G2GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.g2_affine_t)(pointsPointer) + cResults := (*C.g2_projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.bw6_761_g2_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.g2_affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.g2_affine_t)(outputBasesPointer) + + __ret := C.bw6_761_g2_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/bw6761/g2_msm.go b/wrappers/golang/curves/bw6761/g2_msm.go deleted file mode 100644 index 6043e316..00000000 --- a/wrappers/golang/curves/bw6761/g2_msm.go +++ /dev/null @@ -1,82 +0,0 @@ -//go:build g2 - -package bw6761 - -// #cgo CFLAGS: -I./include/ -// #include "g2_msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func G2GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[G2Affine])[0]) - } - cPoints := (*C.g2_affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[G2Projective])[0]) - } - cResults := (*C.g2_projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.bw6_761G2MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func G2PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[G2Affine])[0]) - } - cPoints := (*C.g2_affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.g2_affine_t)(outputBasesPointer) - - __ret := C.bw6_761G2PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/curves/bw6761/helpers_test.go b/wrappers/golang/curves/bw6761/helpers_test.go deleted file mode 100644 index bb3d9e91..00000000 --- a/wrappers/golang/curves/bw6761/helpers_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package bw6761 - -import ( - "math/rand" -) - -func generateRandomLimb(size int) []uint32 { - limbs := make([]uint32, size) - for i := range limbs { - limbs[i] = rand.Uint32() - } - return limbs -} - -func generateLimbOne(size int) []uint32 { - limbs := make([]uint32, size) - limbs[0] = 1 - return limbs -} - -func generateBytesArray(size int) ([]byte, []uint32) { - baseBytes := []byte{1, 2, 3, 4} - var bytes []byte - var limbs []uint32 - for i := 0; i < size; i++ { - bytes = append(bytes, baseBytes...) - limbs = append(limbs, 67305985) - } - - return bytes, limbs -} diff --git a/wrappers/golang/curves/bw6761/include/curve.h b/wrappers/golang/curves/bw6761/include/curve.h index 4630e616..cc6e401c 100644 --- a/wrappers/golang/curves/bw6761/include/curve.h +++ b/wrappers/golang/curves/bw6761/include/curve.h @@ -1,5 +1,4 @@ #include -#include "../../include/types.h" #include #ifndef _BW6_761_CURVE_H @@ -9,12 +8,16 @@ extern "C" { #endif -bool bw6_761Eq(projective_t* point1, projective_t* point2); -void bw6_761ToAffine(projective_t* point, affine_t* point_out); -void bw6_761GenerateProjectivePoints(projective_t* points, int size); -void bw6_761GenerateAffinePoints(affine_t* points, int size); -cudaError_t bw6_761AffineConvertMontgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t bw6_761ProjectiveConvertMontgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct DeviceContext DeviceContext; + +bool bw6_761_eq(projective_t* point1, projective_t* point2); +void bw6_761_to_affine(projective_t* point, affine_t* point_out); +void bw6_761_generate_projective_points(projective_t* points, int size); +void bw6_761_generate_affine_points(affine_t* points, int size); +cudaError_t bw6_761_affine_convert_montgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t bw6_761_projective_convert_montgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); #ifdef __cplusplus } diff --git a/wrappers/golang/curves/bw6761/include/g2_curve.h b/wrappers/golang/curves/bw6761/include/g2_curve.h deleted file mode 100644 index 55ed7044..00000000 --- a/wrappers/golang/curves/bw6761/include/g2_curve.h +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BW6_761_G2CURVE_H -#define _BW6_761_G2CURVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -bool bw6_761G2Eq(g2_projective_t* point1, g2_projective_t* point2); -void bw6_761G2ToAffine(g2_projective_t* point, g2_affine_t* point_out); -void bw6_761G2GenerateProjectivePoints(g2_projective_t* points, int size); -void bw6_761G2GenerateAffinePoints(g2_affine_t* points, int size); -cudaError_t bw6_761G2AffineConvertMontgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t bw6_761G2ProjectiveConvertMontgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bw6761/include/g2_msm.h b/wrappers/golang/curves/bw6761/include/g2_msm.h deleted file mode 100644 index 20f56b39..00000000 --- a/wrappers/golang/curves/bw6761/include/g2_msm.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BW6_761_G2MSM_H -#define _BW6_761_G2MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bw6_761G2MSMCuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); -cudaError_t bw6_761G2PrecomputeMSMBases(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bw6761/include/msm.h b/wrappers/golang/curves/bw6761/include/msm.h deleted file mode 100644 index 097e442e..00000000 --- a/wrappers/golang/curves/bw6761/include/msm.h +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BW6_761_MSM_H -#define _BW6_761_MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bw6_761MSMCuda(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); -cudaError_t bw6_761PrecomputeMSMBases(affine_t* points, int bases_size, int precompute_factor, int _c, bool are_bases_on_device, DeviceContext* ctx, affine_t* output_bases); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/curves/bw6761/include/ntt.h b/wrappers/golang/curves/bw6761/include/ntt.h deleted file mode 100644 index 3a6d795a..00000000 --- a/wrappers/golang/curves/bw6761/include/ntt.h +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _BW6_761_NTT_H -#define _BW6_761_NTT_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t bw6_761NTTCuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); -cudaError_t bw6_761ECNTTCuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); -cudaError_t bw6_761InitializeDomain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); -cudaError_t bw6_761ReleaseDomain(DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bw6761/include/scalar_field.h b/wrappers/golang/curves/bw6761/include/scalar_field.h index 65512600..4a480078 100644 --- a/wrappers/golang/curves/bw6761/include/scalar_field.h +++ b/wrappers/golang/curves/bw6761/include/scalar_field.h @@ -1,5 +1,4 @@ #include -#include "../../include/types.h" #include #ifndef _BW6_761_FIELD_H @@ -9,8 +8,11 @@ extern "C" { #endif -void bw6_761GenerateScalars(scalar_t* scalars, int size); -cudaError_t bw6_761ScalarConvertMontgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void bw6_761_generate_scalars(scalar_t* scalars, int size); +cudaError_t bw6_761_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); #ifdef __cplusplus } diff --git a/wrappers/golang/curves/bw6761/main.go b/wrappers/golang/curves/bw6761/main.go new file mode 100644 index 00000000..9e7f9462 --- /dev/null +++ b/wrappers/golang/curves/bw6761/main.go @@ -0,0 +1,4 @@ +package bw6761 + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build/lib -lingo_curve_bw6_761 -lingo_field_bw6_761 -lstdc++ -lm +import "C" diff --git a/wrappers/golang/curves/bw6761/msm.go b/wrappers/golang/curves/bw6761/msm.go deleted file mode 100644 index 83b550b5..00000000 --- a/wrappers/golang/curves/bw6761/msm.go +++ /dev/null @@ -1,80 +0,0 @@ -package bw6761 - -// #cgo CFLAGS: -I./include/ -// #include "msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Affine])[0]) - } - cPoints := (*C.affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.bw6_761MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Affine])[0]) - } - cPoints := (*C.affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.affine_t)(outputBasesPointer) - - __ret := C.bw6_761PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/curves/bw6761/msm/include/msm.h b/wrappers/golang/curves/bw6761/msm/include/msm.h new file mode 100644 index 00000000..c3936122 --- /dev/null +++ b/wrappers/golang/curves/bw6761/msm/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _BW6_761_MSM_H +#define _BW6_761_MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bw6_761_msm_cuda(const scalar_t* scalars,const affine_t* points, int count, MSMConfig* config, projective_t* out); +cudaError_t bw6_761_precompute_msm_bases_cuda(affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/bw6761/msm/msm.go b/wrappers/golang/curves/bw6761/msm/msm.go new file mode 100644 index 00000000..9c0af155 --- /dev/null +++ b/wrappers/golang/curves/bw6761/msm/msm.go @@ -0,0 +1,45 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.affine_t)(pointsPointer) + cResults := (*C.projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.bw6_761_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.bw6_761_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/bw6761/ntt.go b/wrappers/golang/curves/bw6761/ntt.go deleted file mode 100644 index ceea0084..00000000 --- a/wrappers/golang/curves/bw6761/ntt.go +++ /dev/null @@ -1,97 +0,0 @@ -package bw6761 - -// #cgo CFLAGS: -I./include/ -// #include "ntt.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func GetDefaultNttConfig() core.NTTConfig[[SCALAR_LIMBS]uint32] { - cosetGenField := ScalarField{} - cosetGenField.One() - var cosetGen [SCALAR_LIMBS]uint32 - for i, v := range cosetGenField.GetLimbs() { - cosetGen[i] = v - } - - return core.GetDefaultNTTConfig(cosetGen) -} - -func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](scalars, cfg, results) - - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - cSize := (C.int)(scalars.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[ScalarField])[0]) - } - cResults := (*C.scalar_t)(resultsPointer) - - __ret := C.bw6_761NTTCuda(cScalars, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](points, cfg, results) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Projective])[0]) - } - cPoints := (*C.projective_t)(pointsPointer) - cSize := (C.int)(points.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsPointer = results.(core.DeviceSlice).AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - __ret := C.bw6_761ECNTTCuda(cPoints, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func InitDomain(primitiveRoot ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { - cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - cFastTwiddles := (C._Bool)(fastTwiddles) - __ret := C.bw6_761InitializeDomain(cPrimitiveRoot, cCtx, cFastTwiddles) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - __ret := C.bw6_761ReleaseDomain(cCtx) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} diff --git a/wrappers/golang/curves/bw6761/ntt/include/ntt.h b/wrappers/golang/curves/bw6761/ntt/include/ntt.h new file mode 100644 index 00000000..aea1223e --- /dev/null +++ b/wrappers/golang/curves/bw6761/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#include +#include + +#ifndef _BW6_761_NTT_H +#define _BW6_761_NTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct NTTConfig NTTConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bw6_761_ntt_cuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +cudaError_t bw6_761_initialize_domain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); +cudaError_t bw6_761_release_domain(DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/curves/bw6761/ntt/ntt.go b/wrappers/golang/curves/bw6761/ntt/ntt.go new file mode 100644 index 00000000..2206092c --- /dev/null +++ b/wrappers/golang/curves/bw6761/ntt/ntt.go @@ -0,0 +1,56 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" +) + +import ( + "unsafe" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + scalarsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](scalars, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.scalar_t)(resultsPointer) + + __ret := C.bw6_761_ntt_cuda(cScalars, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func GetDefaultNttConfig() core.NTTConfig[[bw6_761.SCALAR_LIMBS]uint32] { + cosetGenField := bw6_761.ScalarField{} + cosetGenField.One() + var cosetGen [bw6_761.SCALAR_LIMBS]uint32 + for i, v := range cosetGenField.GetLimbs() { + cosetGen[i] = v + } + + return core.GetDefaultNTTConfig(cosetGen) +} + +func InitDomain(primitiveRoot bw6_761.ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cFastTwiddles := (C._Bool)(fastTwiddles) + __ret := C.bw6_761_initialize_domain(cPrimitiveRoot, cCtx, cFastTwiddles) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + __ret := C.bw6_761_release_domain(cCtx) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bw6761/polynomial/include/polynomial.h b/wrappers/golang/curves/bw6761/polynomial/include/polynomial.h new file mode 100644 index 00000000..2568e51f --- /dev/null +++ b/wrappers/golang/curves/bw6761/polynomial/include/polynomial.h @@ -0,0 +1,51 @@ +#include +#include + +#ifndef _BW6_761_POLY_H +#define _BW6_761_POLY_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct PolynomialInst PolynomialInst; +typedef struct IntegrityPointer IntegrityPointer; + +bool bw6_761_polynomial_init_cuda_backend(); +PolynomialInst* bw6_761_polynomial_create_from_coefficients(scalar_t* coeffs, size_t size); +PolynomialInst* bw6_761_polynomial_create_from_rou_evaluations(scalar_t* evals, size_t size); +PolynomialInst* bw6_761_polynomial_clone(const PolynomialInst* p); +void bw6_761_polynomial_print(PolynomialInst* p); +void bw6_761_polynomial_delete(PolynomialInst* instance); +PolynomialInst* bw6_761_polynomial_add(const PolynomialInst* a, const PolynomialInst* b); +void bw6_761_polynomial_add_inplace(PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bw6_761_polynomial_subtract(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bw6_761_polynomial_multiply(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bw6_761_polynomial_multiply_by_scalar(const PolynomialInst* a, const scalar_t* scalar); +void bw6_761_polynomial_division(const PolynomialInst* a, const PolynomialInst* b, PolynomialInst** q /*OUT*/, PolynomialInst** r /*OUT*/); +PolynomialInst* bw6_761_polynomial_quotient(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bw6_761_polynomial_remainder(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* bw6_761_polynomial_divide_by_vanishing(const PolynomialInst* p, size_t vanishing_poly_degree); +void bw6_761_polynomial_add_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void bw6_761_polynomial_sub_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void bw6_761_polynomial_evaluate_on_domain(const PolynomialInst* p, scalar_t* domain, size_t domain_size, scalar_t* evals /*OUT*/); +size_t bw6_761_polynomial_degree(PolynomialInst* p); +size_t bw6_761_polynomial_copy_coeffs_range(PolynomialInst* p, scalar_t* memory, size_t start_idx, size_t end_idx); +PolynomialInst* bw6_761_polynomial_even(PolynomialInst* p); +PolynomialInst* bw6_761_polynomial_odd(PolynomialInst* p); + +// scalar_t* bw6_761_polynomial_get_coeffs_raw_ptr(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// PolynomialInst* bw6_761_polynomial_slice(PolynomialInst* p, size_t offset, size_t stride, size_t size); +// IntegrityPointer* bw6_761_polynomial_get_coeff_view(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// IntegrityPointer* bw6_761_polynomial_get_rou_evaluations_view(PolynomialInst* p, size_t nof_evals, bool is_reversed, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// const scalar_t* bw6_761_polynomial_intergrity_ptr_get(IntegrityPointer* p); +// bool bw6_761_polynomial_intergrity_ptr_is_valid(IntegrityPointer* p); +// void bw6_761_polynomial_intergrity_ptr_destroy(IntegrityPointer* p); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/wrappers/golang/curves/bw6761/polynomial/polynomial.go b/wrappers/golang/curves/bw6761/polynomial/polynomial.go new file mode 100644 index 00000000..8c6e0421 --- /dev/null +++ b/wrappers/golang/curves/bw6761/polynomial/polynomial.go @@ -0,0 +1,176 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +func InitPolyBackend() bool { + return (bool)(C.bw6_761_polynomial_init_cuda_backend()) +} + +func (up *DensePolynomial) Print() { + C.bw6_761_polynomial_print(up.handle) +} + +func (up *DensePolynomial) CreateFromCoeffecitients(coeffs core.HostOrDeviceSlice) DensePolynomial { + if coeffs.IsOnDevice() { + coeffs.(core.DeviceSlice).CheckDevice() + } + coeffsPointer := (*C.scalar_t)(coeffs.AsUnsafePointer()) + cSize := (C.size_t)(coeffs.Len()) + up.handle = C.bw6_761_polynomial_create_from_coefficients(coeffsPointer, cSize) + return *up +} + +func (up *DensePolynomial) CreateFromROUEvaluations(evals core.HostOrDeviceSlice) DensePolynomial { + evalsPointer := (*C.scalar_t)(evals.AsUnsafePointer()) + cSize := (C.size_t)(evals.Len()) + up.handle = C.bw6_761_polynomial_create_from_coefficients(evalsPointer, cSize) + return *up +} + +func (up *DensePolynomial) Clone() DensePolynomial { + return DensePolynomial{ + handle: C.bw6_761_polynomial_clone(up.handle), + } +} + +// TODO @jeremyfelder: Maybe this should be in a SetFinalizer that is set on Create functions? +func (up *DensePolynomial) Delete() { + C.bw6_761_polynomial_delete(up.handle) +} + +func (up *DensePolynomial) Add(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bw6_761_polynomial_add(up.handle, b.handle), + } +} + +func (up *DensePolynomial) AddInplace(b *DensePolynomial) { + C.bw6_761_polynomial_add_inplace(up.handle, b.handle) +} + +func (up *DensePolynomial) Subtract(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bw6_761_polynomial_subtract(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Multiply(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bw6_761_polynomial_multiply(up.handle, b.handle), + } +} + +func (up *DensePolynomial) MultiplyByScalar(scalar bw6_761.ScalarField) DensePolynomial { + cScalar := (*C.scalar_t)(unsafe.Pointer(scalar.AsPointer())) + return DensePolynomial{ + handle: C.bw6_761_polynomial_multiply_by_scalar(up.handle, cScalar), + } +} + +func (up *DensePolynomial) Divide(b *DensePolynomial) (DensePolynomial, DensePolynomial) { + var q, r *PolynomialHandle + C.bw6_761_polynomial_division(up.handle, b.handle, &q, &r) + return DensePolynomial{ + handle: q, + }, DensePolynomial{ + handle: r, + } +} + +func (up *DensePolynomial) Quotient(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bw6_761_polynomial_quotient(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Remainder(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.bw6_761_polynomial_remainder(up.handle, b.handle), + } +} + +func (up *DensePolynomial) DivideByVanishing(vanishing_degree uint64) DensePolynomial { + cVanishingDegree := (C.ulong)(vanishing_degree) + return DensePolynomial{ + handle: C.bw6_761_polynomial_divide_by_vanishing(up.handle, cVanishingDegree), + } +} + +func (up *DensePolynomial) AddMonomial(monomialCoeff bw6_761.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]bw6_761.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.bw6_761_polynomial_add_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) SubMonomial(monomialCoeff bw6_761.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]bw6_761.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.bw6_761_polynomial_sub_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) Eval(x bw6_761.ScalarField) bw6_761.ScalarField { + domains := make(core.HostSlice[bw6_761.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[bw6_761.ScalarField], 1) + up.EvalOnDomain(domains, evals) + return evals[0] +} + +func (up *DensePolynomial) EvalOnDomain(domain, evals core.HostOrDeviceSlice) core.HostOrDeviceSlice { + cDomain := (*C.scalar_t)(domain.AsUnsafePointer()) + cDomainSize := (C.size_t)(domain.Len()) + cEvals := (*C.scalar_t)(evals.AsUnsafePointer()) + C.bw6_761_polynomial_evaluate_on_domain(up.handle, cDomain, cDomainSize, cEvals) + return evals +} + +func (up *DensePolynomial) Degree() int { + return int(C.bw6_761_polynomial_degree(up.handle)) +} + +func (up *DensePolynomial) CopyCoeffsRange(start, end int, out core.HostOrDeviceSlice) (int, core.HostOrDeviceSlice) { + cStart := (C.size_t)(start) + cEnd := (C.size_t)(end) + cScalarOut := (*C.scalar_t)(out.AsUnsafePointer()) + __cNumCoeffsRead := C.bw6_761_polynomial_copy_coeffs_range(up.handle, cScalarOut, cStart, cEnd) + return int(__cNumCoeffsRead), out +} + +func (up *DensePolynomial) GetCoeff(idx int) bw6_761.ScalarField { + out := make(core.HostSlice[bw6_761.ScalarField], 1) + up.CopyCoeffsRange(idx, idx, out) + return out[0] +} + +func (up *DensePolynomial) Even() DensePolynomial { + evenPoly := C.bw6_761_polynomial_even(up.handle) + return DensePolynomial{ + handle: evenPoly, + } +} + +func (up *DensePolynomial) Odd() DensePolynomial { + oddPoly := C.bw6_761_polynomial_odd(up.handle) + return DensePolynomial{ + handle: oddPoly, + } +} diff --git a/wrappers/golang/curves/bw6761/scalar_field.go b/wrappers/golang/curves/bw6761/scalar_field.go index 7d2e65b4..4fd80d5e 100644 --- a/wrappers/golang/curves/bw6761/scalar_field.go +++ b/wrappers/golang/curves/bw6761/scalar_field.go @@ -12,7 +12,7 @@ import ( ) const ( - SCALAR_LIMBS int8 = 12 + SCALAR_LIMBS int = 12 ) type ScalarField struct { @@ -35,6 +35,11 @@ func (f ScalarField) AsPointer() *uint32 { return &f.limbs[0] } +func (f *ScalarField) FromUint32(v uint32) ScalarField { + f.limbs[0] = v + return *f +} + func (f *ScalarField) FromLimbs(limbs []uint32) ScalarField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") @@ -89,18 +94,18 @@ func GenerateScalars(size int) core.HostSlice[ScalarField] { cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) cSize := (C.int)(size) - C.bw6_761GenerateScalars(cScalars, cSize) + C.bw6_761_generate_scalars(cScalars, cSize) return scalarSlice } func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.scalar_t)(scalars.AsPointer()) + cValues := (*C.scalar_t)(scalars.AsUnsafePointer()) cSize := (C.size_t)(scalars.Len()) cIsInto := (C._Bool)(isInto) defaultCtx, _ := cr.GetDefaultDeviceContext() cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.bw6_761ScalarConvertMontgomery(cValues, cSize, cIsInto, cCtx) + __ret := C.bw6_761_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx) err := (cr.CudaError)(__ret) return err } diff --git a/wrappers/golang/curves/bw6761/base_field_test.go b/wrappers/golang/curves/bw6761/tests/base_field_test.go similarity index 59% rename from wrappers/golang/curves/bw6761/base_field_test.go rename to wrappers/golang/curves/bw6761/tests/base_field_test.go index e5bbf9f7..c6737635 100644 --- a/wrappers/golang/curves/bw6761/base_field_test.go +++ b/wrappers/golang/curves/bw6761/tests/base_field_test.go @@ -1,34 +1,40 @@ -package bw6761 +package tests import ( + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + BASE_LIMBS = bw6_761.BASE_LIMBS +) + func TestBaseFieldFromLimbs(t *testing.T) { - emptyField := BaseField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + emptyField := bw6_761.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the BaseField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestBaseFieldGetLimbs(t *testing.T) { - emptyField := BaseField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + emptyField := bw6_761.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") } func TestBaseFieldOne(t *testing.T) { - var emptyField BaseField + var emptyField bw6_761.BaseField emptyField.One() - limbOne := generateLimbOne(int(BASE_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(BASE_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -36,12 +42,12 @@ func TestBaseFieldOne(t *testing.T) { } func TestBaseFieldZero(t *testing.T) { - var emptyField BaseField + var emptyField bw6_761.BaseField emptyField.Zero() limbsZero := make([]uint32, BASE_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -49,24 +55,24 @@ func TestBaseFieldZero(t *testing.T) { } func TestBaseFieldSize(t *testing.T) { - var emptyField BaseField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + var emptyField bw6_761.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestBaseFieldAsPointer(t *testing.T) { - var emptyField BaseField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + var emptyField bw6_761.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestBaseFieldFromBytes(t *testing.T) { - var emptyField BaseField - bytes, expected := generateBytesArray(int(BASE_LIMBS)) + var emptyField bw6_761.BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -74,8 +80,8 @@ func TestBaseFieldFromBytes(t *testing.T) { } func TestBaseFieldToBytes(t *testing.T) { - var emptyField BaseField - expected, limbs := generateBytesArray(int(BASE_LIMBS)) + var emptyField bw6_761.BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") diff --git a/wrappers/golang/curves/bls12381/curve_test.go b/wrappers/golang/curves/bw6761/tests/curve_test.go similarity index 50% rename from wrappers/golang/curves/bls12381/curve_test.go rename to wrappers/golang/curves/bw6761/tests/curve_test.go index 788f3efd..c2089df6 100644 --- a/wrappers/golang/curves/bls12381/curve_test.go +++ b/wrappers/golang/curves/bw6761/tests/curve_test.go @@ -1,20 +1,22 @@ -package bls12381 +package tests import ( + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) func TestAffineZero(t *testing.T) { - var fieldZero = BaseField{} + var fieldZero = bw6_761.BaseField{} - var affineZero Affine + var affineZero bw6_761.Affine assert.Equal(t, affineZero.X, fieldZero) assert.Equal(t, affineZero.Y, fieldZero) - x := generateRandomLimb(int(BASE_LIMBS)) - y := generateRandomLimb(int(BASE_LIMBS)) - var affine Affine + x := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var affine bw6_761.Affine affine.FromLimbs(x, y) affine.Zero() @@ -23,10 +25,10 @@ func TestAffineZero(t *testing.T) { } func TestAffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) - var affine Affine + var affine bw6_761.Affine affine.FromLimbs(randLimbs, randLimbs2) assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) @@ -34,15 +36,15 @@ func TestAffineFromLimbs(t *testing.T) { } func TestAffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne bw6_761.BaseField fieldOne.One() - var expected Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected bw6_761.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine Affine + var affine bw6_761.Affine affine.FromLimbs(randLimbs, randLimbs2) projectivePoint := affine.ToProjective() @@ -50,18 +52,18 @@ func TestAffineToProjective(t *testing.T) { } func TestProjectiveZero(t *testing.T) { - var projectiveZero Projective + var projectiveZero bw6_761.Projective projectiveZero.Zero() - var fieldZero = BaseField{} - var fieldOne BaseField + var fieldZero = bw6_761.BaseField{} + var fieldOne bw6_761.BaseField fieldOne.One() assert.Equal(t, projectiveZero.X, fieldZero) assert.Equal(t, projectiveZero.Y, fieldOne) assert.Equal(t, projectiveZero.Z, fieldZero) - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - var projective Projective + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective bw6_761.Projective projective.FromLimbs(randLimbs, randLimbs, randLimbs) projective.Zero() @@ -71,11 +73,11 @@ func TestProjectiveZero(t *testing.T) { } func TestProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) - var projective Projective + var projective bw6_761.Projective projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) @@ -84,18 +86,18 @@ func TestProjectiveFromLimbs(t *testing.T) { } func TestProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne bw6_761.BaseField fieldOne.One() - var expected Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected bw6_761.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine Affine + var affine bw6_761.Affine affine.FromLimbs(randLimbs, randLimbs2) - var projectivePoint Projective + var projectivePoint bw6_761.Projective projectivePoint.FromAffine(affine) assert.Equal(t, expected, projectivePoint) } diff --git a/wrappers/golang/curves/bw6761/tests/ecntt_test.go b/wrappers/golang/curves/bw6761/tests/ecntt_test.go new file mode 100644 index 00000000..3d37a988 --- /dev/null +++ b/wrappers/golang/curves/bw6761/tests/ecntt_test.go @@ -0,0 +1,30 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + ecntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/ecntt" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/ntt" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + points := bw6_761.GenerateProjectivePoints(1 << largestTestSize) + + for _, size := range []int{4, 5, 6, 7, 8} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[bw6_761.Projective](points[:testSize]) + cfg.Ordering = v + cfg.NttAlgorithm = core.Radix2 + + output := make(core.HostSlice[bw6_761.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + } + } +} diff --git a/wrappers/golang/curves/bn254/g2_curve_test.go b/wrappers/golang/curves/bw6761/tests/g2_curve_test.go similarity index 51% rename from wrappers/golang/curves/bn254/g2_curve_test.go rename to wrappers/golang/curves/bw6761/tests/g2_curve_test.go index 5370adec..09797b2a 100644 --- a/wrappers/golang/curves/bn254/g2_curve_test.go +++ b/wrappers/golang/curves/bw6761/tests/g2_curve_test.go @@ -1,22 +1,22 @@ -//go:build g2 - -package bn254 +package tests import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/g2" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) func TestG2AffineZero(t *testing.T) { - var fieldZero = G2BaseField{} + var fieldZero = g2.G2BaseField{} - var affineZero G2Affine + var affineZero g2.G2Affine assert.Equal(t, affineZero.X, fieldZero) assert.Equal(t, affineZero.Y, fieldZero) - x := generateRandomLimb(int(G2_BASE_LIMBS)) - y := generateRandomLimb(int(G2_BASE_LIMBS)) - var affine G2Affine + x := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var affine g2.G2Affine affine.FromLimbs(x, y) affine.Zero() @@ -25,10 +25,10 @@ func TestG2AffineZero(t *testing.T) { } func TestG2AffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) @@ -36,15 +36,15 @@ func TestG2AffineFromLimbs(t *testing.T) { } func TestG2AffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - var fieldOne G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField fieldOne.One() - var expected G2Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) projectivePoint := affine.ToProjective() @@ -52,18 +52,18 @@ func TestG2AffineToProjective(t *testing.T) { } func TestG2ProjectiveZero(t *testing.T) { - var projectiveZero G2Projective + var projectiveZero g2.G2Projective projectiveZero.Zero() - var fieldZero = G2BaseField{} - var fieldOne G2BaseField + var fieldZero = g2.G2BaseField{} + var fieldOne g2.G2BaseField fieldOne.One() assert.Equal(t, projectiveZero.X, fieldZero) assert.Equal(t, projectiveZero.Y, fieldOne) assert.Equal(t, projectiveZero.Z, fieldZero) - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - var projective G2Projective + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var projective g2.G2Projective projective.FromLimbs(randLimbs, randLimbs, randLimbs) projective.Zero() @@ -73,11 +73,11 @@ func TestG2ProjectiveZero(t *testing.T) { } func TestG2ProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) - var projective G2Projective + var projective g2.G2Projective projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) @@ -86,18 +86,18 @@ func TestG2ProjectiveFromLimbs(t *testing.T) { } func TestG2ProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(G2_BASE_LIMBS)) - var fieldOne G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField fieldOne.One() - var expected G2Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine G2Affine + var affine g2.G2Affine affine.FromLimbs(randLimbs, randLimbs2) - var projectivePoint G2Projective + var projectivePoint g2.G2Projective projectivePoint.FromAffine(affine) assert.Equal(t, expected, projectivePoint) } diff --git a/wrappers/golang/curves/bls12377/g2_base_field_test.go b/wrappers/golang/curves/bw6761/tests/g2_g2base_field_test.go similarity index 57% rename from wrappers/golang/curves/bls12377/g2_base_field_test.go rename to wrappers/golang/curves/bw6761/tests/g2_g2base_field_test.go index 8f5a4562..41c21133 100644 --- a/wrappers/golang/curves/bls12377/g2_base_field_test.go +++ b/wrappers/golang/curves/bw6761/tests/g2_g2base_field_test.go @@ -1,36 +1,40 @@ -//go:build g2 - -package bls12377 +package tests import ( + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/g2" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + G2BASE_LIMBS = bw6_761.G2BASE_LIMBS +) + func TestG2BaseFieldFromLimbs(t *testing.T) { - emptyField := G2BaseField{} - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + emptyField := bw6_761.G2BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the G2BaseField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the G2BaseField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestG2BaseFieldGetLimbs(t *testing.T) { - emptyField := G2BaseField{} - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + emptyField := bw6_761.G2BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the G2BaseField's limbs") } func TestG2BaseFieldOne(t *testing.T) { - var emptyField G2BaseField + var emptyField bw6_761.G2BaseField emptyField.One() - limbOne := generateLimbOne(int(G2_BASE_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(G2BASE_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -38,12 +42,12 @@ func TestG2BaseFieldOne(t *testing.T) { } func TestG2BaseFieldZero(t *testing.T) { - var emptyField G2BaseField + var emptyField bw6_761.G2BaseField emptyField.Zero() - limbsZero := make([]uint32, G2_BASE_LIMBS) + limbsZero := make([]uint32, G2BASE_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -51,24 +55,24 @@ func TestG2BaseFieldZero(t *testing.T) { } func TestG2BaseFieldSize(t *testing.T) { - var emptyField G2BaseField - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + var emptyField bw6_761.G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestG2BaseFieldAsPointer(t *testing.T) { - var emptyField G2BaseField - randLimbs := generateRandomLimb(int(G2_BASE_LIMBS)) + var emptyField bw6_761.G2BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestG2BaseFieldFromBytes(t *testing.T) { - var emptyField G2BaseField - bytes, expected := generateBytesArray(int(G2_BASE_LIMBS)) + var emptyField bw6_761.G2BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -76,8 +80,8 @@ func TestG2BaseFieldFromBytes(t *testing.T) { } func TestG2BaseFieldToBytes(t *testing.T) { - var emptyField G2BaseField - expected, limbs := generateBytesArray(int(G2_BASE_LIMBS)) + var emptyField bw6_761.G2BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") diff --git a/wrappers/golang/curves/bw6761/g2_msm_test.go b/wrappers/golang/curves/bw6761/tests/g2_msm_test.go similarity index 60% rename from wrappers/golang/curves/bw6761/g2_msm_test.go rename to wrappers/golang/curves/bw6761/tests/g2_msm_test.go index 70f1d3bd..2f03a15d 100644 --- a/wrappers/golang/curves/bw6761/g2_msm_test.go +++ b/wrappers/golang/curves/bw6761/tests/g2_msm_test.go @@ -1,6 +1,4 @@ -//go:build g2 - -package bw6761 +package tests import ( "fmt" @@ -9,16 +7,18 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bw6-761" "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleBw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/g2" ) -func projectiveToGnarkAffineG2(p G2Projective) bw6761.G2Affine { +func projectiveToGnarkAffineG2(p g2.G2Projective) bw6761.G2Affine { px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.X).ToBytesLittleEndian())) py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Y).ToBytesLittleEndian())) pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Z).ToBytesLittleEndian())) @@ -35,7 +35,7 @@ func projectiveToGnarkAffineG2(p G2Projective) bw6761.G2Affine { return bw6761.G2Affine{X: *x, Y: *y} } -func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points core.HostSlice[G2Affine], out G2Projective) bool { +func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[icicleBw6_761.ScalarField], points core.HostSlice[g2.G2Affine], out g2.G2Projective) bool { scalarsFr := make([]fr.Element, len(scalars)) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -46,6 +46,11 @@ func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points cor for i, v := range points { pointsFp[i] = projectiveToGnarkAffineG2(v.ToProjective()) } + + return testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bw6761.G2Affine], out g2.G2Projective) bool { var msmRes bw6761.G2Jac msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) @@ -56,54 +61,104 @@ func testAgainstGnarkCryptoMsmG2(scalars core.HostSlice[ScalarField], points cor return msmRes.Equal(&icicleResAsJac) } +func convertIcicleAffineToG2Affine(iciclePoints []g2.G2Affine) []bw6761.G2Affine { + points := make([]bw6761.G2Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes]byte)(iciclePoint.X.ToBytesLittleEndian()) + fpXElem, _ := fp.LittleEndian.Element(&xBytes) + + yBytes := ([fp.Bytes]byte)(iciclePoint.Y.ToBytesLittleEndian()) + fpYElem, _ := fp.LittleEndian.Element(&yBytes) + points[index] = bw6761.G2Affine{ + X: fpXElem, + Y: fpYElem, + } + } + + return points +} + func TestMSMG2(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := G2GenerateAffinePoints(size) + scalars := icicleBw6_761.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) cr.SynchronizeStream(&stream) // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) + + } +} +func TestMSMG2GnarkCryptoTypes(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := g2.G2GenerateAffinePoints(size) + pointsGnark := convertIcicleAffineToG2Affine(points) + pointsHost := (core.HostSlice[bw6761.G2Affine])(pointsGnark) + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = g2.G2Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) } } func TestMSMG2Batch(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := G2GenerateAffinePoints(totalSize) + scalars := icicleBw6_761.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], batchSize) + outHost := make(core.HostSlice[g2.G2Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -116,36 +171,35 @@ func TestMSMG2Batch(t *testing.T) { } func TestPrecomputeBaseG2(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() const precomputeFactor = 8 for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := G2GenerateAffinePoints(totalSize) + scalars := icicleBw6_761.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) var precomputeOut core.DeviceSlice _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - e = G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + e = g2.G2PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e = out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.PrecomputeFactor = precomputeFactor - e = G2Msm(scalars, precomputeOut, &cfg, out) + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], batchSize) + outHost := make(core.HostSlice[g2.G2Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() precomputeOut.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -158,30 +212,29 @@ func TestPrecomputeBaseG2(t *testing.T) { } func TestMSMG2SkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) + scalars := icicleBw6_761.GenerateScalars(size) for i := size / 4; i < size; i++ { scalars[i].One() } - points := G2GenerateAffinePoints(size) + points := g2.G2GenerateAffinePoints(size) for i := 0; i < size/4; i++ { points[i].Zero() } - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.Malloc(p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) } @@ -198,23 +251,23 @@ func TestMSMG2MultiDevice(t *testing.T) { wg.Add(1) cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := g2.G2GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := G2GenerateAffinePoints(size) + scalars := icicleBw6_761.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p G2Projective + var p g2.G2Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = G2Msm(scalars, points, &cfg, out) + e = g2.G2Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[G2Projective], 1) + outHost := make(core.HostSlice[g2.G2Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) diff --git a/wrappers/golang/curves/bw6761/tests/main_test.go b/wrappers/golang/curves/bw6761/tests/main_test.go new file mode 100644 index 00000000..142bc2e3 --- /dev/null +++ b/wrappers/golang/curves/bw6761/tests/main_test.go @@ -0,0 +1,48 @@ +package tests + +import ( + "os" + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/ntt" + poly "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/polynomial" + + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fft" +) + +const ( + largestTestSize = 20 +) + +func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { + rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) + rou := rouMont.Bits() + rouIcicle := bw6_761.ScalarField{} + limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) + + rouIcicle.FromLimbs(limbs) + e := ntt.InitDomain(rouIcicle, cfg.Ctx, false) + return e +} + +func TestMain(m *testing.M) { + poly.InitPolyBackend() + + // setup domain + cfg := ntt.GetDefaultNttConfig() + e := initDomain(largestTestSize, cfg) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("initDomain failed") + } + + // execute tests + os.Exit(m.Run()) + + // release domain + e = ntt.ReleaseDomain(cfg.Ctx) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("ReleaseDomain failed") + } +} diff --git a/wrappers/golang/curves/bw6761/msm_test.go b/wrappers/golang/curves/bw6761/tests/msm_test.go similarity index 59% rename from wrappers/golang/curves/bw6761/msm_test.go rename to wrappers/golang/curves/bw6761/tests/msm_test.go index 173aa243..fcc6d34b 100644 --- a/wrappers/golang/curves/bw6761/msm_test.go +++ b/wrappers/golang/curves/bw6761/tests/msm_test.go @@ -1,4 +1,4 @@ -package bw6761 +package tests import ( "fmt" @@ -7,16 +7,18 @@ import ( "github.com/stretchr/testify/assert" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc" "github.com/consensys/gnark-crypto/ecc/bw6-761" "github.com/consensys/gnark-crypto/ecc/bw6-761/fp" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleBw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/msm" ) -func projectiveToGnarkAffine(p Projective) bw6761.G1Affine { +func projectiveToGnarkAffine(p icicleBw6_761.Projective) bw6761.G1Affine { px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.X).ToBytesLittleEndian())) py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Y).ToBytesLittleEndian())) pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Z).ToBytesLittleEndian())) @@ -33,7 +35,7 @@ func projectiveToGnarkAffine(p Projective) bw6761.G1Affine { return bw6761.G1Affine{X: *x, Y: *y} } -func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core.HostSlice[Affine], out Projective) bool { +func testAgainstGnarkCryptoMsm(scalars core.HostSlice[icicleBw6_761.ScalarField], points core.HostSlice[icicleBw6_761.Affine], out icicleBw6_761.Projective) bool { scalarsFr := make([]fr.Element, len(scalars)) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -44,6 +46,11 @@ func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core. for i, v := range points { pointsFp[i] = projectiveToGnarkAffine(v.ToProjective()) } + + return testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bw6761.G1Affine], out icicleBw6_761.Projective) bool { var msmRes bw6761.G1Jac msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) @@ -54,54 +61,104 @@ func testAgainstGnarkCryptoMsm(scalars core.HostSlice[ScalarField], points core. return msmRes.Equal(&icicleResAsJac) } +func convertIcicleAffineToG1Affine(iciclePoints []icicleBw6_761.Affine) []bw6761.G1Affine { + points := make([]bw6761.G1Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes]byte)(iciclePoint.X.ToBytesLittleEndian()) + fpXElem, _ := fp.LittleEndian.Element(&xBytes) + + yBytes := ([fp.Bytes]byte)(iciclePoint.Y.ToBytesLittleEndian()) + fpYElem, _ := fp.LittleEndian.Element(&yBytes) + points[index] = bw6761.G1Affine{ + X: fpXElem, + Y: fpYElem, + } + } + + return points +} + func TestMSM(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + scalars := icicleBw6_761.GenerateScalars(size) + points := icicleBw6_761.GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p Projective + var p icicleBw6_761.Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) cr.SynchronizeStream(&stream) // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) + + } +} +func TestMSMGnarkCryptoTypes(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := icicleBw6_761.GenerateAffinePoints(size) + pointsGnark := convertIcicleAffineToG1Affine(points) + pointsHost := (core.HostSlice[bw6761.G1Affine])(pointsGnark) + + var p icicleBw6_761.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = msm.Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsmGnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) } } func TestMSMBatch(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := GenerateAffinePoints(totalSize) + scalars := icicleBw6_761.GenerateScalars(totalSize) + points := icicleBw6_761.GenerateAffinePoints(totalSize) - var p Projective + var p icicleBw6_761.Projective var out core.DeviceSlice _, e := out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], batchSize) + outHost := make(core.HostSlice[icicleBw6_761.Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -114,36 +171,35 @@ func TestMSMBatch(t *testing.T) { } func TestPrecomputeBase(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() const precomputeFactor = 8 for _, power := range []int{10, 16} { for _, batchSize := range []int{1, 3, 16} { size := 1 << power totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := GenerateAffinePoints(totalSize) + scalars := icicleBw6_761.GenerateScalars(totalSize) + points := icicleBw6_761.GenerateAffinePoints(totalSize) var precomputeOut core.DeviceSlice _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - e = PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + e = msm.PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - var p Projective + var p icicleBw6_761.Projective var out core.DeviceSlice _, e = out.Malloc(batchSize*p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.PrecomputeFactor = precomputeFactor - e = Msm(scalars, precomputeOut, &cfg, out) + e = msm.Msm(scalars, precomputeOut, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], batchSize) + outHost := make(core.HostSlice[icicleBw6_761.Projective], batchSize) outHost.CopyFromDevice(&out) out.Free() precomputeOut.Free() - // Check with gnark-crypto for i := 0; i < batchSize; i++ { scalarsSlice := scalars[i*size : (i+1)*size] @@ -156,30 +212,29 @@ func TestPrecomputeBase(t *testing.T) { } func TestMSMSkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) + scalars := icicleBw6_761.GenerateScalars(size) for i := size / 4; i < size; i++ { scalars[i].One() } - points := GenerateAffinePoints(size) + points := icicleBw6_761.GenerateAffinePoints(size) for i := 0; i < size/4; i++ { points[i].Zero() } - var p Projective + var p icicleBw6_761.Projective var out core.DeviceSlice _, e := out.Malloc(p.Size(), p.Size()) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) outHost.CopyFromDevice(&out) out.Free() - // Check with gnark-crypto assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) } @@ -196,23 +251,23 @@ func TestMSMMultiDevice(t *testing.T) { wg.Add(1) cr.RunOnDevice(i, func(args ...any) { defer wg.Done() - cfg := GetDefaultMSMConfig() + cfg := msm.GetDefaultMSMConfig() cfg.IsAsync = true for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { size := 1 << power - scalars := GenerateScalars(size) - points := GenerateAffinePoints(size) + scalars := icicleBw6_761.GenerateScalars(size) + points := icicleBw6_761.GenerateAffinePoints(size) stream, _ := cr.CreateStream() - var p Projective + var p icicleBw6_761.Projective var out core.DeviceSlice _, e := out.MallocAsync(p.Size(), p.Size(), stream) assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") cfg.Ctx.Stream = &stream - e = Msm(scalars, points, &cfg, out) + e = msm.Msm(scalars, points, &cfg, out) assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[Projective], 1) + outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) outHost.CopyFromDeviceAsync(&out, stream) out.FreeAsync(stream) diff --git a/wrappers/golang/curves/bw6761/ntt_test.go b/wrappers/golang/curves/bw6761/tests/ntt_test.go similarity index 71% rename from wrappers/golang/curves/bw6761/ntt_test.go rename to wrappers/golang/curves/bw6761/tests/ntt_test.go index 2111e27b..266af8d6 100644 --- a/wrappers/golang/curves/bw6761/ntt_test.go +++ b/wrappers/golang/curves/bw6761/tests/ntt_test.go @@ -1,40 +1,20 @@ -package bw6761 +package tests import ( - "os" "reflect" "testing" - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fft" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" ) -const ( - largestTestSize = 17 -) - -func init() { - cfg := GetDefaultNttConfig() - initDomain(largestTestSize, cfg) -} - -func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { - rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) - rou := rouMont.Bits() - rouIcicle := ScalarField{} - limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) - - rouIcicle.FromLimbs(limbs) - e := InitDomain(rouIcicle, cfg.Ctx, false) - return e -} - -func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], output core.HostSlice[ScalarField], order core.Ordering, direction core.NTTDir) bool { - domainWithPrecompute := fft.NewDomain(uint64(size)) +func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[bw6_761.ScalarField], output core.HostSlice[bw6_761.ScalarField], order core.Ordering, direction core.NTTDir) bool { scalarsFr := make([]fr.Element, size) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -46,6 +26,11 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou outputAsFr[i] = slice64 } + return testAgainstGnarkCryptoNttGnarkTypes(size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) bool { + domainWithPrecompute := fft.NewDomain(uint64(size)) // DIT + BitReverse == Ordering.kRR // DIT == Ordering.kRN // DIF + BitReverse == Ordering.kNN @@ -68,72 +53,77 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou } return reflect.DeepEqual(scalarsFr, outputAsFr) } - func TestNTTGetDefaultConfig(t *testing.T) { - actual := GetDefaultNttConfig() - expected := generateLimbOne(int(SCALAR_LIMBS)) + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(bw6_761.SCALAR_LIMBS)) assert.Equal(t, expected, actual.CosetGen[:]) - cosetGenField := ScalarField{} + cosetGenField := bw6_761.ScalarField{} cosetGenField.One() assert.ElementsMatch(t, cosetGenField.GetLimbs(), actual.CosetGen) } func TestInitDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) } func TestNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := bw6_761.GenerateScalars(1 << largestTestSize) for _, size := range []int{4, largestTestSize} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[bw6_761.ScalarField](scalars[:testSize]) cfg.Ordering = v // run ntt - output := make(core.HostSlice[ScalarField], testSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[bw6_761.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, core.KForward)) } } } +func TestNttFrElement(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := make([]fr.Element, 4) + var x fr.Element + for i := 0; i < 4; i++ { + x.SetRandom() + scalars[i] = x + } -func TestECNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - points := GenerateProjectivePoints(1 << largestTestSize) + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + testSize := size - for _, size := range []int{4, 5, 6, 7, 8} { - for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { - testSize := 1 << size - - pointsCopy := core.HostSliceFromElements[Projective](points[:testSize]) + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) cfg.Ordering = v - cfg.NttAlgorithm = core.Radix2 - output := make(core.HostSlice[Projective], testSize) - e := ECNtt(pointsCopy, core.KForward, &cfg, output) - assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + assert.True(t, testAgainstGnarkCryptoNttGnarkTypes(testSize, scalarsCopy, output, v, core.KForward)) } } } func TestNttDeviceAsync(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := bw6_761.GenerateScalars(1 << largestTestSize) for _, size := range []int{1, 10, largestTestSize} { for _, direction := range []core.NTTDir{core.KForward, core.KInverse} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[bw6_761.ScalarField](scalars[:testSize]) stream, _ := cr.CreateStream() @@ -147,12 +137,11 @@ func TestNttDeviceAsync(t *testing.T) { deviceOutput.MallocAsync(testSize*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) // run ntt - Ntt(deviceInput, direction, &cfg, deviceOutput) - output := make(core.HostSlice[ScalarField], testSize) + ntt.Ntt(deviceInput, direction, &cfg, deviceOutput) + output := make(core.HostSlice[bw6_761.ScalarField], testSize) output.CopyFromDeviceAsync(&deviceOutput, stream) cr.SynchronizeStream(&stream) - // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, direction)) } @@ -161,22 +150,22 @@ func TestNttDeviceAsync(t *testing.T) { } func TestNttBatch(t *testing.T) { - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() largestBatchSize := 100 - scalars := GenerateScalars(1 << largestTestSize * largestBatchSize) + scalars := bw6_761.GenerateScalars(1 << largestTestSize * largestBatchSize) for _, size := range []int{4, largestTestSize} { for _, batchSize := range []int{1, 16, largestBatchSize} { testSize := 1 << size totalSize := testSize * batchSize - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:totalSize]) + scalarsCopy := core.HostSliceFromElements[bw6_761.ScalarField](scalars[:totalSize]) cfg.Ordering = core.KNN cfg.BatchSize = int32(batchSize) // run ntt - output := make(core.HostSlice[ScalarField], totalSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[bw6_761.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) // Compare with gnark-crypto domainWithPrecompute := fft.NewDomain(uint64(testSize)) @@ -205,36 +194,18 @@ func TestNttBatch(t *testing.T) { func TestReleaseDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() - e := ReleaseDomain(cfg.Ctx) + cfg := ntt.GetDefaultNttConfig() + e := ntt.ReleaseDomain(cfg.Ctx) assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ReleasDomain failed") } -func TestMain(m *testing.M) { - // setup domain - cfg := GetDefaultNttConfig() - e := initDomain(largestTestSize, cfg) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("initDomain failed") - } - - // execute tests - os.Exit(m.Run()) - - // release domain - e = ReleaseDomain(cfg.Ctx) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("ReleaseDomain failed") - } -} - // func TestNttArbitraryCoset(t *testing.T) { // for _, size := range []int{20} { // for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { // testSize := 1 << size // scalars := GenerateScalars(testSize) -// cfg := GetDefaultNttConfig() +// cfg := ntt.GetDefaultNttConfig() // var scalarsCopy core.HostSlice[ScalarField] // for _, v := range scalars { diff --git a/wrappers/golang/curves/bw6761/tests/polynomial_test.go b/wrappers/golang/curves/bw6761/tests/polynomial_test.go new file mode 100644 index 00000000..9ceccddc --- /dev/null +++ b/wrappers/golang/curves/bw6761/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + // "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/polynomial" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/vecOps" + "github.com/stretchr/testify/assert" +) + +var one, two, three, four, five bw6_761.ScalarField + +func init() { + one.One() + two.FromUint32(2) + three.FromUint32(3) + four.FromUint32(4) + five.FromUint32(5) +} + +func rand() bw6_761.ScalarField { + return bw6_761.GenerateScalars(1)[0] +} + +func randomPoly(size int) (f polynomial.DensePolynomial) { + f.CreateFromCoeffecitients(core.HostSliceFromElements(bw6_761.GenerateScalars(size))) + return f +} + +func vecOp(a, b bw6_761.ScalarField, op core.VecOps) bw6_761.ScalarField { + ahost := core.HostSliceWithValue(a, 1) + bhost := core.HostSliceWithValue(b, 1) + out := make(core.HostSlice[bw6_761.ScalarField], 1) + + cfg := core.DefaultVecOpsConfig() + vecOps.VecOp(ahost, bhost, out, cfg, op) + return out[0] +} + +func TestPolyCreateFromCoefficients(t *testing.T) { + scalars := bw6_761.GenerateScalars(33) + var uniPoly polynomial.DensePolynomial + + poly := uniPoly.CreateFromCoeffecitients(scalars) + poly.Print() +} + +func TestPolyEval(t *testing.T) { + // testing correct evaluation of f(8) for f(x)=4x^2+2x+5 + coeffs := core.HostSliceFromElements([]bw6_761.ScalarField{five, two, four}) + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(coeffs) + + var x bw6_761.ScalarField + x.FromUint32(8) + domains := make(core.HostSlice[bw6_761.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[bw6_761.ScalarField], 1) + fEvaled := f.EvalOnDomain(domains, evals) + var expected bw6_761.ScalarField + assert.Equal(t, expected.FromUint32(277), fEvaled.(core.HostSlice[bw6_761.ScalarField])[0]) +} + +func TestPolyClone(t *testing.T) { + f := randomPoly(8) + x := rand() + fx := f.Eval(x) + + g := f.Clone() + fg := f.Add(&g) + + gx := g.Eval(x) + fgx := fg.Eval(x) + + assert.Equal(t, fx, gx) + assert.Equal(t, vecOp(fx, gx, core.Add), fgx) +} + +func TestPolyAddSubMul(t *testing.T) { + testSize := 1 << 10 + f := randomPoly(testSize) + g := randomPoly(testSize) + x := rand() + + fx := f.Eval(x) + gx := g.Eval(x) + + polyAdd := f.Add(&g) + fxAddgx := vecOp(fx, gx, core.Add) + assert.Equal(t, polyAdd.Eval(x), fxAddgx) + + polySub := f.Subtract(&g) + fxSubgx := vecOp(fx, gx, core.Sub) + assert.Equal(t, polySub.Eval(x), fxSubgx) + + polyMul := f.Multiply(&g) + fxMulgx := vecOp(fx, gx, core.Mul) + assert.Equal(t, polyMul.Eval(x), fxMulgx) + + s1 := rand() + polMulS1 := f.MultiplyByScalar(s1) + assert.Equal(t, polMulS1.Eval(x), vecOp(fx, s1, core.Mul)) + + s2 := rand() + polMulS2 := f.MultiplyByScalar(s2) + assert.Equal(t, polMulS2.Eval(x), vecOp(fx, s2, core.Mul)) +} + +func TestPolyMonomials(t *testing.T) { + var zero bw6_761.ScalarField + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements([]bw6_761.ScalarField{one, zero, two})) + x := rand() + + fx := f.Eval(x) + f.AddMonomial(three, 1) + fxAdded := f.Eval(x) + assert.Equal(t, fxAdded, vecOp(fx, vecOp(three, x, core.Mul), core.Add)) + + f.SubMonomial(one, 0) + fxSub := f.Eval(x) + assert.Equal(t, fxSub, vecOp(fxAdded, one, core.Sub)) +} + +func TestPolyReadCoeffs(t *testing.T) { + var f polynomial.DensePolynomial + coeffs := core.HostSliceFromElements([]bw6_761.ScalarField{one, two, three, four}) + f.CreateFromCoeffecitients(coeffs) + coeffsCopied := make(core.HostSlice[bw6_761.ScalarField], coeffs.Len()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsCopied) + assert.ElementsMatch(t, coeffs, coeffsCopied) + + var coeffsDevice core.DeviceSlice + coeffsDevice.Malloc(coeffs.Len()*one.Size(), one.Size()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsDevice) + coeffsHost := make(core.HostSlice[bw6_761.ScalarField], coeffs.Len()) + coeffsHost.CopyFromDevice(&coeffsDevice) + + assert.ElementsMatch(t, coeffs, coeffsHost) +} + +func TestPolyOddEvenSlicing(t *testing.T) { + size := 1<<10 - 3 + f := randomPoly(size) + + even := f.Even() + odd := f.Odd() + assert.Equal(t, f.Degree(), even.Degree()+odd.Degree()+1) + + x := rand() + var evenExpected, oddExpected bw6_761.ScalarField + for i := size; i >= 0; i-- { + if i%2 == 0 { + mul := vecOp(evenExpected, x, core.Mul) + evenExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } else { + mul := vecOp(oddExpected, x, core.Mul) + oddExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } + } + + evenEvaled := even.Eval(x) + assert.Equal(t, evenExpected, evenEvaled) + + oddEvaled := odd.Eval(x) + assert.Equal(t, oddExpected, oddEvaled) +} + +func TestPolynomialDivision(t *testing.T) { + // divide f(x)/g(x), compute q(x), r(x) and check f(x)=q(x)*g(x)+r(x) + var f, g polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements(bw6_761.GenerateScalars(1 << 4))) + g.CreateFromCoeffecitients(core.HostSliceFromElements(bw6_761.GenerateScalars(1 << 2))) + + q, r := f.Divide(&g) + + qMulG := q.Multiply(&g) + fRecon := qMulG.Add(&r) + + x := bw6_761.GenerateScalars(1)[0] + fEval := f.Eval(x) + fReconEval := fRecon.Eval(x) + assert.Equal(t, fEval, fReconEval) +} + +func TestDivideByVanishing(t *testing.T) { + // poly of x^4-1 vanishes ad 4th rou + var zero bw6_761.ScalarField + minus_one := vecOp(zero, one, core.Sub) + coeffs := core.HostSliceFromElements([]bw6_761.ScalarField{minus_one, zero, zero, zero, one}) // x^4-1 + var v polynomial.DensePolynomial + v.CreateFromCoeffecitients(coeffs) + + f := randomPoly(1 << 3) + + fv := f.Multiply(&v) + fDegree := f.Degree() + fvDegree := fv.Degree() + assert.Equal(t, fDegree+4, fvDegree) + + fReconstructed := fv.DivideByVanishing(4) + assert.Equal(t, fDegree, fReconstructed.Degree()) + + x := rand() + assert.Equal(t, f.Eval(x), fReconstructed.Eval(x)) +} + +// func TestPolySlice(t *testing.T) { +// size := 4 +// coeffs := bw6_761.GenerateScalars(size) +// var f DensePolynomial +// f.CreateFromCoeffecitients(coeffs) +// fSlice := f.AsSlice() +// assert.True(t, fSlice.IsOnDevice()) +// assert.Equal(t, size, fSlice.Len()) + +// hostSlice := make(core.HostSlice[bw6_761.ScalarField], size) +// hostSlice.CopyFromDevice(fSlice) +// assert.Equal(t, coeffs, hostSlice) + +// cfg := ntt.GetDefaultNttConfig() +// res := make(core.HostSlice[bw6_761.ScalarField], size) +// ntt.Ntt(fSlice, core.KForward, cfg, res) + +// assert.Equal(t, f.Eval(one), res[0]) +// } diff --git a/wrappers/golang/curves/bw6761/scalar_field_test.go b/wrappers/golang/curves/bw6761/tests/scalar_field_test.go similarity index 59% rename from wrappers/golang/curves/bw6761/scalar_field_test.go rename to wrappers/golang/curves/bw6761/tests/scalar_field_test.go index 5a8b6745..acad0ef3 100644 --- a/wrappers/golang/curves/bw6761/scalar_field_test.go +++ b/wrappers/golang/curves/bw6761/tests/scalar_field_test.go @@ -1,35 +1,41 @@ -package bw6761 +package tests import ( "github.com/ingonyama-zk/icicle/wrappers/golang/core" + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + SCALAR_LIMBS = bw6_761.SCALAR_LIMBS +) + func TestScalarFieldFromLimbs(t *testing.T) { - emptyField := ScalarField{} - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + emptyField := bw6_761.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the ScalarField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestScalarFieldGetLimbs(t *testing.T) { - emptyField := ScalarField{} - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + emptyField := bw6_761.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") } func TestScalarFieldOne(t *testing.T) { - var emptyField ScalarField + var emptyField bw6_761.ScalarField emptyField.One() - limbOne := generateLimbOne(int(SCALAR_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(SCALAR_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -37,12 +43,12 @@ func TestScalarFieldOne(t *testing.T) { } func TestScalarFieldZero(t *testing.T) { - var emptyField ScalarField + var emptyField bw6_761.ScalarField emptyField.Zero() limbsZero := make([]uint32, SCALAR_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -50,24 +56,24 @@ func TestScalarFieldZero(t *testing.T) { } func TestScalarFieldSize(t *testing.T) { - var emptyField ScalarField - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + var emptyField bw6_761.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestScalarFieldAsPointer(t *testing.T) { - var emptyField ScalarField - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + var emptyField bw6_761.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestScalarFieldFromBytes(t *testing.T) { - var emptyField ScalarField - bytes, expected := generateBytesArray(int(SCALAR_LIMBS)) + var emptyField bw6_761.ScalarField + bytes, expected := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -75,39 +81,39 @@ func TestScalarFieldFromBytes(t *testing.T) { } func TestScalarFieldToBytes(t *testing.T) { - var emptyField ScalarField - expected, limbs := generateBytesArray(int(SCALAR_LIMBS)) + var emptyField bw6_761.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") } -func TestGenerateScalars(t *testing.T) { +func TestBw6_761GenerateScalars(t *testing.T) { const numScalars = 8 - scalars := GenerateScalars(numScalars) + scalars := bw6_761.GenerateScalars(numScalars) assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) assert.Equal(t, numScalars, scalars.Len()) - zeroScalar := ScalarField{} + zeroScalar := bw6_761.ScalarField{} assert.NotContains(t, scalars, zeroScalar) } -func TestMongtomeryConversion(t *testing.T) { +func TestBw6_761MongtomeryConversion(t *testing.T) { size := 1 << 15 - scalars := GenerateScalars(size) + scalars := bw6_761.GenerateScalars(size) var deviceScalars core.DeviceSlice scalars.CopyToDevice(&deviceScalars, true) - ToMontgomery(&deviceScalars) + bw6_761.ToMontgomery(&deviceScalars) - scalarsMontHost := GenerateScalars(size) + scalarsMontHost := bw6_761.GenerateScalars(size) scalarsMontHost.CopyFromDevice(&deviceScalars) assert.NotEqual(t, scalars, scalarsMontHost) - FromMontgomery(&deviceScalars) + bw6_761.FromMontgomery(&deviceScalars) scalarsMontHost.CopyFromDevice(&deviceScalars) assert.Equal(t, scalars, scalarsMontHost) diff --git a/wrappers/golang/curves/bw6761/tests/vec_ops_test.go b/wrappers/golang/curves/bw6761/tests/vec_ops_test.go new file mode 100644 index 00000000..25dde7a0 --- /dev/null +++ b/wrappers/golang/curves/bw6761/tests/vec_ops_test.go @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + bw6_761 "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/bw6761/vecOps" + "github.com/stretchr/testify/assert" +) + +func TestBw6_761VecOps(t *testing.T) { + testSize := 1 << 14 + + a := bw6_761.GenerateScalars(testSize) + b := bw6_761.GenerateScalars(testSize) + var scalar bw6_761.ScalarField + scalar.One() + ones := core.HostSliceWithValue(scalar, testSize) + + out := make(core.HostSlice[bw6_761.ScalarField], testSize) + out2 := make(core.HostSlice[bw6_761.ScalarField], testSize) + out3 := make(core.HostSlice[bw6_761.ScalarField], testSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.VecOp(a, b, out, cfg, core.Add) + vecOps.VecOp(out, b, out2, cfg, core.Sub) + + assert.Equal(t, a, out2) + + vecOps.VecOp(a, ones, out3, cfg, core.Mul) + + assert.Equal(t, a, out3) +} + +func TestBw6_761Transpose(t *testing.T) { + rowSize := 1 << 6 + columnSize := 1 << 8 + onDevice := false + isAsync := false + + matrix := bw6_761.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[bw6_761.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[bw6_761.ScalarField], rowSize*columnSize) + + ctx, _ := cr.GetDefaultDeviceContext() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, ctx, onDevice, isAsync) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + onDevice = true + + matrix.CopyToDevice(&dMatrix, true) + dOut.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + dOut2.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + + vecOps.TransposeMatrix(dMatrix, dOut, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, ctx, onDevice, isAsync) + output := make(core.HostSlice[bw6_761.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang/curves/bw6761/include/vec_ops.h b/wrappers/golang/curves/bw6761/vecOps/include/vec_ops.h similarity index 51% rename from wrappers/golang/curves/bw6761/include/vec_ops.h rename to wrappers/golang/curves/bw6761/vecOps/include/vec_ops.h index 3a857af4..86bda7e3 100644 --- a/wrappers/golang/curves/bw6761/include/vec_ops.h +++ b/wrappers/golang/curves/bw6761/vecOps/include/vec_ops.h @@ -1,5 +1,5 @@ #include -#include "../../include/types.h" +#include #ifndef _BW6_761_VEC_OPS_H #define _BW6_761_VEC_OPS_H @@ -8,7 +8,11 @@ extern "C" { #endif -cudaError_t bw6_761MulCuda( +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t bw6_761_mul_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -16,7 +20,7 @@ cudaError_t bw6_761MulCuda( scalar_t* result ); -cudaError_t bw6_761AddCuda( +cudaError_t bw6_761_add_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -24,7 +28,7 @@ cudaError_t bw6_761AddCuda( scalar_t* result ); -cudaError_t bw6_761SubCuda( +cudaError_t bw6_761_sub_cuda( scalar_t* vec_a, scalar_t* vec_b, int n, @@ -32,6 +36,16 @@ cudaError_t bw6_761SubCuda( scalar_t* result ); +cudaError_t bw6_761_transpose_matrix_cuda( + scalar_t* mat_in, + int row_size, + int column_size, + scalar_t* mat_out, + DeviceContext* ctx, + bool on_device, + bool is_async +); + #ifdef __cplusplus } #endif diff --git a/wrappers/golang/curves/bw6761/vecOps/vec_ops.go b/wrappers/golang/curves/bw6761/vecOps/vec_ops.go new file mode 100644 index 00000000..4e5a2503 --- /dev/null +++ b/wrappers/golang/curves/bw6761/vecOps/vec_ops.go @@ -0,0 +1,48 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + + "unsafe" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { + aPointer, bPointer, outPointer, cfgPointer, size := core.VecOpCheck(a, b, out, &config) + + cA := (*C.scalar_t)(aPointer) + cB := (*C.scalar_t)(bPointer) + cOut := (*C.scalar_t)(outPointer) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cSize := (C.int)(size) + + switch op { + case core.Sub: + ret = (cr.CudaError)(C.bw6_761_sub_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (cr.CudaError)(C.bw6_761_add_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (cr.CudaError)(C.bw6_761_mul_cuda(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError) { + core.TransposeCheck(in, out, onDevice) + + cIn := (*C.scalar_t)(in.AsUnsafePointer()) + cOut := (*C.scalar_t)(out.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cOnDevice := (C._Bool)(onDevice) + cIsAsync := (C._Bool)(isAsync) + + err := (cr.CudaError)(C.bw6_761_transpose_matrix_cuda(cIn, cRowSize, cColumnSize, cOut, cCtx, cOnDevice, cIsAsync)) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/bw6761/vec_ops.go b/wrappers/golang/curves/bw6761/vec_ops.go deleted file mode 100644 index ba3167aa..00000000 --- a/wrappers/golang/curves/bw6761/vec_ops.go +++ /dev/null @@ -1,55 +0,0 @@ -package bw6761 - -// #cgo CFLAGS: -I./include/ -// #include "vec_ops.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { - core.VecOpCheck(a, b, out, &config) - var cA, cB, cOut *C.scalar_t - - if a.IsOnDevice() { - aDevice := a.(core.DeviceSlice) - aDevice.CheckDevice() - cA = (*C.scalar_t)(aDevice.AsPointer()) - } else { - cA = (*C.scalar_t)(unsafe.Pointer(&a.(core.HostSlice[ScalarField])[0])) - } - - if b.IsOnDevice() { - bDevice := b.(core.DeviceSlice) - bDevice.CheckDevice() - cB = (*C.scalar_t)(bDevice.AsPointer()) - } else { - cB = (*C.scalar_t)(unsafe.Pointer(&b.(core.HostSlice[ScalarField])[0])) - } - - if out.IsOnDevice() { - outDevice := out.(core.DeviceSlice) - outDevice.CheckDevice() - cOut = (*C.scalar_t)(outDevice.AsPointer()) - } else { - cOut = (*C.scalar_t)(unsafe.Pointer(&out.(core.HostSlice[ScalarField])[0])) - } - - cConfig := (*C.VecOpsConfig)(unsafe.Pointer(&config)) - cSize := (C.int)(a.Len()) - - switch op { - case core.Sub: - ret = (cr.CudaError)(C.bw6_761SubCuda(cA, cB, cSize, cConfig, cOut)) - case core.Add: - ret = (cr.CudaError)(C.bw6_761AddCuda(cA, cB, cSize, cConfig, cOut)) - case core.Mul: - ret = (cr.CudaError)(C.bw6_761MulCuda(cA, cB, cSize, cConfig, cOut)) - } - - return ret -} diff --git a/wrappers/golang/curves/bw6761/vec_ops_test.go b/wrappers/golang/curves/bw6761/vec_ops_test.go deleted file mode 100644 index 7da2b534..00000000 --- a/wrappers/golang/curves/bw6761/vec_ops_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package bw6761 - -import ( - "testing" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - "github.com/stretchr/testify/assert" -) - -func TestVecOps(t *testing.T) { - testSize := 1 << 14 - - a := GenerateScalars(testSize) - b := GenerateScalars(testSize) - var scalar ScalarField - scalar.One() - ones := core.HostSliceWithValue(scalar, testSize) - - out := make(core.HostSlice[ScalarField], testSize) - out2 := make(core.HostSlice[ScalarField], testSize) - out3 := make(core.HostSlice[ScalarField], testSize) - - cfg := core.DefaultVecOpsConfig() - - VecOp(a, b, out, cfg, core.Add) - VecOp(out, b, out2, cfg, core.Sub) - - assert.Equal(t, a, out2) - - VecOp(a, ones, out3, cfg, core.Mul) - - assert.Equal(t, a, out3) -} diff --git a/wrappers/golang/core/internal/field.go b/wrappers/golang/curves/grumpkin/base_field.go similarity index 62% rename from wrappers/golang/core/internal/field.go rename to wrappers/golang/curves/grumpkin/base_field.go index b3502556..cacd2767 100644 --- a/wrappers/golang/core/internal/field.go +++ b/wrappers/golang/curves/grumpkin/base_field.go @@ -1,4 +1,4 @@ -package internal +package grumpkin import ( "encoding/binary" @@ -6,30 +6,35 @@ import ( ) const ( - BASE_LIMBS int8 = 8 + BASE_LIMBS int = 8 ) -type MockField struct { +type BaseField struct { limbs [BASE_LIMBS]uint32 } -func (f MockField) Len() int { +func (f BaseField) Len() int { return int(BASE_LIMBS) } -func (f MockField) Size() int { +func (f BaseField) Size() int { return int(BASE_LIMBS * 4) } -func (f MockField) GetLimbs() []uint32 { +func (f BaseField) GetLimbs() []uint32 { return f.limbs[:] } -func (f MockField) AsPointer() *uint32 { +func (f BaseField) AsPointer() *uint32 { return &f.limbs[0] } -func (f *MockField) FromLimbs(limbs []uint32) MockField { +func (f *BaseField) FromUint32(v uint32) BaseField { + f.limbs[0] = v + return *f +} + +func (f *BaseField) FromLimbs(limbs []uint32) BaseField { if len(limbs) != f.Len() { panic("Called FromLimbs with limbs of different length than field") } @@ -40,7 +45,7 @@ func (f *MockField) FromLimbs(limbs []uint32) MockField { return *f } -func (f *MockField) Zero() MockField { +func (f *BaseField) Zero() BaseField { for i := range f.limbs { f.limbs[i] = 0 } @@ -48,7 +53,7 @@ func (f *MockField) Zero() MockField { return *f } -func (f *MockField) One() MockField { +func (f *BaseField) One() BaseField { for i := range f.limbs { f.limbs[i] = 0 } @@ -57,7 +62,7 @@ func (f *MockField) One() MockField { return *f } -func (f *MockField) FromBytesLittleEndian(bytes []byte) MockField { +func (f *BaseField) FromBytesLittleEndian(bytes []byte) BaseField { if len(bytes)/4 != f.Len() { panic(fmt.Sprintf("Called FromBytesLittleEndian with incorrect bytes length; expected %d - got %d", f.Len()*4, len(bytes))) } @@ -69,7 +74,7 @@ func (f *MockField) FromBytesLittleEndian(bytes []byte) MockField { return *f } -func (f MockField) ToBytesLittleEndian() []byte { +func (f BaseField) ToBytesLittleEndian() []byte { bytes := make([]byte, f.Len()*4) for i, v := range f.limbs { binary.LittleEndian.PutUint32(bytes[i*4:], v) diff --git a/wrappers/golang/curves/grumpkin/curve.go b/wrappers/golang/curves/grumpkin/curve.go new file mode 100644 index 00000000..41b5ca29 --- /dev/null +++ b/wrappers/golang/curves/grumpkin/curve.go @@ -0,0 +1,177 @@ +package grumpkin + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +type Projective struct { + X, Y, Z BaseField +} + +func (p Projective) Size() int { + return p.X.Size() * 3 +} + +func (p Projective) AsPointer() *uint32 { + return p.X.AsPointer() +} + +func (p *Projective) Zero() Projective { + p.X.Zero() + p.Y.One() + p.Z.Zero() + + return *p +} + +func (p *Projective) FromLimbs(x, y, z []uint32) Projective { + p.X.FromLimbs(x) + p.Y.FromLimbs(y) + p.Z.FromLimbs(z) + + return *p +} + +func (p *Projective) FromAffine(a Affine) Projective { + z := BaseField{} + z.One() + + p.X = a.X + p.Y = a.Y + p.Z = z + + return *p +} + +func (p Projective) ProjectiveEq(p2 *Projective) bool { + cP := (*C.projective_t)(unsafe.Pointer(&p)) + cP2 := (*C.projective_t)(unsafe.Pointer(&p2)) + __ret := C.grumpkin_eq(cP, cP2) + return __ret == (C._Bool)(true) +} + +func (p *Projective) ProjectiveToAffine() Affine { + var a Affine + + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(&p)) + C.grumpkin_to_affine(cP, cA) + return a +} + +func GenerateProjectivePoints(size int) core.HostSlice[Projective] { + points := make([]Projective, size) + for i := range points { + points[i] = Projective{} + } + + pointsSlice := core.HostSliceFromElements[Projective](points) + pPoints := (*C.projective_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.grumpkin_generate_projective_points(pPoints, cSize) + + return pointsSlice +} + +type Affine struct { + X, Y BaseField +} + +func (a Affine) Size() int { + return a.X.Size() * 2 +} + +func (a Affine) AsPointer() *uint32 { + return a.X.AsPointer() +} + +func (a *Affine) Zero() Affine { + a.X.Zero() + a.Y.Zero() + + return *a +} + +func (a *Affine) FromLimbs(x, y []uint32) Affine { + a.X.FromLimbs(x) + a.Y.FromLimbs(y) + + return *a +} + +func (a Affine) ToProjective() Projective { + var z BaseField + + return Projective{ + X: a.X, + Y: a.Y, + Z: z.One(), + } +} + +func AffineFromProjective(p *Projective) Affine { + return p.ProjectiveToAffine() +} + +func GenerateAffinePoints(size int) core.HostSlice[Affine] { + points := make([]Affine, size) + for i := range points { + points[i] = Affine{} + } + + pointsSlice := core.HostSliceFromElements[Affine](points) + cPoints := (*C.affine_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.grumpkin_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { + cValues := (*C.affine_t)(points.AsUnsafePointer()) + cSize := (C.size_t)(points.Len()) + cIsInto := (C._Bool)(isInto) + defaultCtx, _ := cr.GetDefaultDeviceContext() + cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) + __ret := C.grumpkin_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) + err := (cr.CudaError)(__ret) + return err +} + +func AffineToMontgomery(points *core.DeviceSlice) cr.CudaError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, true) +} + +func AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, false) +} + +func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { + cValues := (*C.projective_t)(points.AsUnsafePointer()) + cSize := (C.size_t)(points.Len()) + cIsInto := (C._Bool)(isInto) + defaultCtx, _ := cr.GetDefaultDeviceContext() + cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) + __ret := C.grumpkin_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) + err := (cr.CudaError)(__ret) + return err +} + +func ProjectiveToMontgomery(points *core.DeviceSlice) cr.CudaError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, true) +} + +func ProjectiveFromMontgomery(points *core.DeviceSlice) cr.CudaError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang/curves/grumpkin/include/curve.h b/wrappers/golang/curves/grumpkin/include/curve.h new file mode 100644 index 00000000..8466982e --- /dev/null +++ b/wrappers/golang/curves/grumpkin/include/curve.h @@ -0,0 +1,26 @@ +#include +#include + +#ifndef _GRUMPKIN_CURVE_H +#define _GRUMPKIN_CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct DeviceContext DeviceContext; + +bool grumpkin_eq(projective_t* point1, projective_t* point2); +void grumpkin_to_affine(projective_t* point, affine_t* point_out); +void grumpkin_generate_projective_points(projective_t* points, int size); +void grumpkin_generate_affine_points(affine_t* points, int size); +cudaError_t grumpkin_affine_convert_montgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t grumpkin_projective_convert_montgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/grumpkin/include/scalar_field.h b/wrappers/golang/curves/grumpkin/include/scalar_field.h new file mode 100644 index 00000000..90517bfd --- /dev/null +++ b/wrappers/golang/curves/grumpkin/include/scalar_field.h @@ -0,0 +1,21 @@ +#include +#include + +#ifndef _GRUMPKIN_FIELD_H +#define _GRUMPKIN_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void grumpkin_generate_scalars(scalar_t* scalars, int size); +cudaError_t grumpkin_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/grumpkin/main.go b/wrappers/golang/curves/grumpkin/main.go new file mode 100644 index 00000000..3404e227 --- /dev/null +++ b/wrappers/golang/curves/grumpkin/main.go @@ -0,0 +1,4 @@ +package grumpkin + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build/lib -lingo_curve_grumpkin -lingo_field_grumpkin -lstdc++ -lm +import "C" diff --git a/wrappers/golang/curves/grumpkin/msm/include/msm.h b/wrappers/golang/curves/grumpkin/msm/include/msm.h new file mode 100644 index 00000000..4f4b996d --- /dev/null +++ b/wrappers/golang/curves/grumpkin/msm/include/msm.h @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _GRUMPKIN_MSM_H +#define _GRUMPKIN_MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t grumpkin_msm_cuda(const scalar_t* scalars,const affine_t* points, int count, MSMConfig* config, projective_t* out); +cudaError_t grumpkin_precompute_msm_bases_cuda(affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/grumpkin/msm/msm.go b/wrappers/golang/curves/grumpkin/msm/msm.go new file mode 100644 index 00000000..4d171784 --- /dev/null +++ b/wrappers/golang/curves/grumpkin/msm/msm.go @@ -0,0 +1,45 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.affine_t)(pointsPointer) + cResults := (*C.projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.grumpkin_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.grumpkin_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/curves/grumpkin/scalar_field.go b/wrappers/golang/curves/grumpkin/scalar_field.go new file mode 100644 index 00000000..a335fb09 --- /dev/null +++ b/wrappers/golang/curves/grumpkin/scalar_field.go @@ -0,0 +1,121 @@ +package grumpkin + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +const ( + SCALAR_LIMBS int = 8 +) + +type ScalarField struct { + limbs [SCALAR_LIMBS]uint32 +} + +func (f ScalarField) Len() int { + return int(SCALAR_LIMBS) +} + +func (f ScalarField) Size() int { + return int(SCALAR_LIMBS * 4) +} + +func (f ScalarField) GetLimbs() []uint32 { + return f.limbs[:] +} + +func (f ScalarField) AsPointer() *uint32 { + return &f.limbs[0] +} + +func (f *ScalarField) FromUint32(v uint32) ScalarField { + f.limbs[0] = v + return *f +} + +func (f *ScalarField) FromLimbs(limbs []uint32) ScalarField { + if len(limbs) != f.Len() { + panic("Called FromLimbs with limbs of different length than field") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *ScalarField) Zero() ScalarField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *ScalarField) One() ScalarField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *ScalarField) FromBytesLittleEndian(bytes []byte) ScalarField { + if len(bytes)/4 != f.Len() { + panic(fmt.Sprintf("Called FromBytesLittleEndian with incorrect bytes length; expected %d - got %d", f.Len()*4, len(bytes))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f ScalarField) ToBytesLittleEndian() []byte { + bytes := make([]byte, f.Len()*4) + for i, v := range f.limbs { + binary.LittleEndian.PutUint32(bytes[i*4:], v) + } + + return bytes +} + +func GenerateScalars(size int) core.HostSlice[ScalarField] { + scalarSlice := make(core.HostSlice[ScalarField], size) + + cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) + cSize := (C.int)(size) + C.grumpkin_generate_scalars(cScalars, cSize) + + return scalarSlice +} + +func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { + cValues := (*C.scalar_t)(scalars.AsUnsafePointer()) + cSize := (C.size_t)(scalars.Len()) + cIsInto := (C._Bool)(isInto) + defaultCtx, _ := cr.GetDefaultDeviceContext() + cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) + __ret := C.grumpkin_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx) + err := (cr.CudaError)(__ret) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) cr.CudaError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) cr.CudaError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang/curves/bn254/base_field_test.go b/wrappers/golang/curves/grumpkin/tests/base_field_test.go similarity index 59% rename from wrappers/golang/curves/bn254/base_field_test.go rename to wrappers/golang/curves/grumpkin/tests/base_field_test.go index 50e14b2d..6acc6855 100644 --- a/wrappers/golang/curves/bn254/base_field_test.go +++ b/wrappers/golang/curves/grumpkin/tests/base_field_test.go @@ -1,34 +1,40 @@ -package bn254 +package tests import ( + grumpkin "github.com/ingonyama-zk/icicle/wrappers/golang/curves/grumpkin" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + BASE_LIMBS = grumpkin.BASE_LIMBS +) + func TestBaseFieldFromLimbs(t *testing.T) { - emptyField := BaseField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + emptyField := grumpkin.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the BaseField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestBaseFieldGetLimbs(t *testing.T) { - emptyField := BaseField{} - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + emptyField := grumpkin.BaseField{} + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the BaseField's limbs") } func TestBaseFieldOne(t *testing.T) { - var emptyField BaseField + var emptyField grumpkin.BaseField emptyField.One() - limbOne := generateLimbOne(int(BASE_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(BASE_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -36,12 +42,12 @@ func TestBaseFieldOne(t *testing.T) { } func TestBaseFieldZero(t *testing.T) { - var emptyField BaseField + var emptyField grumpkin.BaseField emptyField.Zero() limbsZero := make([]uint32, BASE_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -49,24 +55,24 @@ func TestBaseFieldZero(t *testing.T) { } func TestBaseFieldSize(t *testing.T) { - var emptyField BaseField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + var emptyField grumpkin.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestBaseFieldAsPointer(t *testing.T) { - var emptyField BaseField - randLimbs := generateRandomLimb(int(BASE_LIMBS)) + var emptyField grumpkin.BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestBaseFieldFromBytes(t *testing.T) { - var emptyField BaseField - bytes, expected := generateBytesArray(int(BASE_LIMBS)) + var emptyField grumpkin.BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -74,8 +80,8 @@ func TestBaseFieldFromBytes(t *testing.T) { } func TestBaseFieldToBytes(t *testing.T) { - var emptyField BaseField - expected, limbs := generateBytesArray(int(BASE_LIMBS)) + var emptyField grumpkin.BaseField + expected, limbs := test_helpers.GenerateBytesArray(int(BASE_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") diff --git a/wrappers/golang/curves/bw6761/curve_test.go b/wrappers/golang/curves/grumpkin/tests/curve_test.go similarity index 50% rename from wrappers/golang/curves/bw6761/curve_test.go rename to wrappers/golang/curves/grumpkin/tests/curve_test.go index aafbbcdd..c6f94b67 100644 --- a/wrappers/golang/curves/bw6761/curve_test.go +++ b/wrappers/golang/curves/grumpkin/tests/curve_test.go @@ -1,20 +1,22 @@ -package bw6761 +package tests import ( + grumpkin "github.com/ingonyama-zk/icicle/wrappers/golang/curves/grumpkin" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) func TestAffineZero(t *testing.T) { - var fieldZero = BaseField{} + var fieldZero = grumpkin.BaseField{} - var affineZero Affine + var affineZero grumpkin.Affine assert.Equal(t, affineZero.X, fieldZero) assert.Equal(t, affineZero.Y, fieldZero) - x := generateRandomLimb(int(BASE_LIMBS)) - y := generateRandomLimb(int(BASE_LIMBS)) - var affine Affine + x := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var affine grumpkin.Affine affine.FromLimbs(x, y) affine.Zero() @@ -23,10 +25,10 @@ func TestAffineZero(t *testing.T) { } func TestAffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) - var affine Affine + var affine grumpkin.Affine affine.FromLimbs(randLimbs, randLimbs2) assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) @@ -34,15 +36,15 @@ func TestAffineFromLimbs(t *testing.T) { } func TestAffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne grumpkin.BaseField fieldOne.One() - var expected Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected grumpkin.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine Affine + var affine grumpkin.Affine affine.FromLimbs(randLimbs, randLimbs2) projectivePoint := affine.ToProjective() @@ -50,18 +52,18 @@ func TestAffineToProjective(t *testing.T) { } func TestProjectiveZero(t *testing.T) { - var projectiveZero Projective + var projectiveZero grumpkin.Projective projectiveZero.Zero() - var fieldZero = BaseField{} - var fieldOne BaseField + var fieldZero = grumpkin.BaseField{} + var fieldOne grumpkin.BaseField fieldOne.One() assert.Equal(t, projectiveZero.X, fieldZero) assert.Equal(t, projectiveZero.Y, fieldOne) assert.Equal(t, projectiveZero.Z, fieldZero) - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - var projective Projective + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective grumpkin.Projective projective.FromLimbs(randLimbs, randLimbs, randLimbs) projective.Zero() @@ -71,11 +73,11 @@ func TestProjectiveZero(t *testing.T) { } func TestProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int(BASE_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) - var projective Projective + var projective grumpkin.Projective projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) @@ -84,18 +86,18 @@ func TestProjectiveFromLimbs(t *testing.T) { } func TestProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int(BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int(BASE_LIMBS)) - var fieldOne BaseField + randLimbs := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var fieldOne grumpkin.BaseField fieldOne.One() - var expected Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) + var expected grumpkin.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) - var affine Affine + var affine grumpkin.Affine affine.FromLimbs(randLimbs, randLimbs2) - var projectivePoint Projective + var projectivePoint grumpkin.Projective projectivePoint.FromAffine(affine) assert.Equal(t, expected, projectivePoint) } diff --git a/wrappers/golang/curves/grumpkin/tests/main_test.go b/wrappers/golang/curves/grumpkin/tests/main_test.go new file mode 100644 index 00000000..071357d7 --- /dev/null +++ b/wrappers/golang/curves/grumpkin/tests/main_test.go @@ -0,0 +1,17 @@ +package tests + +import ( + "os" + "testing" +) + +const ( + largestTestSize = 20 +) + +func TestMain(m *testing.M) { + + // execute tests + os.Exit(m.Run()) + +} diff --git a/wrappers/golang/curves/grumpkin/tests/msm_test.go b/wrappers/golang/curves/grumpkin/tests/msm_test.go new file mode 100644 index 00000000..0005fdac --- /dev/null +++ b/wrappers/golang/curves/grumpkin/tests/msm_test.go @@ -0,0 +1,168 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicleGrumpkin "github.com/ingonyama-zk/icicle/wrappers/golang/curves/grumpkin" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/grumpkin/msm" +) + +func TestMSM(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { + size := 1 << power + + scalars := icicleGrumpkin.GenerateScalars(size) + points := icicleGrumpkin.GenerateAffinePoints(size) + + stream, _ := cr.CreateStream() + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.Ctx.Stream = &stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + cr.SynchronizeStream(&stream) + + } +} + +func TestMSMBatch(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{10, 16} { + for _, batchSize := range []int{1, 3, 16} { + size := 1 << power + totalSize := size * batchSize + scalars := icicleGrumpkin.GenerateScalars(totalSize) + points := icicleGrumpkin.GenerateAffinePoints(totalSize) + + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], batchSize) + outHost.CopyFromDevice(&out) + out.Free() + + } + } +} + +func TestPrecomputeBase(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + for _, power := range []int{10, 16} { + for _, batchSize := range []int{1, 3, 16} { + size := 1 << power + totalSize := size * batchSize + scalars := icicleGrumpkin.GenerateScalars(totalSize) + points := icicleGrumpkin.GenerateAffinePoints(totalSize) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") + + e = msm.PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") + + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + + cfg.PrecomputeFactor = precomputeFactor + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], batchSize) + outHost.CopyFromDevice(&out) + out.Free() + precomputeOut.Free() + + } + } +} + +func TestMSMSkewedDistribution(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { + size := 1 << power + + scalars := icicleGrumpkin.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := icicleGrumpkin.GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + } +} + +func TestMSMMultiDevice(t *testing.T) { + numDevices, _ := cr.GetDeviceCount() + numDevices = 1 // TODO remove when test env is fixed + fmt.Println("There are ", numDevices, " devices available") + orig_device, _ := cr.GetDevice() + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + wg.Add(1) + cr.RunOnDevice(i, func(args ...any) { + defer wg.Done() + cfg := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { + size := 1 << power + scalars := icicleGrumpkin.GenerateScalars(size) + points := icicleGrumpkin.GenerateAffinePoints(size) + + stream, _ := cr.CreateStream() + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.Ctx.Stream = &stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + cr.SynchronizeStream(&stream) + + } + }) + } + wg.Wait() + cr.SetDevice(orig_device) +} diff --git a/wrappers/golang/curves/bls12377/scalar_field_test.go b/wrappers/golang/curves/grumpkin/tests/scalar_field_test.go similarity index 58% rename from wrappers/golang/curves/bls12377/scalar_field_test.go rename to wrappers/golang/curves/grumpkin/tests/scalar_field_test.go index f4302a8f..f4fb41b0 100644 --- a/wrappers/golang/curves/bls12377/scalar_field_test.go +++ b/wrappers/golang/curves/grumpkin/tests/scalar_field_test.go @@ -1,35 +1,41 @@ -package bls12377 +package tests import ( "github.com/ingonyama-zk/icicle/wrappers/golang/core" + grumpkin "github.com/ingonyama-zk/icicle/wrappers/golang/curves/grumpkin" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + SCALAR_LIMBS = grumpkin.SCALAR_LIMBS +) + func TestScalarFieldFromLimbs(t *testing.T) { - emptyField := ScalarField{} - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + emptyField := grumpkin.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the ScalarField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestScalarFieldGetLimbs(t *testing.T) { - emptyField := ScalarField{} - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + emptyField := grumpkin.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") } func TestScalarFieldOne(t *testing.T) { - var emptyField ScalarField + var emptyField grumpkin.ScalarField emptyField.One() - limbOne := generateLimbOne(int(SCALAR_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(SCALAR_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -37,12 +43,12 @@ func TestScalarFieldOne(t *testing.T) { } func TestScalarFieldZero(t *testing.T) { - var emptyField ScalarField + var emptyField grumpkin.ScalarField emptyField.Zero() limbsZero := make([]uint32, SCALAR_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -50,24 +56,24 @@ func TestScalarFieldZero(t *testing.T) { } func TestScalarFieldSize(t *testing.T) { - var emptyField ScalarField - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + var emptyField grumpkin.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestScalarFieldAsPointer(t *testing.T) { - var emptyField ScalarField - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + var emptyField grumpkin.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestScalarFieldFromBytes(t *testing.T) { - var emptyField ScalarField - bytes, expected := generateBytesArray(int(SCALAR_LIMBS)) + var emptyField grumpkin.ScalarField + bytes, expected := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -75,39 +81,39 @@ func TestScalarFieldFromBytes(t *testing.T) { } func TestScalarFieldToBytes(t *testing.T) { - var emptyField ScalarField - expected, limbs := generateBytesArray(int(SCALAR_LIMBS)) + var emptyField grumpkin.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") } -func TestGenerateScalars(t *testing.T) { +func TestGrumpkinGenerateScalars(t *testing.T) { const numScalars = 8 - scalars := GenerateScalars(numScalars) + scalars := grumpkin.GenerateScalars(numScalars) assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) assert.Equal(t, numScalars, scalars.Len()) - zeroScalar := ScalarField{} + zeroScalar := grumpkin.ScalarField{} assert.NotContains(t, scalars, zeroScalar) } -func TestMongtomeryConversion(t *testing.T) { +func TestGrumpkinMongtomeryConversion(t *testing.T) { size := 1 << 15 - scalars := GenerateScalars(size) + scalars := grumpkin.GenerateScalars(size) var deviceScalars core.DeviceSlice scalars.CopyToDevice(&deviceScalars, true) - ToMontgomery(&deviceScalars) + grumpkin.ToMontgomery(&deviceScalars) - scalarsMontHost := GenerateScalars(size) + scalarsMontHost := grumpkin.GenerateScalars(size) scalarsMontHost.CopyFromDevice(&deviceScalars) assert.NotEqual(t, scalars, scalarsMontHost) - FromMontgomery(&deviceScalars) + grumpkin.FromMontgomery(&deviceScalars) scalarsMontHost.CopyFromDevice(&deviceScalars) assert.Equal(t, scalars, scalarsMontHost) diff --git a/wrappers/golang/curves/grumpkin/tests/vec_ops_test.go b/wrappers/golang/curves/grumpkin/tests/vec_ops_test.go new file mode 100644 index 00000000..067851cc --- /dev/null +++ b/wrappers/golang/curves/grumpkin/tests/vec_ops_test.go @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + grumpkin "github.com/ingonyama-zk/icicle/wrappers/golang/curves/grumpkin" + "github.com/ingonyama-zk/icicle/wrappers/golang/curves/grumpkin/vecOps" + "github.com/stretchr/testify/assert" +) + +func TestGrumpkinVecOps(t *testing.T) { + testSize := 1 << 14 + + a := grumpkin.GenerateScalars(testSize) + b := grumpkin.GenerateScalars(testSize) + var scalar grumpkin.ScalarField + scalar.One() + ones := core.HostSliceWithValue(scalar, testSize) + + out := make(core.HostSlice[grumpkin.ScalarField], testSize) + out2 := make(core.HostSlice[grumpkin.ScalarField], testSize) + out3 := make(core.HostSlice[grumpkin.ScalarField], testSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.VecOp(a, b, out, cfg, core.Add) + vecOps.VecOp(out, b, out2, cfg, core.Sub) + + assert.Equal(t, a, out2) + + vecOps.VecOp(a, ones, out3, cfg, core.Mul) + + assert.Equal(t, a, out3) +} + +func TestGrumpkinTranspose(t *testing.T) { + rowSize := 1 << 6 + columnSize := 1 << 8 + onDevice := false + isAsync := false + + matrix := grumpkin.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[grumpkin.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[grumpkin.ScalarField], rowSize*columnSize) + + ctx, _ := cr.GetDefaultDeviceContext() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, ctx, onDevice, isAsync) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + onDevice = true + + matrix.CopyToDevice(&dMatrix, true) + dOut.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + dOut2.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + + vecOps.TransposeMatrix(dMatrix, dOut, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, ctx, onDevice, isAsync) + output := make(core.HostSlice[grumpkin.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang/curves/grumpkin/vecOps/include/vec_ops.h b/wrappers/golang/curves/grumpkin/vecOps/include/vec_ops.h new file mode 100644 index 00000000..c348dafa --- /dev/null +++ b/wrappers/golang/curves/grumpkin/vecOps/include/vec_ops.h @@ -0,0 +1,53 @@ +#include +#include + +#ifndef _GRUMPKIN_VEC_OPS_H +#define _GRUMPKIN_VEC_OPS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t grumpkin_mul_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t grumpkin_add_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t grumpkin_sub_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t grumpkin_transpose_matrix_cuda( + scalar_t* mat_in, + int row_size, + int column_size, + scalar_t* mat_out, + DeviceContext* ctx, + bool on_device, + bool is_async +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/curves/grumpkin/vecOps/vec_ops.go b/wrappers/golang/curves/grumpkin/vecOps/vec_ops.go new file mode 100644 index 00000000..985bb0f4 --- /dev/null +++ b/wrappers/golang/curves/grumpkin/vecOps/vec_ops.go @@ -0,0 +1,48 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + + "unsafe" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { + aPointer, bPointer, outPointer, cfgPointer, size := core.VecOpCheck(a, b, out, &config) + + cA := (*C.scalar_t)(aPointer) + cB := (*C.scalar_t)(bPointer) + cOut := (*C.scalar_t)(outPointer) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cSize := (C.int)(size) + + switch op { + case core.Sub: + ret = (cr.CudaError)(C.grumpkin_sub_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (cr.CudaError)(C.grumpkin_add_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (cr.CudaError)(C.grumpkin_mul_cuda(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError) { + core.TransposeCheck(in, out, onDevice) + + cIn := (*C.scalar_t)(in.AsUnsafePointer()) + cOut := (*C.scalar_t)(out.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cOnDevice := (C._Bool)(onDevice) + cIsAsync := (C._Bool)(isAsync) + + err := (cr.CudaError)(C.grumpkin_transpose_matrix_cuda(cIn, cRowSize, cColumnSize, cOut, cCtx, cOnDevice, cIsAsync)) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/curves/include/types.h b/wrappers/golang/curves/include/types.h deleted file mode 100644 index e74ccd50..00000000 --- a/wrappers/golang/curves/include/types.h +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#ifndef _TYPES_H -#define _TYPES_H - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct scalar_t scalar_t; -typedef struct projective_t projective_t; -typedef struct g2_projective_t g2_projective_t; -typedef struct affine_t affine_t; -typedef struct g2_affine_t g2_affine_t; - -typedef struct MSMConfig MSMConfig; -typedef struct NTTConfig NTTConfig; -typedef struct VecOpsConfig VecOpsConfig; -typedef struct DeviceContext DeviceContext; - -typedef cudaError_t cudaError_t; - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/wrappers/golang/fields/babybear/extension/extension_field.go b/wrappers/golang/fields/babybear/extension/extension_field.go new file mode 100644 index 00000000..1a2486ec --- /dev/null +++ b/wrappers/golang/fields/babybear/extension/extension_field.go @@ -0,0 +1,121 @@ +package extension + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +const ( + EXTENSION_LIMBS int = 4 +) + +type ExtensionField struct { + limbs [EXTENSION_LIMBS]uint32 +} + +func (f ExtensionField) Len() int { + return int(EXTENSION_LIMBS) +} + +func (f ExtensionField) Size() int { + return int(EXTENSION_LIMBS * 4) +} + +func (f ExtensionField) GetLimbs() []uint32 { + return f.limbs[:] +} + +func (f ExtensionField) AsPointer() *uint32 { + return &f.limbs[0] +} + +func (f *ExtensionField) FromUint32(v uint32) ExtensionField { + f.limbs[0] = v + return *f +} + +func (f *ExtensionField) FromLimbs(limbs []uint32) ExtensionField { + if len(limbs) != f.Len() { + panic("Called FromLimbs with limbs of different length than field") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *ExtensionField) Zero() ExtensionField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *ExtensionField) One() ExtensionField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *ExtensionField) FromBytesLittleEndian(bytes []byte) ExtensionField { + if len(bytes)/4 != f.Len() { + panic(fmt.Sprintf("Called FromBytesLittleEndian with incorrect bytes length; expected %d - got %d", f.Len()*4, len(bytes))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f ExtensionField) ToBytesLittleEndian() []byte { + bytes := make([]byte, f.Len()*4) + for i, v := range f.limbs { + binary.LittleEndian.PutUint32(bytes[i*4:], v) + } + + return bytes +} + +func GenerateScalars(size int) core.HostSlice[ExtensionField] { + scalarSlice := make(core.HostSlice[ExtensionField], size) + + cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) + cSize := (C.int)(size) + C.babybear_extension_generate_scalars(cScalars, cSize) + + return scalarSlice +} + +func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { + cValues := (*C.scalar_t)(scalars.AsUnsafePointer()) + cSize := (C.size_t)(scalars.Len()) + cIsInto := (C._Bool)(isInto) + defaultCtx, _ := cr.GetDefaultDeviceContext() + cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) + __ret := C.babybear_extension_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx) + err := (cr.CudaError)(__ret) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) cr.CudaError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) cr.CudaError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang/fields/babybear/extension/include/scalar_field.h b/wrappers/golang/fields/babybear/extension/include/scalar_field.h new file mode 100644 index 00000000..a6b641aa --- /dev/null +++ b/wrappers/golang/fields/babybear/extension/include/scalar_field.h @@ -0,0 +1,21 @@ +#include +#include + +#ifndef _BABYBEAR_EXTENSION_FIELD_H +#define _BABYBEAR_EXTENSION_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void babybear_extension_generate_scalars(scalar_t* scalars, int size); +cudaError_t babybear_extension_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/fields/babybear/extension/main.go b/wrappers/golang/fields/babybear/extension/main.go new file mode 100644 index 00000000..2a924f07 --- /dev/null +++ b/wrappers/golang/fields/babybear/extension/main.go @@ -0,0 +1,4 @@ +package extension + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../../icicle/build/lib -lingo_field_babybear -lstdc++ -lm +import "C" diff --git a/wrappers/golang/fields/babybear/extension/ntt/include/ntt.h b/wrappers/golang/fields/babybear/extension/ntt/include/ntt.h new file mode 100644 index 00000000..eea73ea9 --- /dev/null +++ b/wrappers/golang/fields/babybear/extension/ntt/include/ntt.h @@ -0,0 +1,22 @@ +#include +#include + +#ifndef _BABYBEAR_EXTENSION_NTT_H +#define _BABYBEAR_EXTENSION_NTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct NTTConfig NTTConfig; + + +cudaError_t babybear_extension_ntt_cuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); + + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/fields/babybear/extension/ntt/ntt.go b/wrappers/golang/fields/babybear/extension/ntt/ntt.go new file mode 100644 index 00000000..6238fbce --- /dev/null +++ b/wrappers/golang/fields/babybear/extension/ntt/ntt.go @@ -0,0 +1,24 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + scalarsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](scalars, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.scalar_t)(resultsPointer) + + __ret := C.babybear_extension_ntt_cuda(cScalars, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/fields/babybear/extension/vecOps/include/vec_ops.h b/wrappers/golang/fields/babybear/extension/vecOps/include/vec_ops.h new file mode 100644 index 00000000..70f9bc54 --- /dev/null +++ b/wrappers/golang/fields/babybear/extension/vecOps/include/vec_ops.h @@ -0,0 +1,53 @@ +#include +#include + +#ifndef _BABYBEAR_EXTENSION_VEC_OPS_H +#define _BABYBEAR_EXTENSION_VEC_OPS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t babybear_extension_mul_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t babybear_extension_add_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t babybear_extension_sub_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t babybear_extension_transpose_matrix_cuda( + scalar_t* mat_in, + int row_size, + int column_size, + scalar_t* mat_out, + DeviceContext* ctx, + bool on_device, + bool is_async +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/fields/babybear/extension/vecOps/vec_ops.go b/wrappers/golang/fields/babybear/extension/vecOps/vec_ops.go new file mode 100644 index 00000000..c2cc71e0 --- /dev/null +++ b/wrappers/golang/fields/babybear/extension/vecOps/vec_ops.go @@ -0,0 +1,48 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + + "unsafe" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { + aPointer, bPointer, outPointer, cfgPointer, size := core.VecOpCheck(a, b, out, &config) + + cA := (*C.scalar_t)(aPointer) + cB := (*C.scalar_t)(bPointer) + cOut := (*C.scalar_t)(outPointer) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cSize := (C.int)(size) + + switch op { + case core.Sub: + ret = (cr.CudaError)(C.babybear_extension_sub_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (cr.CudaError)(C.babybear_extension_add_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (cr.CudaError)(C.babybear_extension_mul_cuda(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError) { + core.TransposeCheck(in, out, onDevice) + + cIn := (*C.scalar_t)(in.AsUnsafePointer()) + cOut := (*C.scalar_t)(out.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cOnDevice := (C._Bool)(onDevice) + cIsAsync := (C._Bool)(isAsync) + + err := (cr.CudaError)(C.babybear_extension_transpose_matrix_cuda(cIn, cRowSize, cColumnSize, cOut, cCtx, cOnDevice, cIsAsync)) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/fields/babybear/include/scalar_field.h b/wrappers/golang/fields/babybear/include/scalar_field.h new file mode 100644 index 00000000..f3a72b25 --- /dev/null +++ b/wrappers/golang/fields/babybear/include/scalar_field.h @@ -0,0 +1,21 @@ +#include +#include + +#ifndef _BABYBEAR_FIELD_H +#define _BABYBEAR_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void babybear_generate_scalars(scalar_t* scalars, int size); +cudaError_t babybear_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/fields/babybear/main.go b/wrappers/golang/fields/babybear/main.go new file mode 100644 index 00000000..79d65721 --- /dev/null +++ b/wrappers/golang/fields/babybear/main.go @@ -0,0 +1,4 @@ +package babybear + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build/lib -lingo_field_babybear -lstdc++ -lm +import "C" diff --git a/wrappers/golang/fields/babybear/ntt/include/ntt.h b/wrappers/golang/fields/babybear/ntt/include/ntt.h new file mode 100644 index 00000000..959c81d2 --- /dev/null +++ b/wrappers/golang/fields/babybear/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#include +#include + +#ifndef _BABYBEAR_NTT_H +#define _BABYBEAR_NTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct NTTConfig NTTConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t babybear_ntt_cuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +cudaError_t babybear_initialize_domain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); +cudaError_t babybear_release_domain(DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/fields/babybear/ntt/ntt.go b/wrappers/golang/fields/babybear/ntt/ntt.go new file mode 100644 index 00000000..99e2a3ca --- /dev/null +++ b/wrappers/golang/fields/babybear/ntt/ntt.go @@ -0,0 +1,56 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + babybear "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear" +) + +import ( + "unsafe" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + scalarsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](scalars, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.scalar_t)(resultsPointer) + + __ret := C.babybear_ntt_cuda(cScalars, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func GetDefaultNttConfig() core.NTTConfig[[babybear.SCALAR_LIMBS]uint32] { + cosetGenField := babybear.ScalarField{} + cosetGenField.One() + var cosetGen [babybear.SCALAR_LIMBS]uint32 + for i, v := range cosetGenField.GetLimbs() { + cosetGen[i] = v + } + + return core.GetDefaultNTTConfig(cosetGen) +} + +func InitDomain(primitiveRoot babybear.ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cFastTwiddles := (C._Bool)(fastTwiddles) + __ret := C.babybear_initialize_domain(cPrimitiveRoot, cCtx, cFastTwiddles) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + __ret := C.babybear_release_domain(cCtx) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/fields/babybear/polynomial/include/polynomial.h b/wrappers/golang/fields/babybear/polynomial/include/polynomial.h new file mode 100644 index 00000000..14e8dbf1 --- /dev/null +++ b/wrappers/golang/fields/babybear/polynomial/include/polynomial.h @@ -0,0 +1,51 @@ +#include +#include + +#ifndef _BABYBEAR_POLY_H +#define _BABYBEAR_POLY_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct PolynomialInst PolynomialInst; +typedef struct IntegrityPointer IntegrityPointer; + +bool babybear_polynomial_init_cuda_backend(); +PolynomialInst* babybear_polynomial_create_from_coefficients(scalar_t* coeffs, size_t size); +PolynomialInst* babybear_polynomial_create_from_rou_evaluations(scalar_t* evals, size_t size); +PolynomialInst* babybear_polynomial_clone(const PolynomialInst* p); +void babybear_polynomial_print(PolynomialInst* p); +void babybear_polynomial_delete(PolynomialInst* instance); +PolynomialInst* babybear_polynomial_add(const PolynomialInst* a, const PolynomialInst* b); +void babybear_polynomial_add_inplace(PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* babybear_polynomial_subtract(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* babybear_polynomial_multiply(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* babybear_polynomial_multiply_by_scalar(const PolynomialInst* a, const scalar_t* scalar); +void babybear_polynomial_division(const PolynomialInst* a, const PolynomialInst* b, PolynomialInst** q /*OUT*/, PolynomialInst** r /*OUT*/); +PolynomialInst* babybear_polynomial_quotient(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* babybear_polynomial_remainder(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* babybear_polynomial_divide_by_vanishing(const PolynomialInst* p, size_t vanishing_poly_degree); +void babybear_polynomial_add_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void babybear_polynomial_sub_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void babybear_polynomial_evaluate_on_domain(const PolynomialInst* p, scalar_t* domain, size_t domain_size, scalar_t* evals /*OUT*/); +size_t babybear_polynomial_degree(PolynomialInst* p); +size_t babybear_polynomial_copy_coeffs_range(PolynomialInst* p, scalar_t* memory, size_t start_idx, size_t end_idx); +PolynomialInst* babybear_polynomial_even(PolynomialInst* p); +PolynomialInst* babybear_polynomial_odd(PolynomialInst* p); + +// scalar_t* babybear_polynomial_get_coeffs_raw_ptr(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// PolynomialInst* babybear_polynomial_slice(PolynomialInst* p, size_t offset, size_t stride, size_t size); +// IntegrityPointer* babybear_polynomial_get_coeff_view(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// IntegrityPointer* babybear_polynomial_get_rou_evaluations_view(PolynomialInst* p, size_t nof_evals, bool is_reversed, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// const scalar_t* babybear_polynomial_intergrity_ptr_get(IntegrityPointer* p); +// bool babybear_polynomial_intergrity_ptr_is_valid(IntegrityPointer* p); +// void babybear_polynomial_intergrity_ptr_destroy(IntegrityPointer* p); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/wrappers/golang/fields/babybear/polynomial/polynomial.go b/wrappers/golang/fields/babybear/polynomial/polynomial.go new file mode 100644 index 00000000..73650f00 --- /dev/null +++ b/wrappers/golang/fields/babybear/polynomial/polynomial.go @@ -0,0 +1,176 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + babybear "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +func InitPolyBackend() bool { + return (bool)(C.babybear_polynomial_init_cuda_backend()) +} + +func (up *DensePolynomial) Print() { + C.babybear_polynomial_print(up.handle) +} + +func (up *DensePolynomial) CreateFromCoeffecitients(coeffs core.HostOrDeviceSlice) DensePolynomial { + if coeffs.IsOnDevice() { + coeffs.(core.DeviceSlice).CheckDevice() + } + coeffsPointer := (*C.scalar_t)(coeffs.AsUnsafePointer()) + cSize := (C.size_t)(coeffs.Len()) + up.handle = C.babybear_polynomial_create_from_coefficients(coeffsPointer, cSize) + return *up +} + +func (up *DensePolynomial) CreateFromROUEvaluations(evals core.HostOrDeviceSlice) DensePolynomial { + evalsPointer := (*C.scalar_t)(evals.AsUnsafePointer()) + cSize := (C.size_t)(evals.Len()) + up.handle = C.babybear_polynomial_create_from_coefficients(evalsPointer, cSize) + return *up +} + +func (up *DensePolynomial) Clone() DensePolynomial { + return DensePolynomial{ + handle: C.babybear_polynomial_clone(up.handle), + } +} + +// TODO @jeremyfelder: Maybe this should be in a SetFinalizer that is set on Create functions? +func (up *DensePolynomial) Delete() { + C.babybear_polynomial_delete(up.handle) +} + +func (up *DensePolynomial) Add(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.babybear_polynomial_add(up.handle, b.handle), + } +} + +func (up *DensePolynomial) AddInplace(b *DensePolynomial) { + C.babybear_polynomial_add_inplace(up.handle, b.handle) +} + +func (up *DensePolynomial) Subtract(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.babybear_polynomial_subtract(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Multiply(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.babybear_polynomial_multiply(up.handle, b.handle), + } +} + +func (up *DensePolynomial) MultiplyByScalar(scalar babybear.ScalarField) DensePolynomial { + cScalar := (*C.scalar_t)(unsafe.Pointer(scalar.AsPointer())) + return DensePolynomial{ + handle: C.babybear_polynomial_multiply_by_scalar(up.handle, cScalar), + } +} + +func (up *DensePolynomial) Divide(b *DensePolynomial) (DensePolynomial, DensePolynomial) { + var q, r *PolynomialHandle + C.babybear_polynomial_division(up.handle, b.handle, &q, &r) + return DensePolynomial{ + handle: q, + }, DensePolynomial{ + handle: r, + } +} + +func (up *DensePolynomial) Quotient(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.babybear_polynomial_quotient(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Remainder(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.babybear_polynomial_remainder(up.handle, b.handle), + } +} + +func (up *DensePolynomial) DivideByVanishing(vanishing_degree uint64) DensePolynomial { + cVanishingDegree := (C.ulong)(vanishing_degree) + return DensePolynomial{ + handle: C.babybear_polynomial_divide_by_vanishing(up.handle, cVanishingDegree), + } +} + +func (up *DensePolynomial) AddMonomial(monomialCoeff babybear.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]babybear.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.babybear_polynomial_add_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) SubMonomial(monomialCoeff babybear.ScalarField, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]babybear.ScalarField{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.babybear_polynomial_sub_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) Eval(x babybear.ScalarField) babybear.ScalarField { + domains := make(core.HostSlice[babybear.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[babybear.ScalarField], 1) + up.EvalOnDomain(domains, evals) + return evals[0] +} + +func (up *DensePolynomial) EvalOnDomain(domain, evals core.HostOrDeviceSlice) core.HostOrDeviceSlice { + cDomain := (*C.scalar_t)(domain.AsUnsafePointer()) + cDomainSize := (C.size_t)(domain.Len()) + cEvals := (*C.scalar_t)(evals.AsUnsafePointer()) + C.babybear_polynomial_evaluate_on_domain(up.handle, cDomain, cDomainSize, cEvals) + return evals +} + +func (up *DensePolynomial) Degree() int { + return int(C.babybear_polynomial_degree(up.handle)) +} + +func (up *DensePolynomial) CopyCoeffsRange(start, end int, out core.HostOrDeviceSlice) (int, core.HostOrDeviceSlice) { + cStart := (C.size_t)(start) + cEnd := (C.size_t)(end) + cScalarOut := (*C.scalar_t)(out.AsUnsafePointer()) + __cNumCoeffsRead := C.babybear_polynomial_copy_coeffs_range(up.handle, cScalarOut, cStart, cEnd) + return int(__cNumCoeffsRead), out +} + +func (up *DensePolynomial) GetCoeff(idx int) babybear.ScalarField { + out := make(core.HostSlice[babybear.ScalarField], 1) + up.CopyCoeffsRange(idx, idx, out) + return out[0] +} + +func (up *DensePolynomial) Even() DensePolynomial { + evenPoly := C.babybear_polynomial_even(up.handle) + return DensePolynomial{ + handle: evenPoly, + } +} + +func (up *DensePolynomial) Odd() DensePolynomial { + oddPoly := C.babybear_polynomial_odd(up.handle) + return DensePolynomial{ + handle: oddPoly, + } +} diff --git a/wrappers/golang/fields/babybear/scalar_field.go b/wrappers/golang/fields/babybear/scalar_field.go new file mode 100644 index 00000000..97f80d82 --- /dev/null +++ b/wrappers/golang/fields/babybear/scalar_field.go @@ -0,0 +1,121 @@ +package babybear + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +const ( + SCALAR_LIMBS int = 1 +) + +type ScalarField struct { + limbs [SCALAR_LIMBS]uint32 +} + +func (f ScalarField) Len() int { + return int(SCALAR_LIMBS) +} + +func (f ScalarField) Size() int { + return int(SCALAR_LIMBS * 4) +} + +func (f ScalarField) GetLimbs() []uint32 { + return f.limbs[:] +} + +func (f ScalarField) AsPointer() *uint32 { + return &f.limbs[0] +} + +func (f *ScalarField) FromUint32(v uint32) ScalarField { + f.limbs[0] = v + return *f +} + +func (f *ScalarField) FromLimbs(limbs []uint32) ScalarField { + if len(limbs) != f.Len() { + panic("Called FromLimbs with limbs of different length than field") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *ScalarField) Zero() ScalarField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *ScalarField) One() ScalarField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *ScalarField) FromBytesLittleEndian(bytes []byte) ScalarField { + if len(bytes)/4 != f.Len() { + panic(fmt.Sprintf("Called FromBytesLittleEndian with incorrect bytes length; expected %d - got %d", f.Len()*4, len(bytes))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f ScalarField) ToBytesLittleEndian() []byte { + bytes := make([]byte, f.Len()*4) + for i, v := range f.limbs { + binary.LittleEndian.PutUint32(bytes[i*4:], v) + } + + return bytes +} + +func GenerateScalars(size int) core.HostSlice[ScalarField] { + scalarSlice := make(core.HostSlice[ScalarField], size) + + cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) + cSize := (C.int)(size) + C.babybear_generate_scalars(cScalars, cSize) + + return scalarSlice +} + +func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { + cValues := (*C.scalar_t)(scalars.AsUnsafePointer()) + cSize := (C.size_t)(scalars.Len()) + cIsInto := (C._Bool)(isInto) + defaultCtx, _ := cr.GetDefaultDeviceContext() + cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) + __ret := C.babybear_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx) + err := (cr.CudaError)(__ret) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) cr.CudaError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) cr.CudaError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang/fields/babybear/tests/extension_field_test.go b/wrappers/golang/fields/babybear/tests/extension_field_test.go new file mode 100644 index 00000000..e0f01421 --- /dev/null +++ b/wrappers/golang/fields/babybear/tests/extension_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + babybear_extension "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/extension" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + EXTENSION_LIMBS = babybear_extension.EXTENSION_LIMBS +) + +func TestExtensionFieldFromLimbs(t *testing.T) { + emptyField := babybear_extension.ExtensionField{} + randLimbs := test_helpers.GenerateRandomLimb(int(EXTENSION_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ExtensionField's limbs") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestExtensionFieldGetLimbs(t *testing.T) { + emptyField := babybear_extension.ExtensionField{} + randLimbs := test_helpers.GenerateRandomLimb(int(EXTENSION_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ExtensionField's limbs") +} + +func TestExtensionFieldOne(t *testing.T) { + var emptyField babybear_extension.ExtensionField + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int(EXTENSION_LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int(EXTENSION_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "ExtensionField with limbs to field one did not work") +} + +func TestExtensionFieldZero(t *testing.T) { + var emptyField babybear_extension.ExtensionField + emptyField.Zero() + limbsZero := make([]uint32, EXTENSION_LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int(EXTENSION_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "ExtensionField with limbs to field zero failed") +} + +func TestExtensionFieldSize(t *testing.T) { + var emptyField babybear_extension.ExtensionField + randLimbs := test_helpers.GenerateRandomLimb(int(EXTENSION_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") +} + +func TestExtensionFieldAsPointer(t *testing.T) { + var emptyField babybear_extension.ExtensionField + randLimbs := test_helpers.GenerateRandomLimb(int(EXTENSION_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") +} + +func TestExtensionFieldFromBytes(t *testing.T) { + var emptyField babybear_extension.ExtensionField + bytes, expected := test_helpers.GenerateBytesArray(int(EXTENSION_LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func TestExtensionFieldToBytes(t *testing.T) { + var emptyField babybear_extension.ExtensionField + expected, limbs := test_helpers.GenerateBytesArray(int(EXTENSION_LIMBS)) + emptyField.FromLimbs(limbs) + + assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") +} + +func TestBabybear_extensionGenerateScalars(t *testing.T) { + const numScalars = 8 + scalars := babybear_extension.GenerateScalars(numScalars) + + assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) + + assert.Equal(t, numScalars, scalars.Len()) + zeroScalar := babybear_extension.ExtensionField{} + assert.NotContains(t, scalars, zeroScalar) +} + +func TestBabybear_extensionMongtomeryConversion(t *testing.T) { + size := 1 << 15 + scalars := babybear_extension.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + babybear_extension.ToMontgomery(&deviceScalars) + + scalarsMontHost := babybear_extension.GenerateScalars(size) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.NotEqual(t, scalars, scalarsMontHost) + + babybear_extension.FromMontgomery(&deviceScalars) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.Equal(t, scalars, scalarsMontHost) +} diff --git a/wrappers/golang/fields/babybear/tests/extension_vec_ops_test.go b/wrappers/golang/fields/babybear/tests/extension_vec_ops_test.go new file mode 100644 index 00000000..ba8e9d54 --- /dev/null +++ b/wrappers/golang/fields/babybear/tests/extension_vec_ops_test.go @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + babybear_extension "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/extension" + "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/extension/vecOps" + "github.com/stretchr/testify/assert" +) + +func TestBabybear_extensionVecOps(t *testing.T) { + testSize := 1 << 14 + + a := babybear_extension.GenerateScalars(testSize) + b := babybear_extension.GenerateScalars(testSize) + var scalar babybear_extension.ExtensionField + scalar.One() + ones := core.HostSliceWithValue(scalar, testSize) + + out := make(core.HostSlice[babybear_extension.ExtensionField], testSize) + out2 := make(core.HostSlice[babybear_extension.ExtensionField], testSize) + out3 := make(core.HostSlice[babybear_extension.ExtensionField], testSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.VecOp(a, b, out, cfg, core.Add) + vecOps.VecOp(out, b, out2, cfg, core.Sub) + + assert.Equal(t, a, out2) + + vecOps.VecOp(a, ones, out3, cfg, core.Mul) + + assert.Equal(t, a, out3) +} + +func TestBabybear_extensionTranspose(t *testing.T) { + rowSize := 1 << 6 + columnSize := 1 << 8 + onDevice := false + isAsync := false + + matrix := babybear_extension.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[babybear_extension.ExtensionField], rowSize*columnSize) + out2 := make(core.HostSlice[babybear_extension.ExtensionField], rowSize*columnSize) + + ctx, _ := cr.GetDefaultDeviceContext() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, ctx, onDevice, isAsync) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + onDevice = true + + matrix.CopyToDevice(&dMatrix, true) + dOut.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + dOut2.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + + vecOps.TransposeMatrix(dMatrix, dOut, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, ctx, onDevice, isAsync) + output := make(core.HostSlice[babybear_extension.ExtensionField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang/fields/babybear/tests/main_test.go b/wrappers/golang/fields/babybear/tests/main_test.go new file mode 100644 index 00000000..c3b80f7f --- /dev/null +++ b/wrappers/golang/fields/babybear/tests/main_test.go @@ -0,0 +1,42 @@ +package tests + +import ( + "os" + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + babybear "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/ntt" + poly "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/polynomial" +) + +const ( + largestTestSize = 20 +) + +func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { + rouIcicle := babybear.ScalarField{} + rouIcicle.FromUint32(1461624142) + e := ntt.InitDomain(rouIcicle, cfg.Ctx, false) + return e +} + +func TestMain(m *testing.M) { + poly.InitPolyBackend() + + // setup domain + cfg := ntt.GetDefaultNttConfig() + e := initDomain(largestTestSize, cfg) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("initDomain failed") + } + + // execute tests + os.Exit(m.Run()) + + // release domain + e = ntt.ReleaseDomain(cfg.Ctx) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("ReleaseDomain failed") + } +} diff --git a/wrappers/golang/fields/babybear/tests/ntt_no_domain_test.go b/wrappers/golang/fields/babybear/tests/ntt_no_domain_test.go new file mode 100644 index 00000000..e21bc5b9 --- /dev/null +++ b/wrappers/golang/fields/babybear/tests/ntt_no_domain_test.go @@ -0,0 +1,81 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + babybear_extension "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/extension" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/ntt" +) + +func TestNttNoDomain(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := babybear_extension.GenerateScalars(1 << largestTestSize) + + for _, size := range []int{4, largestTestSize} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + + scalarsCopy := core.HostSliceFromElements[babybear_extension.ExtensionField](scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[babybear_extension.ExtensionField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + } + } +} + +func TestNttDeviceAsyncNoDomain(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := babybear_extension.GenerateScalars(1 << largestTestSize) + + for _, size := range []int{1, 10, largestTestSize} { + for _, direction := range []core.NTTDir{core.KForward, core.KInverse} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[babybear_extension.ExtensionField](scalars[:testSize]) + + stream, _ := cr.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.Ctx.Stream = &stream + + var deviceInput core.DeviceSlice + scalarsCopy.CopyToDeviceAsync(&deviceInput, stream, true) + var deviceOutput core.DeviceSlice + deviceOutput.MallocAsync(testSize*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) + + // run ntt + ntt.Ntt(deviceInput, direction, &cfg, deviceOutput) + output := make(core.HostSlice[babybear_extension.ExtensionField], testSize) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + cr.SynchronizeStream(&stream) + } + } + } +} + +func TestNttBatchNoDomain(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestBatchSize := 100 + scalars := babybear_extension.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{1, 16, largestBatchSize} { + testSize := 1 << size + totalSize := testSize * batchSize + + scalarsCopy := core.HostSliceFromElements[babybear_extension.ExtensionField](scalars[:totalSize]) + + cfg.Ordering = core.KNN + cfg.BatchSize = int32(batchSize) + // run ntt + output := make(core.HostSlice[babybear_extension.ExtensionField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + } + } +} diff --git a/wrappers/golang/fields/babybear/tests/ntt_test.go b/wrappers/golang/fields/babybear/tests/ntt_test.go new file mode 100644 index 00000000..cf152f3e --- /dev/null +++ b/wrappers/golang/fields/babybear/tests/ntt_test.go @@ -0,0 +1,170 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + babybear "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + "github.com/stretchr/testify/assert" +) + +func TestNTTGetDefaultConfig(t *testing.T) { + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(babybear.SCALAR_LIMBS)) + assert.Equal(t, expected, actual.CosetGen[:]) + + cosetGenField := babybear.ScalarField{} + cosetGenField.One() + assert.ElementsMatch(t, cosetGenField.GetLimbs(), actual.CosetGen) +} + +func TestInitDomain(t *testing.T) { + t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") + cfg := ntt.GetDefaultNttConfig() + assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) +} + +func TestNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := babybear.GenerateScalars(1 << largestTestSize) + + for _, size := range []int{4, largestTestSize} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + + scalarsCopy := core.HostSliceFromElements[babybear.ScalarField](scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[babybear.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + } + } +} + +func TestNttDeviceAsync(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := babybear.GenerateScalars(1 << largestTestSize) + + for _, size := range []int{1, 10, largestTestSize} { + for _, direction := range []core.NTTDir{core.KForward, core.KInverse} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[babybear.ScalarField](scalars[:testSize]) + + stream, _ := cr.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.Ctx.Stream = &stream + + var deviceInput core.DeviceSlice + scalarsCopy.CopyToDeviceAsync(&deviceInput, stream, true) + var deviceOutput core.DeviceSlice + deviceOutput.MallocAsync(testSize*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) + + // run ntt + ntt.Ntt(deviceInput, direction, &cfg, deviceOutput) + output := make(core.HostSlice[babybear.ScalarField], testSize) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + cr.SynchronizeStream(&stream) + } + } + } +} + +func TestNttBatch(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestBatchSize := 100 + scalars := babybear.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{1, 16, largestBatchSize} { + testSize := 1 << size + totalSize := testSize * batchSize + + scalarsCopy := core.HostSliceFromElements[babybear.ScalarField](scalars[:totalSize]) + + cfg.Ordering = core.KNN + cfg.BatchSize = int32(batchSize) + // run ntt + output := make(core.HostSlice[babybear.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + } + } +} + +func TestReleaseDomain(t *testing.T) { + t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") + cfg := ntt.GetDefaultNttConfig() + e := ntt.ReleaseDomain(cfg.Ctx) + assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ReleasDomain failed") +} + +// func TestNttArbitraryCoset(t *testing.T) { +// for _, size := range []int{20} { +// for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { +// testSize := 1 << size +// scalars := GenerateScalars(testSize) + +// cfg := ntt.GetDefaultNttConfig() + +// var scalarsCopy core.HostSlice[ScalarField] +// for _, v := range scalars { +// var scalar ScalarField +// scalarsCopy = append(scalarsCopy, scalar.FromLimbs(v.GetLimbs())) +// } + +// // init domain +// rouMont, _ := fft.Generator(1 << 20) +// rou := rouMont.Bits() +// rouIcicle := ScalarField{} +// limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) + +// rouIcicle.FromLimbs(limbs) +// InitDomain(rouIcicle, cfg.Ctx) +// cfg.Ordering = v + +// // run ntt +// output := make(core.HostSlice[ScalarField], testSize) +// Ntt(scalars, core.KForward, &cfg, output) + +// // Compare with gnark-crypto +// domainWithPrecompute := fft.NewDomain(uint64(testSize)) +// scalarsFr := make([]fr.Element, testSize) +// for i, v := range scalarsCopy { +// slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) +// scalarsFr[i] = slice64 +// } +// outputAsFr := make([]fr.Element, testSize) +// for i, v := range output { +// slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) +// outputAsFr[i] = slice64 +// } + +// // DIT + BitReverse == Ordering.kRR +// // DIT == Ordering.kRN +// // DIF + BitReverse == Ordering.kNN +// // DIF == Ordering.kNR +// var decimation fft.Decimation +// if v == core.KRN || v == core.KRR { +// decimation = fft.DIT +// } else { +// decimation = fft.DIF +// } +// domainWithPrecompute.FFT(scalarsFr, decimation, fft.OnCoset()) +// if v == core.KNN || v == core.KRR { +// fft.BitReverse(scalarsFr) +// } +// if !assert.True(t, reflect.DeepEqual(scalarsFr, outputAsFr)) { +// t.FailNow() +// } +// } +// } +// } diff --git a/wrappers/golang/fields/babybear/tests/polynomial_test.go b/wrappers/golang/fields/babybear/tests/polynomial_test.go new file mode 100644 index 00000000..cc428ab6 --- /dev/null +++ b/wrappers/golang/fields/babybear/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + babybear "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear" + // "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/polynomial" + "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/vecOps" + "github.com/stretchr/testify/assert" +) + +var one, two, three, four, five babybear.ScalarField + +func init() { + one.One() + two.FromUint32(2) + three.FromUint32(3) + four.FromUint32(4) + five.FromUint32(5) +} + +func rand() babybear.ScalarField { + return babybear.GenerateScalars(1)[0] +} + +func randomPoly(size int) (f polynomial.DensePolynomial) { + f.CreateFromCoeffecitients(core.HostSliceFromElements(babybear.GenerateScalars(size))) + return f +} + +func vecOp(a, b babybear.ScalarField, op core.VecOps) babybear.ScalarField { + ahost := core.HostSliceWithValue(a, 1) + bhost := core.HostSliceWithValue(b, 1) + out := make(core.HostSlice[babybear.ScalarField], 1) + + cfg := core.DefaultVecOpsConfig() + vecOps.VecOp(ahost, bhost, out, cfg, op) + return out[0] +} + +func TestPolyCreateFromCoefficients(t *testing.T) { + scalars := babybear.GenerateScalars(33) + var uniPoly polynomial.DensePolynomial + + poly := uniPoly.CreateFromCoeffecitients(scalars) + poly.Print() +} + +func TestPolyEval(t *testing.T) { + // testing correct evaluation of f(8) for f(x)=4x^2+2x+5 + coeffs := core.HostSliceFromElements([]babybear.ScalarField{five, two, four}) + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(coeffs) + + var x babybear.ScalarField + x.FromUint32(8) + domains := make(core.HostSlice[babybear.ScalarField], 1) + domains[0] = x + evals := make(core.HostSlice[babybear.ScalarField], 1) + fEvaled := f.EvalOnDomain(domains, evals) + var expected babybear.ScalarField + assert.Equal(t, expected.FromUint32(277), fEvaled.(core.HostSlice[babybear.ScalarField])[0]) +} + +func TestPolyClone(t *testing.T) { + f := randomPoly(8) + x := rand() + fx := f.Eval(x) + + g := f.Clone() + fg := f.Add(&g) + + gx := g.Eval(x) + fgx := fg.Eval(x) + + assert.Equal(t, fx, gx) + assert.Equal(t, vecOp(fx, gx, core.Add), fgx) +} + +func TestPolyAddSubMul(t *testing.T) { + testSize := 1 << 10 + f := randomPoly(testSize) + g := randomPoly(testSize) + x := rand() + + fx := f.Eval(x) + gx := g.Eval(x) + + polyAdd := f.Add(&g) + fxAddgx := vecOp(fx, gx, core.Add) + assert.Equal(t, polyAdd.Eval(x), fxAddgx) + + polySub := f.Subtract(&g) + fxSubgx := vecOp(fx, gx, core.Sub) + assert.Equal(t, polySub.Eval(x), fxSubgx) + + polyMul := f.Multiply(&g) + fxMulgx := vecOp(fx, gx, core.Mul) + assert.Equal(t, polyMul.Eval(x), fxMulgx) + + s1 := rand() + polMulS1 := f.MultiplyByScalar(s1) + assert.Equal(t, polMulS1.Eval(x), vecOp(fx, s1, core.Mul)) + + s2 := rand() + polMulS2 := f.MultiplyByScalar(s2) + assert.Equal(t, polMulS2.Eval(x), vecOp(fx, s2, core.Mul)) +} + +func TestPolyMonomials(t *testing.T) { + var zero babybear.ScalarField + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements([]babybear.ScalarField{one, zero, two})) + x := rand() + + fx := f.Eval(x) + f.AddMonomial(three, 1) + fxAdded := f.Eval(x) + assert.Equal(t, fxAdded, vecOp(fx, vecOp(three, x, core.Mul), core.Add)) + + f.SubMonomial(one, 0) + fxSub := f.Eval(x) + assert.Equal(t, fxSub, vecOp(fxAdded, one, core.Sub)) +} + +func TestPolyReadCoeffs(t *testing.T) { + var f polynomial.DensePolynomial + coeffs := core.HostSliceFromElements([]babybear.ScalarField{one, two, three, four}) + f.CreateFromCoeffecitients(coeffs) + coeffsCopied := make(core.HostSlice[babybear.ScalarField], coeffs.Len()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsCopied) + assert.ElementsMatch(t, coeffs, coeffsCopied) + + var coeffsDevice core.DeviceSlice + coeffsDevice.Malloc(coeffs.Len()*one.Size(), one.Size()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsDevice) + coeffsHost := make(core.HostSlice[babybear.ScalarField], coeffs.Len()) + coeffsHost.CopyFromDevice(&coeffsDevice) + + assert.ElementsMatch(t, coeffs, coeffsHost) +} + +func TestPolyOddEvenSlicing(t *testing.T) { + size := 1<<10 - 3 + f := randomPoly(size) + + even := f.Even() + odd := f.Odd() + assert.Equal(t, f.Degree(), even.Degree()+odd.Degree()+1) + + x := rand() + var evenExpected, oddExpected babybear.ScalarField + for i := size; i >= 0; i-- { + if i%2 == 0 { + mul := vecOp(evenExpected, x, core.Mul) + evenExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } else { + mul := vecOp(oddExpected, x, core.Mul) + oddExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } + } + + evenEvaled := even.Eval(x) + assert.Equal(t, evenExpected, evenEvaled) + + oddEvaled := odd.Eval(x) + assert.Equal(t, oddExpected, oddEvaled) +} + +func TestPolynomialDivision(t *testing.T) { + // divide f(x)/g(x), compute q(x), r(x) and check f(x)=q(x)*g(x)+r(x) + var f, g polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements(babybear.GenerateScalars(1 << 4))) + g.CreateFromCoeffecitients(core.HostSliceFromElements(babybear.GenerateScalars(1 << 2))) + + q, r := f.Divide(&g) + + qMulG := q.Multiply(&g) + fRecon := qMulG.Add(&r) + + x := babybear.GenerateScalars(1)[0] + fEval := f.Eval(x) + fReconEval := fRecon.Eval(x) + assert.Equal(t, fEval, fReconEval) +} + +func TestDivideByVanishing(t *testing.T) { + // poly of x^4-1 vanishes ad 4th rou + var zero babybear.ScalarField + minus_one := vecOp(zero, one, core.Sub) + coeffs := core.HostSliceFromElements([]babybear.ScalarField{minus_one, zero, zero, zero, one}) // x^4-1 + var v polynomial.DensePolynomial + v.CreateFromCoeffecitients(coeffs) + + f := randomPoly(1 << 3) + + fv := f.Multiply(&v) + fDegree := f.Degree() + fvDegree := fv.Degree() + assert.Equal(t, fDegree+4, fvDegree) + + fReconstructed := fv.DivideByVanishing(4) + assert.Equal(t, fDegree, fReconstructed.Degree()) + + x := rand() + assert.Equal(t, f.Eval(x), fReconstructed.Eval(x)) +} + +// func TestPolySlice(t *testing.T) { +// size := 4 +// coeffs := babybear.GenerateScalars(size) +// var f DensePolynomial +// f.CreateFromCoeffecitients(coeffs) +// fSlice := f.AsSlice() +// assert.True(t, fSlice.IsOnDevice()) +// assert.Equal(t, size, fSlice.Len()) + +// hostSlice := make(core.HostSlice[babybear.ScalarField], size) +// hostSlice.CopyFromDevice(fSlice) +// assert.Equal(t, coeffs, hostSlice) + +// cfg := ntt.GetDefaultNttConfig() +// res := make(core.HostSlice[babybear.ScalarField], size) +// ntt.Ntt(fSlice, core.KForward, cfg, res) + +// assert.Equal(t, f.Eval(one), res[0]) +// } diff --git a/wrappers/golang/curves/bls12381/scalar_field_test.go b/wrappers/golang/fields/babybear/tests/scalar_field_test.go similarity index 58% rename from wrappers/golang/curves/bls12381/scalar_field_test.go rename to wrappers/golang/fields/babybear/tests/scalar_field_test.go index fa44d836..c64b433e 100644 --- a/wrappers/golang/curves/bls12381/scalar_field_test.go +++ b/wrappers/golang/fields/babybear/tests/scalar_field_test.go @@ -1,35 +1,41 @@ -package bls12381 +package tests import ( "github.com/ingonyama-zk/icicle/wrappers/golang/core" + babybear "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" "github.com/stretchr/testify/assert" "testing" ) +const ( + SCALAR_LIMBS = babybear.SCALAR_LIMBS +) + func TestScalarFieldFromLimbs(t *testing.T) { - emptyField := ScalarField{} - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + emptyField := babybear.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the ScalarField's limbs") + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) } func TestScalarFieldGetLimbs(t *testing.T) { - emptyField := ScalarField{} - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + emptyField := babybear.ScalarField{} + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the ScalarField's limbs") } func TestScalarFieldOne(t *testing.T) { - var emptyField ScalarField + var emptyField babybear.ScalarField emptyField.One() - limbOne := generateLimbOne(int(SCALAR_LIMBS)) + limbOne := test_helpers.GenerateLimbOne(int(SCALAR_LIMBS)) assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.One() @@ -37,12 +43,12 @@ func TestScalarFieldOne(t *testing.T) { } func TestScalarFieldZero(t *testing.T) { - var emptyField ScalarField + var emptyField babybear.ScalarField emptyField.Zero() limbsZero := make([]uint32, SCALAR_LIMBS) assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) emptyField.Zero() @@ -50,24 +56,24 @@ func TestScalarFieldZero(t *testing.T) { } func TestScalarFieldSize(t *testing.T) { - var emptyField ScalarField - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + var emptyField babybear.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") } func TestScalarFieldAsPointer(t *testing.T) { - var emptyField ScalarField - randLimbs := generateRandomLimb(int(SCALAR_LIMBS)) + var emptyField babybear.ScalarField + randLimbs := test_helpers.GenerateRandomLimb(int(SCALAR_LIMBS)) emptyField.FromLimbs(randLimbs[:]) assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") } func TestScalarFieldFromBytes(t *testing.T) { - var emptyField ScalarField - bytes, expected := generateBytesArray(int(SCALAR_LIMBS)) + var emptyField babybear.ScalarField + bytes, expected := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) emptyField.FromBytesLittleEndian(bytes) @@ -75,39 +81,39 @@ func TestScalarFieldFromBytes(t *testing.T) { } func TestScalarFieldToBytes(t *testing.T) { - var emptyField ScalarField - expected, limbs := generateBytesArray(int(SCALAR_LIMBS)) + var emptyField babybear.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) emptyField.FromLimbs(limbs) assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") } -func TestGenerateScalars(t *testing.T) { +func TestBabybearGenerateScalars(t *testing.T) { const numScalars = 8 - scalars := GenerateScalars(numScalars) + scalars := babybear.GenerateScalars(numScalars) assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) assert.Equal(t, numScalars, scalars.Len()) - zeroScalar := ScalarField{} + zeroScalar := babybear.ScalarField{} assert.NotContains(t, scalars, zeroScalar) } -func TestMongtomeryConversion(t *testing.T) { +func TestBabybearMongtomeryConversion(t *testing.T) { size := 1 << 15 - scalars := GenerateScalars(size) + scalars := babybear.GenerateScalars(size) var deviceScalars core.DeviceSlice scalars.CopyToDevice(&deviceScalars, true) - ToMontgomery(&deviceScalars) + babybear.ToMontgomery(&deviceScalars) - scalarsMontHost := GenerateScalars(size) + scalarsMontHost := babybear.GenerateScalars(size) scalarsMontHost.CopyFromDevice(&deviceScalars) assert.NotEqual(t, scalars, scalarsMontHost) - FromMontgomery(&deviceScalars) + babybear.FromMontgomery(&deviceScalars) scalarsMontHost.CopyFromDevice(&deviceScalars) assert.Equal(t, scalars, scalarsMontHost) diff --git a/wrappers/golang/fields/babybear/tests/vec_ops_test.go b/wrappers/golang/fields/babybear/tests/vec_ops_test.go new file mode 100644 index 00000000..535ec23f --- /dev/null +++ b/wrappers/golang/fields/babybear/tests/vec_ops_test.go @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + babybear "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear" + "github.com/ingonyama-zk/icicle/wrappers/golang/fields/babybear/vecOps" + "github.com/stretchr/testify/assert" +) + +func TestBabybearVecOps(t *testing.T) { + testSize := 1 << 14 + + a := babybear.GenerateScalars(testSize) + b := babybear.GenerateScalars(testSize) + var scalar babybear.ScalarField + scalar.One() + ones := core.HostSliceWithValue(scalar, testSize) + + out := make(core.HostSlice[babybear.ScalarField], testSize) + out2 := make(core.HostSlice[babybear.ScalarField], testSize) + out3 := make(core.HostSlice[babybear.ScalarField], testSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.VecOp(a, b, out, cfg, core.Add) + vecOps.VecOp(out, b, out2, cfg, core.Sub) + + assert.Equal(t, a, out2) + + vecOps.VecOp(a, ones, out3, cfg, core.Mul) + + assert.Equal(t, a, out3) +} + +func TestBabybearTranspose(t *testing.T) { + rowSize := 1 << 6 + columnSize := 1 << 8 + onDevice := false + isAsync := false + + matrix := babybear.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[babybear.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[babybear.ScalarField], rowSize*columnSize) + + ctx, _ := cr.GetDefaultDeviceContext() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, ctx, onDevice, isAsync) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + onDevice = true + + matrix.CopyToDevice(&dMatrix, true) + dOut.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + dOut2.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + + vecOps.TransposeMatrix(dMatrix, dOut, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, ctx, onDevice, isAsync) + output := make(core.HostSlice[babybear.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang/fields/babybear/vecOps/include/vec_ops.h b/wrappers/golang/fields/babybear/vecOps/include/vec_ops.h new file mode 100644 index 00000000..61cd8cdf --- /dev/null +++ b/wrappers/golang/fields/babybear/vecOps/include/vec_ops.h @@ -0,0 +1,53 @@ +#include +#include + +#ifndef _BABYBEAR_VEC_OPS_H +#define _BABYBEAR_VEC_OPS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t babybear_mul_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t babybear_add_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t babybear_sub_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t babybear_transpose_matrix_cuda( + scalar_t* mat_in, + int row_size, + int column_size, + scalar_t* mat_out, + DeviceContext* ctx, + bool on_device, + bool is_async +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/fields/babybear/vecOps/vec_ops.go b/wrappers/golang/fields/babybear/vecOps/vec_ops.go new file mode 100644 index 00000000..37671e41 --- /dev/null +++ b/wrappers/golang/fields/babybear/vecOps/vec_ops.go @@ -0,0 +1,48 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + + "unsafe" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { + aPointer, bPointer, outPointer, cfgPointer, size := core.VecOpCheck(a, b, out, &config) + + cA := (*C.scalar_t)(aPointer) + cB := (*C.scalar_t)(bPointer) + cOut := (*C.scalar_t)(outPointer) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cSize := (C.int)(size) + + switch op { + case core.Sub: + ret = (cr.CudaError)(C.babybear_sub_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (cr.CudaError)(C.babybear_add_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (cr.CudaError)(C.babybear_mul_cuda(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError) { + core.TransposeCheck(in, out, onDevice) + + cIn := (*C.scalar_t)(in.AsUnsafePointer()) + cOut := (*C.scalar_t)(out.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cOnDevice := (C._Bool)(onDevice) + cIsAsync := (C._Bool)(isAsync) + + err := (cr.CudaError)(C.babybear_transpose_matrix_cuda(cIn, cRowSize, cColumnSize, cOut, cCtx, cOnDevice, cIsAsync)) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/internal/generator/config/babybear.go b/wrappers/golang/internal/generator/config/babybear.go new file mode 100644 index 00000000..15b58b5c --- /dev/null +++ b/wrappers/golang/internal/generator/config/babybear.go @@ -0,0 +1,17 @@ +package config + +func init() { + var babybear = FieldData{ + PackageName: "babybear", + Field: "babybear", + LimbsNum: 1, + SupportsExtension: true, + ExtensionLimbsNum: 4, + SupportsNTT: true, + SupportsPoseidon: false, + SupportsPoly: true, + ROU: 1461624142, + } + + addField(babybear) +} diff --git a/wrappers/golang/internal/generator/config/bls12377.go b/wrappers/golang/internal/generator/config/bls12377.go new file mode 100644 index 00000000..ea44fd62 --- /dev/null +++ b/wrappers/golang/internal/generator/config/bls12377.go @@ -0,0 +1,19 @@ +package config + +func init() { + var bls12377 = CurveData{ + PackageName: "bls12377", + Curve: "bls12_377", + GnarkImport: "bls12-377", + SupportsPoly: true, + SupportsPoseidon: true, + SupportsNTT: true, + SupportsECNTT: true, + SupportsG2: true, + ScalarFieldNumLimbs: 8, + BaseFieldNumLimbs: 12, + G2FieldNumLimbs: 24, + } + + addCurve(bls12377) +} diff --git a/wrappers/golang/internal/generator/config/bls12381.go b/wrappers/golang/internal/generator/config/bls12381.go new file mode 100644 index 00000000..3f3e1a4e --- /dev/null +++ b/wrappers/golang/internal/generator/config/bls12381.go @@ -0,0 +1,19 @@ +package config + +func init() { + var bls12381 = CurveData{ + PackageName: "bls12381", + Curve: "bls12_381", + GnarkImport: "bls12-381", + SupportsPoly: true, + SupportsPoseidon: true, + SupportsNTT: true, + SupportsECNTT: true, + SupportsG2: true, + ScalarFieldNumLimbs: 8, + BaseFieldNumLimbs: 12, + G2FieldNumLimbs: 24, + } + + addCurve(bls12381) +} diff --git a/wrappers/golang/internal/generator/config/bn254.go b/wrappers/golang/internal/generator/config/bn254.go new file mode 100644 index 00000000..cdc83afc --- /dev/null +++ b/wrappers/golang/internal/generator/config/bn254.go @@ -0,0 +1,19 @@ +package config + +func init() { + var bn254 = CurveData{ + PackageName: "bn254", + Curve: "bn254", + GnarkImport: "bn254", + SupportsPoly: true, + SupportsPoseidon: true, + SupportsNTT: true, + SupportsECNTT: true, + SupportsG2: true, + ScalarFieldNumLimbs: 8, + BaseFieldNumLimbs: 8, + G2FieldNumLimbs: 16, + } + + addCurve(bn254) +} diff --git a/wrappers/golang/internal/generator/config/bw6761.go b/wrappers/golang/internal/generator/config/bw6761.go new file mode 100644 index 00000000..90920c87 --- /dev/null +++ b/wrappers/golang/internal/generator/config/bw6761.go @@ -0,0 +1,19 @@ +package config + +func init() { + var bw6761 = CurveData{ + PackageName: "bw6761", + Curve: "bw6_761", + GnarkImport: "bw6-761", + SupportsPoly: true, + SupportsPoseidon: true, + SupportsNTT: true, + SupportsECNTT: true, + SupportsG2: true, + ScalarFieldNumLimbs: 12, + BaseFieldNumLimbs: 24, + G2FieldNumLimbs: 24, + } + + addCurve(bw6761) +} diff --git a/wrappers/golang/internal/generator/config/config.go b/wrappers/golang/internal/generator/config/config.go new file mode 100644 index 00000000..bb1f422e --- /dev/null +++ b/wrappers/golang/internal/generator/config/config.go @@ -0,0 +1,50 @@ +package config + +type FieldData struct { + PackageName string + Field string + LimbsNum int + GnarkImport string + SupportsExtension bool + ExtensionLimbsNum int + SupportsNTT bool + SupportsPoseidon bool + SupportsPoly bool + ROU int +} + +type HashData struct { + PackageName string + Hash string +} + +// Maybe just put limbs in CurveData and no need for individual Field objects +type CurveData struct { + PackageName string + Curve string + GnarkImport string + SupportsPoly bool + SupportsPoseidon bool + SupportsNTT bool + SupportsECNTT bool + SupportsG2 bool + ScalarFieldNumLimbs int + BaseFieldNumLimbs int + G2FieldNumLimbs int +} + +var Curves []CurveData +var Fields []FieldData +var Hashes []HashData + +func addCurve(curve CurveData) { + Curves = append(Curves, curve) +} + +func addField(field FieldData) { + Fields = append(Fields, field) +} + +func addHash(hash HashData) { + Hashes = append(Hashes, hash) +} diff --git a/wrappers/golang/internal/generator/config/grumpkin.go b/wrappers/golang/internal/generator/config/grumpkin.go new file mode 100644 index 00000000..84279b98 --- /dev/null +++ b/wrappers/golang/internal/generator/config/grumpkin.go @@ -0,0 +1,19 @@ +package config + +func init() { + var grumpkin = CurveData{ + PackageName: "grumpkin", + Curve: "grumpkin", + GnarkImport: "", + SupportsPoly: false, + SupportsPoseidon: true, + SupportsNTT: false, + SupportsECNTT: false, + SupportsG2: false, + ScalarFieldNumLimbs: 8, + BaseFieldNumLimbs: 8, + G2FieldNumLimbs: 0, + } + + addCurve(grumpkin) +} diff --git a/wrappers/golang/internal/generator/curves/generate.go b/wrappers/golang/internal/generator/curves/generate.go new file mode 100644 index 00000000..67e8d740 --- /dev/null +++ b/wrappers/golang/internal/generator/curves/generate.go @@ -0,0 +1,42 @@ +package curves + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" +) + +var curveTemplates = map[string]string{ + "src": "curves/templates/curve.go.tmpl", + "test": "curves/templates/curve_test.go.tmpl", + "header": "curves/templates/curve.h.tmpl", +} + +func Generate(baseDir, packageName, curve, curvePrefix string) { + data := struct { + PackageName string + Curve string + CurvePrefix string + BaseImportPath string + }{ + packageName, + curve, + curvePrefix, + baseDir, + } + + filePrefix := "" + if packageName == "g2" { + filePrefix = "g2_" + } + + testDir := "tests" + parentDir := path.Base(baseDir) + if parentDir == "g2" || parentDir == "extension" { + testDir = "../tests" + } + + generator.GenerateFile(curveTemplates["src"], baseDir, "", "", data) + generator.GenerateFile(curveTemplates["header"], path.Join(baseDir, "include"), "", "", data) + generator.GenerateFile(curveTemplates["test"], path.Join(baseDir, testDir), filePrefix, "", data) +} diff --git a/wrappers/golang/internal/generator/curves/templates/curve.go.tmpl b/wrappers/golang/internal/generator/curves/templates/curve.go.tmpl new file mode 100644 index 00000000..bfab4629 --- /dev/null +++ b/wrappers/golang/internal/generator/curves/templates/curve.go.tmpl @@ -0,0 +1,178 @@ +package {{.PackageName}} +{{if ne .CurvePrefix "Mock"}} +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) +{{end}} +type {{.CurvePrefix}}Projective struct { + X, Y, Z {{.CurvePrefix}}BaseField +} + +func (p {{.CurvePrefix}}Projective) Size() int { + return p.X.Size() * 3 +} + +func (p {{.CurvePrefix}}Projective) AsPointer() *uint32 { + return p.X.AsPointer() +} + +func (p *{{.CurvePrefix}}Projective) Zero() {{.CurvePrefix}}Projective { + p.X.Zero() + p.Y.One() + p.Z.Zero() + + return *p +} + +func (p *{{.CurvePrefix}}Projective) FromLimbs(x, y, z []uint32) {{.CurvePrefix}}Projective { + p.X.FromLimbs(x) + p.Y.FromLimbs(y) + p.Z.FromLimbs(z) + + return *p +} + +func (p *{{.CurvePrefix}}Projective) FromAffine(a {{.CurvePrefix}}Affine) {{.CurvePrefix}}Projective { + z := {{.CurvePrefix}}BaseField{} + z.One() + + p.X = a.X + p.Y = a.Y + p.Z = z + + return *p +} +{{if ne .CurvePrefix "Mock"}} +func (p {{.CurvePrefix}}Projective) ProjectiveEq(p2 *{{.CurvePrefix}}Projective) bool { + cP := (*C.{{toCName .CurvePrefix}}projective_t)(unsafe.Pointer(&p)) + cP2 := (*C.{{toCName .CurvePrefix}}projective_t)(unsafe.Pointer(&p2)) + __ret := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_eq(cP, cP2) + return __ret == (C._Bool)(true) +} + +func (p *{{.CurvePrefix}}Projective) ProjectiveToAffine() {{.CurvePrefix}}Affine { + var a {{.CurvePrefix}}Affine + + cA := (*C.{{toCName .CurvePrefix}}affine_t)(unsafe.Pointer(&a)) + cP := (*C.{{toCName .CurvePrefix}}projective_t)(unsafe.Pointer(&p)) + C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_to_affine(cP, cA) + return a +} + +func {{.CurvePrefix}}GenerateProjectivePoints(size int) core.HostSlice[{{.CurvePrefix}}Projective] { + points := make([]{{.CurvePrefix}}Projective, size) + for i := range points { + points[i] = {{.CurvePrefix}}Projective{} + } + + pointsSlice := core.HostSliceFromElements[{{.CurvePrefix}}Projective](points) + pPoints := (*C.{{toCName .CurvePrefix}}projective_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_generate_projective_points(pPoints, cSize) + + return pointsSlice +} +{{end}} +type {{.CurvePrefix}}Affine struct { + X, Y {{.CurvePrefix}}BaseField +} + +func (a {{.CurvePrefix}}Affine) Size() int { + return a.X.Size() * 2 +} + +func (a {{.CurvePrefix}}Affine) AsPointer() *uint32 { + return a.X.AsPointer() +} + +func (a *{{.CurvePrefix}}Affine) Zero() {{.CurvePrefix}}Affine { + a.X.Zero() + a.Y.Zero() + + return *a +} + +func (a *{{.CurvePrefix}}Affine) FromLimbs(x, y []uint32) {{.CurvePrefix}}Affine { + a.X.FromLimbs(x) + a.Y.FromLimbs(y) + + return *a +} + +func (a {{.CurvePrefix}}Affine) ToProjective() {{.CurvePrefix}}Projective { + var z {{.CurvePrefix}}BaseField + + return {{.CurvePrefix}}Projective{ + X: a.X, + Y: a.Y, + Z: z.One(), + } +} +{{if ne .CurvePrefix "Mock"}} +func {{.CurvePrefix}}AffineFromProjective(p *{{.CurvePrefix}}Projective) {{.CurvePrefix}}Affine { + return p.ProjectiveToAffine() +} + +func {{.CurvePrefix}}GenerateAffinePoints(size int) core.HostSlice[{{.CurvePrefix}}Affine] { + points := make([]{{.CurvePrefix}}Affine, size) + for i := range points { + points[i] = {{.CurvePrefix}}Affine{} + } + + pointsSlice := core.HostSliceFromElements[{{.CurvePrefix}}Affine](points) + cPoints := (*C.{{toCName .CurvePrefix}}affine_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convert{{.CurvePrefix}}AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { + cValues := (*C.{{toCName .CurvePrefix}}affine_t)(points.AsUnsafePointer()) + cSize := (C.size_t)(points.Len()) + cIsInto := (C._Bool)(isInto) + defaultCtx, _ := cr.GetDefaultDeviceContext() + cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) + __ret := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_affine_convert_montgomery(cValues, cSize, cIsInto, cCtx) + err := (cr.CudaError)(__ret) + return err +} + +func {{.CurvePrefix}}AffineToMontgomery(points *core.DeviceSlice) cr.CudaError { + points.CheckDevice() + return convert{{.CurvePrefix}}AffinePointsMontgomery(points, true) +} + +func {{.CurvePrefix}}AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { + points.CheckDevice() + return convert{{.CurvePrefix}}AffinePointsMontgomery(points, false) +} + +func convert{{.CurvePrefix}}ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { + cValues := (*C.{{toCName .CurvePrefix}}projective_t)(points.AsUnsafePointer()) + cSize := (C.size_t)(points.Len()) + cIsInto := (C._Bool)(isInto) + defaultCtx, _ := cr.GetDefaultDeviceContext() + cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) + __ret := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_projective_convert_montgomery(cValues, cSize, cIsInto, cCtx) + err := (cr.CudaError)(__ret) + return err +} + +func {{.CurvePrefix}}ProjectiveToMontgomery(points *core.DeviceSlice) cr.CudaError { + points.CheckDevice() + return convert{{.CurvePrefix}}ProjectivePointsMontgomery(points, true) +} + +func {{.CurvePrefix}}ProjectiveFromMontgomery(points *core.DeviceSlice) cr.CudaError { + points.CheckDevice() + return convert{{.CurvePrefix}}ProjectivePointsMontgomery(points, false) +} +{{end}} \ No newline at end of file diff --git a/wrappers/golang/internal/generator/curves/templates/curve.h.tmpl b/wrappers/golang/internal/generator/curves/templates/curve.h.tmpl new file mode 100644 index 00000000..22179e6f --- /dev/null +++ b/wrappers/golang/internal/generator/curves/templates/curve.h.tmpl @@ -0,0 +1,26 @@ +#include +#include + +#ifndef _{{toUpper .Curve}}_{{.CurvePrefix}}CURVE_H +#define _{{toUpper .Curve}}_{{.CurvePrefix}}CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct {{toCName .CurvePrefix}}projective_t {{toCName .CurvePrefix}}projective_t; +typedef struct {{toCName .CurvePrefix}}affine_t {{toCName .CurvePrefix}}affine_t; +typedef struct DeviceContext DeviceContext; + +bool {{.Curve}}{{toCNameBackwards .CurvePrefix}}_eq({{toCName .CurvePrefix}}projective_t* point1, {{toCName .CurvePrefix}}projective_t* point2); +void {{.Curve}}{{toCNameBackwards .CurvePrefix}}_to_affine({{toCName .CurvePrefix}}projective_t* point, {{toCName .CurvePrefix}}affine_t* point_out); +void {{.Curve}}{{toCNameBackwards .CurvePrefix}}_generate_projective_points({{toCName .CurvePrefix}}projective_t* points, int size); +void {{.Curve}}{{toCNameBackwards .CurvePrefix}}_generate_affine_points({{toCName .CurvePrefix}}affine_t* points, int size); +cudaError_t {{.Curve}}{{toCNameBackwards .CurvePrefix}}_affine_convert_montgomery({{toCName .CurvePrefix}}affine_t* points, size_t n, bool is_into, DeviceContext* ctx); +cudaError_t {{.Curve}}{{toCNameBackwards .CurvePrefix}}_projective_convert_montgomery({{toCName .CurvePrefix}}projective_t* points, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/internal/generator/curves/templates/curve_test.go.tmpl b/wrappers/golang/internal/generator/curves/templates/curve_test.go.tmpl new file mode 100644 index 00000000..5d6d92dc --- /dev/null +++ b/wrappers/golang/internal/generator/curves/templates/curve_test.go.tmpl @@ -0,0 +1,103 @@ +package tests + +import ( + {{if ne .CurvePrefix "G2"}}{{.Curve}}{{end}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test{{.CurvePrefix}}AffineZero(t *testing.T) { + var fieldZero = {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}BaseField{} + + var affineZero {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Affine + assert.Equal(t, affineZero.X, fieldZero) + assert.Equal(t, affineZero.Y, fieldZero) + + x := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + var affine {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Affine + affine.FromLimbs(x, y) + + affine.Zero() + assert.Equal(t, affine.X, fieldZero) + assert.Equal(t, affine.Y, fieldZero) +} + +func Test{{.CurvePrefix}}AffineFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + + var affine {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Affine + affine.FromLimbs(randLimbs, randLimbs2) + + assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) + assert.ElementsMatch(t, randLimbs2, affine.Y.GetLimbs()) +} + +func Test{{.CurvePrefix}}AffineToProjective(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + var fieldOne {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}BaseField + fieldOne.One() + + var expected {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func Test{{.CurvePrefix}}ProjectiveZero(t *testing.T) { + var projectiveZero {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Projective + projectiveZero.Zero() + var fieldZero = {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}BaseField{} + var fieldOne {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}BaseField + fieldOne.One() + + assert.Equal(t, projectiveZero.X, fieldZero) + assert.Equal(t, projectiveZero.Y, fieldOne) + assert.Equal(t, projectiveZero.Z, fieldZero) + + randLimbs := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + var projective {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Projective + projective.FromLimbs(randLimbs, randLimbs, randLimbs) + + projective.Zero() + assert.Equal(t, projective.X, fieldZero) + assert.Equal(t, projective.Y, fieldOne) + assert.Equal(t, projective.Z, fieldZero) +} + +func Test{{.CurvePrefix}}ProjectiveFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + + var projective {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Projective + projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) + + assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) + assert.ElementsMatch(t, randLimbs2, projective.Y.GetLimbs()) + assert.ElementsMatch(t, randLimbs3, projective.Z.GetLimbs()) +} + +func Test{{.CurvePrefix}}ProjectiveFromAffine(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int({{.CurvePrefix}}BASE_LIMBS)) + var fieldOne {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}BaseField + fieldOne.One() + + var expected {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint {{if eq .CurvePrefix "G2"}}g2{{else}}{{.Curve}}{{end}}.{{.CurvePrefix}}Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang/internal/generator/curves/templates/main.go.tmpl b/wrappers/golang/internal/generator/curves/templates/main.go.tmpl new file mode 100644 index 00000000..3a85167d --- /dev/null +++ b/wrappers/golang/internal/generator/curves/templates/main.go.tmpl @@ -0,0 +1,4 @@ +package {{.PackageName}} + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../{{.UpDirs}}icicle/build/lib -lingo_curve_{{.Field}} -lingo_field_{{.Field}} -lstdc++ -lm +import "C" diff --git a/wrappers/golang/internal/generator/ecntt/generate.go b/wrappers/golang/internal/generator/ecntt/generate.go new file mode 100644 index 00000000..a239a7dc --- /dev/null +++ b/wrappers/golang/internal/generator/ecntt/generate.go @@ -0,0 +1,29 @@ +package ntt + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" +) + +var ecnttTemplates = map[string]string{ + "src": "ecntt/templates/ecntt.go.tmpl", + "test": "ecntt/templates/ecntt_test.go.tmpl", + "header": "ecntt/templates/ecntt.h.tmpl", +} + +func Generate(baseDir, curve, gnarkImport string) { + data := struct { + Curve string + BaseImportPath string + GnarkImport string + }{ + curve, + baseDir, + gnarkImport, + } + + generator.GenerateFile(ecnttTemplates["src"], path.Join(baseDir, "ecntt"), "", "", data) + generator.GenerateFile(ecnttTemplates["header"], path.Join(baseDir, "ecntt", "include"), "", "", data) + generator.GenerateFile(ecnttTemplates["test"], path.Join(baseDir, "tests"), "", "", data) +} diff --git a/wrappers/golang/internal/generator/ecntt/templates/ecntt.go.tmpl b/wrappers/golang/internal/generator/ecntt/templates/ecntt.go.tmpl new file mode 100644 index 00000000..cf80bf54 --- /dev/null +++ b/wrappers/golang/internal/generator/ecntt/templates/ecntt.go.tmpl @@ -0,0 +1,24 @@ +package ecntt + +// #cgo CFLAGS: -I./include/ +// #include "ecntt.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + pointsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](points, cfg, results) + + cPoints := (*C.projective_t)(pointsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.projective_t)(resultsPointer) + + __ret := C.{{.Curve}}_ecntt_cuda(cPoints, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/internal/generator/ecntt/templates/ecntt.h.tmpl b/wrappers/golang/internal/generator/ecntt/templates/ecntt.h.tmpl new file mode 100644 index 00000000..3180f534 --- /dev/null +++ b/wrappers/golang/internal/generator/ecntt/templates/ecntt.h.tmpl @@ -0,0 +1,19 @@ +#include + +#ifndef _{{toUpper .Curve}}_ECNTT_H +#define _{{toUpper .Curve}}_ECNTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct NTTConfig NTTConfig; +typedef struct projective_t projective_t; + +cudaError_t {{.Curve}}_ecntt_cuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/internal/generator/ecntt/templates/ecntt_test.go.tmpl b/wrappers/golang/internal/generator/ecntt/templates/ecntt_test.go.tmpl new file mode 100644 index 00000000..7786e72e --- /dev/null +++ b/wrappers/golang/internal/generator/ecntt/templates/ecntt_test.go.tmpl @@ -0,0 +1,30 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + {{.Curve}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}" + ecntt "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/ecntt" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/ntt" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + points := {{.Curve}}.GenerateProjectivePoints(1 << largestTestSize) + + for _, size := range []int{4, 5, 6, 7, 8} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[{{.Curve}}.Projective](points[:testSize]) + cfg.Ordering = v + cfg.NttAlgorithm = core.Radix2 + + output := make(core.HostSlice[{{.Curve}}.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + } + } +} diff --git a/wrappers/golang/internal/generator/fields/generate.go b/wrappers/golang/internal/generator/fields/generate.go new file mode 100644 index 00000000..69279277 --- /dev/null +++ b/wrappers/golang/internal/generator/fields/generate.go @@ -0,0 +1,46 @@ +package fields + +import ( + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" + "path" + "strings" +) + +var fieldTemplates = map[string]string{ + "src": "fields/templates/field.go.tmpl", + "test": "fields/templates/field_test.go.tmpl", + "header": "fields/templates/scalar_field.h.tmpl", +} + +func Generate(baseDir, packageName, field, fieldPrefix string, isScalar bool, numLimbs int) { + data := struct { + PackageName string + Field string + FieldPrefix string + BaseImportPath string + IsScalar bool + NUM_LIMBS int + }{ + packageName, + field, + fieldPrefix, + baseDir, + isScalar, + numLimbs, + } + + filePrefix := "" + if packageName == "g2" { + filePrefix = "g2_" + } + + testDir := "tests" + parentDir := path.Base(baseDir) + if parentDir == "g2" || parentDir == "extension" { + testDir = "../tests" + } + + generator.GenerateFile(fieldTemplates["src"], baseDir, strings.ToLower(fieldPrefix)+"_", "", data) + generator.GenerateFile(fieldTemplates["header"], path.Join(baseDir, "include"), "", "", data) + generator.GenerateFile(fieldTemplates["test"], path.Join(baseDir, testDir), filePrefix+strings.ToLower(fieldPrefix)+"_", "", data) +} diff --git a/wrappers/golang/internal/generator/fields/templates/field.go.tmpl b/wrappers/golang/internal/generator/fields/templates/field.go.tmpl new file mode 100644 index 00000000..da34e47a --- /dev/null +++ b/wrappers/golang/internal/generator/fields/templates/field.go.tmpl @@ -0,0 +1,124 @@ +package {{.PackageName}} +{{if .IsScalar -}} +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +{{- end}} +import ( + "encoding/binary" + "fmt" + {{- if .IsScalar}} + "unsafe" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + {{- end}} +) + +const ( + {{toConst .FieldPrefix}}LIMBS int = {{.NUM_LIMBS}} +) + +type {{.FieldPrefix}}Field struct { + limbs [{{toConst .FieldPrefix}}LIMBS]uint32 +} + +func (f {{.FieldPrefix}}Field) Len() int { + return int({{toConst .FieldPrefix}}LIMBS) +} + +func (f {{.FieldPrefix}}Field) Size() int { + return int({{toConst .FieldPrefix}}LIMBS * 4) +} + +func (f {{.FieldPrefix}}Field) GetLimbs() []uint32 { + return f.limbs[:] +} + +func (f {{.FieldPrefix}}Field) AsPointer() *uint32 { + return &f.limbs[0] +} + +func (f *{{.FieldPrefix}}Field) FromUint32(v uint32) {{.FieldPrefix}}Field { + f.limbs[0] = v + return *f +} + +func (f *{{.FieldPrefix}}Field) FromLimbs(limbs []uint32) {{.FieldPrefix}}Field { + if len(limbs) != f.Len() { + panic("Called FromLimbs with limbs of different length than field") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *{{.FieldPrefix}}Field) Zero() {{.FieldPrefix}}Field { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *{{.FieldPrefix}}Field) One() {{.FieldPrefix}}Field { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *{{.FieldPrefix}}Field) FromBytesLittleEndian(bytes []byte) {{.FieldPrefix}}Field { + if len(bytes)/4 != f.Len() { + panic(fmt.Sprintf("Called FromBytesLittleEndian with incorrect bytes length; expected %d - got %d", f.Len()*4, len(bytes))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f {{.FieldPrefix}}Field) ToBytesLittleEndian() []byte { + bytes := make([]byte, f.Len()*4) + for i, v := range f.limbs { + binary.LittleEndian.PutUint32(bytes[i*4:], v) + } + + return bytes +} +{{if .IsScalar}} +func GenerateScalars(size int) core.HostSlice[{{.FieldPrefix}}Field] { + scalarSlice := make(core.HostSlice[{{.FieldPrefix}}Field], size) + + cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) + cSize := (C.int)(size) + C.{{.Field}}_generate_scalars(cScalars, cSize) + + return scalarSlice +} + +func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { + cValues := (*C.scalar_t)(scalars.AsUnsafePointer()) + cSize := (C.size_t)(scalars.Len()) + cIsInto := (C._Bool)(isInto) + defaultCtx, _ := cr.GetDefaultDeviceContext() + cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) + __ret := C.{{.Field}}_scalar_convert_montgomery(cValues, cSize, cIsInto, cCtx) + err := (cr.CudaError)(__ret) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) cr.CudaError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) cr.CudaError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +}{{end}} diff --git a/wrappers/golang/internal/generator/fields/templates/field_test.go.tmpl b/wrappers/golang/internal/generator/fields/templates/field_test.go.tmpl new file mode 100644 index 00000000..6fca06e3 --- /dev/null +++ b/wrappers/golang/internal/generator/fields/templates/field_test.go.tmpl @@ -0,0 +1,121 @@ +package tests + +import ( + {{- if .IsScalar}} + "github.com/ingonyama-zk/icicle/wrappers/golang/core"{{end}} + {{if ne .FieldPrefix "G2"}}{{.Field}}{{end}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}{{if eq .FieldPrefix "G2"}}/g2{{end}}" + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + + const ( + {{toConst .FieldPrefix}}LIMBS = {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{toConst .FieldPrefix}}LIMBS +) + +func Test{{.FieldPrefix}}FieldFromLimbs(t *testing.T) { + emptyField := {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{.FieldPrefix}}Field{} + randLimbs := test_helpers.GenerateRandomLimb(int({{toConst .FieldPrefix}}LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the {{.FieldPrefix}}Field's limbs") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func Test{{.FieldPrefix}}FieldGetLimbs(t *testing.T) { + emptyField := {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{.FieldPrefix}}Field{} + randLimbs := test_helpers.GenerateRandomLimb(int({{toConst .FieldPrefix}}LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the {{.FieldPrefix}}Field's limbs") +} + +func Test{{.FieldPrefix}}FieldOne(t *testing.T) { + var emptyField {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{.FieldPrefix}}Field + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int({{toConst .FieldPrefix}}LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int({{toConst .FieldPrefix}}LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "{{.FieldPrefix}}Field with limbs to field one did not work") +} + +func Test{{.FieldPrefix}}FieldZero(t *testing.T) { + var emptyField {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{.FieldPrefix}}Field + emptyField.Zero() + limbsZero := make([]uint32, {{toConst .FieldPrefix}}LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int({{toConst .FieldPrefix}}LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "{{.FieldPrefix}}Field with limbs to field zero failed") +} + +func Test{{.FieldPrefix}}FieldSize(t *testing.T) { + var emptyField {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{.FieldPrefix}}Field + randLimbs := test_helpers.GenerateRandomLimb(int({{toConst .FieldPrefix}}LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") +} + +func Test{{.FieldPrefix}}FieldAsPointer(t *testing.T) { + var emptyField {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{.FieldPrefix}}Field + randLimbs := test_helpers.GenerateRandomLimb(int({{toConst .FieldPrefix}}LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") +} + +func Test{{.FieldPrefix}}FieldFromBytes(t *testing.T) { + var emptyField {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{.FieldPrefix}}Field + bytes, expected := test_helpers.GenerateBytesArray(int({{toConst .FieldPrefix}}LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func Test{{.FieldPrefix}}FieldToBytes(t *testing.T) { + var emptyField {{if eq .FieldPrefix "G2"}}g2{{else}}{{.Field}}{{end}}.{{.FieldPrefix}}Field + expected, limbs := test_helpers.GenerateBytesArray(int({{toConst .FieldPrefix}}LIMBS)) + emptyField.FromLimbs(limbs) + + assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") +} +{{if .IsScalar}} +func Test{{capitalize .Field}}GenerateScalars(t *testing.T) { + const numScalars = 8 + scalars := {{.Field}}.GenerateScalars(numScalars) + + assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) + + assert.Equal(t, numScalars, scalars.Len()) + zeroScalar := {{.Field}}.{{.FieldPrefix}}Field{} + assert.NotContains(t, scalars, zeroScalar) +} + +func Test{{capitalize .Field}}MongtomeryConversion(t *testing.T) { + size := 1 << 15 + scalars := {{.Field}}.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + {{.Field}}.ToMontgomery(&deviceScalars) + + scalarsMontHost := {{.Field}}.GenerateScalars(size) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.NotEqual(t, scalars, scalarsMontHost) + + {{.Field}}.FromMontgomery(&deviceScalars) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.Equal(t, scalars, scalarsMontHost) +}{{end}} diff --git a/wrappers/golang/internal/generator/fields/templates/main.go.tmpl b/wrappers/golang/internal/generator/fields/templates/main.go.tmpl new file mode 100644 index 00000000..f2ee003e --- /dev/null +++ b/wrappers/golang/internal/generator/fields/templates/main.go.tmpl @@ -0,0 +1,4 @@ +package {{.PackageName}} + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../{{.UpDirs}}icicle/build/lib -lingo_field_{{.Field}} -lstdc++ -lm +import "C" diff --git a/wrappers/golang/internal/generator/fields/templates/scalar_field.h.tmpl b/wrappers/golang/internal/generator/fields/templates/scalar_field.h.tmpl new file mode 100644 index 00000000..2401d098 --- /dev/null +++ b/wrappers/golang/internal/generator/fields/templates/scalar_field.h.tmpl @@ -0,0 +1,21 @@ +#include +#include + +#ifndef _{{toUpper .Field}}_FIELD_H +#define _{{toUpper .Field}}_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct DeviceContext DeviceContext; + +void {{.Field}}_generate_scalars(scalar_t* scalars, int size); +cudaError_t {{.Field}}_scalar_convert_montgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/internal/generator/generator_utils/generate.go b/wrappers/golang/internal/generator/generator_utils/generate.go new file mode 100644 index 00000000..902fc9d3 --- /dev/null +++ b/wrappers/golang/internal/generator/generator_utils/generate.go @@ -0,0 +1,107 @@ +package generator_utils + +import ( + "bytes" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + "text/template" +) + +const ( + // Since path.Join joins from the cwd we only need to go up two directories + // from wrappers/golang/internal/generator/main.go to get to wrappers/golang + GOLANG_WRAPPER_ROOT_DIR = "../../" +) + +func create(output string, buf *bytes.Buffer) error { + // create output dir if not exist + _ = os.MkdirAll(filepath.Dir(output), os.ModePerm) + + // create output file + file, err := os.Create(output) + if err != nil { + return err + } + + if _, err := io.Copy(file, buf); err != nil { + file.Close() + return err + } + + file.Close() + return nil +} + +type entry struct { + outputName string + parsedTemplate *template.Template +} + +func toPackage(s string) string { + return strings.ReplaceAll(s, "-", "") +} + +func toCName(s string) string { + if s == "" { + return "" + } + return strings.ToLower(s) + "_" +} + +func toCNameBackwards(s string) string { + if s == "" { + return "" + } + return "_" + strings.ToLower(s) +} + +func toConst(s string) string { + if s == "" { + return "" + } + return strings.ToUpper(s) + "_" +} +func capitalize(s string) string { + if s == "" { + return "" + } + return strings.ToUpper(s[:1]) + s[1:] +} + +var templateFuncs = template.FuncMap{ + "log": fmt.Println, + "toLower": strings.ToLower, + "toUpper": strings.ToUpper, + "toPackage": toPackage, + "toCName": toCName, + "toCNameBackwards": toCNameBackwards, + "toConst": toConst, + "capitalize": capitalize, +} + +func parseTemplateFile(tmplPath string) entry { + tmplName := tmplPath[strings.LastIndex(tmplPath, "/")+1:] + tmpl := template.New(tmplName).Funcs(templateFuncs) + tmplParsed, err := tmpl.ParseFiles(tmplPath) + if err != nil { + panic(err) + } + fileName, ok := strings.CutSuffix(tmplName, ".tmpl") + if !ok { + panic(".tmpl suffix not found") + } + + return entry{outputName: fileName, parsedTemplate: tmplParsed} +} + +func GenerateFile(templateFilePath, baseDir, fileNamePrefix, fileNameSuffix string, data any) { + entry := parseTemplateFile(templateFilePath) + var buf bytes.Buffer + entry.parsedTemplate.Execute(&buf, data) + outFile := path.Join(GOLANG_WRAPPER_ROOT_DIR, baseDir, fileNamePrefix+entry.outputName+fileNameSuffix) + create(outFile, &buf) +} diff --git a/wrappers/golang/internal/generator/lib_linker/generate.go b/wrappers/golang/internal/generator/lib_linker/generate.go new file mode 100644 index 00000000..2a3146ba --- /dev/null +++ b/wrappers/golang/internal/generator/lib_linker/generate.go @@ -0,0 +1,32 @@ +package lib_linker + +import ( + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" + "strings" +) + +type MainTemplateType string + +const ( + CURVE MainTemplateType = "curves" + FIELD MainTemplateType = "fields" +) + +var mainTemplates = map[MainTemplateType]string{ + "fields": "fields/templates/main.go.tmpl", + "curves": "curves/templates/main.go.tmpl", +} + +func Generate(baseDir, packageName, field string, templateType MainTemplateType, numAdditionalDirectoriesToLib int) { + data := struct { + PackageName string + Field string + UpDirs string + }{ + packageName, + field, + strings.Repeat("../", numAdditionalDirectoriesToLib), + } + + generator.GenerateFile(mainTemplates[templateType], baseDir, "", "", data) +} diff --git a/wrappers/golang/internal/generator/main.go b/wrappers/golang/internal/generator/main.go index d670745e..f707f7f5 100644 --- a/wrappers/golang/internal/generator/main.go +++ b/wrappers/golang/internal/generator/main.go @@ -1,251 +1,98 @@ package main import ( - "bytes" "fmt" - "io" "os" - "path/filepath" - "strings" - "text/template" + "os/exec" + "path" + + config "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/config" + curves "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/curves" + ecntt "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/ecntt" + fields "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/fields" + lib_linker "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/lib_linker" + mock "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/mock" + msm "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/msm" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/ntt" + poly "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/polynomial" + "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/tests" + vecops "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/vecOps" ) -const ( - baseDir = "../../curves/" // wrappers/golang/curves -) - -func create(output string, buf *bytes.Buffer) error { - // create output dir if not exist - _ = os.MkdirAll(filepath.Dir(output), os.ModePerm) - - // create output file - file, err := os.Create(output) - if err != nil { - return err - } - - if _, err := io.Copy(file, buf); err != nil { - file.Close() - return err - } - - file.Close() - return nil -} - -type CurveData struct { - PackageName string - Curve string - GnarkImport string - ScalarLimbsNum int - BaseLimbsNum int - G2BaseLimbsNum int -} - -var bn254 = CurveData{ - PackageName: "bn254", - Curve: "bn254", - GnarkImport: "bn254", - ScalarLimbsNum: 8, - BaseLimbsNum: 8, - G2BaseLimbsNum: 16, -} -var bls12381 = CurveData{ - PackageName: "bls12381", - Curve: "bls12_381", - GnarkImport: "bls12-381", - ScalarLimbsNum: 8, - BaseLimbsNum: 12, - G2BaseLimbsNum: 24, -} -var bls12377 = CurveData{ - PackageName: "bls12377", - Curve: "bls12_377", - GnarkImport: "bls12-377", - ScalarLimbsNum: 8, - BaseLimbsNum: 12, - G2BaseLimbsNum: 24, -} -var bw6761 = CurveData{ - PackageName: "bw6761", - Curve: "bw6_761", - GnarkImport: "bw6-761", - ScalarLimbsNum: 12, - BaseLimbsNum: 24, - G2BaseLimbsNum: 24, -} - -type Entry struct { - outputName string - parsedTemplate *template.Template -} - -func toPackage(s string) string { - return strings.ReplaceAll(s, "-", "") -} - func generateFiles() { fmt.Println("Generating files") - funcs := template.FuncMap{ - "log": fmt.Println, - "toLower": strings.ToLower, - "toUpper": strings.ToUpper, - "toPackage": toPackage, - } - curvesData := []CurveData{bn254, bls12377, bls12381, bw6761} - var entries []Entry - - templateFiles := []string{ - "main.go.tmpl", - "msm.go.tmpl", - "msm_test.go.tmpl", - "ntt.go.tmpl", - "ntt_test.go.tmpl", - "curve_test.go.tmpl", - "curve.go.tmpl", - "vec_ops_test.go.tmpl", - "vec_ops.go.tmpl", - "helpers_test.go.tmpl", - } - - for _, tmplName := range templateFiles { - tmpl := template.New(tmplName).Funcs(funcs) - tmplParsed, err := tmpl.ParseFiles(fmt.Sprintf("templates/%s", tmplName)) - if err != nil { - panic(err) + for _, curve := range config.Curves { + curveDir := path.Join("curves", curve.PackageName) + scalarFieldPrefix := "Scalar" + fields.Generate(curveDir, curve.PackageName, curve.Curve, scalarFieldPrefix, true, curve.ScalarFieldNumLimbs) + fields.Generate(curveDir, curve.PackageName, curve.Curve, "Base", false, curve.BaseFieldNumLimbs) + curves.Generate(curveDir, curve.PackageName, curve.Curve, "") + vecops.Generate(curveDir, curve.Curve, scalarFieldPrefix) + if curve.SupportsPoly { + poly.Generate(curveDir, curve.Curve, scalarFieldPrefix, curve.GnarkImport) } - fileName, ok := strings.CutSuffix(tmplName, ".tmpl") - if !ok { - panic(err) + lib_linker.Generate(curveDir, curve.PackageName, curve.Curve, lib_linker.CURVE, 0) + + if curve.SupportsNTT { + ntt.Generate(curveDir, "", curve.Curve, scalarFieldPrefix, curve.GnarkImport, 0, true, "", "") } - entries = append(entries, Entry{outputName: fileName, parsedTemplate: tmplParsed}) - } - templateG2Files := []string{ - "msm.go.tmpl", - "msm_test.go.tmpl", - "curve_test.go.tmpl", - "curve.go.tmpl", - } - - for _, tmplName := range templateG2Files { - tmpl := template.New(tmplName).Funcs(funcs) - tmplParsed, err := tmpl.ParseFiles(fmt.Sprintf("templates/%s", tmplName)) - if err != nil { - panic(err) + if curve.SupportsECNTT { + ecntt.Generate(curveDir, curve.Curve, curve.GnarkImport) } - rawFileName, _ := strings.CutSuffix(tmplName, ".tmpl") - fileName := fmt.Sprintf("g2_%s", rawFileName) - entries = append(entries, Entry{outputName: fileName, parsedTemplate: tmplParsed}) - } - templateFieldFiles := []string{ - "field.go.tmpl", - "field_test.go.tmpl", - } - - fieldFilePrefixes := []string{ - "base", - "g2_base", - "scalar", - } - - for _, tmplName := range templateFieldFiles { - tmpl := template.New(tmplName).Funcs(funcs) - tmplParsed, err := tmpl.ParseFiles(fmt.Sprintf("templates/%s", tmplName), "templates/scalar_field.go.tmpl", "templates/scalar_field_test.go.tmpl") - if err != nil { - panic(err) + msm.Generate(curveDir, "msm", curve.Curve, "", curve.GnarkImport) + if curve.SupportsG2 { + g2BaseDir := path.Join(curveDir, "g2") + packageName := "g2" + fields.Generate(g2BaseDir, packageName, curve.Curve, "G2Base", false, curve.G2FieldNumLimbs) + curves.Generate(g2BaseDir, packageName, curve.Curve, "G2") + msm.Generate(curveDir, "g2", curve.Curve, "G2", curve.GnarkImport) } - fieldFile, _ := strings.CutSuffix(tmplName, ".tmpl") - for _, fieldPrefix := range fieldFilePrefixes { - fileName := strings.Join([]string{fieldPrefix, fieldFile}, "_") - entries = append(entries, Entry{outputName: fileName, parsedTemplate: tmplParsed}) + + tests.Generate(curveDir, curve.Curve, scalarFieldPrefix, curve.GnarkImport, 0, curve.SupportsNTT, curve.SupportsPoly) + } + + for _, field := range config.Fields { + fieldDir := path.Join("fields", field.PackageName) + scalarFieldPrefix := "Scalar" + fields.Generate(fieldDir, field.PackageName, field.Field, scalarFieldPrefix, true, field.LimbsNum) + vecops.Generate(fieldDir, field.Field, scalarFieldPrefix) + if field.SupportsPoly { + poly.Generate(fieldDir, field.Field, scalarFieldPrefix, field.GnarkImport) } - } + ntt.Generate(fieldDir, "", field.Field, scalarFieldPrefix, field.GnarkImport, field.ROU, true, "", "") + lib_linker.Generate(fieldDir, field.PackageName, field.Field, lib_linker.FIELD, 0) - // Header files - templateIncludeFiles := []string{ - "curve.h.tmpl", - "g2_curve.h.tmpl", - "scalar_field.h.tmpl", - "msm.h.tmpl", - "g2_msm.h.tmpl", - "ntt.h.tmpl", - "vec_ops.h.tmpl", - } - - for _, includeFile := range templateIncludeFiles { - tmpl := template.New(includeFile).Funcs(funcs) - tmplParsed, err := tmpl.ParseFiles(fmt.Sprintf("templates/include/%s", includeFile)) - if err != nil { - panic(err) + if field.SupportsExtension { + extensionsDir := path.Join(fieldDir, "extension") + extensionField := field.Field + "_extension" + extensionFieldPrefix := "Extension" + fields.Generate(extensionsDir, "extension", extensionField, extensionFieldPrefix, true, field.ExtensionLimbsNum) + vecops.Generate(extensionsDir, extensionField, extensionFieldPrefix) + ntt.Generate(fieldDir, "extension", field.Field, scalarFieldPrefix, field.GnarkImport, field.ROU, false, extensionField, extensionFieldPrefix) + lib_linker.Generate(extensionsDir, "extension", field.Field, lib_linker.FIELD, 1) } - fileName, _ := strings.CutSuffix(includeFile, ".tmpl") - entries = append(entries, Entry{outputName: fmt.Sprintf("include/%s", fileName), parsedTemplate: tmplParsed}) + + tests.Generate(fieldDir, field.Field, scalarFieldPrefix, field.GnarkImport, field.ROU, field.SupportsNTT, field.SupportsPoly) } - for _, curveData := range curvesData { - for _, entry := range entries { - fileName := entry.outputName - if strings.Contains(fileName, "main") { - fileName = strings.Replace(fileName, "main", curveData.Curve, 1) - } - outFile := filepath.Join(baseDir, curveData.PackageName, fileName) - var buf bytes.Buffer - data := struct { - CurveData - IsScalar bool - IsG2 bool - IsMock bool - }{ - curveData, - strings.Contains(fileName, "scalar_field"), - strings.Contains(fileName, "g2"), - false, - } - entry.parsedTemplate.Execute(&buf, data) - create(outFile, &buf) - } - } - - // Generate internal mock field and mock curve files for testing core package - internalTemplateFiles := []string{ - "curve_test.go.tmpl", - "curve.go.tmpl", - "field_test.go.tmpl", - "field.go.tmpl", - "helpers_test.go.tmpl", - } - - for _, internalTemplate := range internalTemplateFiles { - tmpl := template.New(internalTemplate).Funcs(funcs) - tmplParsed, err := tmpl.ParseFiles(fmt.Sprintf("templates/%s", internalTemplate)) - if err != nil { - panic(err) - } - fileName, _ := strings.CutSuffix(internalTemplate, ".tmpl") - outFile := filepath.Join("../../core/internal/", fileName) - - var buf bytes.Buffer - data := struct { - PackageName string - BaseLimbsNum int - IsMock bool - IsG2 bool - IsScalar bool - }{ - "internal", - 8, - true, - false, - false, - } - tmplParsed.Execute(&buf, data) - create(outFile, &buf) - } + // Mock field and curve files for core + mock.Generate("core/internal", "internal", "Mock", "MockBase", false, 4) } +//go:generate go run main.go func main() { generateFiles() + + cmd := exec.Command("gofmt", "-w", "../../") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + fmt.Printf("\n%s\n", err.Error()) + os.Exit(-1) + } } diff --git a/wrappers/golang/internal/generator/mock/generate.go b/wrappers/golang/internal/generator/mock/generate.go new file mode 100644 index 00000000..f162c60e --- /dev/null +++ b/wrappers/golang/internal/generator/mock/generate.go @@ -0,0 +1,37 @@ +package fields + +import ( + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" + "strings" +) + +var mockTemplates = map[string]string{ + "field": "fields/templates/field.go.tmpl", + "curve": "curves/templates/curve.go.tmpl", +} + +func Generate(baseDir, packageName, field, fieldPrefix string, isScalar bool, numLimbs int) { + fieldData := struct { + PackageName string + Field string + FieldPrefix string + IsScalar bool + NUM_LIMBS int + }{ + packageName, + field, + fieldPrefix, + isScalar, + numLimbs, + } + generator.GenerateFile(mockTemplates["field"], baseDir, strings.ToLower(field)+"_", "", fieldData) + + curveData := struct { + PackageName string + CurvePrefix string + }{ + packageName, + field, + } + generator.GenerateFile(mockTemplates["curve"], baseDir, strings.ToLower(field)+"_", "", curveData) +} diff --git a/wrappers/golang/internal/generator/msm/generate.go b/wrappers/golang/internal/generator/msm/generate.go new file mode 100644 index 00000000..d1265ca6 --- /dev/null +++ b/wrappers/golang/internal/generator/msm/generate.go @@ -0,0 +1,39 @@ +package msm + +import ( + "path" + "path/filepath" + + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" +) + +var msmTemplates = map[string]string{ + "src": "msm/templates/msm.go.tmpl", + "test": "msm/templates/msm_test.go.tmpl", + "header": "msm/templates/msm.h.tmpl", +} + +func Generate(baseDir, packageName, curve, curvePrefix, gnarkImport string) { + data := struct { + PackageName string + Curve string + CurvePrefix string + BaseImportPath string + GnarkImport string + }{ + packageName, + curve, + curvePrefix, + baseDir, + gnarkImport, + } + + filePrefix := "" + if packageName == "g2" { + filePrefix = "g2_" + } + + generator.GenerateFile(msmTemplates["src"], path.Join(baseDir, packageName), "", "", data) + generator.GenerateFile(msmTemplates["header"], path.Join(baseDir, packageName, "include"), "", "", data) + generator.GenerateFile(msmTemplates["test"], filepath.Join(baseDir, "tests"), filePrefix, "", data) +} diff --git a/wrappers/golang/internal/generator/msm/templates/msm.go.tmpl b/wrappers/golang/internal/generator/msm/templates/msm.go.tmpl new file mode 100644 index 00000000..ef3f48e8 --- /dev/null +++ b/wrappers/golang/internal/generator/msm/templates/msm.go.tmpl @@ -0,0 +1,45 @@ +package {{.PackageName}} + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "unsafe" +) + +func {{.CurvePrefix}}GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func {{.CurvePrefix}}Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { + scalarsPointer, pointsPointer, resultsPointer, size, cfgPointer := core.MsmCheck(scalars, points, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cPoints := (*C.{{toCName .CurvePrefix}}affine_t)(pointsPointer) + cResults := (*C.{{toCName .CurvePrefix}}projective_t)(resultsPointer) + cSize := (C.int)(size) + cCfg := (*C.MSMConfig)(cfgPointer) + + __ret := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_msm_cuda(cScalars, cPoints, cSize, cCfg, cResults) + err := (cr.CudaError)(__ret) + return err +} + +func {{.CurvePrefix}}PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { + pointsPointer, outputBasesPointer := core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) + + cPoints := (*C.{{toCName .CurvePrefix}}affine_t)(pointsPointer) + cPointsLen := (C.int)(points.Len()) + cPrecomputeFactor := (C.int)(precomputeFactor) + cC := (C.int)(c) + cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) + cOutputBases := (*C.{{toCName .CurvePrefix}}affine_t)(outputBasesPointer) + + __ret := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_precompute_msm_bases_cuda(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) + err := (cr.CudaError)(__ret) + return err +} diff --git a/wrappers/golang/internal/generator/msm/templates/msm.h.tmpl b/wrappers/golang/internal/generator/msm/templates/msm.h.tmpl new file mode 100644 index 00000000..f6dd461b --- /dev/null +++ b/wrappers/golang/internal/generator/msm/templates/msm.h.tmpl @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _{{toUpper .Curve}}_{{.CurvePrefix}}MSM_H +#define _{{toUpper .Curve}}_{{.CurvePrefix}}MSM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct {{toCName .CurvePrefix}}projective_t {{toCName .CurvePrefix}}projective_t; +typedef struct {{toCName .CurvePrefix}}affine_t {{toCName .CurvePrefix}}affine_t; +typedef struct MSMConfig MSMConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t {{.Curve}}{{toCNameBackwards .CurvePrefix}}_msm_cuda(const scalar_t* scalars,const {{toCName .CurvePrefix}}affine_t* points, int count, MSMConfig* config, {{toCName .CurvePrefix}}projective_t* out); +cudaError_t {{.Curve}}{{toCNameBackwards .CurvePrefix}}_precompute_msm_bases_cuda({{toCName .CurvePrefix}}affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, {{toCName .CurvePrefix}}affine_t* out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/internal/generator/msm/templates/msm_test.go.tmpl b/wrappers/golang/internal/generator/msm/templates/msm_test.go.tmpl new file mode 100644 index 00000000..ec283fb4 --- /dev/null +++ b/wrappers/golang/internal/generator/msm/templates/msm_test.go.tmpl @@ -0,0 +1,365 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + {{if ne .GnarkImport "" -}} + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}" + "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}/fp" + "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}/fr" + {{end}} + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + icicle{{capitalize .Curve}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}" + "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/{{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}" +) +{{if ne .GnarkImport "" -}} +{{$isBW6 := eq .Curve "bw6_761"}}{{$isG2 := eq .CurvePrefix "G2"}}{{$isG1 := ne .CurvePrefix "G2"}}{{if or $isBW6 $isG1}} +func projectiveToGnarkAffine{{if and $isBW6 $isG2}}G2{{end}}(p {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{if and $isBW6 $isG2}}G2{{end}}Projective) {{toPackage .GnarkImport}}.{{if and $isBW6 $isG2}}G2{{else}}G1{{end}}Affine { + px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.X).ToBytesLittleEndian())) + py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Y).ToBytesLittleEndian())) + pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Z).ToBytesLittleEndian())) + + zInv := new(fp.Element) + x := new(fp.Element) + y := new(fp.Element) + + zInv.Inverse(&pz) + + x.Mul(&px, zInv) + y.Mul(&py, zInv) + + return {{toPackage .GnarkImport}}.{{if and $isBW6 $isG2}}G2{{else}}G1{{end}}Affine{X: *x, Y: *y} +} +{{end}} +{{- $isNotBW6 := ne .Curve "bw6_761"}}{{$isG2 := eq .CurvePrefix "G2"}}{{if and $isNotBW6 $isG2 }} +func projectiveToGnarkAffineG2(p g2.G2Projective) {{toPackage .GnarkImport}}.G2Affine { + pxBytes := p.X.ToBytesLittleEndian() + pxA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[:fp.Bytes])) + pxA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[fp.Bytes:])) + x := {{toPackage .GnarkImport}}.E2{ + A0: pxA0, + A1: pxA1, + } + + pyBytes := p.Y.ToBytesLittleEndian() + pyA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pyBytes[:fp.Bytes])) + pyA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pyBytes[fp.Bytes:])) + y := {{toPackage .GnarkImport}}.E2{ + A0: pyA0, + A1: pyA1, + } + + pzBytes := p.Z.ToBytesLittleEndian() + pzA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pzBytes[:fp.Bytes])) + pzA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pzBytes[fp.Bytes:])) + z := {{toPackage .GnarkImport}}.E2{ + A0: pzA0, + A1: pzA1, + } + + var zSquared {{toPackage .GnarkImport}}.E2 + zSquared.Mul(&z, &z) + + var X {{toPackage .GnarkImport}}.E2 + X.Mul(&x, &z) + + var Y {{toPackage .GnarkImport}}.E2 + Y.Mul(&y, &zSquared) + + g2Jac := {{toPackage .GnarkImport}}.G2Jac{ + X: X, + Y: Y, + Z: z, + } + + var g2Affine {{toPackage .GnarkImport}}.G2Affine + return *g2Affine.FromJacobian(&g2Jac) +} +{{end}} +func testAgainstGnarkCryptoMsm{{.CurvePrefix}}(scalars core.HostSlice[icicle{{capitalize .Curve}}.ScalarField], points core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Affine], out {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective) bool { + scalarsFr := make([]fr.Element, len(scalars)) + for i, v := range scalars { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + + pointsFp := make([]{{toPackage .GnarkImport}}.{{if eq .CurvePrefix "G2"}}G2{{else}}G1{{end}}Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffine{{.CurvePrefix}}(v.ToProjective()) + } + + return testAgainstGnarkCryptoMsm{{.CurvePrefix}}GnarkCryptoTypes(scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsm{{.CurvePrefix}}GnarkCryptoTypes(scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[{{toPackage .GnarkImport}}.{{if eq .CurvePrefix "G2"}}G2{{else}}G1{{end}}Affine], out {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective) bool { + var msmRes {{toPackage .GnarkImport}}.{{if eq .CurvePrefix "G2"}}G2{{else}}G1{{end}}Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var icicleResAsJac {{toPackage .GnarkImport}}.{{if eq .CurvePrefix "G2"}}G2{{else}}G1{{end}}Jac + proj := projectiveToGnarkAffine{{.CurvePrefix}}(out) + icicleResAsJac.FromAffine(&proj) + + return msmRes.Equal(&icicleResAsJac) +} + +{{$isBW6 := eq .Curve "bw6_761"}}{{$isG2 := eq .CurvePrefix "G2"}}{{$isG1 := ne .CurvePrefix "G2"}}{{if or $isBW6 $isG1 -}} +func convertIcicleAffineTo{{if and $isBW6 $isG2}}G2{{else}}G1{{end}}Affine(iciclePoints []{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Affine) []{{toPackage .GnarkImport}}.{{if and $isBW6 $isG2}}G2{{else}}G1{{end}}Affine { + points := make([]{{toPackage .GnarkImport}}.{{if and $isBW6 $isG2}}G2{{else}}G1{{end}}Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes]byte)(iciclePoint.X.ToBytesLittleEndian()) + fpXElem, _ := fp.LittleEndian.Element(&xBytes) + + yBytes := ([fp.Bytes]byte)(iciclePoint.Y.ToBytesLittleEndian()) + fpYElem, _ := fp.LittleEndian.Element(&yBytes) + points[index] = {{toPackage .GnarkImport}}.{{if and $isBW6 $isG2}}G2{{else}}G1{{end}}Affine{ + X: fpXElem, + Y: fpYElem, + } + } + + return points +}{{end}} +{{ $isNotBW6 := ne .Curve "bw6_761"}}{{$isG2 := eq .CurvePrefix "G2"}}{{if and $isNotBW6 $isG2 -}} +func convertIcicleG2AffineToG2Affine(iciclePoints []g2.G2Affine) []{{toPackage .GnarkImport}}.G2Affine { + points := make([]{{toPackage .GnarkImport}}.G2Affine, len(iciclePoints)) + for index, iciclePoint := range iciclePoints { + xBytes := ([fp.Bytes * 2]byte)(iciclePoint.X.ToBytesLittleEndian()) + xA0Bytes := ([fp.Bytes]byte)(xBytes[:fp.Bytes]) + xA1Bytes := ([fp.Bytes]byte)(xBytes[fp.Bytes:]) + xA0Elem, _ := fp.LittleEndian.Element(&xA0Bytes) + xA1Elem, _ := fp.LittleEndian.Element(&xA1Bytes) + + yBytes := ([fp.Bytes * 2]byte)(iciclePoint.Y.ToBytesLittleEndian()) + yA0Bytes := ([fp.Bytes]byte)(yBytes[:fp.Bytes]) + yA1Bytes := ([fp.Bytes]byte)(yBytes[fp.Bytes:]) + yA0Elem, _ := fp.LittleEndian.Element(&yA0Bytes) + yA1Elem, _ := fp.LittleEndian.Element(&yA1Bytes) + + points[index] = {{toPackage .GnarkImport}}.G2Affine{ + X: {{toPackage .GnarkImport}}.E2{ + A0: xA0Elem, + A1: xA1Elem, + }, + Y: {{toPackage .GnarkImport}}.E2{ + A0: yA0Elem, + A1: yA1Elem, + }, + } + } + + return points +}{{end}}{{end}} + +func TestMSM{{.CurvePrefix}}(t *testing.T) { + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { + size := 1 << power + + scalars := icicle{{capitalize .Curve}}.GenerateScalars(size) + points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(size) + + stream, _ := cr.CreateStream() + var p {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.Ctx.Stream = &stream + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, points, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + cr.SynchronizeStream(&stream) + {{if ne .GnarkImport "" -}} + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsm{{.CurvePrefix}}(scalars, points, outHost[0])) + {{end}} + } +} +{{if ne .GnarkImport "" -}} +func TestMSM{{if eq .CurvePrefix "G2"}}G2{{end}}GnarkCryptoTypes(t *testing.T) { + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + for _, power := range []int{3} { + size := 1 << power + + scalars := make([]fr.Element, size) + var x fr.Element + for i := 0; i < size; i++ { + x.SetRandom() + scalars[i] = x + } + scalarsHost := (core.HostSlice[fr.Element])(scalars) + points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(size) + pointsGnark := convertIcicle{{$isNotBW6 := ne .Curve "bw6_761"}}{{$isG2 := eq .CurvePrefix "G2"}}{{if and $isNotBW6 $isG2}}G2{{end}}AffineTo{{if eq .CurvePrefix "G2"}}G2{{else}}G1{{end}}Affine(points) + pointsHost := (core.HostSlice[{{toPackage .GnarkImport}}.{{if eq .CurvePrefix "G2"}}G2{{else}}G1{{end}}Affine])(pointsGnark) + + var p {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.ArePointsMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsm{{.CurvePrefix}}GnarkCryptoTypes(scalarsHost, pointsHost, outHost[0])) + } +} +{{end}} +func TestMSM{{.CurvePrefix}}Batch(t *testing.T) { + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + for _, power := range []int{10, 16} { + for _, batchSize := range []int{1, 3, 16} { + size := 1 << power + totalSize := size * batchSize + scalars := icicle{{capitalize .Curve}}.GenerateScalars(totalSize) + points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(totalSize) + + var p {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, points, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective], batchSize) + outHost.CopyFromDevice(&out) + out.Free() + {{if ne .GnarkImport "" -}} + // Check with gnark-crypto + for i := 0; i < batchSize; i++ { + scalarsSlice := scalars[i*size : (i+1)*size] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + assert.True(t, testAgainstGnarkCryptoMsm{{.CurvePrefix}}(scalarsSlice, pointsSlice, out)) + }{{end}} + } + } +} + +func TestPrecomputeBase{{.CurvePrefix}}(t *testing.T) { + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + const precomputeFactor = 8 + for _, power := range []int{10, 16} { + for _, batchSize := range []int{1, 3, 16} { + size := 1 << power + totalSize := size * batchSize + scalars := icicle{{capitalize .Curve}}.GenerateScalars(totalSize) + points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(totalSize) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) + assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") + + var p {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + + cfg.PrecomputeFactor = precomputeFactor + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective], batchSize) + outHost.CopyFromDevice(&out) + out.Free() + precomputeOut.Free() + {{if ne .GnarkImport "" -}} + // Check with gnark-crypto + for i := 0; i < batchSize; i++ { + scalarsSlice := scalars[i*size : (i+1)*size] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + assert.True(t, testAgainstGnarkCryptoMsm{{.CurvePrefix}}(scalarsSlice, pointsSlice, out)) + }{{end}} + } + } +} + +func TestMSM{{.CurvePrefix}}SkewedDistribution(t *testing.T) { + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { + size := 1 << power + + scalars := icicle{{capitalize .Curve}}.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, points, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + {{if ne .GnarkImport "" -}} + // Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsm{{.CurvePrefix}}(scalars, points, outHost[0])){{end}} + } +} + +func TestMSM{{.CurvePrefix}}MultiDevice(t *testing.T) { + numDevices, _ := cr.GetDeviceCount() + numDevices = 1 // TODO remove when test env is fixed + fmt.Println("There are ", numDevices, " devices available") + orig_device, _ := cr.GetDevice() + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + wg.Add(1) + cr.RunOnDevice(i, func(args ...any) { + defer wg.Done() + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { + size := 1 << power + scalars := icicle{{capitalize .Curve}}.GenerateScalars(size) + points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(size) + + stream, _ := cr.CreateStream() + var p {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") + cfg.Ctx.Stream = &stream + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, points, &cfg, out) + assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + outHost := make(core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + cr.SynchronizeStream(&stream) + {{if ne .GnarkImport "" -}}// Check with gnark-crypto + assert.True(t, testAgainstGnarkCryptoMsm{{.CurvePrefix}}(scalars, points, outHost[0])){{end}} + } + }) + } + wg.Wait() + cr.SetDevice(orig_device) +} diff --git a/wrappers/golang/internal/generator/ntt/generate.go b/wrappers/golang/internal/generator/ntt/generate.go new file mode 100644 index 00000000..3f58ad94 --- /dev/null +++ b/wrappers/golang/internal/generator/ntt/generate.go @@ -0,0 +1,54 @@ +package ntt + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" +) + +var nttTemplates = map[string]string{ + "src": "ntt/templates/ntt.go.tmpl", + "test": "ntt/templates/ntt_test.go.tmpl", + "testNoDomain": "ntt/templates/ntt_no_domain_test.go.tmpl", + "header": "ntt/templates/ntt.h.tmpl", +} + +func Generate(baseDir, additionalDirPath, field, fieldPrefix, gnarkImport string, rou int, withDomain bool, fieldNoDomain, fieldNoDomainPrefix string) { + baseImportPathNoDomain := "" + if !withDomain { + baseImportPathNoDomain = path.Join(baseDir, additionalDirPath) + } + + data := struct { + PackageName string + Field string + FieldPrefix string + WithDomain bool + BaseImportPath string + GnarkImport string + ROU int + FieldNoDomain string + FieldNoDomainPrefix string + BaseImportPathNoDomain string + }{ + "ntt", + field, + fieldPrefix, + withDomain, + baseDir, + gnarkImport, + rou, + fieldNoDomain, + fieldNoDomainPrefix, + baseImportPathNoDomain, + } + + testPath := nttTemplates["test"] + if !withDomain { + testPath = nttTemplates["testNoDomain"] + } + + generator.GenerateFile(nttTemplates["src"], path.Join(baseDir, additionalDirPath, "ntt"), "", "", data) + generator.GenerateFile(nttTemplates["header"], path.Join(baseDir, additionalDirPath, "ntt", "include"), "", "", data) + generator.GenerateFile(testPath, path.Join(baseDir, "tests"), "", "", data) +} diff --git a/wrappers/golang/internal/generator/ntt/templates/ntt.go.tmpl b/wrappers/golang/internal/generator/ntt/templates/ntt.go.tmpl new file mode 100644 index 00000000..93f63be6 --- /dev/null +++ b/wrappers/golang/internal/generator/ntt/templates/ntt.go.tmpl @@ -0,0 +1,58 @@ +package {{.PackageName}} + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + {{if .WithDomain}}{{.Field}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}"{{end}} + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" +) + +{{if .WithDomain}} +import ( + "unsafe" +){{end}} + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { + scalarsPointer, resultsPointer, size, cfgPointer := core.NttCheck[T](scalars, cfg, results) + + cScalars := (*C.scalar_t)(scalarsPointer) + cSize := (C.int)(size) + cDir := (C.int)(dir) + cCfg := (*C.NTTConfig)(cfgPointer) + cResults := (*C.scalar_t)(resultsPointer) + + __ret := C.{{if .WithDomain}}{{.Field}}{{else}}{{.FieldNoDomain}}{{end}}_ntt_cuda(cScalars, cSize, cDir, cCfg, cResults) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} +{{if .WithDomain}} +func GetDefaultNttConfig() core.NTTConfig[[{{.Field}}.{{toConst .FieldPrefix}}LIMBS]uint32] { + cosetGenField := {{.Field}}.{{.FieldPrefix}}Field{} + cosetGenField.One() + var cosetGen [{{.Field}}.{{toConst .FieldPrefix}}LIMBS]uint32 + for i, v := range cosetGenField.GetLimbs() { + cosetGen[i] = v + } + + return core.GetDefaultNTTConfig(cosetGen) +} + +func InitDomain(primitiveRoot {{.Field}}.{{.FieldPrefix}}Field, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cFastTwiddles := (C._Bool)(fastTwiddles) + __ret := C.{{.Field}}_initialize_domain(cPrimitiveRoot, cCtx, cFastTwiddles) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + __ret := C.{{.Field}}_release_domain(cCtx) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} +{{end}} \ No newline at end of file diff --git a/wrappers/golang/internal/generator/ntt/templates/ntt.h.tmpl b/wrappers/golang/internal/generator/ntt/templates/ntt.h.tmpl new file mode 100644 index 00000000..76f74bdc --- /dev/null +++ b/wrappers/golang/internal/generator/ntt/templates/ntt.h.tmpl @@ -0,0 +1,24 @@ +#include +#include + +#ifndef _{{if .WithDomain}}{{toUpper .Field}}{{else}}{{toUpper .FieldNoDomain}}{{end}}_NTT_H +#define _{{if .WithDomain}}{{toUpper .Field}}{{else}}{{toUpper .FieldNoDomain}}{{end}}_NTT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct NTTConfig NTTConfig; +{{if .WithDomain}}typedef struct DeviceContext DeviceContext;{{end}} + +cudaError_t {{if .WithDomain}}{{.Field}}{{else}}{{.FieldNoDomain}}{{end}}_ntt_cuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +{{if .WithDomain -}} +cudaError_t {{.Field}}_initialize_domain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); +cudaError_t {{.Field}}_release_domain(DeviceContext* ctx);{{end}} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang/internal/generator/ntt/templates/ntt_no_domain_test.go.tmpl b/wrappers/golang/internal/generator/ntt/templates/ntt_no_domain_test.go.tmpl new file mode 100644 index 00000000..1bb61c3a --- /dev/null +++ b/wrappers/golang/internal/generator/ntt/templates/ntt_no_domain_test.go.tmpl @@ -0,0 +1,84 @@ +package tests + +import ( + {{if ne .GnarkImport "" -}} + "reflect" + {{end -}} + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + {{.FieldNoDomain}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPathNoDomain}}" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/ntt" +) + +func TestNttNoDomain(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := {{.FieldNoDomain}}.GenerateScalars(1 << largestTestSize) + + for _, size := range []int{4, largestTestSize} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + + scalarsCopy := core.HostSliceFromElements[{{.FieldNoDomain}}.{{.FieldNoDomainPrefix}}Field](scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[{{.FieldNoDomain}}.{{.FieldNoDomainPrefix}}Field], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + } + } +} + +func TestNttDeviceAsyncNoDomain(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := {{.FieldNoDomain}}.GenerateScalars(1 << largestTestSize) + + for _, size := range []int{1, 10, largestTestSize} { + for _, direction := range []core.NTTDir{core.KForward, core.KInverse} { + for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[{{.FieldNoDomain}}.{{.FieldNoDomainPrefix}}Field](scalars[:testSize]) + + stream, _ := cr.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.Ctx.Stream = &stream + + var deviceInput core.DeviceSlice + scalarsCopy.CopyToDeviceAsync(&deviceInput, stream, true) + var deviceOutput core.DeviceSlice + deviceOutput.MallocAsync(testSize*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) + + // run ntt + ntt.Ntt(deviceInput, direction, &cfg, deviceOutput) + output := make(core.HostSlice[{{.FieldNoDomain}}.{{.FieldNoDomainPrefix}}Field], testSize) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + cr.SynchronizeStream(&stream) + } + } + } +} + +func TestNttBatchNoDomain(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestBatchSize := 100 + scalars := {{.FieldNoDomain}}.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{1, 16, largestBatchSize} { + testSize := 1 << size + totalSize := testSize * batchSize + + scalarsCopy := core.HostSliceFromElements[{{.FieldNoDomain}}.{{.FieldNoDomainPrefix}}Field](scalars[:totalSize]) + + cfg.Ordering = core.KNN + cfg.BatchSize = int32(batchSize) + // run ntt + output := make(core.HostSlice[{{.FieldNoDomain}}.{{.FieldNoDomainPrefix}}Field], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + } + } +} diff --git a/wrappers/golang/internal/generator/templates/ntt_test.go.tmpl b/wrappers/golang/internal/generator/ntt/templates/ntt_test.go.tmpl similarity index 66% rename from wrappers/golang/internal/generator/templates/ntt_test.go.tmpl rename to wrappers/golang/internal/generator/ntt/templates/ntt_test.go.tmpl index 13679721..1901d655 100644 --- a/wrappers/golang/internal/generator/templates/ntt_test.go.tmpl +++ b/wrappers/golang/internal/generator/ntt/templates/ntt_test.go.tmpl @@ -1,40 +1,25 @@ -package {{.PackageName}} +package tests import ( - "os" + {{if ne .GnarkImport "" -}} "reflect" + {{end -}} "testing" "github.com/ingonyama-zk/icicle/wrappers/golang/core" cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - + "github.com/ingonyama-zk/icicle/wrappers/golang/test_helpers" + {{.Field}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}" + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/ntt" + {{if ne .GnarkImport "" -}} "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}/fr" "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}/fr/fft" + {{end -}} "github.com/stretchr/testify/assert" ) -const ( - largestTestSize = 17 -) - -func init() { - cfg := GetDefaultNttConfig() - initDomain(largestTestSize, cfg) -} - -func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { - rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) - rou := rouMont.Bits() - rouIcicle := ScalarField{} - limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) - - rouIcicle.FromLimbs(limbs) - e := InitDomain(rouIcicle, cfg.Ctx, false) - return e -} - -func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], output core.HostSlice[ScalarField], order core.Ordering, direction core.NTTDir) bool { - domainWithPrecompute := fft.NewDomain(uint64(size)) +{{if ne .GnarkImport "" -}} +func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], output core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], order core.Ordering, direction core.NTTDir) bool { scalarsFr := make([]fr.Element, size) for i, v := range scalars { slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) @@ -46,6 +31,11 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou outputAsFr[i] = slice64 } + return testAgainstGnarkCryptoNttGnarkTypes(size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) bool { + domainWithPrecompute := fft.NewDomain(uint64(size)) // DIT + BitReverse == Ordering.kRR // DIT == Ordering.kRN // DIF + BitReverse == Ordering.kNN @@ -68,72 +58,82 @@ func testAgainstGnarkCryptoNtt(size int, scalars core.HostSlice[ScalarField], ou } return reflect.DeepEqual(scalarsFr, outputAsFr) } +{{end -}} func TestNTTGetDefaultConfig(t *testing.T) { - actual := GetDefaultNttConfig() - expected := generateLimbOne(int(SCALAR_LIMBS)) + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int({{.Field}}.{{toConst .FieldPrefix}}LIMBS)) assert.Equal(t, expected, actual.CosetGen[:]) - cosetGenField := ScalarField{} + cosetGenField := {{.Field}}.{{.FieldPrefix}}Field{} cosetGenField.One() assert.ElementsMatch(t, cosetGenField.GetLimbs(), actual.CosetGen) } func TestInitDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) } func TestNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := {{.Field}}.GenerateScalars(1 << largestTestSize) for _, size := range []int{4, largestTestSize} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[{{.Field}}.{{.FieldPrefix}}Field](scalars[:testSize]) cfg.Ordering = v // run ntt - output := make(core.HostSlice[ScalarField], testSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + {{if ne .GnarkImport "" -}} // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, core.KForward)) + {{end -}} } } } +{{if ne .GnarkImport "" -}} +func TestNttFrElement(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + scalars := make([]fr.Element, 4) + var x fr.Element + for i := 0; i < 4; i++ { + x.SetRandom() + scalars[i] = x + } -func TestECNtt(t *testing.T) { - cfg := GetDefaultNttConfig() - points := GenerateProjectivePoints(1 << largestTestSize) + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + testSize := size - for _, size := range []int{4, 5, 6, 7, 8} { - for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { - testSize := 1 << size - - pointsCopy := core.HostSliceFromElements[Projective](points[:testSize]) + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) cfg.Ordering = v - cfg.NttAlgorithm = core.Radix2 - output := make(core.HostSlice[Projective], testSize) - e := ECNtt(pointsCopy, core.KForward, &cfg, output) - assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ECNtt failed") + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + assert.True(t, testAgainstGnarkCryptoNttGnarkTypes(testSize, scalarsCopy, output, v, core.KForward)) } } } - +{{end}} func TestNttDeviceAsync(t *testing.T) { - cfg := GetDefaultNttConfig() - scalars := GenerateScalars(1 << largestTestSize) + cfg := ntt.GetDefaultNttConfig() + scalars := {{.Field}}.GenerateScalars(1 << largestTestSize) for _, size := range []int{1, 10, largestTestSize} { for _, direction := range []core.NTTDir{core.KForward, core.KInverse} { for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { testSize := 1 << size - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:testSize]) + scalarsCopy := core.HostSliceFromElements[{{.Field}}.{{.FieldPrefix}}Field](scalars[:testSize]) stream, _ := cr.CreateStream() @@ -147,37 +147,39 @@ func TestNttDeviceAsync(t *testing.T) { deviceOutput.MallocAsync(testSize*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) // run ntt - Ntt(deviceInput, direction, &cfg, deviceOutput) - output := make(core.HostSlice[ScalarField], testSize) + ntt.Ntt(deviceInput, direction, &cfg, deviceOutput) + output := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], testSize) output.CopyFromDeviceAsync(&deviceOutput, stream) cr.SynchronizeStream(&stream) - + {{if ne .GnarkImport "" -}} // Compare with gnark-crypto assert.True(t, testAgainstGnarkCryptoNtt(testSize, scalarsCopy, output, v, direction)) + {{end -}} } } } } func TestNttBatch(t *testing.T) { - cfg := GetDefaultNttConfig() + cfg := ntt.GetDefaultNttConfig() largestBatchSize := 100 - scalars := GenerateScalars(1 << largestTestSize * largestBatchSize) + scalars := {{.Field}}.GenerateScalars(1 << largestTestSize * largestBatchSize) for _, size := range []int{4, largestTestSize} { for _, batchSize := range []int{1, 16, largestBatchSize} { testSize := 1 << size totalSize := testSize * batchSize - scalarsCopy := core.HostSliceFromElements[ScalarField](scalars[:totalSize]) + scalarsCopy := core.HostSliceFromElements[{{.Field}}.{{.FieldPrefix}}Field](scalars[:totalSize]) cfg.Ordering = core.KNN cfg.BatchSize = int32(batchSize) // run ntt - output := make(core.HostSlice[ScalarField], totalSize) - Ntt(scalarsCopy, core.KForward, &cfg, output) + output := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + {{if ne .GnarkImport "" -}} // Compare with gnark-crypto domainWithPrecompute := fft.NewDomain(uint64(testSize)) outputAsFr := make([]fr.Element, totalSize) @@ -199,53 +201,36 @@ func TestNttBatch(t *testing.T) { t.FailNow() } } + {{end -}} } } } func TestReleaseDomain(t *testing.T) { t.Skip("Skipped because each test requires the domain to be initialized before running. We ensure this using the TestMain() function") - cfg := GetDefaultNttConfig() - e := ReleaseDomain(cfg.Ctx) + cfg := ntt.GetDefaultNttConfig() + e := ntt.ReleaseDomain(cfg.Ctx) assert.Equal(t, core.IcicleErrorCode(0), e.IcicleErrorCode, "ReleasDomain failed") } -func TestMain(m *testing.M) { - // setup domain - cfg := GetDefaultNttConfig() - e := initDomain(largestTestSize, cfg) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("initDomain failed") - } - - // execute tests - os.Exit(m.Run()) - - // release domain - e = ReleaseDomain(cfg.Ctx) - if e.IcicleErrorCode != core.IcicleErrorCode(0) { - panic("ReleaseDomain failed") - } -} - // func TestNttArbitraryCoset(t *testing.T) { // for _, size := range []int{20} { // for _, v := range [4]core.Ordering{core.KNN, core.KNR, core.KRN, core.KRR} { // testSize := 1 << size // scalars := GenerateScalars(testSize) -// cfg := GetDefaultNttConfig() +// cfg := ntt.GetDefaultNttConfig() -// var scalarsCopy core.HostSlice[ScalarField] +// var scalarsCopy core.HostSlice[{{.FieldPrefix}}Field] // for _, v := range scalars { -// var scalar ScalarField +// var scalar {{.FieldPrefix}}Field // scalarsCopy = append(scalarsCopy, scalar.FromLimbs(v.GetLimbs())) // } // // init domain // rouMont, _ := fft.Generator(1 << 20) // rou := rouMont.Bits() -// rouIcicle := ScalarField{} +// rouIcicle := {{.FieldPrefix}}Field{} // limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) // rouIcicle.FromLimbs(limbs) @@ -253,7 +238,7 @@ func TestMain(m *testing.M) { // cfg.Ordering = v // // run ntt -// output := make(core.HostSlice[ScalarField], testSize) +// output := make(core.HostSlice[{{.FieldPrefix}}Field], testSize) // Ntt(scalars, core.KForward, &cfg, output) // // Compare with gnark-crypto diff --git a/wrappers/golang/internal/generator/polynomial/generate.go b/wrappers/golang/internal/generator/polynomial/generate.go new file mode 100644 index 00000000..40a3300b --- /dev/null +++ b/wrappers/golang/internal/generator/polynomial/generate.go @@ -0,0 +1,31 @@ +package polynomial + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" +) + +var polynomialTemplates = map[string]string{ + "src": "polynomial/templates/polynomial.go.tmpl", + "test": "polynomial/templates/polynomial_test.go.tmpl", + "header": "polynomial/templates/polynomial.h.tmpl", +} + +func Generate(baseDir, field, fieldPrefix, gnarkImport string) { + data := struct { + Field string + FieldPrefix string + BaseImportPath string + GnarkImport string + }{ + field, + fieldPrefix, + baseDir, + gnarkImport, + } + + generator.GenerateFile(polynomialTemplates["src"], path.Join(baseDir, "polynomial"), "", "", data) + generator.GenerateFile(polynomialTemplates["header"], path.Join(baseDir, "polynomial", "include"), "", "", data) + generator.GenerateFile(polynomialTemplates["test"], path.Join(baseDir, "tests"), "", "", data) +} diff --git a/wrappers/golang/internal/generator/polynomial/templates/polynomial.go.tmpl b/wrappers/golang/internal/generator/polynomial/templates/polynomial.go.tmpl new file mode 100644 index 00000000..bea3018d --- /dev/null +++ b/wrappers/golang/internal/generator/polynomial/templates/polynomial.go.tmpl @@ -0,0 +1,176 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + {{.Field}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +func InitPolyBackend() bool { + return (bool)(C.{{.Field}}_polynomial_init_cuda_backend()) +} + +func (up *DensePolynomial) Print() { + C.{{.Field}}_polynomial_print(up.handle) +} + +func (up *DensePolynomial) CreateFromCoeffecitients(coeffs core.HostOrDeviceSlice) DensePolynomial { + if coeffs.IsOnDevice() { + coeffs.(core.DeviceSlice).CheckDevice() + } + coeffsPointer := (*C.scalar_t)(coeffs.AsUnsafePointer()) + cSize := (C.size_t)(coeffs.Len()) + up.handle = C.{{.Field}}_polynomial_create_from_coefficients(coeffsPointer, cSize) + return *up +} + +func (up *DensePolynomial) CreateFromROUEvaluations(evals core.HostOrDeviceSlice) DensePolynomial { + evalsPointer := (*C.scalar_t)(evals.AsUnsafePointer()) + cSize := (C.size_t)(evals.Len()) + up.handle = C.{{.Field}}_polynomial_create_from_coefficients(evalsPointer, cSize) + return *up +} + +func (up *DensePolynomial) Clone() DensePolynomial { + return DensePolynomial{ + handle: C.{{.Field}}_polynomial_clone(up.handle), + } +} + +// TODO @jeremyfelder: Maybe this should be in a SetFinalizer that is set on Create functions? +func (up *DensePolynomial) Delete() { + C.{{.Field}}_polynomial_delete(up.handle) +} + +func (up *DensePolynomial) Add(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.{{.Field}}_polynomial_add(up.handle, b.handle), + } +} + +func (up *DensePolynomial) AddInplace(b *DensePolynomial) { + C.{{.Field}}_polynomial_add_inplace(up.handle, b.handle) +} + +func (up *DensePolynomial) Subtract(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.{{.Field}}_polynomial_subtract(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Multiply(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.{{.Field}}_polynomial_multiply(up.handle, b.handle), + } +} + +func (up *DensePolynomial) MultiplyByScalar(scalar {{.Field}}.{{.FieldPrefix}}Field) DensePolynomial { + cScalar := (*C.scalar_t)(unsafe.Pointer(scalar.AsPointer())) + return DensePolynomial{ + handle: C.{{.Field}}_polynomial_multiply_by_scalar(up.handle, cScalar), + } +} + +func (up *DensePolynomial) Divide(b *DensePolynomial) (DensePolynomial, DensePolynomial) { + var q, r *PolynomialHandle + C.{{.Field}}_polynomial_division(up.handle, b.handle, &q, &r) + return DensePolynomial{ + handle: q, + }, DensePolynomial{ + handle: r, + } +} + +func (up *DensePolynomial) Quotient(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.{{.Field}}_polynomial_quotient(up.handle, b.handle), + } +} + +func (up *DensePolynomial) Remainder(b *DensePolynomial) DensePolynomial { + return DensePolynomial{ + handle: C.{{.Field}}_polynomial_remainder(up.handle, b.handle), + } +} + +func (up *DensePolynomial) DivideByVanishing(vanishing_degree uint64) DensePolynomial { + cVanishingDegree := (C.ulong)(vanishing_degree) + return DensePolynomial{ + handle: C.{{.Field}}_polynomial_divide_by_vanishing(up.handle, cVanishingDegree), + } +} + +func (up *DensePolynomial) AddMonomial(monomialCoeff {{.Field}}.{{.FieldPrefix}}Field, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]{{.Field}}.{{.FieldPrefix}}Field{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.{{.Field}}_polynomial_add_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) SubMonomial(monomialCoeff {{.Field}}.{{.FieldPrefix}}Field, monomial uint64) DensePolynomial { + hs := core.HostSliceFromElements([]{{.Field}}.{{.FieldPrefix}}Field{monomialCoeff}) + cMonomialCoeff := (*C.scalar_t)(hs.AsUnsafePointer()) + cMonomial := (C.ulong)(monomial) + C.{{.Field}}_polynomial_sub_monomial_inplace(up.handle, cMonomialCoeff, cMonomial) + return *up +} + +func (up *DensePolynomial) Eval(x {{.Field}}.{{.FieldPrefix}}Field) {{.Field}}.{{.FieldPrefix}}Field { + domains := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], 1) + domains[0] = x + evals := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], 1) + up.EvalOnDomain(domains, evals) + return evals[0] +} + +func (up *DensePolynomial) EvalOnDomain(domain, evals core.HostOrDeviceSlice) core.HostOrDeviceSlice { + cDomain := (*C.scalar_t)(domain.AsUnsafePointer()) + cDomainSize := (C.size_t)(domain.Len()) + cEvals := (*C.scalar_t)(evals.AsUnsafePointer()) + C.{{.Field}}_polynomial_evaluate_on_domain(up.handle, cDomain, cDomainSize, cEvals) + return evals +} + +func (up *DensePolynomial) Degree() int { + return int(C.{{.Field}}_polynomial_degree(up.handle)) +} + +func (up *DensePolynomial) CopyCoeffsRange(start, end int, out core.HostOrDeviceSlice) (int, core.HostOrDeviceSlice) { + cStart := (C.size_t)(start) + cEnd := (C.size_t)(end) + cScalarOut := (*C.scalar_t)(out.AsUnsafePointer()) + __cNumCoeffsRead := C.{{.Field}}_polynomial_copy_coeffs_range(up.handle, cScalarOut, cStart, cEnd) + return int(__cNumCoeffsRead), out +} + +func (up *DensePolynomial) GetCoeff(idx int) {{.Field}}.{{.FieldPrefix}}Field { + out := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], 1) + up.CopyCoeffsRange(idx, idx, out) + return out[0] +} + +func (up *DensePolynomial) Even() DensePolynomial { + evenPoly := C.{{.Field}}_polynomial_even(up.handle) + return DensePolynomial{ + handle: evenPoly, + } +} + +func (up *DensePolynomial) Odd() DensePolynomial { + oddPoly := C.{{.Field}}_polynomial_odd(up.handle) + return DensePolynomial{ + handle: oddPoly, + } +} diff --git a/wrappers/golang/internal/generator/polynomial/templates/polynomial.h.tmpl b/wrappers/golang/internal/generator/polynomial/templates/polynomial.h.tmpl new file mode 100644 index 00000000..4c3c3ded --- /dev/null +++ b/wrappers/golang/internal/generator/polynomial/templates/polynomial.h.tmpl @@ -0,0 +1,51 @@ +#include +#include + +#ifndef _{{toUpper .Field}}_POLY_H +#define _{{toUpper .Field}}_POLY_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct PolynomialInst PolynomialInst; +typedef struct IntegrityPointer IntegrityPointer; + +bool {{.Field}}_polynomial_init_cuda_backend(); +PolynomialInst* {{.Field}}_polynomial_create_from_coefficients(scalar_t* coeffs, size_t size); +PolynomialInst* {{.Field}}_polynomial_create_from_rou_evaluations(scalar_t* evals, size_t size); +PolynomialInst* {{.Field}}_polynomial_clone(const PolynomialInst* p); +void {{.Field}}_polynomial_print(PolynomialInst* p); +void {{.Field}}_polynomial_delete(PolynomialInst* instance); +PolynomialInst* {{.Field}}_polynomial_add(const PolynomialInst* a, const PolynomialInst* b); +void {{.Field}}_polynomial_add_inplace(PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* {{.Field}}_polynomial_subtract(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* {{.Field}}_polynomial_multiply(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* {{.Field}}_polynomial_multiply_by_scalar(const PolynomialInst* a, const scalar_t* scalar); +void {{.Field}}_polynomial_division(const PolynomialInst* a, const PolynomialInst* b, PolynomialInst** q /*OUT*/, PolynomialInst** r /*OUT*/); +PolynomialInst* {{.Field}}_polynomial_quotient(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* {{.Field}}_polynomial_remainder(const PolynomialInst* a, const PolynomialInst* b); +PolynomialInst* {{.Field}}_polynomial_divide_by_vanishing(const PolynomialInst* p, size_t vanishing_poly_degree); +void {{.Field}}_polynomial_add_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void {{.Field}}_polynomial_sub_monomial_inplace(PolynomialInst* p, const scalar_t* monomial_coeff, size_t monomial); +void {{.Field}}_polynomial_evaluate_on_domain(const PolynomialInst* p, scalar_t* domain, size_t domain_size, scalar_t* evals /*OUT*/); +size_t {{.Field}}_polynomial_degree(PolynomialInst* p); +size_t {{.Field}}_polynomial_copy_coeffs_range(PolynomialInst* p, scalar_t* memory, size_t start_idx, size_t end_idx); +PolynomialInst* {{.Field}}_polynomial_even(PolynomialInst* p); +PolynomialInst* {{.Field}}_polynomial_odd(PolynomialInst* p); + +// scalar_t* {{.Field}}_polynomial_get_coeffs_raw_ptr(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// PolynomialInst* {{.Field}}_polynomial_slice(PolynomialInst* p, size_t offset, size_t stride, size_t size); +// IntegrityPointer* {{.Field}}_polynomial_get_coeff_view(PolynomialInst* p, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// IntegrityPointer* {{.Field}}_polynomial_get_rou_evaluations_view(PolynomialInst* p, size_t nof_evals, bool is_reversed, size_t* size /*OUT*/, size_t* device_id /*OUT*/); +// const scalar_t* {{.Field}}_polynomial_intergrity_ptr_get(IntegrityPointer* p); +// bool {{.Field}}_polynomial_intergrity_ptr_is_valid(IntegrityPointer* p); +// void {{.Field}}_polynomial_intergrity_ptr_destroy(IntegrityPointer* p); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/wrappers/golang/internal/generator/polynomial/templates/polynomial_test.go.tmpl b/wrappers/golang/internal/generator/polynomial/templates/polynomial_test.go.tmpl new file mode 100644 index 00000000..6c01aa32 --- /dev/null +++ b/wrappers/golang/internal/generator/polynomial/templates/polynomial_test.go.tmpl @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + {{.Field}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}" + // "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/ntt" + "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/polynomial" + "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/vecOps" + "github.com/stretchr/testify/assert" +) + +var one, two, three, four, five {{.Field}}.{{.FieldPrefix}}Field + +func init() { + one.One() + two.FromUint32(2) + three.FromUint32(3) + four.FromUint32(4) + five.FromUint32(5) +} + +func rand() {{.Field}}.{{.FieldPrefix}}Field { + return {{.Field}}.GenerateScalars(1)[0] +} + +func randomPoly(size int) (f polynomial.DensePolynomial) { + f.CreateFromCoeffecitients(core.HostSliceFromElements({{.Field}}.GenerateScalars(size))) + return f +} + +func vecOp(a, b {{.Field}}.{{.FieldPrefix}}Field, op core.VecOps) {{.Field}}.{{.FieldPrefix}}Field { + ahost := core.HostSliceWithValue(a, 1) + bhost := core.HostSliceWithValue(b, 1) + out := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], 1) + + cfg := core.DefaultVecOpsConfig() + vecOps.VecOp(ahost, bhost, out, cfg, op) + return out[0] +} + +func TestPolyCreateFromCoefficients(t *testing.T) { + scalars := {{.Field}}.GenerateScalars(33) + var uniPoly polynomial.DensePolynomial + + poly := uniPoly.CreateFromCoeffecitients(scalars) + poly.Print() +} + +func TestPolyEval(t *testing.T) { + // testing correct evaluation of f(8) for f(x)=4x^2+2x+5 + coeffs := core.HostSliceFromElements([]{{.Field}}.{{.FieldPrefix}}Field{five, two, four}) + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(coeffs) + + var x {{.Field}}.{{.FieldPrefix}}Field + x.FromUint32(8) + domains := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], 1) + domains[0] = x + evals := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], 1) + fEvaled := f.EvalOnDomain(domains, evals) + var expected {{.Field}}.{{.FieldPrefix}}Field + assert.Equal(t, expected.FromUint32(277), fEvaled.(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field])[0]) +} + +func TestPolyClone(t *testing.T) { + f := randomPoly(8) + x := rand() + fx := f.Eval(x) + + g := f.Clone() + fg := f.Add(&g) + + gx := g.Eval(x) + fgx := fg.Eval(x) + + assert.Equal(t, fx, gx) + assert.Equal(t, vecOp(fx, gx, core.Add), fgx) +} + +func TestPolyAddSubMul(t *testing.T) { + testSize := 1 << 10 + f := randomPoly(testSize) + g := randomPoly(testSize) + x := rand() + + fx := f.Eval(x) + gx := g.Eval(x) + + polyAdd := f.Add(&g) + fxAddgx := vecOp(fx, gx, core.Add) + assert.Equal(t, polyAdd.Eval(x), fxAddgx) + + polySub := f.Subtract(&g) + fxSubgx := vecOp(fx, gx, core.Sub) + assert.Equal(t, polySub.Eval(x), fxSubgx) + + polyMul := f.Multiply(&g) + fxMulgx := vecOp(fx, gx, core.Mul) + assert.Equal(t, polyMul.Eval(x), fxMulgx) + + s1 := rand() + polMulS1 := f.MultiplyByScalar(s1) + assert.Equal(t, polMulS1.Eval(x), vecOp(fx, s1, core.Mul)) + + s2 := rand() + polMulS2 := f.MultiplyByScalar(s2) + assert.Equal(t, polMulS2.Eval(x), vecOp(fx, s2, core.Mul)) +} + +func TestPolyMonomials(t *testing.T) { + var zero {{.Field}}.{{.FieldPrefix}}Field + var f polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements([]{{.Field}}.{{.FieldPrefix}}Field{one, zero, two})) + x := rand() + + fx := f.Eval(x) + f.AddMonomial(three, 1) + fxAdded := f.Eval(x) + assert.Equal(t, fxAdded, vecOp(fx, vecOp(three, x, core.Mul), core.Add)) + + f.SubMonomial(one, 0) + fxSub := f.Eval(x) + assert.Equal(t, fxSub, vecOp(fxAdded, one, core.Sub)) +} + +func TestPolyReadCoeffs(t *testing.T) { + var f polynomial.DensePolynomial + coeffs := core.HostSliceFromElements([]{{.Field}}.{{.FieldPrefix}}Field{one, two, three, four}) + f.CreateFromCoeffecitients(coeffs) + coeffsCopied := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], coeffs.Len()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsCopied) + assert.ElementsMatch(t, coeffs, coeffsCopied) + + var coeffsDevice core.DeviceSlice + coeffsDevice.Malloc(coeffs.Len()*one.Size(), one.Size()) + _, _ = f.CopyCoeffsRange(0, coeffs.Len()-1, coeffsDevice) + coeffsHost := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], coeffs.Len()) + coeffsHost.CopyFromDevice(&coeffsDevice) + + assert.ElementsMatch(t, coeffs, coeffsHost) +} + +func TestPolyOddEvenSlicing(t *testing.T) { + size := 1<<10 - 3 + f := randomPoly(size) + + even := f.Even() + odd := f.Odd() + assert.Equal(t, f.Degree(), even.Degree()+odd.Degree()+1) + + x := rand() + var evenExpected, oddExpected {{.Field}}.{{.FieldPrefix}}Field + for i := size; i >= 0; i-- { + if i%2 == 0 { + mul := vecOp(evenExpected, x, core.Mul) + evenExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } else { + mul := vecOp(oddExpected, x, core.Mul) + oddExpected = vecOp(mul, f.GetCoeff(i), core.Add) + } + } + + evenEvaled := even.Eval(x) + assert.Equal(t, evenExpected, evenEvaled) + + oddEvaled := odd.Eval(x) + assert.Equal(t, oddExpected, oddEvaled) +} + +func TestPolynomialDivision(t *testing.T) { + // divide f(x)/g(x), compute q(x), r(x) and check f(x)=q(x)*g(x)+r(x) + var f, g polynomial.DensePolynomial + f.CreateFromCoeffecitients(core.HostSliceFromElements({{.Field}}.GenerateScalars(1 << 4))) + g.CreateFromCoeffecitients(core.HostSliceFromElements({{.Field}}.GenerateScalars(1 << 2))) + + q, r := f.Divide(&g) + + qMulG := q.Multiply(&g) + fRecon := qMulG.Add(&r) + + x := {{.Field}}.GenerateScalars(1)[0] + fEval := f.Eval(x) + fReconEval := fRecon.Eval(x) + assert.Equal(t, fEval, fReconEval) +} + +func TestDivideByVanishing(t *testing.T) { + // poly of x^4-1 vanishes ad 4th rou + var zero {{.Field}}.{{.FieldPrefix}}Field + minus_one := vecOp(zero, one, core.Sub) + coeffs := core.HostSliceFromElements([]{{.Field}}.{{.FieldPrefix}}Field{minus_one, zero, zero, zero, one}) // x^4-1 + var v polynomial.DensePolynomial + v.CreateFromCoeffecitients(coeffs) + + f := randomPoly(1 << 3) + + fv := f.Multiply(&v) + fDegree := f.Degree() + fvDegree := fv.Degree() + assert.Equal(t, fDegree+4, fvDegree) + + fReconstructed := fv.DivideByVanishing(4) + assert.Equal(t, fDegree, fReconstructed.Degree()) + + x := rand() + assert.Equal(t, f.Eval(x), fReconstructed.Eval(x)) +} + +// func TestPolySlice(t *testing.T) { +// size := 4 +// coeffs := {{.Field}}.GenerateScalars(size) +// var f DensePolynomial +// f.CreateFromCoeffecitients(coeffs) +// fSlice := f.AsSlice() +// assert.True(t, fSlice.IsOnDevice()) +// assert.Equal(t, size, fSlice.Len()) + +// hostSlice := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], size) +// hostSlice.CopyFromDevice(fSlice) +// assert.Equal(t, coeffs, hostSlice) + +// cfg := ntt.GetDefaultNttConfig() +// res := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], size) +// ntt.Ntt(fSlice, core.KForward, cfg, res) + +// assert.Equal(t, f.Eval(one), res[0]) +// } diff --git a/wrappers/golang/internal/generator/templates/curve.go.tmpl b/wrappers/golang/internal/generator/templates/curve.go.tmpl deleted file mode 100644 index ced2244c..00000000 --- a/wrappers/golang/internal/generator/templates/curve.go.tmpl +++ /dev/null @@ -1,182 +0,0 @@ -{{if .IsG2 -}} -//go:build g2 - -{{end -}} -package {{.PackageName}} -{{if not .IsMock}} -// #cgo CFLAGS: -I./include/ -// #include "{{if .IsG2 -}}g2_{{end}}curve.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) -{{end}} -type {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective struct { - X, Y, Z {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field -} - -func (p {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective) Size() int { - return p.X.Size() * 3 -} - -func (p {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective) AsPointer() *uint32 { - return p.X.AsPointer() -} - -func (p *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective) Zero() {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective { - p.X.Zero() - p.Y.One() - p.Z.Zero() - - return *p -} - -func (p *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective) FromLimbs(x, y, z []uint32) {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective { - p.X.FromLimbs(x) - p.Y.FromLimbs(y) - p.Z.FromLimbs(z) - - return *p -} - -func (p *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective) FromAffine(a {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine) {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective { - z := {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field{} - z.One() - - p.X = a.X - p.Y = a.Y - p.Z = z - - return *p -} -{{if not .IsMock}} -func (p {{if .IsG2}}G2{{end}}Projective) ProjectiveEq(p2 *{{if .IsG2}}G2{{end}}Projective) bool { - cP := (*C.{{if .IsG2}}g2_{{end}}projective_t)(unsafe.Pointer(&p)) - cP2 := (*C.{{if .IsG2}}g2_{{end}}projective_t)(unsafe.Pointer(&p2)) - __ret := C.{{.Curve}}{{if .IsG2}}G2{{end}}Eq(cP, cP2) - return __ret == (C._Bool)(true) -} - -func (p *{{if .IsG2}}G2{{end}}Projective) ProjectiveToAffine() {{if .IsG2}}G2{{end}}Affine { - var a {{if .IsG2}}G2{{end}}Affine - - cA := (*C.{{if .IsG2}}g2_{{end}}affine_t)(unsafe.Pointer(&a)) - cP := (*C.{{if .IsG2}}g2_{{end}}projective_t)(unsafe.Pointer(&p)) - C.{{.Curve}}{{if .IsG2}}G2{{end}}ToAffine(cP, cA) - return a -} - -func {{if .IsG2}}G2{{end}}GenerateProjectivePoints(size int) core.HostSlice[{{if .IsG2}}G2{{end}}Projective] { - points := make([]{{if .IsG2}}G2{{end}}Projective, size) - for i := range points { - points[i] = {{if .IsG2}}G2{{end}}Projective{} - } - - pointsSlice := core.HostSliceFromElements[{{if .IsG2}}G2{{end}}Projective](points) - pPoints := (*C.{{if .IsG2}}g2_{{end}}projective_t)(unsafe.Pointer(&pointsSlice[0])) - cSize := (C.int)(size) - C.{{.Curve}}{{if .IsG2}}G2{{end}}GenerateProjectivePoints(pPoints, cSize) - - return pointsSlice -} -{{end}} -type {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine struct { - X, Y {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field -} - -func (a {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine) Size() int { - return a.X.Size() * 2 -} - -func (a {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine) AsPointer() *uint32 { - return a.X.AsPointer() -} - -func (a *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine) Zero() {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine { - a.X.Zero() - a.Y.Zero() - - return *a -} - -func (a *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine) FromLimbs(x, y []uint32) {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine { - a.X.FromLimbs(x) - a.Y.FromLimbs(y) - - return *a -} - -func (a {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine) ToProjective() {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective { - var z {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field - - return {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } -} -{{if not .IsMock}} -func {{if .IsG2}}G2{{end}}AffineFromProjective(p *{{if .IsG2}}G2{{end}}Projective) {{if .IsG2}}G2{{end}}Affine { - return p.ProjectiveToAffine() -} - -func {{if .IsG2}}G2{{end}}GenerateAffinePoints(size int) core.HostSlice[{{if .IsG2}}G2{{end}}Affine] { - points := make([]{{if .IsG2}}G2{{end}}Affine, size) - for i := range points { - points[i] = {{if .IsG2}}G2{{end}}Affine{} - } - - pointsSlice := core.HostSliceFromElements[{{if .IsG2}}G2{{end}}Affine](points) - cPoints := (*C.{{if .IsG2}}g2_{{end}}affine_t)(unsafe.Pointer(&pointsSlice[0])) - cSize := (C.int)(size) - C.{{.Curve}}{{if .IsG2}}G2{{end}}GenerateAffinePoints(cPoints, cSize) - - return pointsSlice -} - -func convert{{if .IsG2}}G2{{end}}AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.{{if .IsG2}}g2_{{end}}affine_t)(points.AsPointer()) - cSize := (C.size_t)(points.Len()) - cIsInto := (C._Bool)(isInto) - defaultCtx, _ := cr.GetDefaultDeviceContext() - cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.{{.Curve}}{{if .IsG2}}G2{{end}}AffineConvertMontgomery(cValues, cSize, cIsInto, cCtx) - err := (cr.CudaError)(__ret) - return err -} - -func {{if .IsG2}}G2{{end}}AffineToMontgomery(points *core.DeviceSlice) cr.CudaError { - points.CheckDevice() - return convert{{if .IsG2}}G2{{end}}AffinePointsMontgomery(points, true) -} - -func {{if .IsG2}}G2{{end}}AffineFromMontgomery(points *core.DeviceSlice) cr.CudaError { - points.CheckDevice() - return convert{{if .IsG2}}G2{{end}}AffinePointsMontgomery(points, false) -} - -func convert{{if .IsG2}}G2{{end}}ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.{{if .IsG2}}g2_{{end}}projective_t)(points.AsPointer()) - cSize := (C.size_t)(points.Len()) - cIsInto := (C._Bool)(isInto) - defaultCtx, _ := cr.GetDefaultDeviceContext() - cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.{{.Curve}}{{if .IsG2}}G2{{end}}ProjectiveConvertMontgomery(cValues, cSize, cIsInto, cCtx) - err := (cr.CudaError)(__ret) - return err -} - -func {{if .IsG2}}G2{{end}}ProjectiveToMontgomery(points *core.DeviceSlice) cr.CudaError { - points.CheckDevice() - return convert{{if .IsG2}}G2{{end}}ProjectivePointsMontgomery(points, true) -} - -func {{if .IsG2}}G2{{end}}ProjectiveFromMontgomery(points *core.DeviceSlice) cr.CudaError { - points.CheckDevice() - return convert{{if .IsG2}}G2{{end}}ProjectivePointsMontgomery(points, false) -} -{{end}} \ No newline at end of file diff --git a/wrappers/golang/internal/generator/templates/curve_test.go.tmpl b/wrappers/golang/internal/generator/templates/curve_test.go.tmpl deleted file mode 100644 index 2be5ffe7..00000000 --- a/wrappers/golang/internal/generator/templates/curve_test.go.tmpl +++ /dev/null @@ -1,105 +0,0 @@ -{{if .IsG2 -}} -//go:build g2 - -{{end -}} -package {{.PackageName}} - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func Test{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}AffineZero(t *testing.T) { - var fieldZero = {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field{} - - var affineZero {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine - assert.Equal(t, affineZero.X, fieldZero) - assert.Equal(t, affineZero.Y, fieldZero) - - x := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - y := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - var affine {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine - affine.FromLimbs(x, y) - - affine.Zero() - assert.Equal(t, affine.X, fieldZero) - assert.Equal(t, affine.Y, fieldZero) -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}AffineFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - - var affine {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine - affine.FromLimbs(randLimbs, randLimbs2) - - assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) - assert.ElementsMatch(t, randLimbs2, affine.Y.GetLimbs()) -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}AffineToProjective(t *testing.T) { - randLimbs := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - var fieldOne {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field - fieldOne.One() - - var expected {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) - - var affine {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine - affine.FromLimbs(randLimbs, randLimbs2) - - projectivePoint := affine.ToProjective() - assert.Equal(t, expected, projectivePoint) -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}ProjectiveZero(t *testing.T) { - var projectiveZero {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective - projectiveZero.Zero() - var fieldZero = {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field{} - var fieldOne {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field - fieldOne.One() - - assert.Equal(t, projectiveZero.X, fieldZero) - assert.Equal(t, projectiveZero.Y, fieldOne) - assert.Equal(t, projectiveZero.Z, fieldZero) - - randLimbs := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - var projective {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective - projective.FromLimbs(randLimbs, randLimbs, randLimbs) - - projective.Zero() - assert.Equal(t, projective.X, fieldZero) - assert.Equal(t, projective.Y, fieldOne) - assert.Equal(t, projective.Z, fieldZero) -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}ProjectiveFromLimbs(t *testing.T) { - randLimbs := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - randLimbs3 := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - - var projective {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective - projective.FromLimbs(randLimbs, randLimbs2, randLimbs3) - - assert.ElementsMatch(t, randLimbs, projective.X.GetLimbs()) - assert.ElementsMatch(t, randLimbs2, projective.Y.GetLimbs()) - assert.ElementsMatch(t, randLimbs3, projective.Z.GetLimbs()) -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}ProjectiveFromAffine(t *testing.T) { - randLimbs := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - randLimbs2 := generateRandomLimb(int({{if .IsG2}}G2_{{end}}BASE_LIMBS)) - var fieldOne {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}Base{{end}}Field - fieldOne.One() - - var expected {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective - expected.FromLimbs(randLimbs, randLimbs2, fieldOne.limbs[:]) - - var affine {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Affine - affine.FromLimbs(randLimbs, randLimbs2) - - var projectivePoint {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{end}}Projective - projectivePoint.FromAffine(affine) - assert.Equal(t, expected, projectivePoint) -} diff --git a/wrappers/golang/internal/generator/templates/field.go.tmpl b/wrappers/golang/internal/generator/templates/field.go.tmpl deleted file mode 100644 index b6fdfb1a..00000000 --- a/wrappers/golang/internal/generator/templates/field.go.tmpl +++ /dev/null @@ -1,90 +0,0 @@ -{{if .IsG2 -}} -//go:build g2 - -{{end -}} -package {{.PackageName}} -{{if .IsScalar}} -{{- template "scalar_field_c_imports" . -}} -{{end}} -import ( - "encoding/binary" - "fmt" - {{- if .IsScalar}} - {{- template "scalar_field_go_imports" -}} - {{end}} -) - -const ( - {{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS int8 = {{if .IsScalar}}{{.ScalarLimbsNum}}{{else}}{{if .IsG2}}{{.G2BaseLimbsNum}}{{else}}{{.BaseLimbsNum}}{{end}}{{end}} -) - -type {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field struct { - limbs [{{- if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS]uint32 -} - -func (f {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) Len() int { - return int({{- if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS) -} - -func (f {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) Size() int { - return int({{- if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS * 4) -} - -func (f {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) GetLimbs() []uint32 { - return f.limbs[:] -} - -func (f {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) AsPointer() *uint32 { - return &f.limbs[0] -} - -func (f *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) FromLimbs(limbs []uint32) {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field { - if len(limbs) != f.Len() { - panic("Called FromLimbs with limbs of different length than field") - } - for i := range f.limbs { - f.limbs[i] = limbs[i] - } - - return *f -} - -func (f *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) Zero() {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field { - for i := range f.limbs { - f.limbs[i] = 0 - } - - return *f -} - -func (f *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) One() {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field { - for i := range f.limbs { - f.limbs[i] = 0 - } - f.limbs[0] = 1 - - return *f -} - -func (f *{{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) FromBytesLittleEndian(bytes []byte) {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field { - if len(bytes)/4 != f.Len() { - panic(fmt.Sprintf("Called FromBytesLittleEndian with incorrect bytes length; expected %d - got %d", f.Len()*4, len(bytes))) - } - - for i := range f.limbs { - f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) - } - - return *f -} - -func (f {{if .IsMock}}Mock{{else}}{{if .IsG2}}G2{{end}}{{- if .IsScalar}}Scalar{{else}}Base{{end}}{{end}}Field) ToBytesLittleEndian() []byte { - bytes := make([]byte, f.Len()*4) - for i, v := range f.limbs { - binary.LittleEndian.PutUint32(bytes[i*4:], v) - } - - return bytes -} -{{- if .IsScalar}} -{{template "scalar_field_funcs" . }}{{end}} diff --git a/wrappers/golang/internal/generator/templates/field_test.go.tmpl b/wrappers/golang/internal/generator/templates/field_test.go.tmpl deleted file mode 100644 index 6fefdf51..00000000 --- a/wrappers/golang/internal/generator/templates/field_test.go.tmpl +++ /dev/null @@ -1,91 +0,0 @@ -{{if .IsG2 -}} -//go:build g2 - -{{end -}} -package {{.PackageName}} - -import ( - {{- if .IsScalar}} - {{- template "scalar_field_tests_imports" . -}}{{end}} - "github.com/stretchr/testify/assert" - "testing" -) - -func Test{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}FieldFromLimbs(t *testing.T) { - emptyField := {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field{} - randLimbs := generateRandomLimb(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - assert.ElementsMatch(t, randLimbs, emptyField.limbs, "Limbs do not match; there was an issue with setting the {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field's limbs") - randLimbs[0] = 100 - assert.NotEqual(t, randLimbs, emptyField.limbs) -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}FieldGetLimbs(t *testing.T) { - emptyField := {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field{} - randLimbs := generateRandomLimb(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - assert.ElementsMatch(t, randLimbs, emptyField.GetLimbs(), "Limbs do not match; there was an issue with setting the {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field's limbs") -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}FieldOne(t *testing.T) { - var emptyField {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field - emptyField.One() - limbOne := generateLimbOne(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") - - randLimbs := generateRandomLimb(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - emptyField.One() - assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field with limbs to field one did not work") -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}FieldZero(t *testing.T) { - var emptyField {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field - emptyField.Zero() - limbsZero := make([]uint32, {{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS) - assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") - - randLimbs := generateRandomLimb(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - emptyField.Zero() - assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field with limbs to field zero failed") -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}FieldSize(t *testing.T) { - var emptyField {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field - randLimbs := generateRandomLimb(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - assert.Equal(t, len(randLimbs)*4, emptyField.Size(), "Size returned an incorrect value of bytes") -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}FieldAsPointer(t *testing.T) { - var emptyField {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field - randLimbs := generateRandomLimb(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - emptyField.FromLimbs(randLimbs[:]) - - assert.Equal(t, randLimbs[0], *emptyField.AsPointer(), "AsPointer returned pointer to incorrect value") -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}FieldFromBytes(t *testing.T) { - var emptyField {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field - bytes, expected := generateBytesArray(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - - emptyField.FromBytesLittleEndian(bytes) - - assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") -} - -func Test{{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}FieldToBytes(t *testing.T) { - var emptyField {{if .IsMock}}Mock{{else}}{{if .IsScalar}}Scalar{{else}}{{if .IsG2}}G2{{end}}Base{{end}}{{end}}Field - expected, limbs := generateBytesArray(int({{if .IsScalar}}SCALAR{{else}}{{if .IsG2}}G2_{{end}}BASE{{end}}_LIMBS)) - emptyField.FromLimbs(limbs) - - assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") -} -{{if .IsScalar}} -{{- template "scalar_field_tests" .}} -{{end}} \ No newline at end of file diff --git a/wrappers/golang/internal/generator/templates/helpers_test.go.tmpl b/wrappers/golang/internal/generator/templates/helpers_test.go.tmpl deleted file mode 100644 index a469e7b6..00000000 --- a/wrappers/golang/internal/generator/templates/helpers_test.go.tmpl +++ /dev/null @@ -1,31 +0,0 @@ -package {{.PackageName}} - -import ( - "math/rand" -) - -func generateRandomLimb(size int) []uint32 { - limbs := make([]uint32, size) - for i := range limbs { - limbs[i] = rand.Uint32() - } - return limbs -} - -func generateLimbOne(size int) []uint32 { - limbs := make([]uint32, size) - limbs[0] = 1 - return limbs -} - -func generateBytesArray(size int) ([]byte, []uint32) { - baseBytes := []byte{1, 2, 3, 4} - var bytes []byte - var limbs []uint32 - for i := 0; i < size; i++ { - bytes = append(bytes, baseBytes...) - limbs = append(limbs, 67305985) - } - - return bytes, limbs -} diff --git a/wrappers/golang/internal/generator/templates/include/curve.h.tmpl b/wrappers/golang/internal/generator/templates/include/curve.h.tmpl deleted file mode 100644 index 76ede8b7..00000000 --- a/wrappers/golang/internal/generator/templates/include/curve.h.tmpl +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _{{toUpper .Curve}}_CURVE_H -#define _{{toUpper .Curve}}_CURVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -bool {{.Curve}}Eq(projective_t* point1, projective_t* point2); -void {{.Curve}}ToAffine(projective_t* point, affine_t* point_out); -void {{.Curve}}GenerateProjectivePoints(projective_t* points, int size); -void {{.Curve}}GenerateAffinePoints(affine_t* points, int size); -cudaError_t {{.Curve}}AffineConvertMontgomery(affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t {{.Curve}}ProjectiveConvertMontgomery(projective_t* points, size_t n, bool is_into, DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/internal/generator/templates/include/g2_curve.h.tmpl b/wrappers/golang/internal/generator/templates/include/g2_curve.h.tmpl deleted file mode 100644 index dfe1dcad..00000000 --- a/wrappers/golang/internal/generator/templates/include/g2_curve.h.tmpl +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _{{toUpper .Curve}}_G2CURVE_H -#define _{{toUpper .Curve}}_G2CURVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -bool {{.Curve}}G2Eq(g2_projective_t* point1, g2_projective_t* point2); -void {{.Curve}}G2ToAffine(g2_projective_t* point, g2_affine_t* point_out); -void {{.Curve}}G2GenerateProjectivePoints(g2_projective_t* points, int size); -void {{.Curve}}G2GenerateAffinePoints(g2_affine_t* points, int size); -cudaError_t {{.Curve}}G2AffineConvertMontgomery(g2_affine_t* points, size_t n, bool is_into, DeviceContext* ctx); -cudaError_t {{.Curve}}G2ProjectiveConvertMontgomery(g2_projective_t* points, size_t n, bool is_into, DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/internal/generator/templates/include/g2_msm.h.tmpl b/wrappers/golang/internal/generator/templates/include/g2_msm.h.tmpl deleted file mode 100644 index a290e66c..00000000 --- a/wrappers/golang/internal/generator/templates/include/g2_msm.h.tmpl +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _{{toUpper .Curve}}_G2MSM_H -#define _{{toUpper .Curve}}_G2MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t {{.Curve}}G2MSMCuda(const scalar_t* scalars,const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); -cudaError_t {{.Curve}}G2PrecomputeMSMBases(g2_affine_t* points, int count, int precompute_factor, int _c, bool bases_on_device, DeviceContext* ctx, g2_affine_t* out); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/internal/generator/templates/include/msm.h.tmpl b/wrappers/golang/internal/generator/templates/include/msm.h.tmpl deleted file mode 100644 index 8df682eb..00000000 --- a/wrappers/golang/internal/generator/templates/include/msm.h.tmpl +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _{{toUpper .Curve}}_MSM_H -#define _{{toUpper .Curve}}_MSM_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t {{.Curve}}MSMCuda(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); -cudaError_t {{.Curve}}PrecomputeMSMBases(affine_t* points, int bases_size, int precompute_factor, int _c, bool are_bases_on_device, DeviceContext* ctx, affine_t* output_bases); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/internal/generator/templates/include/ntt.h.tmpl b/wrappers/golang/internal/generator/templates/include/ntt.h.tmpl deleted file mode 100644 index 9f16b83a..00000000 --- a/wrappers/golang/internal/generator/templates/include/ntt.h.tmpl +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _{{toUpper .Curve}}_NTT_H -#define _{{toUpper .Curve}}_NTT_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t {{.Curve}}NTTCuda(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); -cudaError_t {{.Curve}}ECNTTCuda(const projective_t* input, int size, int dir, NTTConfig* config, projective_t* output); -cudaError_t {{.Curve}}InitializeDomain(scalar_t* primitive_root, DeviceContext* ctx, bool fast_twiddles); -cudaError_t {{.Curve}}ReleaseDomain(DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/wrappers/golang/internal/generator/templates/include/scalar_field.h.tmpl b/wrappers/golang/internal/generator/templates/include/scalar_field.h.tmpl deleted file mode 100644 index 5cee5322..00000000 --- a/wrappers/golang/internal/generator/templates/include/scalar_field.h.tmpl +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include "../../include/types.h" -#include - -#ifndef _{{toUpper .Curve}}_FIELD_H -#define _{{toUpper .Curve}}_FIELD_H - -#ifdef __cplusplus -extern "C" { -#endif - -void {{.Curve}}GenerateScalars(scalar_t* scalars, int size); -cudaError_t {{.Curve}}ScalarConvertMontgomery(scalar_t* d_inout, size_t n, bool is_into, DeviceContext* ctx); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/internal/generator/templates/include/vec_ops.h.tmpl b/wrappers/golang/internal/generator/templates/include/vec_ops.h.tmpl deleted file mode 100644 index 2c2eb630..00000000 --- a/wrappers/golang/internal/generator/templates/include/vec_ops.h.tmpl +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include "../../include/types.h" - -#ifndef _{{toUpper .Curve}}_VEC_OPS_H -#define _{{toUpper .Curve}}_VEC_OPS_H - -#ifdef __cplusplus -extern "C" { -#endif - -cudaError_t {{.Curve}}MulCuda( - scalar_t* vec_a, - scalar_t* vec_b, - int n, - VecOpsConfig* config, - scalar_t* result -); - -cudaError_t {{.Curve}}AddCuda( - scalar_t* vec_a, - scalar_t* vec_b, - int n, - VecOpsConfig* config, - scalar_t* result -); - -cudaError_t {{.Curve}}SubCuda( - scalar_t* vec_a, - scalar_t* vec_b, - int n, - VecOpsConfig* config, - scalar_t* result -); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/wrappers/golang/internal/generator/templates/main.go.tmpl b/wrappers/golang/internal/generator/templates/main.go.tmpl deleted file mode 100644 index 0645bb25..00000000 --- a/wrappers/golang/internal/generator/templates/main.go.tmpl +++ /dev/null @@ -1,4 +0,0 @@ -package {{.PackageName}} - -// #cgo LDFLAGS: -L${SRCDIR}/../../../../icicle/build -lingo_{{.Curve}} -lstdc++ -lm -import "C" diff --git a/wrappers/golang/internal/generator/templates/msm.go.tmpl b/wrappers/golang/internal/generator/templates/msm.go.tmpl deleted file mode 100644 index e60c691d..00000000 --- a/wrappers/golang/internal/generator/templates/msm.go.tmpl +++ /dev/null @@ -1,84 +0,0 @@ -{{if .IsG2 -}} -//go:build g2 - -{{end -}} -package {{.PackageName}} - -// #cgo CFLAGS: -I./include/ -// #include "{{if .IsG2 -}}g2_{{end}}msm.h" -import "C" - -import ( - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -) - -func {{if .IsG2}}G2{{end}}GetDefaultMSMConfig() core.MSMConfig { - return core.GetDefaultMSMConfig() -} - -func {{if .IsG2}}G2{{end}}Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) cr.CudaError { - core.MsmCheck(scalars, points, cfg, results) - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsDevice := points.(core.DeviceSlice) - pointsDevice.CheckDevice() - pointsPointer = pointsDevice.AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[{{if .IsG2}}G2{{end}}Affine])[0]) - } - cPoints := (*C.{{if .IsG2}}g2_{{end}}affine_t)(pointsPointer) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[{{if .IsG2}}G2{{end}}Projective])[0]) - } - cResults := (*C.{{if .IsG2}}g2_{{end}}projective_t)(resultsPointer) - - cSize := (C.int)(scalars.Len() / results.Len()) - cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) - - __ret := C.{{.Curve}}{{if .IsG2}}G2{{end}}MSMCuda(cScalars, cPoints, cSize, cCfg, cResults) - err := (cr.CudaError)(__ret) - return err -} - -func {{if .IsG2}}G2{{end}}PrecomputeBases(points core.HostOrDeviceSlice, precomputeFactor int32, c int32, ctx *cr.DeviceContext, outputBases core.DeviceSlice) cr.CudaError { - core.PrecomputeBasesCheck(points, precomputeFactor, outputBases) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[{{if .IsG2}}G2{{end}}Affine])[0]) - } - cPoints := (*C.{{if .IsG2}}g2_{{end}}affine_t)(pointsPointer) - - cPointsLen := (C.int)(points.Len()) - cPrecomputeFactor := (C.int)(precomputeFactor) - cC := (C.int)(c) - cPointsIsOnDevice := (C._Bool)(points.IsOnDevice()) - cCtx := (*C.DeviceContext)(unsafe.Pointer(ctx)) - - outputBasesPointer := outputBases.AsPointer() - cOutputBases := (*C.{{if .IsG2}}g2_{{end}}affine_t)(outputBasesPointer) - - __ret := C.{{.Curve}}{{if .IsG2}}G2{{end}}PrecomputeMSMBases(cPoints, cPointsLen, cPrecomputeFactor, cC, cPointsIsOnDevice, cCtx, cOutputBases) - err := (cr.CudaError)(__ret) - return err -} diff --git a/wrappers/golang/internal/generator/templates/msm_test.go.tmpl b/wrappers/golang/internal/generator/templates/msm_test.go.tmpl deleted file mode 100644 index 334b1f5a..00000000 --- a/wrappers/golang/internal/generator/templates/msm_test.go.tmpl +++ /dev/null @@ -1,276 +0,0 @@ -{{if .IsG2 -}} -//go:build g2 - -{{end -}} -package {{.PackageName}} - -import ( - "fmt" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}" - "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}/fp" - "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}/fr" -) -{{$isBW6 := eq .Curve "bw6_761"}}{{$isG1 := not .IsG2}}{{if or $isBW6 $isG1}} -func projectiveToGnarkAffine{{if and $isBW6 .IsG2}}G2{{end}}(p {{if and $isBW6 .IsG2}}G2{{end}}Projective) {{toPackage .GnarkImport}}.{{if and $isBW6 .IsG2}}G2{{else}}G1{{end}}Affine { - px, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.X).ToBytesLittleEndian())) - py, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Y).ToBytesLittleEndian())) - pz, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)((&p.Z).ToBytesLittleEndian())) - - zInv := new(fp.Element) - x := new(fp.Element) - y := new(fp.Element) - - zInv.Inverse(&pz) - - x.Mul(&px, zInv) - y.Mul(&py, zInv) - - return {{toPackage .GnarkImport}}.{{if and $isBW6 .IsG2}}G2{{else}}G1{{end}}Affine{X: *x, Y: *y} -} -{{end}} -{{- $isNotBW6 := ne .Curve "bw6_761"}}{{if and $isNotBW6 .IsG2 }} -func projectiveToGnarkAffineG2(p G2Projective) {{toPackage .GnarkImport}}.G2Affine { - pxBytes := p.X.ToBytesLittleEndian() - pxA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[:fp.Bytes])) - pxA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pxBytes[fp.Bytes:])) - x := {{toPackage .GnarkImport}}.E2{ - A0: pxA0, - A1: pxA1, - } - - pyBytes := p.Y.ToBytesLittleEndian() - pyA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pyBytes[:fp.Bytes])) - pyA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pyBytes[fp.Bytes:])) - y := {{toPackage .GnarkImport}}.E2{ - A0: pyA0, - A1: pyA1, - } - - pzBytes := p.Z.ToBytesLittleEndian() - pzA0, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pzBytes[:fp.Bytes])) - pzA1, _ := fp.LittleEndian.Element((*[fp.Bytes]byte)(pzBytes[fp.Bytes:])) - z := {{toPackage .GnarkImport}}.E2{ - A0: pzA0, - A1: pzA1, - } - - var zSquared {{toPackage .GnarkImport}}.E2 - zSquared.Mul(&z, &z) - - var X {{toPackage .GnarkImport}}.E2 - X.Mul(&x, &z) - - var Y {{toPackage .GnarkImport}}.E2 - Y.Mul(&y, &zSquared) - - g2Jac := {{toPackage .GnarkImport}}.G2Jac{ - X: X, - Y: Y, - Z: z, - } - - var g2Affine {{toPackage .GnarkImport}}.G2Affine - return *g2Affine.FromJacobian(&g2Jac) -} -{{end}} -func testAgainstGnarkCryptoMsm{{if .IsG2}}G2{{end}}(scalars core.HostSlice[ScalarField], points core.HostSlice[{{if .IsG2}}G2{{end}}Affine], out {{if .IsG2}}G2{{end}}Projective) bool { - scalarsFr := make([]fr.Element, len(scalars)) - for i, v := range scalars { - slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) - scalarsFr[i] = slice64 - } - - pointsFp := make([]{{toPackage .GnarkImport}}.{{if .IsG2}}G2{{else}}G1{{end}}Affine, len(points)) - for i, v := range points { - pointsFp[i] = projectiveToGnarkAffine{{if .IsG2}}G2{{end}}(v.ToProjective()) - } - var msmRes {{toPackage .GnarkImport}}.{{if .IsG2}}G2{{else}}G1{{end}}Jac - msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) - - var icicleResAsJac {{toPackage .GnarkImport}}.{{if .IsG2}}G2{{else}}G1{{end}}Jac - proj := projectiveToGnarkAffine{{if .IsG2}}G2{{end}}(out) - icicleResAsJac.FromAffine(&proj) - - return msmRes.Equal(&icicleResAsJac) -} - -func TestMSM{{if .IsG2}}G2{{end}}(t *testing.T) { - cfg := GetDefaultMSMConfig() - cfg.IsAsync = true - for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { - size := 1 << power - - scalars := GenerateScalars(size) - points := {{if .IsG2}}G2{{end}}GenerateAffinePoints(size) - - stream, _ := cr.CreateStream() - var p {{if .IsG2}}G2{{end}}Projective - var out core.DeviceSlice - _, e := out.MallocAsync(p.Size(), p.Size(), stream) - assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - cfg.Ctx.Stream = &stream - - e = {{if .IsG2}}G2{{end}}Msm(scalars, points, &cfg, out) - assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[{{if .IsG2}}G2{{end}}Projective], 1) - outHost.CopyFromDeviceAsync(&out, stream) - out.FreeAsync(stream) - - cr.SynchronizeStream(&stream) - // Check with gnark-crypto - assert.True(t, testAgainstGnarkCryptoMsm{{if .IsG2}}G2{{end}}(scalars, points, outHost[0])) - } -} - -func TestMSM{{if .IsG2}}G2{{end}}Batch(t *testing.T) { - cfg := GetDefaultMSMConfig() - for _, power := range []int{10, 16} { - for _, batchSize := range []int{1, 3, 16} { - size := 1 << power - totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := {{if .IsG2}}G2{{end}}GenerateAffinePoints(totalSize) - - var p {{if .IsG2}}G2{{end}}Projective - var out core.DeviceSlice - _, e := out.Malloc(batchSize*p.Size(), p.Size()) - assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - - e = {{if .IsG2}}G2{{end}}Msm(scalars, points, &cfg, out) - assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[{{if .IsG2}}G2{{end}}Projective], batchSize) - outHost.CopyFromDevice(&out) - out.Free() - - // Check with gnark-crypto - for i := 0; i < batchSize; i++ { - scalarsSlice := scalars[i*size : (i+1)*size] - pointsSlice := points[i*size : (i+1)*size] - out := outHost[i] - assert.True(t, testAgainstGnarkCryptoMsm{{if .IsG2}}G2{{end}}(scalarsSlice, pointsSlice, out)) - } - } - } -} - -func TestPrecomputeBase{{if .IsG2}}G2{{end}}(t *testing.T) { - cfg := GetDefaultMSMConfig() - const precomputeFactor = 8 - for _, power := range []int{10, 16} { - for _, batchSize := range []int{1, 3, 16} { - size := 1 << power - totalSize := size * batchSize - scalars := GenerateScalars(totalSize) - points := {{if .IsG2}}G2{{end}}GenerateAffinePoints(totalSize) - - var precomputeOut core.DeviceSlice - _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) - assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for PrecomputeBases results failed") - - e = {{if .IsG2}}G2{{end}}PrecomputeBases(points, precomputeFactor, 0, &cfg.Ctx, precomputeOut) - assert.Equal(t, e, cr.CudaSuccess, "PrecomputeBases failed") - - var p {{if .IsG2}}G2{{end}}Projective - var out core.DeviceSlice - _, e = out.Malloc(batchSize*p.Size(), p.Size()) - assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - - cfg.PrecomputeFactor = precomputeFactor - - e = {{if .IsG2}}G2{{end}}Msm(scalars, precomputeOut, &cfg, out) - assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[{{if .IsG2}}G2{{end}}Projective], batchSize) - outHost.CopyFromDevice(&out) - out.Free() - precomputeOut.Free() - - // Check with gnark-crypto - for i := 0; i < batchSize; i++ { - scalarsSlice := scalars[i*size : (i+1)*size] - pointsSlice := points[i*size : (i+1)*size] - out := outHost[i] - assert.True(t, testAgainstGnarkCryptoMsm{{if .IsG2}}G2{{end}}(scalarsSlice, pointsSlice, out)) - } - } - } -} - -func TestMSM{{if .IsG2}}G2{{end}}SkewedDistribution(t *testing.T) { - cfg := GetDefaultMSMConfig() - for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { - size := 1 << power - - scalars := GenerateScalars(size) - for i := size / 4; i < size; i++ { - scalars[i].One() - } - points := {{if .IsG2}}G2{{end}}GenerateAffinePoints(size) - for i := 0; i < size/4; i++ { - points[i].Zero() - } - - var p {{if .IsG2}}G2{{end}}Projective - var out core.DeviceSlice - _, e := out.Malloc(p.Size(), p.Size()) - assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - - e = {{if .IsG2}}G2{{end}}Msm(scalars, points, &cfg, out) - assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[{{if .IsG2}}G2{{end}}Projective], 1) - outHost.CopyFromDevice(&out) - out.Free() - - // Check with gnark-crypto - assert.True(t, testAgainstGnarkCryptoMsm{{if .IsG2}}G2{{end}}(scalars, points, outHost[0])) - } -} - -func TestMSM{{if .IsG2}}G2{{end}}MultiDevice(t *testing.T) { - numDevices, _ := cr.GetDeviceCount() - numDevices = 1 // TODO remove when test env is fixed - fmt.Println("There are ", numDevices, " devices available") - orig_device, _ := cr.GetDevice() - wg := sync.WaitGroup{} - - for i := 0; i < numDevices; i++ { - wg.Add(1) - cr.RunOnDevice(i, func(args ...any) { - defer wg.Done() - cfg := GetDefaultMSMConfig() - cfg.IsAsync = true - for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { - size := 1 << power - scalars := GenerateScalars(size) - points := {{if .IsG2}}G2{{end}}GenerateAffinePoints(size) - - stream, _ := cr.CreateStream() - var p {{if .IsG2}}G2{{end}}Projective - var out core.DeviceSlice - _, e := out.MallocAsync(p.Size(), p.Size(), stream) - assert.Equal(t, e, cr.CudaSuccess, "Allocating bytes on device for Projective results failed") - cfg.Ctx.Stream = &stream - - e = {{if .IsG2}}G2{{end}}Msm(scalars, points, &cfg, out) - assert.Equal(t, e, cr.CudaSuccess, "Msm failed") - outHost := make(core.HostSlice[{{if .IsG2}}G2{{end}}Projective], 1) - outHost.CopyFromDeviceAsync(&out, stream) - out.FreeAsync(stream) - - cr.SynchronizeStream(&stream) - // Check with gnark-crypto - assert.True(t, testAgainstGnarkCryptoMsm{{if .IsG2}}G2{{end}}(scalars, points, outHost[0])) - } - }) - } - wg.Wait() - cr.SetDevice(orig_device) -} diff --git a/wrappers/golang/internal/generator/templates/ntt.go.tmpl b/wrappers/golang/internal/generator/templates/ntt.go.tmpl deleted file mode 100644 index 03bef18e..00000000 --- a/wrappers/golang/internal/generator/templates/ntt.go.tmpl +++ /dev/null @@ -1,97 +0,0 @@ -package {{.PackageName}} - -// #cgo CFLAGS: -I./include/ -// #include "ntt.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func GetDefaultNttConfig() core.NTTConfig[[SCALAR_LIMBS]uint32] { - cosetGenField := ScalarField{} - cosetGenField.One() - var cosetGen [SCALAR_LIMBS]uint32 - for i, v := range cosetGenField.GetLimbs() { - cosetGen[i] = v - } - - return core.GetDefaultNTTConfig(cosetGen) -} - -func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](scalars, cfg, results) - - var scalarsPointer unsafe.Pointer - if scalars.IsOnDevice() { - scalarsDevice := scalars.(core.DeviceSlice) - scalarsDevice.CheckDevice() - scalarsPointer = scalarsDevice.AsPointer() - } else { - scalarsPointer = unsafe.Pointer(&scalars.(core.HostSlice[ScalarField])[0]) - } - cScalars := (*C.scalar_t)(scalarsPointer) - cSize := (C.int)(scalars.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsDevice := results.(core.DeviceSlice) - resultsDevice.CheckDevice() - resultsPointer = resultsDevice.AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[ScalarField])[0]) - } - cResults := (*C.scalar_t)(resultsPointer) - - __ret := C.{{.Curve}}NTTCuda(cScalars, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) core.IcicleError { - core.NttCheck[T](points, cfg, results) - - var pointsPointer unsafe.Pointer - if points.IsOnDevice() { - pointsPointer = points.(core.DeviceSlice).AsPointer() - } else { - pointsPointer = unsafe.Pointer(&points.(core.HostSlice[Projective])[0]) - } - cPoints := (*C.projective_t)(pointsPointer) - cSize := (C.int)(points.Len() / int(cfg.BatchSize)) - cDir := (C.int)(dir) - cCfg := (*C.NTTConfig)(unsafe.Pointer(cfg)) - - var resultsPointer unsafe.Pointer - if results.IsOnDevice() { - resultsPointer = results.(core.DeviceSlice).AsPointer() - } else { - resultsPointer = unsafe.Pointer(&results.(core.HostSlice[Projective])[0]) - } - cResults := (*C.projective_t)(resultsPointer) - - __ret := C.{{.Curve}}ECNTTCuda(cPoints, cSize, cDir, cCfg, cResults) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func InitDomain(primitiveRoot ScalarField, ctx cr.DeviceContext, fastTwiddles bool) core.IcicleError { - cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - cFastTwiddles := (C._Bool)(fastTwiddles) - __ret := C.{{.Curve}}InitializeDomain(cPrimitiveRoot, cCtx, cFastTwiddles) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} - -func ReleaseDomain(ctx cr.DeviceContext) core.IcicleError { - cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) - __ret := C.{{.Curve}}ReleaseDomain(cCtx) - err := (cr.CudaError)(__ret) - return core.FromCudaError(err) -} diff --git a/wrappers/golang/internal/generator/templates/scalar_field.go.tmpl b/wrappers/golang/internal/generator/templates/scalar_field.go.tmpl deleted file mode 100644 index 723eb3fa..00000000 --- a/wrappers/golang/internal/generator/templates/scalar_field.go.tmpl +++ /dev/null @@ -1,43 +0,0 @@ -{{- define "scalar_field_c_imports" }} -// #cgo CFLAGS: -I./include/ -// #include "scalar_field.h" -import "C" -{{- end }} - -{{- define "scalar_field_go_imports" }} - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" - "unsafe" -{{- end }} - -{{- define "scalar_field_funcs" }} -func GenerateScalars(size int) core.HostSlice[ScalarField] { - scalarSlice := make(core.HostSlice[ScalarField], size) - - cScalars := (*C.scalar_t)(unsafe.Pointer(&scalarSlice[0])) - cSize := (C.int)(size) - C.{{.Curve}}GenerateScalars(cScalars, cSize) - - return scalarSlice -} - -func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) cr.CudaError { - cValues := (*C.scalar_t)(scalars.AsPointer()) - cSize := (C.size_t)(scalars.Len()) - cIsInto := (C._Bool)(isInto) - defaultCtx, _ := cr.GetDefaultDeviceContext() - cCtx := (*C.DeviceContext)(unsafe.Pointer(&defaultCtx)) - __ret := C.{{.Curve}}ScalarConvertMontgomery(cValues, cSize, cIsInto, cCtx) - err := (cr.CudaError)(__ret) - return err -} - -func ToMontgomery(scalars *core.DeviceSlice) cr.CudaError { - scalars.CheckDevice() - return convertScalarsMontgomery(scalars, true) -} - -func FromMontgomery(scalars *core.DeviceSlice) cr.CudaError { - scalars.CheckDevice() - return convertScalarsMontgomery(scalars, false) -}{{- end}} \ No newline at end of file diff --git a/wrappers/golang/internal/generator/templates/scalar_field_test.go.tmpl b/wrappers/golang/internal/generator/templates/scalar_field_test.go.tmpl deleted file mode 100644 index 0e942295..00000000 --- a/wrappers/golang/internal/generator/templates/scalar_field_test.go.tmpl +++ /dev/null @@ -1,34 +0,0 @@ -{{- define "scalar_field_tests_imports"}} - "github.com/ingonyama-zk/icicle/wrappers/golang/core" -{{- end -}} -{{- define "scalar_field_tests"}} -func TestGenerateScalars(t *testing.T) { - const numScalars = 8 - scalars := GenerateScalars(numScalars) - - assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) - - assert.Equal(t, numScalars, scalars.Len()) - zeroScalar := ScalarField{} - assert.NotContains(t, scalars, zeroScalar) -} - -func TestMongtomeryConversion(t *testing.T) { - size := 1 << 15 - scalars := GenerateScalars(size) - - var deviceScalars core.DeviceSlice - scalars.CopyToDevice(&deviceScalars, true) - - ToMontgomery(&deviceScalars) - - scalarsMontHost := GenerateScalars(size) - - scalarsMontHost.CopyFromDevice(&deviceScalars) - assert.NotEqual(t, scalars, scalarsMontHost) - - FromMontgomery(&deviceScalars) - - scalarsMontHost.CopyFromDevice(&deviceScalars) - assert.Equal(t, scalars, scalarsMontHost) -}{{end}} \ No newline at end of file diff --git a/wrappers/golang/internal/generator/templates/vec_ops.go.tmpl b/wrappers/golang/internal/generator/templates/vec_ops.go.tmpl deleted file mode 100644 index 0e887229..00000000 --- a/wrappers/golang/internal/generator/templates/vec_ops.go.tmpl +++ /dev/null @@ -1,55 +0,0 @@ -package {{.PackageName}} - -// #cgo CFLAGS: -I./include/ -// #include "vec_ops.h" -import "C" - -import ( - "unsafe" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" -) - -func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { - core.VecOpCheck(a, b, out, &config) - var cA, cB, cOut *C.scalar_t - - if a.IsOnDevice() { - aDevice := a.(core.DeviceSlice) - aDevice.CheckDevice() - cA = (*C.scalar_t)(aDevice.AsPointer()) - } else { - cA = (*C.scalar_t)(unsafe.Pointer(&a.(core.HostSlice[ScalarField])[0])) - } - - if b.IsOnDevice() { - bDevice := b.(core.DeviceSlice) - bDevice.CheckDevice() - cB = (*C.scalar_t)(bDevice.AsPointer()) - } else { - cB = (*C.scalar_t)(unsafe.Pointer(&b.(core.HostSlice[ScalarField])[0])) - } - - if out.IsOnDevice() { - outDevice := out.(core.DeviceSlice) - outDevice.CheckDevice() - cOut = (*C.scalar_t)(outDevice.AsPointer()) - } else { - cOut = (*C.scalar_t)(unsafe.Pointer(&out.(core.HostSlice[ScalarField])[0])) - } - - cConfig := (*C.VecOpsConfig)(unsafe.Pointer(&config)) - cSize := (C.int)(a.Len()) - - switch op { - case core.Sub: - ret = (cr.CudaError)(C.{{.Curve}}SubCuda(cA, cB, cSize, cConfig, cOut)) - case core.Add: - ret = (cr.CudaError)(C.{{.Curve}}AddCuda(cA, cB, cSize, cConfig, cOut)) - case core.Mul: - ret = (cr.CudaError)(C.{{.Curve}}MulCuda(cA, cB, cSize, cConfig, cOut)) - } - - return ret -} diff --git a/wrappers/golang/internal/generator/templates/vec_ops_test.go.tmpl b/wrappers/golang/internal/generator/templates/vec_ops_test.go.tmpl deleted file mode 100644 index f1daff1f..00000000 --- a/wrappers/golang/internal/generator/templates/vec_ops_test.go.tmpl +++ /dev/null @@ -1,33 +0,0 @@ -package {{.PackageName}} - -import ( - "testing" - - "github.com/ingonyama-zk/icicle/wrappers/golang/core" - "github.com/stretchr/testify/assert" -) - -func TestVecOps(t *testing.T) { - testSize := 1 << 14 - - a := GenerateScalars(testSize) - b := GenerateScalars(testSize) - var scalar ScalarField - scalar.One() - ones := core.HostSliceWithValue(scalar, testSize) - - out := make(core.HostSlice[ScalarField], testSize) - out2 := make(core.HostSlice[ScalarField], testSize) - out3 := make(core.HostSlice[ScalarField], testSize) - - cfg := core.DefaultVecOpsConfig() - - VecOp(a, b, out, cfg, core.Add) - VecOp(out, b, out2, cfg, core.Sub) - - assert.Equal(t, a, out2) - - VecOp(a, ones, out3, cfg, core.Mul) - - assert.Equal(t, a, out3) -} diff --git a/wrappers/golang/internal/generator/tests/generate.go b/wrappers/golang/internal/generator/tests/generate.go new file mode 100644 index 00000000..47b44a17 --- /dev/null +++ b/wrappers/golang/internal/generator/tests/generate.go @@ -0,0 +1,29 @@ +package tests + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" +) + +func Generate(baseDir, field, fieldPrefix, gnarkImport string, rou int, supportsNTT, supportsPoly bool) { + data := struct { + Field string + FieldPrefix string + BaseImportPath string + GnarkImport string + ROU int + SupportsNTT bool + SupportsPoly bool + }{ + field, + fieldPrefix, + baseDir, + gnarkImport, + rou, + supportsNTT, + supportsPoly, + } + + generator.GenerateFile("tests/templates/main_test.go.tmpl", path.Join(baseDir, "tests"), "", "", data) +} diff --git a/wrappers/golang/internal/generator/tests/templates/main_test.go.tmpl b/wrappers/golang/internal/generator/tests/templates/main_test.go.tmpl new file mode 100644 index 00000000..17d342da --- /dev/null +++ b/wrappers/golang/internal/generator/tests/templates/main_test.go.tmpl @@ -0,0 +1,58 @@ +package tests + +import ( + "os" + "testing" + + {{if or .SupportsNTT .SupportsPoly -}} + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + {{.Field}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}"{{end}} + {{if .SupportsNTT -}} + ntt "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/ntt"{{end}} + {{if .SupportsPoly -}} + poly "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/polynomial"{{end}} + + {{if ne .GnarkImport "" -}} + "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}/fr/fft" + {{end -}} +) + +const ( + largestTestSize = 20 +) +{{if or .SupportsNTT .SupportsPoly -}} +func initDomain[T any](largestTestSize int, cfg core.NTTConfig[T]) core.IcicleError { + {{if ne .GnarkImport "" -}} + rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) + rou := rouMont.Bits() + rouIcicle := {{.Field}}.{{.FieldPrefix}}Field{} + limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) + + rouIcicle.FromLimbs(limbs) + {{else -}} + rouIcicle := {{.Field}}.{{.FieldPrefix}}Field{} + rouIcicle.FromUint32({{.ROU}}) + {{end -}} + e := ntt.InitDomain(rouIcicle, cfg.Ctx, false) + return e +}{{end}} + +func TestMain(m *testing.M) { + {{if .SupportsPoly -}}poly.InitPolyBackend(){{end}} + + {{if or .SupportsNTT .SupportsPoly -}}// setup domain + cfg := ntt.GetDefaultNttConfig() + e := initDomain(largestTestSize, cfg) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("initDomain failed") + }{{end}} + + // execute tests + os.Exit(m.Run()) + + {{if or .SupportsNTT .SupportsPoly -}}// release domain + e = ntt.ReleaseDomain(cfg.Ctx) + if e.IcicleErrorCode != core.IcicleErrorCode(0) { + panic("ReleaseDomain failed") + }{{end}} +} diff --git a/wrappers/golang/internal/generator/vecOps/generate.go b/wrappers/golang/internal/generator/vecOps/generate.go new file mode 100644 index 00000000..938b7495 --- /dev/null +++ b/wrappers/golang/internal/generator/vecOps/generate.go @@ -0,0 +1,39 @@ +package vecops + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/wrappers/golang/internal/generator/generator_utils" +) + +var vecOpsTemplates = map[string]string{ + "src": "vecOps/templates/vec_ops.go.tmpl", + "test": "vecOps/templates/vec_ops_test.go.tmpl", + "header": "vecOps/templates/vec_ops.h.tmpl", +} + +func Generate(baseDir, field, fieldPrefix string) { + data := struct { + PackageName string + Field string + FieldPrefix string + BaseImportPath string + }{ + "vecOps", + field, + fieldPrefix, + baseDir, + } + + testDir := "tests" + filePrefix := "" + parentDir := path.Base(baseDir) + if parentDir == "extension" { + testDir = "../tests" + filePrefix = "extension_" + } + + generator.GenerateFile(vecOpsTemplates["src"], path.Join(baseDir, "vecOps"), "", "", data) + generator.GenerateFile(vecOpsTemplates["header"], path.Join(baseDir, "vecOps", "include"), "", "", data) + generator.GenerateFile(vecOpsTemplates["test"], path.Join(baseDir, testDir), filePrefix, "", data) +} diff --git a/wrappers/golang/internal/generator/vecOps/templates/vec_ops.go.tmpl b/wrappers/golang/internal/generator/vecOps/templates/vec_ops.go.tmpl new file mode 100644 index 00000000..90b887ad --- /dev/null +++ b/wrappers/golang/internal/generator/vecOps/templates/vec_ops.go.tmpl @@ -0,0 +1,48 @@ +package {{.PackageName}} + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + + "unsafe" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret cr.CudaError) { + aPointer, bPointer, outPointer, cfgPointer, size := core.VecOpCheck(a, b, out, &config) + + cA := (*C.scalar_t)(aPointer) + cB := (*C.scalar_t)(bPointer) + cOut := (*C.scalar_t)(outPointer) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cSize := (C.int)(size) + + switch op { + case core.Sub: + ret = (cr.CudaError)(C.{{.Field}}_sub_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (cr.CudaError)(C.{{.Field}}_add_cuda(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (cr.CudaError)(C.{{.Field}}_mul_cuda(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, ctx cr.DeviceContext, onDevice, isAsync bool) (ret core.IcicleError){ + core.TransposeCheck(in, out, onDevice) + + cIn := (*C.scalar_t)(in.AsUnsafePointer()) + cOut := (*C.scalar_t)(out.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cOnDevice := (C._Bool)(onDevice) + cIsAsync := (C._Bool)(isAsync) + + err := (cr.CudaError)(C.{{.Field}}_transpose_matrix_cuda( cIn, cRowSize, cColumnSize, cOut, cCtx, cOnDevice, cIsAsync)) + return core.FromCudaError(err) +} diff --git a/wrappers/golang/internal/generator/vecOps/templates/vec_ops.h.tmpl b/wrappers/golang/internal/generator/vecOps/templates/vec_ops.h.tmpl new file mode 100644 index 00000000..9f3c9eff --- /dev/null +++ b/wrappers/golang/internal/generator/vecOps/templates/vec_ops.h.tmpl @@ -0,0 +1,53 @@ +#include +#include + +#ifndef _{{toUpper .Field}}_VEC_OPS_H +#define _{{toUpper .Field}}_VEC_OPS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +cudaError_t {{.Field}}_mul_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t {{.Field}}_add_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t {{.Field}}_sub_cuda( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +cudaError_t {{.Field}}_transpose_matrix_cuda( + scalar_t* mat_in, + int row_size, + int column_size, + scalar_t* mat_out, + DeviceContext* ctx, + bool on_device, + bool is_async +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang/internal/generator/vecOps/templates/vec_ops_test.go.tmpl b/wrappers/golang/internal/generator/vecOps/templates/vec_ops_test.go.tmpl new file mode 100644 index 00000000..c2537639 --- /dev/null +++ b/wrappers/golang/internal/generator/vecOps/templates/vec_ops_test.go.tmpl @@ -0,0 +1,69 @@ +package tests + +import ( + "testing" + + {{.Field}} "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}" + "github.com/ingonyama-zk/icicle/wrappers/golang/core" + cr "github.com/ingonyama-zk/icicle/wrappers/golang/cuda_runtime" + "github.com/ingonyama-zk/icicle/wrappers/golang/{{.BaseImportPath}}/vecOps" + "github.com/stretchr/testify/assert" +) + +func Test{{capitalize .Field}}VecOps(t *testing.T) { + testSize := 1 << 14 + + a := {{.Field}}.GenerateScalars(testSize) + b := {{.Field}}.GenerateScalars(testSize) + var scalar {{.Field}}.{{.FieldPrefix}}Field + scalar.One() + ones := core.HostSliceWithValue(scalar, testSize) + + out := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], testSize) + out2 := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], testSize) + out3 := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], testSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.VecOp(a, b, out, cfg, core.Add) + vecOps.VecOp(out, b, out2, cfg, core.Sub) + + assert.Equal(t, a, out2) + + vecOps.VecOp(a, ones, out3, cfg, core.Mul) + + assert.Equal(t, a, out3) +} + +func Test{{capitalize .Field}}Transpose(t *testing.T) { + rowSize := 1 << 6 + columnSize := 1 << 8 + onDevice := false + isAsync := false + + matrix := {{.Field}}.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], rowSize*columnSize) + out2 := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], rowSize*columnSize) + + ctx, _ := cr.GetDefaultDeviceContext() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, ctx, onDevice, isAsync) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + onDevice = true + + matrix.CopyToDevice(&dMatrix, true) + dOut.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + dOut2.Malloc(columnSize*rowSize*matrix.SizeOfElement(), matrix.SizeOfElement()) + + vecOps.TransposeMatrix(dMatrix, dOut, columnSize, rowSize, ctx, onDevice, isAsync) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, ctx, onDevice, isAsync) + output := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang/core/internal/helpers_test.go b/wrappers/golang/test_helpers/helpers.go similarity index 70% rename from wrappers/golang/core/internal/helpers_test.go rename to wrappers/golang/test_helpers/helpers.go index 2c24f794..09a2a7df 100644 --- a/wrappers/golang/core/internal/helpers_test.go +++ b/wrappers/golang/test_helpers/helpers.go @@ -1,10 +1,10 @@ -package internal +package test_helpers import ( "math/rand" ) -func generateRandomLimb(size int) []uint32 { +func GenerateRandomLimb(size int) []uint32 { limbs := make([]uint32, size) for i := range limbs { limbs[i] = rand.Uint32() @@ -12,13 +12,13 @@ func generateRandomLimb(size int) []uint32 { return limbs } -func generateLimbOne(size int) []uint32 { +func GenerateLimbOne(size int) []uint32 { limbs := make([]uint32, size) limbs[0] = 1 return limbs } -func generateBytesArray(size int) ([]byte, []uint32) { +func GenerateBytesArray(size int) ([]byte, []uint32) { baseBytes := []byte{1, 2, 3, 4} var bytes []byte var limbs []uint32 diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 58add04c..d8f66f62 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -8,6 +8,8 @@ members = [ "icicle-curves/icicle-bls12-381", "icicle-curves/icicle-bn254", "icicle-curves/icicle-grumpkin", + "icicle-fields/icicle-babybear", + "icicle-hash", ] exclude = [ "icicle-curves/icicle-curve-template", diff --git a/wrappers/rust/icicle-core/Cargo.toml b/wrappers/rust/icicle-core/Cargo.toml index ecd6bd94..0d962ae9 100644 --- a/wrappers/rust/icicle-core/Cargo.toml +++ b/wrappers/rust/icicle-core/Cargo.toml @@ -19,6 +19,13 @@ ark-std = { version = "0.4.0", optional = true } rayon = "1.8.1" +criterion = "0.3" + +[dev-dependencies] +criterion = "0.3" +serial_test = "3.0.0" + + [features] default = [] arkworks = ["ark-ff", "ark-ec", "ark-poly", "ark-std"] diff --git a/wrappers/rust/icicle-core/src/curve.rs b/wrappers/rust/icicle-core/src/curve.rs index d8b0ef6b..89bec1c2 100644 --- a/wrappers/rust/icicle-core/src/curve.rs +++ b/wrappers/rust/icicle-core/src/curve.rs @@ -7,8 +7,10 @@ use ark_ec::models::CurveConfig as ArkCurveConfig; use ark_ec::short_weierstrass::{Affine as ArkAffine, Projective as ArkProjective, SWCurveConfig}; #[cfg(feature = "arkworks")] use ark_ec::AffineRepr; +use icicle_cuda_runtime::device::check_device; +use icicle_cuda_runtime::device_context::DeviceContext; use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{DeviceSlice, HostOrDeviceSlice}; use std::fmt::Debug; pub trait Curve: Debug + PartialEq + Copy + Clone { @@ -24,9 +26,19 @@ pub trait Curve: Debug + PartialEq + Copy + Clone { #[doc(hidden)] fn generate_random_affine_points(size: usize) -> Vec>; #[doc(hidden)] - fn convert_affine_montgomery(points: &mut HostOrDeviceSlice>, is_into: bool) -> CudaError; + fn convert_affine_montgomery( + points: *mut Affine, + len: usize, + is_into: bool, + ctx: &DeviceContext, + ) -> CudaError; #[doc(hidden)] - fn convert_projective_montgomery(points: &mut HostOrDeviceSlice>, is_into: bool) -> CudaError; + fn convert_projective_montgomery( + points: *mut Projective, + len: usize, + is_into: bool, + ctx: &DeviceContext, + ) -> CudaError; #[cfg(feature = "arkworks")] type ArkSWConfig: SWCurveConfig; @@ -77,6 +89,9 @@ impl Affine { impl From> for Projective { fn from(item: Affine) -> Self { + if item == (Affine::::zero()) { + return Self::zero(); + } Self { x: item.x, y: item.y, @@ -109,35 +124,67 @@ impl Projective { impl PartialEq for Projective { fn eq(&self, other: &Self) -> bool { - C::eq_proj(self as *const _, other as *const _) + C::eq_proj(self as *const Self, other as *const Self) } } impl From> for Affine { fn from(proj: Projective) -> Self { let mut aff = Self::zero(); - C::to_affine(&proj as *const _, &mut aff as *mut _); + C::to_affine(&proj as *const Projective, &mut aff as *mut Self); aff } } -impl MontgomeryConvertible for Affine { - fn to_mont(values: &mut HostOrDeviceSlice) -> CudaError { - C::convert_affine_montgomery(values, true) +impl<'a, C: Curve> MontgomeryConvertible<'a> for Affine { + fn to_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError { + check_device(ctx.device_id); + assert_eq!( + values + .device_id() + .unwrap(), + ctx.device_id, + "Device ids are different in slice and context" + ); + C::convert_affine_montgomery(unsafe { values.as_mut_ptr() }, values.len(), true, ctx) } - fn from_mont(values: &mut HostOrDeviceSlice) -> CudaError { - C::convert_affine_montgomery(values, false) + fn from_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError { + check_device(ctx.device_id); + assert_eq!( + values + .device_id() + .unwrap(), + ctx.device_id, + "Device ids are different in slice and context" + ); + C::convert_affine_montgomery(unsafe { values.as_mut_ptr() }, values.len(), false, ctx) } } -impl MontgomeryConvertible for Projective { - fn to_mont(values: &mut HostOrDeviceSlice) -> CudaError { - C::convert_projective_montgomery(values, true) +impl<'a, C: Curve> MontgomeryConvertible<'a> for Projective { + fn to_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError { + check_device(ctx.device_id); + assert_eq!( + values + .device_id() + .unwrap(), + ctx.device_id, + "Device ids are different in slice and context" + ); + C::convert_projective_montgomery(unsafe { values.as_mut_ptr() }, values.len(), true, ctx) } - fn from_mont(values: &mut HostOrDeviceSlice) -> CudaError { - C::convert_projective_montgomery(values, false) + fn from_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError { + check_device(ctx.device_id); + assert_eq!( + values + .device_id() + .unwrap(), + ctx.device_id, + "Device ids are different in slice and context" + ); + C::convert_projective_montgomery(unsafe { values.as_mut_ptr() }, values.len(), false, ctx) } } @@ -228,22 +275,22 @@ macro_rules! impl_curve { use super::{$affine_type, $projective_type, CudaError, DeviceContext}; extern "C" { - #[link_name = concat!($curve_prefix, "Eq")] + #[link_name = concat!($curve_prefix, "_eq")] pub(crate) fn eq(point1: *const $projective_type, point2: *const $projective_type) -> bool; - #[link_name = concat!($curve_prefix, "ToAffine")] + #[link_name = concat!($curve_prefix, "_to_affine")] pub(crate) fn proj_to_affine(point: *const $projective_type, point_out: *mut $affine_type); - #[link_name = concat!($curve_prefix, "GenerateProjectivePoints")] + #[link_name = concat!($curve_prefix, "_generate_projective_points")] pub(crate) fn generate_projective_points(points: *mut $projective_type, size: usize); - #[link_name = concat!($curve_prefix, "GenerateAffinePoints")] + #[link_name = concat!($curve_prefix, "_generate_affine_points")] pub(crate) fn generate_affine_points(points: *mut $affine_type, size: usize); - #[link_name = concat!($curve_prefix, "AffineConvertMontgomery")] + #[link_name = concat!($curve_prefix, "_affine_convert_montgomery")] pub(crate) fn _convert_affine_montgomery( points: *mut $affine_type, size: usize, is_into: bool, ctx: *const DeviceContext, ) -> CudaError; - #[link_name = concat!($curve_prefix, "ProjectiveConvertMontgomery")] + #[link_name = concat!($curve_prefix, "_projective_convert_montgomery")] pub(crate) fn _convert_projective_montgomery( points: *mut $projective_type, size: usize, @@ -284,27 +331,29 @@ macro_rules! impl_curve { res } - fn convert_affine_montgomery(points: &mut HostOrDeviceSlice<$affine_type>, is_into: bool) -> CudaError { + fn convert_affine_montgomery( + points: *mut $affine_type, + len: usize, + is_into: bool, + ctx: &DeviceContext, + ) -> CudaError { unsafe { - $curve_prefix_ident::_convert_affine_montgomery( - points.as_mut_ptr(), - points.len(), - is_into, - &DeviceContext::default() as *const _ as *const DeviceContext, - ) + $curve_prefix_ident::_convert_affine_montgomery(points, len, is_into, ctx as *const DeviceContext) } } fn convert_projective_montgomery( - points: &mut HostOrDeviceSlice<$projective_type>, + points: *mut $projective_type, + len: usize, is_into: bool, + ctx: &DeviceContext, ) -> CudaError { unsafe { $curve_prefix_ident::_convert_projective_montgomery( - points.as_mut_ptr(), - points.len(), + points, + len, is_into, - &DeviceContext::default() as *const _ as *const DeviceContext, + ctx as *const DeviceContext, ) } } @@ -321,11 +370,6 @@ macro_rules! impl_curve_tests { $base_limbs:ident, $curve:ident ) => { - #[test] - fn test_scalar_equality() { - check_scalar_equality::<<$curve as Curve>::ScalarField>() - } - #[test] fn test_affine_projective_convert() { check_affine_projective_convert::<$curve>() diff --git a/wrappers/rust/icicle-core/src/ecntt/mod.rs b/wrappers/rust/icicle-core/src/ecntt/mod.rs new file mode 100644 index 00000000..25a77f25 --- /dev/null +++ b/wrappers/rust/icicle-core/src/ecntt/mod.rs @@ -0,0 +1,271 @@ +#![cfg(feature = "ec_ntt")] +use icicle_cuda_runtime::memory::HostOrDeviceSlice; + +use crate::{ + curve::Curve, + ntt::{FieldImpl, IcicleResult, NTTConfig, NTTDir}, +}; + +pub use crate::curve::Projective; + +// #[cfg(feature = "arkworks")] //TODO: uncomment on correctness test +#[doc(hidden)] +pub mod tests; + +#[doc(hidden)] +pub trait ECNTT: ECNTTUnchecked, C::ScalarField> {} + +#[doc(hidden)] +pub trait ECNTTUnchecked { + fn ntt_unchecked( + input: &(impl HostOrDeviceSlice + ?Sized), + dir: NTTDir, + cfg: &NTTConfig, + output: &mut (impl HostOrDeviceSlice + ?Sized), + ) -> IcicleResult<()>; + + fn ntt_inplace_unchecked( + inout: &mut (impl HostOrDeviceSlice + ?Sized), + dir: NTTDir, + cfg: &NTTConfig, + ) -> IcicleResult<()>; +} + +#[macro_export] +macro_rules! impl_ecntt { + ( + $field_prefix:literal, + $field_prefix_ident:ident, + $field:ident, + $field_config:ident, + $curve:ident + ) => { + mod $field_prefix_ident { + use crate::curve; + use crate::curve::BaseCfg; + use crate::ecntt::IcicleResult; + use crate::ecntt::Projective; + use crate::ecntt::{ + $curve, $field, $field_config, CudaError, DeviceContext, NTTConfig, NTTDir, DEFAULT_DEVICE_ID, + }; + use icicle_core::ecntt::ECNTTUnchecked; + use icicle_core::ecntt::ECNTT; + use icicle_core::impl_ntt_without_domain; + use icicle_core::ntt::NTT; + use icicle_core::traits::IcicleResultWrap; + use icicle_cuda_runtime::memory::HostOrDeviceSlice; + + pub type ProjectiveC = Projective<$curve>; + impl_ntt_without_domain!( + $field_prefix, + $field, + $field_config, + ECNTTUnchecked, + "_ecntt_", + ProjectiveC + ); + + impl ECNTT<$curve> for $field_config {} + } + }; +} + +/// Computes the ECNTT, or a batch of several ECNTTs. +/// +/// # Arguments +/// +/// * `input` - inputs of the ECNTT. +/// +/// * `dir` - whether to compute forward of inverse ECNTT. +/// +/// * `cfg` - config used to specify extra arguments of the ECNTT. +/// +/// * `output` - buffer to write the ECNTT outputs into. Must be of the same size as `input`. +pub fn ecntt( + input: &(impl HostOrDeviceSlice> + ?Sized), + dir: NTTDir, + cfg: &NTTConfig, + output: &mut (impl HostOrDeviceSlice> + ?Sized), +) -> IcicleResult<()> +where + C::ScalarField: FieldImpl, + ::Config: ECNTT, +{ + <::Config as ECNTTUnchecked, C::ScalarField>>::ntt_unchecked( + input, dir, &cfg, output, + ) +} + +/// Computes the ECNTT, or a batch of several ECNTTs inplace. +/// +/// # Arguments +/// +/// * `inout` - buffer with inputs to also write the ECNTT outputs into. +/// +/// * `dir` - whether to compute forward of inverse ECNTT. +/// +/// * `cfg` - config used to specify extra arguments of the ECNTT. +pub fn ecntt_inplace( + inout: &mut (impl HostOrDeviceSlice> + ?Sized), + dir: NTTDir, + cfg: &NTTConfig, +) -> IcicleResult<()> +where + C::ScalarField: FieldImpl, + ::Config: ECNTT, +{ + <::Config as ECNTTUnchecked, C::ScalarField>>::ntt_inplace_unchecked( + inout, dir, &cfg, + ) +} + +#[macro_export] +macro_rules! impl_ecntt_tests { + ( + $field:ident, + $curve:ident + ) => { + use icicle_core::ntt::tests::init_domain; + use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; + const MAX_SIZE: u64 = 1 << 18; + static INIT: OnceLock<()> = OnceLock::new(); + const FAST_TWIDDLES_MODE: bool = false; + + #[test] + fn test_ecntt() { + INIT.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)); + check_ecntt::<$curve>() + } + + #[test] + fn test_ecntt_batch() { + INIT.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)); + check_ecntt_batch::<$curve>() + } + + // #[test] //TODO: multi-device test + // fn test_ntt_device_async() { + // // init_domain is in this test is performed per-device + // check_ecntt_device_async::<$field>() + // } + }; +} + +#[macro_export] +macro_rules! impl_ecntt_bench { + ( + $field_prefix:literal, + $field:ident, + $curve:ident + ) => { + use icicle_core::ntt::ntt; + use icicle_core::ntt::NTTDomain; + use icicle_cuda_runtime::memory::HostOrDeviceSlice; + use std::sync::OnceLock; + + use criterion::{black_box, criterion_group, criterion_main, Criterion}; + use icicle_core::{ + curve::Curve, + ecntt::{ecntt, Projective}, + ntt::{FieldImpl, NTTConfig, NTTDir, NttAlgorithm, Ordering}, + traits::ArkConvertible, + }; + + use icicle_core::ecntt::ECNTT; + use icicle_core::ntt::NTT; + use icicle_cuda_runtime::memory::HostSlice; + + fn ecntt_for_bench( + points: &(impl HostOrDeviceSlice> + ?Sized), + mut batch_ntt_result: &mut (impl HostOrDeviceSlice> + ?Sized), + test_sizes: usize, + batch_size: usize, + is_inverse: NTTDir, + ordering: Ordering, + config: &mut NTTConfig, + _seed: u32, + ) where + C::ScalarField: ArkConvertible, + ::Config: ECNTT, + ::Config: NTTDomain, + { + ecntt(points, is_inverse, config, batch_ntt_result).unwrap(); + } + + static INIT: OnceLock<()> = OnceLock::new(); + + fn benchmark_ecntt(c: &mut Criterion) + where + C::ScalarField: ArkConvertible, + ::Config: ECNTT, + ::Config: NTTDomain, + { + use criterion::SamplingMode; + use icicle_core::ntt::ntt; + use icicle_core::ntt::tests::init_domain; + use icicle_core::ntt::NTTDomain; + use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; + + let group_id = format!("{} EC NTT", $field_prefix); + let mut group = c.benchmark_group(&group_id); + group.sampling_mode(SamplingMode::Flat); + group.sample_size(10); + + const MAX_SIZE: u64 = 1 << 18; + const FAST_TWIDDLES_MODE: bool = false; + + INIT.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)); + + let test_sizes = [1 << 4, 1 << 8]; + let batch_sizes = [1, 1 << 4, 128]; + for test_size in test_sizes { + for batch_size in batch_sizes { + let points = C::generate_random_projective_points(test_size); + let points = HostSlice::from_slice(&points); + let mut batch_ntt_result = vec![Projective::::zero(); batch_size * test_size]; + let batch_ntt_result = HostSlice::from_mut_slice(&mut batch_ntt_result); + let mut config = NTTConfig::default(); + for is_inverse in [NTTDir::kInverse, NTTDir::kForward] { + for ordering in [ + Ordering::kNN, + // Ordering::kNR, // times are ~ same as kNN + // Ordering::kRN, + // Ordering::kRR, + // Ordering::kNM, // no mixed radix ecntt + // Ordering::kMN, + ] { + config.ordering = ordering; + for alg in [NttAlgorithm::Radix2] { + config.batch_size = batch_size as i32; + config.ntt_algorithm = alg; + let bench_descr = format!( + "{:?} {:?} {:?} {} x {}", + alg, ordering, is_inverse, test_size, batch_size + ); + group.bench_function(&bench_descr, |b| { + b.iter(|| { + ecntt_for_bench::( + points, + batch_ntt_result, + test_size, + batch_size, + is_inverse, + ordering, + &mut config, + black_box(1), + ) + }) + }); + } + } + } + } + } + + group.finish(); + } + + criterion_group!(benches, benchmark_ecntt<$curve>); + criterion_main!(benches); + }; +} diff --git a/wrappers/rust/icicle-core/src/ecntt/tests.rs b/wrappers/rust/icicle-core/src/ecntt/tests.rs new file mode 100644 index 00000000..287124dd --- /dev/null +++ b/wrappers/rust/icicle-core/src/ecntt/tests.rs @@ -0,0 +1,92 @@ +#![cfg(feature = "ec_ntt")] +use icicle_cuda_runtime::memory::HostSlice; + +use crate::curve::Curve; +use crate::curve::*; +use crate::{ + ecntt::*, + ntt::{NTTDir, NttAlgorithm, Ordering}, + traits::FieldImpl, +}; + +use crate::ntt::NTTConfig; + +pub fn check_ecntt() +where + ::Config: ECNTT, +{ + let test_sizes = [1 << 4, 1 << 9]; + for test_size in test_sizes { + let points = C::generate_random_projective_points(test_size); + + let slice = &points.clone(); + let config: NTTConfig<'_, C::ScalarField> = NTTConfig::default(); + let mut out_p = vec![Projective::::zero(); test_size]; + let ecntt_result = HostSlice::from_mut_slice(&mut out_p); + let input = HostSlice::from_slice(slice); + ecntt(input, NTTDir::kForward, &config, ecntt_result).unwrap(); + assert_ne!(ecntt_result.as_slice(), points); + + let mut slice = vec![Projective::::zero(); test_size]; + let iecntt_result = HostSlice::from_mut_slice(&mut slice); + ecntt(ecntt_result, NTTDir::kInverse, &config, iecntt_result).unwrap(); + + assert_eq!(iecntt_result.as_slice(), points); + } +} + +pub fn check_ecntt_batch() +where + ::Config: ECNTT, +{ + let test_sizes = [1 << 4, 1 << 9]; + let batch_sizes = [1, 1 << 4, 21]; + for test_size in test_sizes { + // let coset_generators = [F::one(), F::Config::generate_random(1)[0]]; + let mut config: NTTConfig<'_, C::ScalarField> = NTTConfig::default(); + for batch_size in batch_sizes { + let slice = &C::generate_random_projective_points(test_size * batch_size); + let points = HostSlice::from_slice(slice); + + for is_inverse in [NTTDir::kInverse, NTTDir::kForward] { + for ordering in [ + Ordering::kNN, // ~same performance + // Ordering::kNR, + // Ordering::kRN, + // Ordering::kRR, + // Ordering::kNM, // no mixed radix ecntt + // Ordering::kMN, + ] { + config.ordering = ordering; + let mut slice = vec![Projective::zero(); batch_size * test_size]; + let batch_ntt_result = HostSlice::from_mut_slice(&mut slice); + for alg in [NttAlgorithm::Radix2] { + config.batch_size = batch_size as i32; + config.ntt_algorithm = alg; + ecntt(points, is_inverse, &config, batch_ntt_result).unwrap(); + config.batch_size = 1; + let mut slice = vec![Projective::zero(); test_size]; + let one_ntt_result = HostSlice::from_mut_slice(&mut slice); + for i in 0..batch_size { + ecntt( + HostSlice::from_slice( + &points[i * test_size..(i + 1) * test_size] + .as_slice() + .to_vec(), + ), + is_inverse, + &config, + one_ntt_result, + ) + .unwrap(); + assert_eq!( + batch_ntt_result[i * test_size..(i + 1) * test_size].as_slice(), + one_ntt_result.as_slice() + ); + } + } + } + } + } + } +} diff --git a/wrappers/rust/icicle-core/src/field.rs b/wrappers/rust/icicle-core/src/field.rs index be556545..6eafeb65 100644 --- a/wrappers/rust/icicle-core/src/field.rs +++ b/wrappers/rust/icicle-core/src/field.rs @@ -3,15 +3,16 @@ use crate::traits::ArkConvertible; use crate::traits::{FieldConfig, FieldImpl, MontgomeryConvertible}; #[cfg(feature = "arkworks")] use ark_ff::{BigInteger, Field as ArkField, PrimeField}; +use icicle_cuda_runtime::device_context::DeviceContext; use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::DeviceSlice; use std::fmt::{Debug, Display}; use std::marker::PhantomData; #[derive(PartialEq, Copy, Clone)] #[repr(C)] pub struct Field { - limbs: [u64; NUM_LIMBS], + limbs: [u32; NUM_LIMBS], p: PhantomData, } @@ -26,7 +27,7 @@ impl Display for Field { .iter() .rev() { - write!(f, "{:016x}", b)?; + write!(f, "{:08x}", b)?; } Ok(()) } @@ -38,21 +39,21 @@ impl Debug for Field { } } -impl Into<[u64; NUM_LIMBS]> for Field { - fn into(self) -> [u64; NUM_LIMBS] { +impl Into<[u32; NUM_LIMBS]> for Field { + fn into(self) -> [u32; NUM_LIMBS] { self.limbs } } -impl From<[u64; NUM_LIMBS]> for Field { - fn from(limbs: [u64; NUM_LIMBS]) -> Self { +impl From<[u32; NUM_LIMBS]> for Field { + fn from(limbs: [u32; NUM_LIMBS]) -> Self { Self { limbs, p: PhantomData } } } impl FieldImpl for Field { type Config = F; - type Repr = [u64; NUM_LIMBS]; + type Repr = [u32; NUM_LIMBS]; fn to_bytes_le(&self) -> Vec { self.limbs @@ -68,49 +69,50 @@ impl FieldImpl for Field { // please note that this function zero-pads if there are not enough bytes // and only takes the first bytes in there are too many of them fn from_bytes_le(bytes: &[u8]) -> Self { - let mut limbs: [u64; NUM_LIMBS] = [0; NUM_LIMBS]; + let mut limbs: [u32; NUM_LIMBS] = [0; NUM_LIMBS]; for (i, chunk) in bytes - .chunks(8) + .chunks(4) .take(NUM_LIMBS) .enumerate() { - let mut chunk_array: [u8; 8] = [0; 8]; + let mut chunk_array: [u8; 4] = [0; 4]; chunk_array[..chunk.len()].clone_from_slice(chunk); - limbs[i] = u64::from_le_bytes(chunk_array); + limbs[i] = u32::from_le_bytes(chunk_array); } Self::from(limbs) } fn zero() -> Self { - Field { - limbs: [0u64; NUM_LIMBS], - p: PhantomData, - } + FieldImpl::from_u32(0) } fn one() -> Self { - let mut limbs = [0u64; NUM_LIMBS]; - limbs[0] = 1; + FieldImpl::from_u32(1) + } + + fn from_u32(val: u32) -> Self { + let mut limbs = [0u32; NUM_LIMBS]; + limbs[0] = val; Field { limbs, p: PhantomData } } } #[doc(hidden)] -pub trait MontgomeryConvertibleField { - fn to_mont(values: &mut HostOrDeviceSlice) -> CudaError; - fn from_mont(values: &mut HostOrDeviceSlice) -> CudaError; +pub trait MontgomeryConvertibleField<'a, F: FieldImpl> { + fn to_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError; + fn from_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError; } -impl MontgomeryConvertible for Field +impl<'a, const NUM_LIMBS: usize, F: FieldConfig> MontgomeryConvertible<'a> for Field where - F: MontgomeryConvertibleField, + F: MontgomeryConvertibleField<'a, Self>, { - fn to_mont(values: &mut HostOrDeviceSlice) -> CudaError { - F::to_mont(values) + fn to_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError { + F::to_mont(values, ctx) } - fn from_mont(values: &mut HostOrDeviceSlice) -> CudaError { - F::from_mont(values) + fn from_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError { + F::from_mont(values, ctx) } } @@ -168,13 +170,13 @@ macro_rules! impl_scalar_field { impl_field!($num_limbs, $field_name, $field_cfg, $ark_equiv); mod $field_prefix_ident { - use crate::curve::{$field_name, CudaError, DeviceContext, HostOrDeviceSlice}; + use super::{$field_name, CudaError, DeviceContext, HostOrDeviceSlice}; extern "C" { - #[link_name = concat!($field_prefix, "GenerateScalars")] + #[link_name = concat!($field_prefix, "_generate_scalars")] pub(crate) fn generate_scalars(scalars: *mut $field_name, size: usize); - #[link_name = concat!($field_prefix, "ScalarConvertMontgomery")] + #[link_name = concat!($field_prefix, "_scalar_convert_montgomery")] fn _convert_scalars_montgomery( scalars: *mut $field_name, size: usize, @@ -184,17 +186,12 @@ macro_rules! impl_scalar_field { } pub(crate) fn convert_scalars_montgomery( - scalars: &mut HostOrDeviceSlice<$field_name>, + scalars: *mut $field_name, + len: usize, is_into: bool, + ctx: &DeviceContext, ) -> CudaError { - unsafe { - _convert_scalars_montgomery( - scalars.as_mut_ptr(), - scalars.len(), - is_into, - &DeviceContext::default() as *const _ as *const DeviceContext, - ) - } + unsafe { _convert_scalars_montgomery(scalars, len, is_into, ctx as *const DeviceContext) } } } @@ -206,13 +203,34 @@ macro_rules! impl_scalar_field { } } - impl MontgomeryConvertibleField<$field_name> for $field_cfg { - fn to_mont(values: &mut HostOrDeviceSlice<$field_name>) -> CudaError { - $field_prefix_ident::convert_scalars_montgomery(values, true) + impl<'a> MontgomeryConvertibleField<'a, $field_name> for $field_cfg { + fn to_mont(values: &mut DeviceSlice<$field_name>, ctx: &DeviceContext<'a>) -> CudaError { + check_device(ctx.device_id); + assert_eq!( + values + .device_id() + .unwrap(), + ctx.device_id, + "Device ids are different in slice and context" + ); + $field_prefix_ident::convert_scalars_montgomery(unsafe { values.as_mut_ptr() }, values.len(), true, ctx) } - fn from_mont(values: &mut HostOrDeviceSlice<$field_name>) -> CudaError { - $field_prefix_ident::convert_scalars_montgomery(values, false) + fn from_mont(values: &mut DeviceSlice<$field_name>, ctx: &DeviceContext<'a>) -> CudaError { + check_device(ctx.device_id); + assert_eq!( + values + .device_id() + .unwrap(), + ctx.device_id, + "Device ids are different in slice and context" + ); + $field_prefix_ident::convert_scalars_montgomery( + unsafe { values.as_mut_ptr() }, + values.len(), + false, + ctx, + ) } } }; @@ -227,5 +245,10 @@ macro_rules! impl_field_tests { fn test_field_convert_montgomery() { check_field_convert_montgomery::<$field_name>() } + + #[test] + fn test_field_equality() { + check_field_equality::<$field_name>() + } }; } diff --git a/wrappers/rust/icicle-core/src/lib.rs b/wrappers/rust/icicle-core/src/lib.rs index d60f86f0..9719bcca 100644 --- a/wrappers/rust/icicle-core/src/lib.rs +++ b/wrappers/rust/icicle-core/src/lib.rs @@ -1,10 +1,11 @@ pub mod curve; +pub mod ecntt; pub mod error; pub mod field; pub mod msm; pub mod ntt; +pub mod polynomials; pub mod poseidon; -#[cfg(feature = "arkworks")] #[doc(hidden)] pub mod tests; pub mod traits; @@ -13,6 +14,6 @@ pub mod vec_ops; pub trait SNARKCurve: curve::Curve + msm::MSM where - ::Config: ntt::NTT, + ::Config: ntt::NTT, { } diff --git a/wrappers/rust/icicle-core/src/msm/mod.rs b/wrappers/rust/icicle-core/src/msm/mod.rs index 4f5a6e40..571a8f53 100644 --- a/wrappers/rust/icicle-core/src/msm/mod.rs +++ b/wrappers/rust/icicle-core/src/msm/mod.rs @@ -1,7 +1,8 @@ use crate::curve::{Affine, Curve, Projective}; use crate::error::IcicleResult; +use icicle_cuda_runtime::device::check_device; use icicle_cuda_runtime::device_context::{DeviceContext, DEFAULT_DEVICE_ID}; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{DeviceSlice, HostOrDeviceSlice}; #[cfg(feature = "arkworks")] #[doc(hidden)] @@ -91,18 +92,18 @@ impl<'a> MSMConfig<'a> { #[doc(hidden)] pub trait MSM { fn msm_unchecked( - scalars: &HostOrDeviceSlice, - points: &HostOrDeviceSlice>, + scalars: &(impl HostOrDeviceSlice + ?Sized), + points: &(impl HostOrDeviceSlice> + ?Sized), cfg: &MSMConfig, - results: &mut HostOrDeviceSlice>, + results: &mut (impl HostOrDeviceSlice> + ?Sized), ) -> IcicleResult<()>; fn precompute_bases_unchecked( - points: &HostOrDeviceSlice>, + points: &(impl HostOrDeviceSlice> + ?Sized), precompute_factor: i32, _c: i32, ctx: &DeviceContext, - output_bases: &mut HostOrDeviceSlice>, + output_bases: &mut DeviceSlice>, ) -> IcicleResult<()>; } @@ -122,10 +123,10 @@ pub trait MSM { /// /// Returns `Ok(())` if no errors occurred or a `CudaError` otherwise. pub fn msm>( - scalars: &HostOrDeviceSlice, - points: &HostOrDeviceSlice>, + scalars: &(impl HostOrDeviceSlice + ?Sized), + points: &(impl HostOrDeviceSlice> + ?Sized), cfg: &MSMConfig, - results: &mut HostOrDeviceSlice>, + results: &mut (impl HostOrDeviceSlice> + ?Sized), ) -> IcicleResult<()> { if points.len() % (cfg.precompute_factor as usize) != 0 { panic!( @@ -149,6 +150,28 @@ pub fn msm>( scalars.len() ); } + let ctx_device_id = cfg + .ctx + .device_id; + if let Some(device_id) = scalars.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in scalars and context are different" + ); + } + if let Some(device_id) = points.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in points and context are different" + ); + } + if let Some(device_id) = results.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in results and context are different" + ); + } + check_device(ctx_device_id); let mut local_cfg = cfg.clone(); local_cfg.points_size = points_size as i32; local_cfg.batch_size = results.len() as i32; @@ -179,11 +202,11 @@ pub fn msm>( /// /// Returns `Ok(())` if no errors occurred or a `CudaError` otherwise. pub fn precompute_bases>( - points: &HostOrDeviceSlice>, + points: &(impl HostOrDeviceSlice> + ?Sized), precompute_factor: i32, _c: i32, ctx: &DeviceContext, - output_bases: &mut HostOrDeviceSlice>, + output_bases: &mut DeviceSlice>, ) -> IcicleResult<()> { assert_eq!( output_bases.len(), @@ -208,7 +231,7 @@ macro_rules! impl_msm { use super::{$curve, Affine, CudaError, Curve, DeviceContext, MSMConfig, Projective}; extern "C" { - #[link_name = concat!($curve_prefix, "MSMCuda")] + #[link_name = concat!($curve_prefix, "_msm_cuda")] pub(crate) fn msm_cuda( scalars: *const <$curve as Curve>::ScalarField, points: *const Affine<$curve>, @@ -217,7 +240,7 @@ macro_rules! impl_msm { out: *mut Projective<$curve>, ) -> CudaError; - #[link_name = concat!($curve_prefix, "PrecomputeMSMBases")] + #[link_name = concat!($curve_prefix, "_precompute_msm_bases_cuda")] pub(crate) fn precompute_bases_cuda( points: *const Affine<$curve>, bases_size: i32, @@ -232,10 +255,10 @@ macro_rules! impl_msm { impl MSM<$curve> for $curve { fn msm_unchecked( - scalars: &HostOrDeviceSlice<<$curve as Curve>::ScalarField>, - points: &HostOrDeviceSlice>, + scalars: &(impl HostOrDeviceSlice<<$curve as Curve>::ScalarField> + ?Sized), + points: &(impl HostOrDeviceSlice> + ?Sized), cfg: &MSMConfig, - results: &mut HostOrDeviceSlice>, + results: &mut (impl HostOrDeviceSlice> + ?Sized), ) -> IcicleResult<()> { unsafe { $curve_prefix_indent::msm_cuda( @@ -250,11 +273,11 @@ macro_rules! impl_msm { } fn precompute_bases_unchecked( - points: &HostOrDeviceSlice>, + points: &(impl HostOrDeviceSlice> + ?Sized), precompute_factor: i32, _c: i32, ctx: &DeviceContext, - output_bases: &mut HostOrDeviceSlice>, + output_bases: &mut DeviceSlice>, ) -> IcicleResult<()> { unsafe { $curve_prefix_indent::precompute_bases_cuda( diff --git a/wrappers/rust/icicle-core/src/msm/tests.rs b/wrappers/rust/icicle-core/src/msm/tests.rs index 5fe6cb3a..02a05537 100644 --- a/wrappers/rust/icicle-core/src/msm/tests.rs +++ b/wrappers/rust/icicle-core/src/msm/tests.rs @@ -2,7 +2,7 @@ use crate::curve::{Affine, Curve, Projective}; use crate::msm::{msm, precompute_bases, MSMConfig, MSM}; use crate::traits::{FieldImpl, GenerateRandom}; use icicle_cuda_runtime::device::{get_device_count, set_device, warmup}; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{DeviceVec, HostSlice}; use icicle_cuda_runtime::stream::CudaStream; use rayon::iter::{IntoParallelIterator, ParallelIterator}; @@ -42,7 +42,7 @@ where set_device(device_id).unwrap(); let test_sizes = [4, 8, 16, 32, 64, 128, 256, 1000, 1 << 18]; - let mut msm_results = HostOrDeviceSlice::cuda_malloc(1).unwrap(); + let mut msm_results = DeviceVec::>::cuda_malloc_for_device(1, device_id).unwrap(); for test_size in test_sizes { let points = generate_random_affine_points_with_zeroes(test_size, 2); let scalars = ::Config::generate_random(test_size); @@ -58,10 +58,10 @@ where // (just beware the possible extra flag in affine point types, can't transmute ark Affine because of that) let scalars_mont = unsafe { &*(&scalars_ark[..] as *const _ as *const [C::ScalarField]) }; - let mut scalars_d = HostOrDeviceSlice::cuda_malloc(test_size).unwrap(); + let mut scalars_d = DeviceVec::::cuda_malloc(test_size).unwrap(); let stream = CudaStream::create().unwrap(); scalars_d - .copy_from_host_async(&scalars_mont, &stream) + .copy_from_host_async(HostSlice::from_slice(&scalars_mont), &stream) .unwrap(); let mut cfg = MSMConfig::default_for_device(device_id); @@ -69,17 +69,23 @@ where .stream = &stream; cfg.is_async = true; cfg.are_scalars_montgomery_form = true; - msm(&scalars_d, &HostOrDeviceSlice::on_host(points), &cfg, &mut msm_results).unwrap(); + msm( + &scalars_d[..], + HostSlice::from_slice(&points), + &cfg, + &mut msm_results[..], + ) + .unwrap(); // need to make sure that scalars_d weren't mutated by the previous call let mut scalars_mont_after = vec![C::ScalarField::zero(); test_size]; scalars_d - .copy_to_host_async(&mut scalars_mont_after, &stream) + .copy_to_host_async(HostSlice::from_mut_slice(&mut scalars_mont_after), &stream) .unwrap(); assert_eq!(scalars_mont, scalars_mont_after); let mut msm_host_result = vec![Projective::::zero(); 1]; msm_results - .copy_to_host(&mut msm_host_result[..]) + .copy_to_host(HostSlice::from_mut_slice(&mut msm_host_result[..])) .unwrap(); stream .synchronize() @@ -118,10 +124,9 @@ where for test_size in test_sizes { let precompute_factor = 8; let points = generate_random_affine_points_with_zeroes(test_size, 10); - let points_h = HostOrDeviceSlice::on_host(points.clone()); - let mut precomputed_points_d = HostOrDeviceSlice::cuda_malloc(precompute_factor * test_size).unwrap(); + let mut precomputed_points_d = DeviceVec::cuda_malloc(precompute_factor * test_size).unwrap(); precompute_bases( - &points_h, + HostSlice::from_slice(&points), precompute_factor as i32, 0, &cfg.ctx, @@ -135,27 +140,27 @@ where .take(batch_size) .flatten() .collect(); - let scalars_h = HostOrDeviceSlice::on_host(scalars); + let scalars_h = HostSlice::from_slice(&scalars); - let mut msm_results_1 = HostOrDeviceSlice::cuda_malloc(batch_size).unwrap(); - let mut msm_results_2 = HostOrDeviceSlice::cuda_malloc(batch_size).unwrap(); - let mut points_d = HostOrDeviceSlice::cuda_malloc(test_size * batch_size).unwrap(); + let mut msm_results_1 = DeviceVec::>::cuda_malloc(batch_size).unwrap(); + let mut msm_results_2 = DeviceVec::>::cuda_malloc(batch_size).unwrap(); + let mut points_d = DeviceVec::>::cuda_malloc(test_size * batch_size).unwrap(); points_d - .copy_from_host_async(&points_cloned, &stream) + .copy_from_host_async(HostSlice::from_slice(&points_cloned), &stream) .unwrap(); cfg.precompute_factor = precompute_factor as i32; - msm(&scalars_h, &precomputed_points_d, &cfg, &mut msm_results_1).unwrap(); + msm(scalars_h, &precomputed_points_d[..], &cfg, &mut msm_results_1[..]).unwrap(); cfg.precompute_factor = 1; - msm(&scalars_h, &points_d, &cfg, &mut msm_results_2).unwrap(); + msm(scalars_h, &points_d[..], &cfg, &mut msm_results_2[..]).unwrap(); let mut msm_host_result_1 = vec![Projective::::zero(); batch_size]; let mut msm_host_result_2 = vec![Projective::::zero(); batch_size]; msm_results_1 - .copy_to_host_async(&mut msm_host_result_1[..], &stream) + .copy_to_host_async(HostSlice::from_mut_slice(&mut msm_host_result_1), &stream) .unwrap(); msm_results_2 - .copy_to_host_async(&mut msm_host_result_2[..], &stream) + .copy_to_host_async(HostSlice::from_mut_slice(&mut msm_host_result_2), &stream) .unwrap(); stream .synchronize() @@ -166,7 +171,6 @@ where .map(|x| x.to_ark()) .collect(); let scalars_ark: Vec<_> = scalars_h - .as_slice() .iter() .map(|x| x.to_ark()) .collect(); @@ -217,17 +221,17 @@ where .map(|x| x.to_ark()) .collect(); - let mut msm_results = HostOrDeviceSlice::on_host(vec![Projective::::zero(); batch_size]); + let mut msm_results = vec![Projective::::zero(); batch_size]; let mut cfg = MSMConfig::default(); if test_size < test_threshold { cfg.bitsize = 1; } msm( - &HostOrDeviceSlice::on_host(scalars), - &HostOrDeviceSlice::on_host(points), + HostSlice::from_slice(&scalars), + HostSlice::from_slice(&points), &cfg, - &mut msm_results, + HostSlice::from_mut_slice(&mut msm_results), ) .unwrap(); @@ -238,7 +242,7 @@ where { let msm_result_ark: ark_ec::models::short_weierstrass::Projective = VariableBaseMSM::msm(&points_chunk, &scalars_chunk).unwrap(); - assert_eq!(msm_results.as_slice()[i].to_ark(), msm_result_ark); + assert_eq!(msm_results[i].to_ark(), msm_result_ark); } } } diff --git a/wrappers/rust/icicle-core/src/ntt/mod.rs b/wrappers/rust/icicle-core/src/ntt/mod.rs index 84796c60..c1df383e 100644 --- a/wrappers/rust/icicle-core/src/ntt/mod.rs +++ b/wrappers/rust/icicle-core/src/ntt/mod.rs @@ -1,7 +1,8 @@ +use icicle_cuda_runtime::device::check_device; use icicle_cuda_runtime::device_context::{DeviceContext, DEFAULT_DEVICE_ID}; use icicle_cuda_runtime::memory::HostOrDeviceSlice; -use crate::{error::IcicleResult, traits::FieldImpl}; +pub use crate::{error::IcicleResult, traits::FieldImpl}; #[cfg(feature = "arkworks")] #[doc(hidden)] @@ -71,7 +72,7 @@ pub enum NttAlgorithm { #[repr(C)] #[derive(Debug, Clone)] pub struct NTTConfig<'a, S> { - /// Details related to the device such as its id and stream id. See [DeviceContext](@ref device_context::DeviceContext). + /// Details related to the device such as its id and stream id. See [DeviceContext](DeviceContext). pub ctx: DeviceContext<'a>, /// Coset generator. Used to perform coset (i)NTTs. Default value: `S::one()` (corresponding to no coset being used). pub coset_gen: S, @@ -79,15 +80,15 @@ pub struct NTTConfig<'a, S> { pub batch_size: i32, /// If true the function will compute the NTTs over the columns of the input matrix and not over the rows. pub columns_batch: bool, - /// Ordering of inputs and outputs. See [Ordering](@ref Ordering). Default value: `Ordering::kNN`. + /// Ordering of inputs and outputs. See [Ordering](Ordering). Default value: `Ordering::kNN`. pub ordering: Ordering, - are_inputs_on_device: bool, - are_outputs_on_device: bool, + pub are_inputs_on_device: bool, + pub are_outputs_on_device: bool, /// Whether to run the NTT asynchronously. If set to `true`, the NTT function will be non-blocking and you'd need to synchronize /// it explicitly by running `stream.synchronize()`. If set to false, the NTT function will block the current CPU thread. pub is_async: bool, /// Explicitly select the NTT algorithm. Default value: Auto (the implementation selects radix-2 or mixed-radix algorithm based - /// on heuristics + /// on heuristics). pub ntt_algorithm: NttAlgorithm, } @@ -114,17 +115,25 @@ impl<'a, S: FieldImpl> NTTConfig<'a, S> { } #[doc(hidden)] -pub trait NTT { +pub trait NTTDomain { + fn get_root_of_unity(max_size: u64) -> F; + fn initialize_domain(primitive_root: F, ctx: &DeviceContext, fast_twiddles: bool) -> IcicleResult<()>; + fn release_domain(ctx: &DeviceContext) -> IcicleResult<()>; +} + +#[doc(hidden)] +pub trait NTT: NTTDomain { fn ntt_unchecked( - input: &HostOrDeviceSlice, + input: &(impl HostOrDeviceSlice + ?Sized), + dir: NTTDir, + cfg: &NTTConfig, + output: &mut (impl HostOrDeviceSlice + ?Sized), + ) -> IcicleResult<()>; + fn ntt_inplace_unchecked( + inout: &mut (impl HostOrDeviceSlice + ?Sized), dir: NTTDir, cfg: &NTTConfig, - output: &mut HostOrDeviceSlice, ) -> IcicleResult<()>; - fn ntt_inplace_unchecked(inout: &mut HostOrDeviceSlice, dir: NTTDir, cfg: &NTTConfig) -> IcicleResult<()>; - fn initialize_domain(primitive_root: F, ctx: &DeviceContext) -> IcicleResult<()>; - fn initialize_domain_fast_twiddles_mode(primitive_root: F, ctx: &DeviceContext) -> IcicleResult<()>; - fn release_domain(ctx: &DeviceContext) -> IcicleResult<()>; } /// Computes the NTT, or a batch of several NTTs. @@ -138,15 +147,15 @@ pub trait NTT { /// * `cfg` - config used to specify extra arguments of the NTT. /// /// * `output` - buffer to write the NTT outputs into. Must be of the same size as `input`. -pub fn ntt( - input: &HostOrDeviceSlice, +pub fn ntt( + input: &(impl HostOrDeviceSlice + ?Sized), dir: NTTDir, cfg: &NTTConfig, - output: &mut HostOrDeviceSlice, + output: &mut (impl HostOrDeviceSlice + ?Sized), ) -> IcicleResult<()> where F: FieldImpl, - ::Config: NTT, + ::Config: NTT, { if input.len() != output.len() { panic!( @@ -155,11 +164,27 @@ where output.len() ); } + let ctx_device_id = cfg + .ctx + .device_id; + if let Some(device_id) = input.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in input and context are different" + ); + } + if let Some(device_id) = output.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in output and context are different" + ); + } + check_device(ctx_device_id); let mut local_cfg = cfg.clone(); local_cfg.are_inputs_on_device = input.is_on_device(); local_cfg.are_outputs_on_device = output.is_on_device(); - <::Config as NTT>::ntt_unchecked(input, dir, &local_cfg, output) + <::Config as NTT>::ntt_unchecked(input, dir, &local_cfg, output) } /// Computes the NTT, or a batch of several NTTs inplace. @@ -171,16 +196,20 @@ where /// * `dir` - whether to compute forward of inverse NTT. /// /// * `cfg` - config used to specify extra arguments of the NTT. -pub fn ntt_inplace(inout: &mut HostOrDeviceSlice, dir: NTTDir, cfg: &NTTConfig) -> IcicleResult<()> +pub fn ntt_inplace( + inout: &mut (impl HostOrDeviceSlice + ?Sized), + dir: NTTDir, + cfg: &NTTConfig, +) -> IcicleResult<()> where F: FieldImpl, - ::Config: NTT, + ::Config: NTT, { let mut local_cfg = cfg.clone(); local_cfg.are_inputs_on_device = inout.is_on_device(); local_cfg.are_outputs_on_device = inout.is_on_device(); - <::Config as NTT>::ntt_inplace_unchecked(inout, dir, &local_cfg) + <::Config as NTT>::ntt_inplace_unchecked(inout, dir, &local_cfg) } /// Generates twiddle factors which will be used to compute NTTs. @@ -192,71 +221,60 @@ where /// This function will panic if the order of `primitive_root` is not a power of two. /// /// * `ctx` - GPU index and stream to perform the computation. -pub fn initialize_domain(primitive_root: F, ctx: &DeviceContext) -> IcicleResult<()> +pub fn initialize_domain(primitive_root: F, ctx: &DeviceContext, fast_twiddles: bool) -> IcicleResult<()> where F: FieldImpl, - ::Config: NTT, + ::Config: NTTDomain, { - <::Config as NTT>::initialize_domain(primitive_root, ctx) -} -pub fn initialize_domain_fast_twiddles_mode(primitive_root: F, ctx: &DeviceContext) -> IcicleResult<()> -where - F: FieldImpl, - ::Config: NTT, -{ - <::Config as NTT>::initialize_domain_fast_twiddles_mode(primitive_root, ctx) + <::Config as NTTDomain>::initialize_domain(primitive_root, ctx, fast_twiddles) } pub fn release_domain(ctx: &DeviceContext) -> IcicleResult<()> where F: FieldImpl, - ::Config: NTT, + ::Config: NTTDomain, { - <::Config as NTT>::release_domain(ctx) + <::Config as NTTDomain>::release_domain(ctx) +} + +pub fn get_root_of_unity(max_size: u64) -> F +where + F: FieldImpl, + ::Config: NTTDomain, +{ + <::Config as NTTDomain>::get_root_of_unity(max_size) } #[macro_export] -macro_rules! impl_ntt { +macro_rules! impl_ntt_without_domain { ( $field_prefix:literal, - $field_prefix_ident:ident, - $field:ident, - $field_config:ident + $domain_field:ident, + $domain_config:ident, + $ntt_type:ident, + $ntt_type_lit:literal, + $inout:ident ) => { - mod $field_prefix_ident { - use crate::ntt::{$field, $field_config, CudaError, DeviceContext, NTTConfig, NTTDir, DEFAULT_DEVICE_ID}; - - extern "C" { - #[link_name = concat!($field_prefix, "NTTCuda")] - pub(crate) fn ntt_cuda( - input: *const $field, - size: i32, - dir: NTTDir, - config: &NTTConfig<$field>, - output: *mut $field, - ) -> CudaError; - - #[link_name = concat!($field_prefix, "InitializeDomain")] - pub(crate) fn initialize_ntt_domain( - primitive_root: &$field, - ctx: &DeviceContext, - fast_twiddles_mode: bool, - ) -> CudaError; - - #[link_name = concat!($field_prefix, "ReleaseDomain")] - pub(crate) fn release_ntt_domain(ctx: &DeviceContext) -> CudaError; - } + extern "C" { + #[link_name = concat!($field_prefix, concat!($ntt_type_lit, "_cuda"))] + fn ntt_cuda( + input: *const $inout, + size: i32, + dir: NTTDir, + config: &NTTConfig<$domain_field>, + output: *mut $inout, + ) -> CudaError; } - impl NTT<$field> for $field_config { + impl $ntt_type<$inout, $domain_field> for $domain_config { fn ntt_unchecked( - input: &HostOrDeviceSlice<$field>, + input: &(impl HostOrDeviceSlice<$inout> + ?Sized), dir: NTTDir, - cfg: &NTTConfig<$field>, - output: &mut HostOrDeviceSlice<$field>, + cfg: &NTTConfig<$domain_field>, + output: &mut (impl HostOrDeviceSlice<$inout> + ?Sized), ) -> IcicleResult<()> { unsafe { - $field_prefix_ident::ntt_cuda( + ntt_cuda( input.as_ptr(), (input.len() / (cfg.batch_size as usize)) as i32, dir, @@ -268,13 +286,13 @@ macro_rules! impl_ntt { } fn ntt_inplace_unchecked( - inout: &mut HostOrDeviceSlice<$field>, + inout: &mut (impl HostOrDeviceSlice<$inout> + ?Sized), dir: NTTDir, - cfg: &NTTConfig<$field>, + cfg: &NTTConfig<$domain_field>, ) -> IcicleResult<()> { unsafe { - $field_prefix_ident::ntt_cuda( - inout.as_ptr(), + ntt_cuda( + inout.as_mut_ptr(), (inout.len() / (cfg.batch_size as usize)) as i32, dir, cfg, @@ -283,16 +301,55 @@ macro_rules! impl_ntt { .wrap() } } + } + }; +} - fn initialize_domain(primitive_root: $field, ctx: &DeviceContext) -> IcicleResult<()> { - unsafe { $field_prefix_ident::initialize_ntt_domain(&primitive_root, ctx, false).wrap() } +#[macro_export] +macro_rules! impl_ntt { + ( + $field_prefix:literal, + $field_prefix_ident:ident, + $field:ident, + $field_config:ident + ) => { + mod $field_prefix_ident { + use crate::ntt::*; + + extern "C" { + #[link_name = concat!($field_prefix, "_initialize_domain")] + fn initialize_ntt_domain( + primitive_root: &$field, + ctx: &DeviceContext, + fast_twiddles_mode: bool, + ) -> CudaError; + + #[link_name = concat!($field_prefix, "_release_domain")] + fn release_ntt_domain(ctx: &DeviceContext) -> CudaError; + + #[link_name = concat!($field_prefix, "_get_root_of_unity")] + fn get_root_of_unity(max_size: u64) -> $field; } - fn initialize_domain_fast_twiddles_mode(primitive_root: $field, ctx: &DeviceContext) -> IcicleResult<()> { - unsafe { $field_prefix_ident::initialize_ntt_domain(&primitive_root, ctx, true).wrap() } - } - fn release_domain(ctx: &DeviceContext) -> IcicleResult<()> { - unsafe { $field_prefix_ident::release_ntt_domain(ctx).wrap() } + + impl NTTDomain<$field> for $field_config { + fn initialize_domain( + primitive_root: $field, + ctx: &DeviceContext, + fast_twiddles: bool, + ) -> IcicleResult<()> { + unsafe { initialize_ntt_domain(&primitive_root, ctx, fast_twiddles).wrap() } + } + + fn release_domain(ctx: &DeviceContext) -> IcicleResult<()> { + unsafe { release_ntt_domain(ctx).wrap() } + } + + fn get_root_of_unity(max_size: u64) -> $field { + unsafe { get_root_of_unity(max_size) } + } } + + impl_ntt_without_domain!($field_prefix, $field, $field_config, NTT, "_ntt", $field); } }; } @@ -308,40 +365,45 @@ macro_rules! impl_ntt_tests { const FAST_TWIDDLES_MODE: bool = false; #[test] + #[parallel] fn test_ntt() { INIT.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)); check_ntt::<$field>() } #[test] + #[parallel] fn test_ntt_coset_from_subgroup() { INIT.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)); check_ntt_coset_from_subgroup::<$field>() } #[test] + #[parallel] fn test_ntt_arbitrary_coset() { INIT.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)); check_ntt_arbitrary_coset::<$field>() } #[test] + #[parallel] fn test_ntt_batch() { INIT.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)); check_ntt_batch::<$field>() } #[test] + #[parallel] fn test_ntt_device_async() { // init_domain is in this test is performed per-device check_ntt_device_async::<$field>() } #[test] + #[serial] fn test_ntt_release_domain() { INIT.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)); - check_release_domain::<$field>(); - *RELEASE.get_or_init(move || init_domain::<$field>(MAX_SIZE, DEFAULT_DEVICE_ID, FAST_TWIDDLES_MODE)) + check_release_domain::<$field>() } }; } diff --git a/wrappers/rust/icicle-core/src/ntt/tests.rs b/wrappers/rust/icicle-core/src/ntt/tests.rs index f9f3bc36..878013ea 100644 --- a/wrappers/rust/icicle-core/src/ntt/tests.rs +++ b/wrappers/rust/icicle-core/src/ntt/tests.rs @@ -3,39 +3,33 @@ use ark_poly::{EvaluationDomain, GeneralEvaluationDomain}; use ark_std::{ops::Neg, test_rng, UniformRand}; use icicle_cuda_runtime::device::{get_device_count, set_device}; use icicle_cuda_runtime::device_context::DeviceContext; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{DeviceVec, HostSlice}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use crate::error::IcicleResult; use crate::{ ntt::{ - initialize_domain, initialize_domain_fast_twiddles_mode, ntt, ntt_inplace, release_domain, NTTDir, - NttAlgorithm, Ordering, + initialize_domain, ntt, ntt_inplace, release_domain, NTTConfig, NTTDir, NTTDomain, NttAlgorithm, Ordering, NTT, }, traits::{ArkConvertible, FieldImpl, GenerateRandom}, vec_ops::{transpose_matrix, VecOps}, }; -use super::{NTTConfig, NTT}; - pub fn init_domain(max_size: u64, device_id: usize, fast_twiddles_mode: bool) where F::ArkEquivalent: FftField, - ::Config: NTT, + ::Config: NTTDomain, { let ctx = DeviceContext::default_for_device(device_id); let ark_rou = F::ArkEquivalent::get_root_of_unity(max_size).unwrap(); - if fast_twiddles_mode { - initialize_domain_fast_twiddles_mode(F::from_ark(ark_rou), &ctx).unwrap(); - } else { - initialize_domain(F::from_ark(ark_rou), &ctx).unwrap(); - } + initialize_domain(F::from_ark(ark_rou), &ctx, fast_twiddles_mode).unwrap(); } -pub fn rel_domain(ctx: &DeviceContext) +pub fn rel_domain(ctx: &DeviceContext) -> IcicleResult<()> where - ::Config: NTT, + ::Config: NTTDomain, { - release_domain::(&ctx).unwrap(); + release_domain::(&ctx) } pub fn reverse_bit_order(n: u32, order: u32) -> u32 { @@ -52,14 +46,6 @@ pub fn reverse_bit_order(n: u32, order: u32) -> u32 { u32::from_str_radix(&reversed, 2).unwrap() } -pub fn transpose_flattened_matrix(m: &[T], nrows: usize) -> Vec { - let ncols = m.len() / nrows; - assert!(nrows * ncols == m.len()); - (0..m.len()) - .map(|i| m[(i % nrows) * ncols + i / nrows]) - .collect() -} - pub fn list_to_reverse_bit_order(l: &[T]) -> Vec { l.iter() .enumerate() @@ -70,7 +56,7 @@ pub fn list_to_reverse_bit_order(l: &[T]) -> Vec { pub fn check_ntt() where F::ArkEquivalent: FftField, - ::Config: NTT + GenerateRandom, + ::Config: NTT + GenerateRandom, { let test_sizes = [1 << 4, 1 << 17]; for test_size in test_sizes { @@ -83,13 +69,14 @@ where .collect::>(); // if we simply transmute arkworks types, we'll get scalars in Montgomery format let scalars_mont = unsafe { &*(&ark_scalars[..] as *const _ as *const [F]) }; - let scalars_mont_h = HostOrDeviceSlice::on_host(scalars_mont.to_vec()); + let scalars_mont_h = HostSlice::from_slice(&scalars_mont); - let mut config = NTTConfig::default(); + let mut config: NTTConfig<'_, F> = NTTConfig::default(); for alg in [NttAlgorithm::Radix2, NttAlgorithm::MixedRadix] { config.ntt_algorithm = alg; - let mut ntt_result = HostOrDeviceSlice::on_host(vec![F::zero(); test_size]); - ntt(&scalars_mont_h, NTTDir::kForward, &config, &mut ntt_result).unwrap(); + let mut ntt_result = vec![F::zero(); test_size]; + let ntt_result = HostSlice::from_mut_slice(&mut ntt_result); + ntt(scalars_mont_h, NTTDir::kForward, &config, ntt_result).unwrap(); assert_ne!(ntt_result.as_slice(), scalars_mont); let mut ark_ntt_result = ark_scalars.clone(); @@ -100,8 +87,14 @@ where unsafe { &*(ntt_result.as_slice() as *const _ as *const [::ArkEquivalent]) }; assert_eq!(ark_ntt_result, ntt_result_as_ark); - let mut intt_result = HostOrDeviceSlice::on_host(vec![F::zero(); test_size]); - ntt(&ntt_result, NTTDir::kInverse, &config, &mut intt_result).unwrap(); + let mut intt_result = vec![F::zero(); test_size]; + ntt( + ntt_result, + NTTDir::kInverse, + &config, + HostSlice::from_mut_slice(&mut intt_result), + ) + .unwrap(); assert_eq!(intt_result.as_slice(), scalars_mont); } @@ -111,7 +104,7 @@ where pub fn check_ntt_coset_from_subgroup() where F::ArkEquivalent: FftField, - ::Config: NTT + GenerateRandom, + ::Config: NTT + GenerateRandom, { let test_sizes = [1 << 4, 1 << 16]; for test_size in test_sizes { @@ -124,7 +117,6 @@ where let ark_large_domain = GeneralEvaluationDomain::::new(test_size).unwrap(); let mut scalars: Vec = F::Config::generate_random(small_size); - let scalars_h = HostOrDeviceSlice::on_host(scalars.clone()); let mut ark_scalars = scalars .iter() .map(|v| v.to_ark()) @@ -134,21 +126,29 @@ where let mut config = NTTConfig::default(); config.ordering = Ordering::kNR; config.ntt_algorithm = alg; - let mut ntt_result_1 = HostOrDeviceSlice::on_host(vec![F::zero(); small_size]); - let mut ntt_result_2 = HostOrDeviceSlice::on_host(vec![F::zero(); small_size]); - ntt(&scalars_h, NTTDir::kForward, &config, &mut ntt_result_1).unwrap(); + let mut ntt_result_1 = vec![F::zero(); small_size]; + let mut ntt_result_2 = vec![F::zero(); small_size]; + let ntt_result_2 = HostSlice::from_mut_slice(&mut ntt_result_2); + let scalars_h = HostSlice::from_slice(&scalars[..small_size]); + ntt( + scalars_h, + NTTDir::kForward, + &config, + HostSlice::from_mut_slice(&mut ntt_result_1), + ) + .unwrap(); assert_ne!(*ntt_result_1.as_slice(), scalars); config.coset_gen = F::from_ark(test_size_rou); - ntt(&scalars_h, NTTDir::kForward, &config, &mut ntt_result_2).unwrap(); - let mut ntt_large_result = HostOrDeviceSlice::on_host(vec![F::zero(); test_size]); + ntt(scalars_h, NTTDir::kForward, &config, ntt_result_2).unwrap(); + let mut ntt_large_result = vec![F::zero(); test_size]; // back to non-coset NTT config.coset_gen = F::one(); scalars.resize(test_size, F::zero()); ntt( - &HostOrDeviceSlice::on_host(scalars.clone()), + HostSlice::from_slice(&scalars), NTTDir::kForward, &config, - &mut ntt_large_result, + HostSlice::from_mut_slice(&mut ntt_large_result), ) .unwrap(); assert_eq!(*ntt_result_1.as_slice(), ntt_large_result.as_slice()[..small_size]); @@ -170,13 +170,18 @@ where config.coset_gen = F::from_ark(test_size_rou); config.ordering = Ordering::kRN; - let mut intt_result = HostOrDeviceSlice::on_host(vec![F::zero(); small_size]); - ntt(&ntt_result_2, NTTDir::kInverse, &config, &mut intt_result).unwrap(); + let mut intt_result = vec![F::zero(); small_size]; + ntt( + ntt_result_2, + NTTDir::kInverse, + &config, + HostSlice::from_mut_slice(&mut intt_result), + ) + .unwrap(); assert_eq!(*intt_result.as_slice(), scalars[..small_size]); ark_small_domain.ifft_in_place(&mut ark_scalars); let intt_result_as_ark = intt_result - .as_slice() .iter() .map(|p| p.to_ark()) .collect::>(); @@ -188,7 +193,7 @@ where pub fn check_ntt_arbitrary_coset() where F::ArkEquivalent: FftField + ArkField, - ::Config: NTT + GenerateRandom, + ::Config: NTT + GenerateRandom, { let mut seed = test_rng(); let test_sizes = [1 << 4, 1 << 17]; @@ -204,10 +209,10 @@ where .get_coset(coset_gen) .unwrap(); - let mut scalars = HostOrDeviceSlice::on_host(F::Config::generate_random(test_size)); + let mut scalars = F::Config::generate_random(test_size); + let scalars = HostSlice::from_mut_slice(&mut scalars); // here you can see how arkworks type can be easily created without any purpose-built conversions let mut ark_scalars = scalars - .as_slice() .iter() .map(|v| F::ArkEquivalent::from_random_bytes(&v.to_bytes_le()).unwrap()) .collect::>(); @@ -217,7 +222,7 @@ where for alg in [NttAlgorithm::Radix2, NttAlgorithm::MixedRadix] { config.ordering = Ordering::kNR; config.ntt_algorithm = alg; - ntt_inplace(&mut scalars, NTTDir::kForward, &config).unwrap(); + ntt_inplace(scalars, NTTDir::kForward, &config).unwrap(); let ark_scalars_copy = ark_scalars.clone(); ark_domain.fft_in_place(&mut ark_scalars); @@ -231,9 +236,8 @@ where assert_eq!(ark_scalars, ark_scalars_copy); config.ordering = Ordering::kRN; - ntt_inplace(&mut scalars, NTTDir::kInverse, &config).unwrap(); + ntt_inplace(scalars, NTTDir::kInverse, &config).unwrap(); let ntt_result_as_ark = scalars - .as_slice() .iter() .map(|p| p.to_ark()) .collect::>(); @@ -245,7 +249,7 @@ where pub fn check_ntt_batch() where - ::Config: NTT + GenerateRandom, + ::Config: NTT + GenerateRandom, ::Config: VecOps, { let test_sizes = [1 << 4, 1 << 12]; @@ -254,7 +258,8 @@ where let coset_generators = [F::one(), F::Config::generate_random(1)[0]]; let mut config = NTTConfig::default(); for batch_size in batch_sizes { - let scalars = HostOrDeviceSlice::on_host(F::Config::generate_random(test_size * batch_size)); + let scalars = F::Config::generate_random(test_size * batch_size); + let scalars = HostSlice::from_slice(&scalars); for coset_gen in coset_generators { for is_inverse in [NTTDir::kInverse, NTTDir::kForward] { @@ -268,19 +273,25 @@ where ] { config.coset_gen = coset_gen; config.ordering = ordering; - let mut batch_ntt_result = HostOrDeviceSlice::on_host(vec![F::zero(); batch_size * test_size]); + let mut batch_ntt_result = vec![F::zero(); batch_size * test_size]; for alg in [NttAlgorithm::Radix2, NttAlgorithm::MixedRadix] { config.batch_size = batch_size as i32; config.ntt_algorithm = alg; - ntt(&scalars, is_inverse, &config, &mut batch_ntt_result).unwrap(); + ntt( + scalars, + is_inverse, + &config, + HostSlice::from_mut_slice(&mut batch_ntt_result), + ) + .unwrap(); config.batch_size = 1; - let mut one_ntt_result = HostOrDeviceSlice::on_host(vec![F::one(); test_size]); + let mut one_ntt_result = vec![F::one(); test_size]; for i in 0..batch_size { ntt( - &HostOrDeviceSlice::on_host(scalars[i * test_size..(i + 1) * test_size].to_vec()), + &scalars[i * test_size..(i + 1) * test_size], is_inverse, &config, - &mut one_ntt_result, + HostSlice::from_mut_slice(&mut one_ntt_result), ) .unwrap(); assert_eq!( @@ -297,25 +308,30 @@ where // for now, columns batching only works with MixedRadix NTT config.batch_size = batch_size as i32; config.columns_batch = true; - let mut transposed_input = HostOrDeviceSlice::on_host(vec![F::zero(); batch_size * test_size]); + let mut transposed_input = vec![F::zero(); batch_size * test_size]; transpose_matrix( - &scalars, + scalars, row_size, column_size, - &mut transposed_input, + HostSlice::from_mut_slice(&mut transposed_input), &config.ctx, on_device, is_async, ) .unwrap(); - let mut col_batch_ntt_result = - HostOrDeviceSlice::on_host(vec![F::zero(); batch_size * test_size]); - ntt(&transposed_input, is_inverse, &config, &mut col_batch_ntt_result).unwrap(); + let mut col_batch_ntt_result = vec![F::zero(); batch_size * test_size]; + ntt( + HostSlice::from_slice(&transposed_input), + is_inverse, + &config, + HostSlice::from_mut_slice(&mut col_batch_ntt_result), + ) + .unwrap(); transpose_matrix( - &col_batch_ntt_result, + HostSlice::from_slice(&col_batch_ntt_result), column_size, row_size, - &mut transposed_input, + HostSlice::from_mut_slice(&mut transposed_input), &config.ctx, on_device, is_async, @@ -333,7 +349,7 @@ where pub fn check_ntt_device_async() where F::ArkEquivalent: FftField, - ::Config: NTT + GenerateRandom, + ::Config: NTT + GenerateRandom, { let device_count = get_device_count().unwrap(); @@ -352,14 +368,14 @@ where .ctx .stream; for batch_size in batch_sizes { - let scalars_h: Vec = F::Config::generate_random(test_size * batch_size); - let sum_of_coeffs: F::ArkEquivalent = scalars_h[..test_size] + let scalars: Vec = F::Config::generate_random(test_size * batch_size); + let sum_of_coeffs: F::ArkEquivalent = scalars[..test_size] .iter() .map(|x| x.to_ark()) .sum(); - let mut scalars_d = HostOrDeviceSlice::cuda_malloc(test_size * batch_size).unwrap(); + let mut scalars_d = DeviceVec::::cuda_malloc(test_size * batch_size).unwrap(); scalars_d - .copy_from_host(&scalars_h) + .copy_from_host(HostSlice::from_slice(&scalars)) .unwrap(); for coset_gen in coset_generators { @@ -374,21 +390,22 @@ where for alg in [NttAlgorithm::Radix2, NttAlgorithm::MixedRadix] { config.ntt_algorithm = alg; let mut ntt_result_h = vec![F::zero(); test_size * batch_size]; - ntt_inplace(&mut scalars_d, NTTDir::kForward, &config).unwrap(); + let mut ntt_result_slice = HostSlice::from_mut_slice(&mut ntt_result_h); + ntt_inplace(&mut *scalars_d, NTTDir::kForward, &config).unwrap(); if coset_gen == F::one() { scalars_d - .copy_to_host(&mut ntt_result_h) + .copy_to_host(ntt_result_slice) .unwrap(); - assert_eq!(sum_of_coeffs, ntt_result_h[0].to_ark()); + assert_eq!(sum_of_coeffs, ntt_result_slice[0].to_ark()); } - ntt_inplace(&mut scalars_d, NTTDir::kInverse, &config).unwrap(); + ntt_inplace(&mut *scalars_d, NTTDir::kInverse, &config).unwrap(); scalars_d - .copy_to_host_async(&mut ntt_result_h, &stream) + .copy_to_host_async(&mut ntt_result_slice, &stream) .unwrap(); stream .synchronize() .unwrap(); - assert_eq!(scalars_h, ntt_result_h); + assert_eq!(scalars, *ntt_result_h.as_slice()); } } } @@ -397,11 +414,11 @@ where }); } -pub fn check_release_domain() +pub fn check_release_domain() where - F::ArkEquivalent: FftField, - ::Config: NTT + GenerateRandom, + ::Config: NTTDomain, { let config: NTTConfig<'static, F> = NTTConfig::default(); - rel_domain::(&config.ctx); + let err = rel_domain::(&config.ctx); + assert!(err.is_ok()) } diff --git a/wrappers/rust/icicle-core/src/polynomials/mod.rs b/wrappers/rust/icicle-core/src/polynomials/mod.rs new file mode 100644 index 00000000..8cad36de --- /dev/null +++ b/wrappers/rust/icicle-core/src/polynomials/mod.rs @@ -0,0 +1,814 @@ +use crate::traits::{FieldConfig, FieldImpl}; +use icicle_cuda_runtime::memory::HostOrDeviceSlice; + +pub trait UnivariatePolynomial +where + Self::Field: FieldImpl, + Self::FieldConfig: FieldConfig, +{ + type Field; + type FieldConfig; + + fn from_coeffs + ?Sized>(coeffs: &S, size: usize) -> Self; + fn from_rou_evals + ?Sized>(evals: &S, size: usize) -> Self; + fn divide(&self, denominator: &Self) -> (Self, Self) + where + Self: Sized; + fn div_by_vanishing(&self, degree: u64) -> Self; + fn add_monomial_inplace(&mut self, monomial_coeff: &Self::Field, monomial: u64); + fn sub_monomial_inplace(&mut self, monomial_coeff: &Self::Field, monomial: u64); + fn slice(&self, offset: u64, stride: u64, size: u64) -> Self; + fn even(&self) -> Self; + fn odd(&self) -> Self; + fn eval(&self, x: &Self::Field) -> Self::Field; + fn degree(&self) -> i64; + fn eval_on_domain + ?Sized, E: HostOrDeviceSlice + ?Sized>( + &self, + domain: &D, + evals: &mut E, + ); + fn get_nof_coeffs(&self) -> u64; + fn get_coeff(&self, idx: u64) -> Self::Field; + fn copy_coeffs + ?Sized>(&self, start_idx: u64, coeffs: &mut S); +} + +#[macro_export] +macro_rules! impl_univariate_polynomial_api { + ( + $field_prefix:literal, + $field_prefix_ident:ident, + $field:ident, + $field_cfg:ident + ) => { + use icicle_core::{polynomials::UnivariatePolynomial, traits::FieldImpl}; + use icicle_cuda_runtime::memory::{DeviceSlice, HostOrDeviceSlice}; + use std::{ + clone, cmp, + ffi::c_void, + ops::{Add, AddAssign, Div, Mul, Rem, Sub}, + ptr, slice, + }; + + type PolynomialHandle = *const c_void; + + extern "C" { + #[link_name = concat!($field_prefix, "_polynomial_init_cuda_backend")] + fn init_cuda_backend() -> bool; + + #[link_name = concat!($field_prefix, "_polynomial_create_from_coefficients")] + fn create_from_coeffs(coeffs: *const $field, size: usize) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_create_from_rou_evaluations")] + fn create_from_rou_evals(coeffs: *const $field, size: usize) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_clone")] + fn clone(p: PolynomialHandle) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_delete")] + fn delete(ptr: PolynomialHandle); + + #[link_name = concat!($field_prefix, "_polynomial_print")] + fn print(ptr: PolynomialHandle); + + #[link_name = concat!($field_prefix, "_polynomial_add")] + fn add(a: PolynomialHandle, b: PolynomialHandle) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_add_inplace")] + fn add_inplace(a: PolynomialHandle, b: PolynomialHandle) -> c_void; + + #[link_name = concat!($field_prefix, "_polynomial_subtract")] + fn subtract(a: PolynomialHandle, b: PolynomialHandle) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_multiply")] + fn multiply(a: PolynomialHandle, b: PolynomialHandle) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_multiply_by_scalar")] + fn multiply_by_scalar(a: PolynomialHandle, b: &$field) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_quotient")] + fn quotient(a: PolynomialHandle, b: PolynomialHandle) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_remainder")] + fn remainder(a: PolynomialHandle, b: PolynomialHandle) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_division")] + fn divide(a: PolynomialHandle, b: PolynomialHandle, q: *mut PolynomialHandle, r: *mut PolynomialHandle); + + #[link_name = concat!($field_prefix, "_polynomial_divide_by_vanishing")] + fn div_by_vanishing(a: PolynomialHandle, deg: u64) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_add_monomial_inplace")] + fn add_monomial_inplace(a: PolynomialHandle, monomial_coeff: &$field, monomial: u64) -> c_void; + + #[link_name = concat!($field_prefix, "_polynomial_sub_monomial_inplace")] + fn sub_monomial_inplace(a: PolynomialHandle, monomial_coeff: &$field, monomial: u64) -> c_void; + + #[link_name = concat!($field_prefix, "_polynomial_slice")] + fn slice(a: PolynomialHandle, offset: u64, stride: u64, size: u64) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_even")] + fn even(a: PolynomialHandle) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_odd")] + fn odd(a: PolynomialHandle) -> PolynomialHandle; + + #[link_name = concat!($field_prefix, "_polynomial_evaluate_on_domain")] + fn eval_on_domain(a: PolynomialHandle, domain: *const $field, domain_size: u64, evals: *mut $field); + + #[link_name = concat!($field_prefix, "_polynomial_degree")] + fn degree(a: PolynomialHandle) -> i64; + + #[link_name = concat!($field_prefix, "_polynomial_copy_coeffs_range")] + fn copy_coeffs(a: PolynomialHandle, host_coeffs: *mut $field, start_idx: u64, end_idx: u64) -> u64; + + #[link_name = concat!($field_prefix, "_polynomial_get_coeffs_raw_ptr")] + fn get_coeffs_ptr(a: PolynomialHandle, len: *mut u64, device_id: *mut u64) -> *mut $field; + } + + pub struct DensePolynomial { + handle: PolynomialHandle, + } + + impl DensePolynomial { + pub fn init_cuda_backend() -> bool { + unsafe { init_cuda_backend() } + } + + // TODO Yuval: implement Display trait + pub fn print(&self) { + unsafe { + print(self.handle); + } + } + + pub fn coeffs_mut_slice(&mut self) -> &mut DeviceSlice<$field> { + unsafe { + let mut len: u64 = 0; + let mut device_id: u64 = 0; + let mut coeffs_mut = get_coeffs_ptr(self.handle, &mut len, &mut device_id); + let s = slice::from_raw_parts_mut(coeffs_mut, len as usize); + DeviceSlice::from_mut_slice(s) + } + } + } + + impl UnivariatePolynomial for DensePolynomial { + type Field = $field; + type FieldConfig = $field_cfg; + + fn from_coeffs + ?Sized>(coeffs: &S, size: usize) -> Self { + unsafe { + DensePolynomial { + handle: create_from_coeffs(coeffs.as_ptr(), size), + } + } + } + + fn from_rou_evals + ?Sized>(evals: &S, size: usize) -> Self { + unsafe { + Self { + handle: create_from_rou_evals(evals.as_ptr(), size), + } + } + } + + fn divide(&self, denominator: &Self) -> (Self, Self) { + let mut q_handle: PolynomialHandle = std::ptr::null_mut(); + let mut r_handle: PolynomialHandle = std::ptr::null_mut(); + unsafe { + divide(self.handle, denominator.handle, &mut q_handle, &mut r_handle); + } + (Self { handle: q_handle }, Self { handle: r_handle }) + } + + fn div_by_vanishing(&self, degree: u64) -> Self { + unsafe { + Self { + handle: div_by_vanishing(self.handle, degree), + } + } + } + + fn add_monomial_inplace(&mut self, monomial_coeff: &Self::Field, monomial: u64) { + unsafe { + add_monomial_inplace(self.handle, monomial_coeff, monomial); + } + } + + fn sub_monomial_inplace(&mut self, monomial_coeff: &Self::Field, monomial: u64) { + unsafe { + sub_monomial_inplace(self.handle, monomial_coeff, monomial); + } + } + + fn slice(&self, offset: u64, stride: u64, size: u64) -> Self { + unsafe { + Self { + handle: slice(self.handle, offset, stride, size), + } + } + } + + fn even(&self) -> Self { + unsafe { + Self { + handle: even(self.handle), + } + } + } + + fn odd(&self) -> Self { + unsafe { + Self { + handle: odd(self.handle), + } + } + } + + fn eval(&self, x: &Self::Field) -> Self::Field { + let mut eval = Self::Field::zero(); + unsafe { + eval_on_domain(self.handle, x, 1, &mut eval); + } + eval + } + + fn eval_on_domain< + D: HostOrDeviceSlice + ?Sized, + E: HostOrDeviceSlice + ?Sized, + >( + &self, + domain: &D, + evals: &mut E, + ) { + assert!( + domain.len() <= evals.len(), + "eval_on_domain(): eval size must not be smaller then domain" + ); + unsafe { + eval_on_domain( + self.handle, + domain.as_ptr(), + domain.len() as u64, + evals.as_mut_ptr(), + ); + } + } + + fn get_nof_coeffs(&self) -> u64 { + unsafe { + // returns total #coeffs. Not copying when null + let nof_coeffs = copy_coeffs(self.handle, std::ptr::null_mut(), 0, 0); + nof_coeffs + } + } + + fn get_coeff(&self, idx: u64) -> Self::Field { + let mut coeff: Self::Field = Self::Field::zero(); + unsafe { copy_coeffs(self.handle, &mut coeff, idx, idx) }; + coeff + } + + fn copy_coeffs + ?Sized>(&self, start_idx: u64, coeffs: &mut S) { + let coeffs_len = coeffs.len() as u64; + let nof_coeffs = self.get_nof_coeffs(); + let end_idx = cmp::min(nof_coeffs, start_idx + coeffs_len - 1); + + unsafe { + copy_coeffs(self.handle, coeffs.as_mut_ptr(), start_idx, end_idx); + } + } + + fn degree(&self) -> i64 { + unsafe { degree(self.handle) } + } + } + + impl Drop for DensePolynomial { + fn drop(&mut self) { + unsafe { + delete(self.handle); + } + } + } + + impl Clone for DensePolynomial { + fn clone(&self) -> Self { + unsafe { + DensePolynomial { + handle: clone(self.handle), + } + } + } + } + + impl Add for &DensePolynomial { + type Output = DensePolynomial; + + fn add(self: Self, rhs: Self) -> Self::Output { + unsafe { + DensePolynomial { + handle: add(self.handle, rhs.handle), + } + } + } + } + + impl AddAssign<&DensePolynomial> for DensePolynomial { + fn add_assign(&mut self, other: &DensePolynomial) { + unsafe { add_inplace(self.handle, other.handle) }; + } + } + + impl Sub for &DensePolynomial { + type Output = DensePolynomial; + + fn sub(self: Self, rhs: Self) -> Self::Output { + unsafe { + DensePolynomial { + handle: subtract(self.handle, rhs.handle), + } + } + } + } + + impl Mul for &DensePolynomial { + type Output = DensePolynomial; + + fn mul(self: Self, rhs: Self) -> Self::Output { + unsafe { + DensePolynomial { + handle: multiply(self.handle, rhs.handle), + } + } + } + } + + // poly * scalar + impl Mul<&$field> for &DensePolynomial { + type Output = DensePolynomial; + + fn mul(self: Self, rhs: &$field) -> Self::Output { + unsafe { + DensePolynomial { + handle: multiply_by_scalar(self.handle, rhs), + } + } + } + } + + // scalar * poly + impl Mul<&DensePolynomial> for &$field { + type Output = DensePolynomial; + + fn mul(self, rhs: &DensePolynomial) -> Self::Output { + unsafe { + DensePolynomial { + handle: multiply_by_scalar(rhs.handle, self), + } + } + } + } + + impl Div for &DensePolynomial { + type Output = DensePolynomial; + + fn div(self: Self, rhs: Self) -> Self::Output { + unsafe { + DensePolynomial { + handle: quotient(self.handle, rhs.handle), + } + } + } + } + + impl Rem for &DensePolynomial { + type Output = DensePolynomial; + + fn rem(self: Self, rhs: Self) -> Self::Output { + unsafe { + DensePolynomial { + handle: remainder(self.handle, rhs.handle), + } + } + } + } + }; +} + +#[macro_export] +macro_rules! impl_polynomial_tests { + ( + $field_prefix_ident:ident, + $field:ident + ) => { + use super::*; + use icicle_core::ntt::{get_root_of_unity, initialize_domain, release_domain, NTTDomain}; + use icicle_core::vec_ops::{add_scalars, mul_scalars, sub_scalars, VecOps, VecOpsConfig}; + use icicle_cuda_runtime::device_context::DeviceContext; + use icicle_cuda_runtime::memory::{DeviceVec, HostSlice}; + use std::sync::Once; + + use icicle_core::traits::{FieldImpl, GenerateRandom}; + + type Poly = DensePolynomial; + + fn init_domain(max_size: u64, ctx: &DeviceContext, fast_twiddles_mode: bool) + where + ::Config: NTTDomain, + { + let rou: F = get_root_of_unity(max_size); + initialize_domain(rou, &ctx, fast_twiddles_mode).unwrap(); + } + + fn rel_domain(ctx: &DeviceContext) + where + ::Config: NTTDomain, + { + release_domain::(&ctx).unwrap() + } + + fn randomize_coeffs(size: usize) -> Vec + where + ::Config: GenerateRandom, + { + let coeffs = F::Config::generate_random(size); + coeffs + } + + fn rand() -> $field { + let r = randomize_coeffs::<$field>(1); + // let coeffs = $field::Config::generate_random(1); + r[0] + } + + // Note: implementing field arithmetic (+,-,*) for fields via vec_ops since they are not implemented on host + fn add(a: &$field, b: &$field) -> $field { + let a = [a.clone()]; + let b = [b.clone()]; + let mut result = [$field::zero()]; + + let cfg = VecOpsConfig::default(); + add_scalars( + HostSlice::from_slice(&a), + HostSlice::from_slice(&b), + HostSlice::from_mut_slice(&mut result), + &cfg, + ) + .unwrap(); + result[0] + } + + fn sub(a: &$field, b: &$field) -> $field { + let a = [a.clone()]; + let b = [b.clone()]; + let mut result = [$field::zero()]; + + let cfg = VecOpsConfig::default(); + sub_scalars( + HostSlice::from_slice(&a), + HostSlice::from_slice(&b), + HostSlice::from_mut_slice(&mut result), + &cfg, + ) + .unwrap(); + result[0] + } + + fn mul(a: &$field, b: &$field) -> $field { + let a = [a.clone()]; + let b = [b.clone()]; + let mut result = [$field::zero()]; + + let cfg = VecOpsConfig::default(); + mul_scalars( + HostSlice::from_slice(&a), + HostSlice::from_slice(&b), + HostSlice::from_mut_slice(&mut result), + &cfg, + ) + .unwrap(); + result[0] + } + + fn randomize_poly(size: usize) -> Poly { + let coeffs = randomize_coeffs::<$field>(size); + let p = Poly::from_coeffs(HostSlice::from_slice(&coeffs), size); + p + } + + static INIT: Once = Once::new(); + pub fn setup() -> () { + INIT.call_once(|| { + let device_id: usize = 0; + // using logn=20 since babybear NTT tests is using it and I don't want order of tests to be a problem + let domain_max_size: u64 = 1 << 20; // + // TODO Yuval: how to consolidate this with NTT tests and avoid releaseDomain being called too early??? + let ctx = DeviceContext::default_for_device(device_id); + init_domain::(domain_max_size, &ctx, false /*=fast twiddle */); + + Poly::init_cuda_backend(); + }); + } + + // Note: tests are marked with #[ignore] since they conflict with NTT tests domain. This is a (hopefully temporary) workaround. + // The poly tests are executed via 'cargo test -- --ignored' as an additional step + + #[test] + #[ignore] + fn test_poly_eval() { + setup(); + + // testing correct evaluation of f(8) for f(x)=4x^2+2x+5 + let coeffs = [$field::from_u32(5), $field::from_u32(2), $field::from_u32(4)]; + let f = Poly::from_coeffs(HostSlice::from_slice(&coeffs), coeffs.len()); + let x = $field::from_u32(8); + let f_x = f.eval(&x); + assert_eq!(f_x, $field::from_u32(277)); + } + + #[test] + #[ignore] + fn test_poly_clone() { + setup(); + + // testing that the clone g(x) is independent of f(x) and cloned correctly + let mut f = randomize_poly(8); + let x = rand(); + let fx = f.eval(&x); + + let g = f.clone(); + f += &g; + + let gx = g.eval(&x); + let new_fx = f.eval(&x); + + assert_eq!(fx, gx); // cloned correctly + assert_eq!(add(&fx, &gx), new_fx); + } + + #[test] + #[ignore] + fn test_poly_add_sub_mul() { + setup(); + + // testing add/sub operations + let size = 1 << 10; + let mut f = randomize_poly(size); + let mut g = randomize_poly(size); + + let x = rand(); + let fx = f.eval(&x); + let gx = g.eval(&x); + + let poly_add = &f + &g; + let poly_sub = &f - &g; + let poly_mul = &f * &g; + + assert_eq!(poly_add.eval(&x), add(&fx, &gx)); + assert_eq!(poly_sub.eval(&x), sub(&fx, &gx)); + assert_eq!(poly_mul.eval(&x), mul(&fx, &gx)); + + // test scalar multiplication + let s1 = rand(); + let s2 = rand(); + let poly_mul_s1 = &f * &s1; + let poly_mul_s2 = &s2 * &f; + assert_eq!(poly_mul_s1.eval(&x), mul(&fx, &s1)); + assert_eq!(poly_mul_s2.eval(&x), mul(&fx, &s2)); + + // test inplace add + f += &g; + assert_eq!(f.eval(&x), add(&fx, &gx)); + } + + #[test] + #[ignore] + fn test_poly_monomials() { + setup(); + + // testing add/sub monomials inplace + let zero = $field::from_u32(0); + let one = $field::from_u32(1); + let two = $field::from_u32(2); + let three = $field::from_u32(3); + + // f(x) = 1+2x^2 + let coeffs = [one, zero, two]; + let mut f = Poly::from_coeffs(HostSlice::from_slice(&coeffs), coeffs.len()); + let x = rand(); + let fx = f.eval(&x); + + f.add_monomial_inplace(&three, 1); // +3x + let fx_add = f.eval(&x); + assert_eq!(fx_add, add(&fx, &mul(&three, &x))); + + f.sub_monomial_inplace(&one, 0); // -1 + let fx_sub = f.eval(&x); + assert_eq!(fx_sub, sub(&fx_add, &one)); + } + + #[test] + #[ignore] + fn test_poly_read_coeffs() { + setup(); + + let zero = $field::from_u32(0); + let one = $field::from_u32(1); + let two = $field::from_u32(2); + let three = $field::from_u32(3); + let four = $field::from_u32(4); + + let coeffs = [one, two, three, four]; + let mut f = Poly::from_coeffs(HostSlice::from_slice(&coeffs), coeffs.len()); + + // read coeffs to host memory + let mut host_mem = vec![$field::zero(); coeffs.len()]; + f.copy_coeffs(0, HostSlice::from_mut_slice(&mut host_mem)); + assert_eq!(host_mem, coeffs); + + // read coeffs to device memory + let mut device_mem = DeviceVec::<$field>::cuda_malloc(coeffs.len()).unwrap(); + f.copy_coeffs(0, &mut device_mem[..]); + let mut host_coeffs_from_dev = vec![ScalarField::zero(); coeffs.len() as usize]; + device_mem + .copy_to_host(HostSlice::from_mut_slice(&mut host_coeffs_from_dev)) + .unwrap(); + + assert_eq!(host_mem, host_coeffs_from_dev); + + // multiply by two and read single coeff + f = &f * &two; + // read single coeff + let x_squared_coeff = f.get_coeff(2); + assert_eq!(x_squared_coeff, mul(&two, &three)); + } + + #[test] + #[ignore] + fn test_poly_division() { + setup(); + + // divide f(x)/g(x), compute q(x), r(x) and check f(x)=q(x)*g(x)+r(x) + + let f = randomize_poly(1 << 12); + let g = randomize_poly(1 << 4); + + let (q, r) = f.divide(&g); + + let f_reconstructed = &(&q * &g) + &r; + let x = rand(); + + assert_eq!(f.eval(&x), f_reconstructed.eval(&x)); + } + + #[test] + #[ignore] + fn test_poly_divide_by_vanishing() { + setup(); + + let zero = $field::from_u32(0); + let one = $field::from_u32(1); + let minus_one = sub(&zero, &one); + // compute random f(x) and compute f(x)*v(x) for v(x) vanishing poly + // divide by vanishing and check that f(x) is reconstructed + + let f = randomize_poly(1 << 12); + let v_coeffs = [minus_one, zero, zero, zero, one]; // x^4-1 + let v = Poly::from_coeffs(HostSlice::from_slice(&v_coeffs), v_coeffs.len()); + + let fv = &f * &v; + let deg_f = f.degree(); + let deg_fv = fv.degree(); + assert_eq!(deg_f + 4, deg_fv); + + let f_reconstructed = fv.div_by_vanishing(4); + assert_eq!(deg_f, f_reconstructed.degree()); + + let x = rand(); + assert_eq!(f.eval(&x), f_reconstructed.eval(&x)); + } + + #[test] + #[ignore] + fn test_poly_eval_on_domain() { + setup(); + + let one = $field::from_u32(1); + let two = $field::from_u32(2); + let three = $field::from_u32(3); + + let f = randomize_poly(1 << 12); + let domain = [one, two, three]; + + // evaluate to host memory + let mut host_evals = vec![ScalarField::zero(); domain.len()]; + f.eval_on_domain( + HostSlice::from_slice(&domain), + HostSlice::from_mut_slice(&mut host_evals), + ); + + // check eval on domain agrees with eval() method + assert_eq!(f.eval(&one), host_evals[0]); + assert_eq!(f.eval(&two), host_evals[1]); + assert_eq!(f.eval(&three), host_evals[2]); + + // evaluate to device memory + let mut device_evals = DeviceVec::::cuda_malloc(domain.len()).unwrap(); + f.eval_on_domain(HostSlice::from_slice(&domain), &mut device_evals[..]); + let mut host_evals_from_device = vec![ScalarField::zero(); domain.len()]; + device_evals + .copy_to_host(HostSlice::from_mut_slice(&mut host_evals_from_device)) + .unwrap(); + + // check that evaluation to device memory is equivalent + assert_eq!(host_evals, host_evals_from_device); + + // use evals as domain (on device) and evaluate from device to host + f.eval_on_domain(&mut device_evals[..], HostSlice::from_mut_slice(&mut host_evals)); + // check that the evaluations are correct + assert_eq!(f.eval(&host_evals_from_device[0]), host_evals[0]); + assert_eq!(f.eval(&host_evals_from_device[1]), host_evals[1]); + assert_eq!(f.eval(&host_evals_from_device[2]), host_evals[2]); + } + + #[test] + #[ignore] + fn test_odd_even_slicing() { + setup(); + let size = (1 << 10) - 3; + // slicing even and odd parts and checking + let f = randomize_poly(size); + let x = rand(); + + let even = f.even(); + let odd = f.odd(); + assert_eq!(f.degree(), even.degree() + odd.degree() + 1); + + // computing even(x) and odd(x) directly + let expected_even = (0..=f.degree()) + .filter(|&i| i % 2 == 0) + .rev() + .fold($field::zero(), |acc, i| { + add(&mul(&acc, &x), &f.get_coeff(i as u64)) + }); + + let expected_odd = (0..=f.degree()) + .filter(|&i| i % 2 != 0) + .rev() + .fold($field::zero(), |acc, i| { + add(&mul(&acc, &x), &f.get_coeff(i as u64)) + }); + + // check that even(x) and odd(x) compute correctly + + let evenx = even.eval(&x); + let oddx = odd.eval(&x); + assert_eq!(expected_even, evenx); + assert_eq!(expected_odd, oddx); + } + + use icicle_core::ntt::{ntt, ntt_inplace, NTTConfig, NTTDir, Ordering}; + + #[test] + #[ignore] + fn test_coeffs_slice() { + setup(); + + let size = 4; + let coeffs = randomize_coeffs::<$field>(size); + let mut f = Poly::from_coeffs(HostSlice::from_slice(&coeffs), size); + + // take a mutable coeffs slice as a DeviceSlice + let coeffs_slice_dev = f.coeffs_mut_slice(); + assert_eq!(coeffs_slice_dev.len(), size); + assert!(coeffs_slice_dev.is_on_device()); + + // let g = &f + &f; // cannot borrow here since s is a mutable slice of f + + // copy to host and check equality + let mut coeffs_copied_from_slice = vec![ScalarField::zero(); coeffs_slice_dev.len()]; + coeffs_slice_dev + .copy_to_host(HostSlice::from_mut_slice(&mut coeffs_copied_from_slice)) + .unwrap(); + assert_eq!(coeffs_copied_from_slice, coeffs); + + // or can use the memory directly + let mut config: NTTConfig<'_, $field> = NTTConfig::default(); + let mut ntt_result = vec![$field::zero(); coeffs_slice_dev.len()]; + ntt( + coeffs_slice_dev, + NTTDir::kForward, + &config, + HostSlice::from_mut_slice(&mut ntt_result), + ) + .unwrap(); + // ntt[0] is f(one) because it's the sum of coeffs + assert_eq!(ntt_result[0], f.eval(&$field::one())); + + // after last use of coeffs_slice_dev, can borrow f again + let g = &f * &f; + assert_eq!(mul(&ntt_result[0], &ntt_result[0]), g.eval(&$field::one())); + } + }; +} diff --git a/wrappers/rust/icicle-core/src/poseidon/mod.rs b/wrappers/rust/icicle-core/src/poseidon/mod.rs index 49cc0e94..3722986a 100644 --- a/wrappers/rust/icicle-core/src/poseidon/mod.rs +++ b/wrappers/rust/icicle-core/src/poseidon/mod.rs @@ -2,14 +2,14 @@ pub mod tests; use icicle_cuda_runtime::{ + device::check_device, device_context::{DeviceContext, DEFAULT_DEVICE_ID}, - memory::HostOrDeviceSlice, + memory::{DeviceSlice, HostOrDeviceSlice}, }; use crate::{error::IcicleResult, traits::FieldImpl}; #[repr(C)] -#[derive(Debug, Clone)] pub struct PoseidonConstants<'a, F: FieldImpl> { arity: u32, @@ -18,10 +18,10 @@ pub struct PoseidonConstants<'a, F: FieldImpl> { full_rounds_half: u32, /// These should be pointers to data allocated on device - round_constants: &'a [F], - mds_matrix: &'a [F], - non_sparse_matrix: &'a [F], - sparse_matrices: &'a [F], + round_constants: &'a DeviceSlice, + mds_matrix: &'a DeviceSlice, + non_sparse_matrix: &'a DeviceSlice, + sparse_matrices: &'a DeviceSlice, /// Domain tag is the first element in the Poseidon state. /// For the Merkle tree mode it should equal 2^arity - 1 @@ -88,8 +88,8 @@ pub trait Poseidon { ) -> IcicleResult>; fn load_optimized_constants<'a>(arity: u32, ctx: &DeviceContext) -> IcicleResult>; fn poseidon_unchecked( - input: &mut HostOrDeviceSlice, - output: &mut HostOrDeviceSlice, + input: &mut (impl HostOrDeviceSlice + ?Sized), + output: &mut (impl HostOrDeviceSlice + ?Sized), number_of_states: u32, arity: u32, constants: &PoseidonConstants, @@ -146,8 +146,8 @@ where /// /// * `config` - config used to specify extra arguments of the Poseidon. pub fn poseidon_hash_many( - input: &mut HostOrDeviceSlice, - output: &mut HostOrDeviceSlice, + input: &mut (impl HostOrDeviceSlice + ?Sized), + output: &mut (impl HostOrDeviceSlice + ?Sized), number_of_states: u32, arity: u32, constants: &PoseidonConstants, @@ -179,6 +179,22 @@ where ); } + let ctx_device_id = config + .ctx + .device_id; + if let Some(device_id) = input.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in input and context are different" + ); + } + if let Some(device_id) = output.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in output and context are different" + ); + } + check_device(ctx_device_id); let mut local_cfg = config.clone(); local_cfg.are_inputs_on_device = input.is_on_device(); local_cfg.are_outputs_on_device = output.is_on_device(); @@ -204,7 +220,7 @@ macro_rules! impl_poseidon { mod $field_prefix_ident { use crate::poseidon::{$field, $field_config, CudaError, DeviceContext, PoseidonConfig, PoseidonConstants}; extern "C" { - #[link_name = concat!($field_prefix, "CreateOptimizedPoseidonConstants")] + #[link_name = concat!($field_prefix, "_create_optimized_poseidon_constants_cuda")] pub(crate) fn _create_optimized_constants( arity: u32, full_rounds_half: u32, @@ -214,14 +230,14 @@ macro_rules! impl_poseidon { poseidon_constants: *mut PoseidonConstants<$field>, ) -> CudaError; - #[link_name = concat!($field_prefix, "InitOptimizedPoseidonConstants")] + #[link_name = concat!($field_prefix, "_init_optimized_poseidon_constants_cuda")] pub(crate) fn _load_optimized_constants( arity: u32, ctx: &DeviceContext, constants: *mut PoseidonConstants<$field>, ) -> CudaError; - #[link_name = concat!($field_prefix, "PoseidonHash")] + #[link_name = concat!($field_prefix, "_poseidon_hash_cuda")] pub(crate) fn hash_many( input: *mut $field, output: *mut $field, @@ -268,8 +284,8 @@ macro_rules! impl_poseidon { } fn poseidon_unchecked( - input: &mut HostOrDeviceSlice<$field>, - output: &mut HostOrDeviceSlice<$field>, + input: &mut (impl HostOrDeviceSlice<$field> + ?Sized), + output: &mut (impl HostOrDeviceSlice<$field> + ?Sized), number_of_states: u32, arity: u32, constants: &PoseidonConstants<$field>, diff --git a/wrappers/rust/icicle-core/src/poseidon/tests.rs b/wrappers/rust/icicle-core/src/poseidon/tests.rs index 9225bb9a..4508a8b5 100644 --- a/wrappers/rust/icicle-core/src/poseidon/tests.rs +++ b/wrappers/rust/icicle-core/src/poseidon/tests.rs @@ -1,6 +1,6 @@ use crate::traits::FieldImpl; use icicle_cuda_runtime::device_context::DeviceContext; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{HostOrDeviceSlice, HostSlice}; use std::io::Read; use std::path::PathBuf; @@ -26,16 +26,16 @@ where { let test_size = 1 << 10; let arity = 2u32; - let inputs = vec![F::one(); test_size * arity as usize]; - let outputs = vec![F::zero(); test_size]; + let mut inputs = vec![F::one(); test_size * arity as usize]; + let mut outputs = vec![F::zero(); test_size]; - let mut input_slice = HostOrDeviceSlice::on_host(inputs); - let mut output_slice = HostOrDeviceSlice::on_host(outputs); + let input_slice = HostSlice::from_mut_slice(&mut inputs); + let output_slice = HostSlice::from_mut_slice(&mut outputs); let config = PoseidonConfig::default(); poseidon_hash_many::( - &mut input_slice, - &mut output_slice, + input_slice, + output_slice, test_size as u32, arity as u32, &constants, @@ -43,8 +43,8 @@ where ) .unwrap(); - let a1 = output_slice[0..1][0]; - let a2 = output_slice[output_slice.len() - 2..output_slice.len() - 1][0]; + let a1 = output_slice[0]; + let a2 = output_slice[output_slice.len() - 2]; println!("first: {:?}, last: {:?}", a1, a2); assert_eq!(a1, a2); diff --git a/wrappers/rust/icicle-core/src/tests.rs b/wrappers/rust/icicle-core/src/tests.rs index 86d3b0a8..1ea65ad1 100644 --- a/wrappers/rust/icicle-core/src/tests.rs +++ b/wrappers/rust/icicle-core/src/tests.rs @@ -7,9 +7,13 @@ use crate::{ }; #[cfg(feature = "arkworks")] use ark_ec::short_weierstrass::{Affine as ArkAffine, Projective as ArkProjective}; -use icicle_cuda_runtime::{error::CudaResultWrap, memory::HostOrDeviceSlice}; +use icicle_cuda_runtime::{ + device_context::DeviceContext, + error::CudaResultWrap, + memory::{DeviceVec, HostSlice}, +}; -pub fn check_scalar_equality() { +pub fn check_field_equality() { let left = F::zero(); let right = F::one(); assert_ne!(left, right); @@ -48,6 +52,7 @@ where assert_eq!(left, right); } +#[cfg(feature = "arkworks")] pub fn check_ark_scalar_convert() where F::Config: GenerateRandom, @@ -60,6 +65,7 @@ where } } +#[cfg(feature = "arkworks")] pub fn check_ark_point_convert() where Affine: ArkConvertible>, @@ -79,27 +85,28 @@ where pub fn check_field_convert_montgomery() where - F: FieldImpl + MontgomeryConvertible, + F: FieldImpl + MontgomeryConvertible<'static>, F::Config: GenerateRandom, { let size = 1 << 10; let scalars = F::Config::generate_random(size); + let device_ctx = DeviceContext::default(); - let mut d_scalars = HostOrDeviceSlice::cuda_malloc(size).unwrap(); + let mut d_scalars = DeviceVec::cuda_malloc(size).unwrap(); d_scalars - .copy_from_host(&scalars) + .copy_from_host(HostSlice::from_slice(&scalars)) .unwrap(); - F::to_mont(&mut d_scalars) + F::to_mont(&mut d_scalars, &device_ctx) .wrap() .unwrap(); - F::from_mont(&mut d_scalars) + F::from_mont(&mut d_scalars, &device_ctx) .wrap() .unwrap(); let mut scalars_copy = vec![F::zero(); size]; d_scalars - .copy_to_host(&mut scalars_copy) + .copy_to_host(HostSlice::from_mut_slice(&mut scalars_copy)) .unwrap(); for (s1, s2) in scalars @@ -112,27 +119,28 @@ where pub fn check_points_convert_montgomery() where - Affine: MontgomeryConvertible, - Projective: MontgomeryConvertible, + Affine: MontgomeryConvertible<'static>, + Projective: MontgomeryConvertible<'static>, { let size = 1 << 10; + let device_ctx = DeviceContext::default(); let affine_points = C::generate_random_affine_points(size); - let mut d_affine = HostOrDeviceSlice::cuda_malloc(size).unwrap(); + let mut d_affine = DeviceVec::cuda_malloc(size).unwrap(); d_affine - .copy_from_host(&affine_points) + .copy_from_host(HostSlice::from_slice(&affine_points)) .unwrap(); - Affine::::to_mont(&mut d_affine) + Affine::::to_mont(&mut d_affine, &device_ctx) .wrap() .unwrap(); - Affine::::from_mont(&mut d_affine) + Affine::::from_mont(&mut d_affine, &device_ctx) .wrap() .unwrap(); let mut affine_copy = vec![Affine::::zero(); size]; d_affine - .copy_to_host(&mut affine_copy) + .copy_to_host(HostSlice::from_mut_slice(&mut affine_copy)) .unwrap(); for (p1, p2) in affine_points @@ -143,21 +151,21 @@ where } let proj_points = C::generate_random_projective_points(size); - let mut d_proj = HostOrDeviceSlice::cuda_malloc(size).unwrap(); + let mut d_proj = DeviceVec::cuda_malloc(size).unwrap(); d_proj - .copy_from_host(&proj_points) + .copy_from_host(HostSlice::from_slice(&proj_points)) .unwrap(); - Projective::::to_mont(&mut d_proj) + Projective::::to_mont(&mut d_proj, &device_ctx) .wrap() .unwrap(); - Projective::::from_mont(&mut d_proj) + Projective::::from_mont(&mut d_proj, &device_ctx) .wrap() .unwrap(); let mut projective_copy = vec![Projective::::zero(); size]; d_proj - .copy_to_host(&mut projective_copy) + .copy_to_host(HostSlice::from_mut_slice(&mut projective_copy)) .unwrap(); for (p1, p2) in proj_points diff --git a/wrappers/rust/icicle-core/src/traits.rs b/wrappers/rust/icicle-core/src/traits.rs index 05eb00ec..f63c6690 100644 --- a/wrappers/rust/icicle-core/src/traits.rs +++ b/wrappers/rust/icicle-core/src/traits.rs @@ -1,7 +1,7 @@ use crate::error::IcicleResult; #[cfg(feature = "arkworks")] use ark_ff::Field as ArkField; -use icicle_cuda_runtime::{error::CudaError, memory::HostOrDeviceSlice}; +use icicle_cuda_runtime::{device_context::DeviceContext, error::CudaError, memory::DeviceSlice}; use std::{ fmt::{Debug, Display}, mem::MaybeUninit, @@ -29,6 +29,7 @@ pub trait FieldImpl: fn from_bytes_le(bytes: &[u8]) -> Self; fn zero() -> Self; fn one() -> Self; + fn from_u32(val: u32) -> Self; } #[cfg(feature = "arkworks")] @@ -39,9 +40,9 @@ pub trait ArkConvertible { fn from_ark(ark: Self::ArkEquivalent) -> Self; } -pub trait MontgomeryConvertible: Sized { - fn to_mont(values: &mut HostOrDeviceSlice) -> CudaError; - fn from_mont(values: &mut HostOrDeviceSlice) -> CudaError; +pub trait MontgomeryConvertible<'a>: Sized { + fn to_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError; + fn from_mont(values: &mut DeviceSlice, ctx: &DeviceContext<'a>) -> CudaError; } pub trait IcicleResultWrap { diff --git a/wrappers/rust/icicle-core/src/tree/mod.rs b/wrappers/rust/icicle-core/src/tree/mod.rs index 3340f472..d32f7279 100644 --- a/wrappers/rust/icicle-core/src/tree/mod.rs +++ b/wrappers/rust/icicle-core/src/tree/mod.rs @@ -1,3 +1,4 @@ +use icicle_cuda_runtime::device::check_device; use icicle_cuda_runtime::{ device_context::{DeviceContext, DEFAULT_DEVICE_ID}, memory::HostOrDeviceSlice, @@ -54,7 +55,7 @@ pub fn merkle_tree_digests_len(height: u32, arity: u32) -> usize { pub trait TreeBuilder { fn build_poseidon_tree_unchecked( - leaves: &mut HostOrDeviceSlice, + leaves: &mut (impl HostOrDeviceSlice + ?Sized), digests: &mut [F], height: u32, arity: u32, @@ -75,7 +76,7 @@ pub trait TreeBuilder { /// /// * `config` - config used to specify extra arguments of the Tree builder. pub fn build_poseidon_merkle_tree( - leaves: &mut HostOrDeviceSlice, + leaves: &mut (impl HostOrDeviceSlice + ?Sized), digests: &mut [F], height: u32, arity: u32, @@ -100,6 +101,16 @@ where ); } + let ctx_device_id = config + .ctx + .device_id; + if let Some(device_id) = leaves.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in leaves and context are different" + ); + } + check_device(ctx_device_id); let mut local_cfg = config.clone(); local_cfg.are_inputs_on_device = leaves.is_on_device(); @@ -121,7 +132,7 @@ macro_rules! impl_tree_builder { use icicle_core::poseidon::PoseidonConstants; extern "C" { - #[link_name = concat!($field_prefix, "BuildPoseidonMerkleTree")] + #[link_name = concat!($field_prefix, "_build_poseidon_merkle_tree")] pub(crate) fn _build_poseidon_merkle_tree( leaves: *mut $field, digests: *mut $field, @@ -135,7 +146,7 @@ macro_rules! impl_tree_builder { impl TreeBuilder<$field> for $field_config { fn build_poseidon_tree_unchecked( - leaves: &mut HostOrDeviceSlice<$field>, + leaves: &mut (impl HostOrDeviceSlice<$field> + ?Sized), digests: &mut [$field], height: u32, arity: u32, diff --git a/wrappers/rust/icicle-core/src/tree/tests.rs b/wrappers/rust/icicle-core/src/tree/tests.rs index bf1dfc32..eebcdedd 100644 --- a/wrappers/rust/icicle-core/src/tree/tests.rs +++ b/wrappers/rust/icicle-core/src/tree/tests.rs @@ -1,4 +1,4 @@ -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::HostSlice; use crate::{ poseidon::{tests::init_poseidon, Poseidon}, @@ -15,16 +15,16 @@ where let height = 20; let arity = 2; let keep_rows = 1; - let leaves = vec![F::one(); 1 << (height - 1)]; + let mut leaves = vec![F::one(); 1 << (height - 1)]; let mut digests = vec![F::zero(); merkle_tree_digests_len(height, arity)]; - let mut leaves_slice = HostOrDeviceSlice::on_host(leaves); + let leaves_slice = HostSlice::from_mut_slice(&mut leaves); let constants = init_poseidon(arity as u32); let mut config = TreeBuilderConfig::default(); config.keep_rows = keep_rows; - build_poseidon_merkle_tree::(&mut leaves_slice, &mut digests, height, arity, &constants, &config).unwrap(); + build_poseidon_merkle_tree::(leaves_slice, &mut digests, height, arity, &constants, &config).unwrap(); - println!("Root: {:?}", digests[0..1][0]); + println!("Root: {:?}", digests[0]); } diff --git a/wrappers/rust/icicle-core/src/vec_ops/mod.rs b/wrappers/rust/icicle-core/src/vec_ops/mod.rs index 55d7d708..5cba1842 100644 --- a/wrappers/rust/icicle-core/src/vec_ops/mod.rs +++ b/wrappers/rust/icicle-core/src/vec_ops/mod.rs @@ -1,3 +1,4 @@ +use icicle_cuda_runtime::device::check_device; use icicle_cuda_runtime::{ device_context::{DeviceContext, DEFAULT_DEVICE_ID}, memory::HostOrDeviceSlice, @@ -16,7 +17,6 @@ pub struct VecOpsConfig<'a> { is_a_on_device: bool, is_b_on_device: bool, is_result_on_device: bool, - is_result_montgomery_form: bool, /// Whether to run the vector operations asynchronously. If set to `true`, the functions will be non-blocking and you'd need to synchronize /// it explicitly by running `stream.synchronize()`. If set to false, the functions will block the current CPU thread. pub is_async: bool, @@ -35,7 +35,6 @@ impl<'a> VecOpsConfig<'a> { is_a_on_device: false, is_b_on_device: false, is_result_on_device: false, - is_result_montgomery_form: false, is_async: false, } } @@ -44,38 +43,43 @@ impl<'a> VecOpsConfig<'a> { #[doc(hidden)] pub trait VecOps { fn add( - a: &HostOrDeviceSlice, - b: &HostOrDeviceSlice, - result: &mut HostOrDeviceSlice, + a: &(impl HostOrDeviceSlice + ?Sized), + b: &(impl HostOrDeviceSlice + ?Sized), + result: &mut (impl HostOrDeviceSlice + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()>; fn sub( - a: &HostOrDeviceSlice, - b: &HostOrDeviceSlice, - result: &mut HostOrDeviceSlice, + a: &(impl HostOrDeviceSlice + ?Sized), + b: &(impl HostOrDeviceSlice + ?Sized), + result: &mut (impl HostOrDeviceSlice + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()>; fn mul( - a: &HostOrDeviceSlice, - b: &HostOrDeviceSlice, - result: &mut HostOrDeviceSlice, + a: &(impl HostOrDeviceSlice + ?Sized), + b: &(impl HostOrDeviceSlice + ?Sized), + result: &mut (impl HostOrDeviceSlice + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()>; fn transpose( - input: &HostOrDeviceSlice, + input: &(impl HostOrDeviceSlice + ?Sized), row_size: u32, column_size: u32, - output: &mut HostOrDeviceSlice, + output: &mut (impl HostOrDeviceSlice + ?Sized), ctx: &DeviceContext, on_device: bool, is_async: bool, ) -> IcicleResult<()>; } -fn check_vec_ops_args(a: &HostOrDeviceSlice, b: &HostOrDeviceSlice, result: &mut HostOrDeviceSlice) { +fn check_vec_ops_args<'a, F>( + a: &(impl HostOrDeviceSlice + ?Sized), + b: &(impl HostOrDeviceSlice + ?Sized), + result: &(impl HostOrDeviceSlice + ?Sized), + cfg: &VecOpsConfig<'a>, +) -> VecOpsConfig<'a> { if a.len() != b.len() || a.len() != result.len() { panic!( "left, right and output lengths {}; {}; {} do not match", @@ -84,55 +88,77 @@ fn check_vec_ops_args(a: &HostOrDeviceSlice, b: &HostOrDeviceSlice, res result.len() ); } + let ctx_device_id = cfg + .ctx + .device_id; + if let Some(device_id) = a.device_id() { + assert_eq!(device_id, ctx_device_id, "Device ids in a and context are different"); + } + if let Some(device_id) = b.device_id() { + assert_eq!(device_id, ctx_device_id, "Device ids in b and context are different"); + } + if let Some(device_id) = result.device_id() { + assert_eq!( + device_id, ctx_device_id, + "Device ids in result and context are different" + ); + } + check_device(ctx_device_id); + + let mut res_cfg = cfg.clone(); + res_cfg.is_a_on_device = a.is_on_device(); + res_cfg.is_b_on_device = b.is_on_device(); + res_cfg.is_result_on_device = result.is_on_device(); + res_cfg } pub fn add_scalars( - a: &HostOrDeviceSlice, - b: &HostOrDeviceSlice, - result: &mut HostOrDeviceSlice, + a: &(impl HostOrDeviceSlice + ?Sized), + b: &(impl HostOrDeviceSlice + ?Sized), + result: &mut (impl HostOrDeviceSlice + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()> where F: FieldImpl, ::Config: VecOps, { - check_vec_ops_args(a, b, result); - <::Config as VecOps>::add(a, b, result, cfg) + let cfg = check_vec_ops_args(a, b, result, cfg); + <::Config as VecOps>::add(a, b, result, &cfg) } pub fn sub_scalars( - a: &HostOrDeviceSlice, - b: &HostOrDeviceSlice, - result: &mut HostOrDeviceSlice, + a: &(impl HostOrDeviceSlice + ?Sized), + b: &(impl HostOrDeviceSlice + ?Sized), + result: &mut (impl HostOrDeviceSlice + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()> where F: FieldImpl, ::Config: VecOps, { - check_vec_ops_args(a, b, result); - <::Config as VecOps>::sub(a, b, result, cfg) + let cfg = check_vec_ops_args(a, b, result, cfg); + <::Config as VecOps>::sub(a, b, result, &cfg) } pub fn mul_scalars( - a: &HostOrDeviceSlice, - b: &HostOrDeviceSlice, - result: &mut HostOrDeviceSlice, + a: &(impl HostOrDeviceSlice + ?Sized), + b: &(impl HostOrDeviceSlice + ?Sized), + result: &mut (impl HostOrDeviceSlice + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()> where F: FieldImpl, ::Config: VecOps, { - check_vec_ops_args(a, b, result); - <::Config as VecOps>::mul(a, b, result, cfg) + let cfg = check_vec_ops_args(a, b, result, cfg); + <::Config as VecOps>::mul(a, b, result, &cfg) } pub fn transpose_matrix( - input: &HostOrDeviceSlice, + input: &(impl HostOrDeviceSlice + ?Sized), row_size: u32, column_size: u32, - output: &mut HostOrDeviceSlice, + output: &mut (impl HostOrDeviceSlice + ?Sized), ctx: &DeviceContext, on_device: bool, is_async: bool, @@ -157,7 +183,7 @@ macro_rules! impl_vec_ops_field { use icicle_core::vec_ops::VecOpsConfig; extern "C" { - #[link_name = concat!($field_prefix, "AddCuda")] + #[link_name = concat!($field_prefix, "_add_cuda")] pub(crate) fn add_scalars_cuda( a: *const $field, b: *const $field, @@ -166,7 +192,7 @@ macro_rules! impl_vec_ops_field { result: *mut $field, ) -> CudaError; - #[link_name = concat!($field_prefix, "SubCuda")] + #[link_name = concat!($field_prefix, "_sub_cuda")] pub(crate) fn sub_scalars_cuda( a: *const $field, b: *const $field, @@ -175,7 +201,7 @@ macro_rules! impl_vec_ops_field { result: *mut $field, ) -> CudaError; - #[link_name = concat!($field_prefix, "MulCuda")] + #[link_name = concat!($field_prefix, "_mul_cuda")] pub(crate) fn mul_scalars_cuda( a: *const $field, b: *const $field, @@ -184,7 +210,7 @@ macro_rules! impl_vec_ops_field { result: *mut $field, ) -> CudaError; - #[link_name = concat!($field_prefix, "TransposeMatrix")] + #[link_name = concat!($field_prefix, "_transpose_matrix_cuda")] pub(crate) fn transpose_cuda( input: *const $field, row_size: u32, @@ -199,9 +225,9 @@ macro_rules! impl_vec_ops_field { impl VecOps<$field> for $field_config { fn add( - a: &HostOrDeviceSlice<$field>, - b: &HostOrDeviceSlice<$field>, - result: &mut HostOrDeviceSlice<$field>, + a: &(impl HostOrDeviceSlice<$field> + ?Sized), + b: &(impl HostOrDeviceSlice<$field> + ?Sized), + result: &mut (impl HostOrDeviceSlice<$field> + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()> { unsafe { @@ -209,7 +235,7 @@ macro_rules! impl_vec_ops_field { a.as_ptr(), b.as_ptr(), a.len() as u32, - cfg as *const _ as *const VecOpsConfig, + cfg as *const VecOpsConfig, result.as_mut_ptr(), ) .wrap() @@ -217,9 +243,9 @@ macro_rules! impl_vec_ops_field { } fn sub( - a: &HostOrDeviceSlice<$field>, - b: &HostOrDeviceSlice<$field>, - result: &mut HostOrDeviceSlice<$field>, + a: &(impl HostOrDeviceSlice<$field> + ?Sized), + b: &(impl HostOrDeviceSlice<$field> + ?Sized), + result: &mut (impl HostOrDeviceSlice<$field> + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()> { unsafe { @@ -227,7 +253,7 @@ macro_rules! impl_vec_ops_field { a.as_ptr(), b.as_ptr(), a.len() as u32, - cfg as *const _ as *const VecOpsConfig, + cfg as *const VecOpsConfig, result.as_mut_ptr(), ) .wrap() @@ -235,9 +261,9 @@ macro_rules! impl_vec_ops_field { } fn mul( - a: &HostOrDeviceSlice<$field>, - b: &HostOrDeviceSlice<$field>, - result: &mut HostOrDeviceSlice<$field>, + a: &(impl HostOrDeviceSlice<$field> + ?Sized), + b: &(impl HostOrDeviceSlice<$field> + ?Sized), + result: &mut (impl HostOrDeviceSlice<$field> + ?Sized), cfg: &VecOpsConfig, ) -> IcicleResult<()> { unsafe { @@ -245,7 +271,7 @@ macro_rules! impl_vec_ops_field { a.as_ptr(), b.as_ptr(), a.len() as u32, - cfg as *const _ as *const VecOpsConfig, + cfg as *const VecOpsConfig, result.as_mut_ptr(), ) .wrap() @@ -253,10 +279,10 @@ macro_rules! impl_vec_ops_field { } fn transpose( - input: &HostOrDeviceSlice<$field>, + input: &(impl HostOrDeviceSlice<$field> + ?Sized), row_size: u32, column_size: u32, - output: &mut HostOrDeviceSlice<$field>, + output: &mut (impl HostOrDeviceSlice<$field> + ?Sized), ctx: &DeviceContext, on_device: bool, is_async: bool, diff --git a/wrappers/rust/icicle-core/src/vec_ops/tests.rs b/wrappers/rust/icicle-core/src/vec_ops/tests.rs index b7a483f0..41ac263b 100644 --- a/wrappers/rust/icicle-core/src/vec_ops/tests.rs +++ b/wrappers/rust/icicle-core/src/vec_ops/tests.rs @@ -1,5 +1,6 @@ use crate::traits::GenerateRandom; -use crate::vec_ops::{add_scalars, mul_scalars, sub_scalars, FieldImpl, HostOrDeviceSlice, VecOps, VecOpsConfig}; +use crate::vec_ops::{add_scalars, mul_scalars, sub_scalars, FieldImpl, VecOps, VecOpsConfig}; +use icicle_cuda_runtime::memory::HostSlice; pub fn check_vec_ops_scalars() where @@ -7,21 +8,27 @@ where { let test_size = 1 << 14; - let a = HostOrDeviceSlice::on_host(F::Config::generate_random(test_size)); - let b = HostOrDeviceSlice::on_host(F::Config::generate_random(test_size)); - let ones = HostOrDeviceSlice::on_host(vec![F::one(); test_size]); - let mut result = HostOrDeviceSlice::on_host(vec![F::zero(); test_size]); - let mut result2 = HostOrDeviceSlice::on_host(vec![F::zero(); test_size]); - let mut result3 = HostOrDeviceSlice::on_host(vec![F::zero(); test_size]); + let a = F::Config::generate_random(test_size); + let b = F::Config::generate_random(test_size); + let ones = vec![F::one(); test_size]; + let mut result = vec![F::zero(); test_size]; + let mut result2 = vec![F::zero(); test_size]; + let mut result3 = vec![F::zero(); test_size]; + let a = HostSlice::from_slice(&a); + let b = HostSlice::from_slice(&b); + let ones = HostSlice::from_slice(&ones); + let result = HostSlice::from_mut_slice(&mut result); + let result2 = HostSlice::from_mut_slice(&mut result2); + let result3 = HostSlice::from_mut_slice(&mut result3); let cfg = VecOpsConfig::default(); - add_scalars(&a, &b, &mut result, &cfg).unwrap(); + add_scalars(a, b, result, &cfg).unwrap(); - sub_scalars(&result, &b, &mut result2, &cfg).unwrap(); + sub_scalars(result, b, result2, &cfg).unwrap(); - assert_eq!(a[0..1][0], result2[0..1][0]); + assert_eq!(a[0], result2[0]); - mul_scalars(&a, &ones, &mut result3, &cfg).unwrap(); + mul_scalars(a, ones, result3, &cfg).unwrap(); - assert_eq!(a[0..1][0], result3[0..1][0]); + assert_eq!(a[0], result3[0]); } diff --git a/wrappers/rust/icicle-cuda-runtime/build.rs b/wrappers/rust/icicle-cuda-runtime/build.rs index 7858bf5c..8c935d7e 100644 --- a/wrappers/rust/icicle-cuda-runtime/build.rs +++ b/wrappers/rust/icicle-cuda-runtime/build.rs @@ -83,6 +83,11 @@ fn main() { // https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__MEMORY__POOLS.html .allowlist_function("cudaFreeAsync") .allowlist_function("cudaMallocAsync") + // Unified Addressing + // https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__UNIFIED.html + .rustified_enum("cudaMemoryType") + .allowlist_type("cudaPointerAttributes") + .allowlist_function("cudaPointerGetAttributes") // .generate() .expect("Unable to generate bindings"); diff --git a/wrappers/rust/icicle-cuda-runtime/src/device.rs b/wrappers/rust/icicle-cuda-runtime/src/device.rs index bc6cedb0..c68c9c3b 100644 --- a/wrappers/rust/icicle-cuda-runtime/src/device.rs +++ b/wrappers/rust/icicle-cuda-runtime/src/device.rs @@ -1,5 +1,8 @@ use crate::{ - bindings::{cudaFreeAsync, cudaGetDevice, cudaGetDeviceCount, cudaMallocAsync, cudaMemGetInfo, cudaSetDevice}, + bindings::{ + cudaFreeAsync, cudaGetDevice, cudaGetDeviceCount, cudaMallocAsync, cudaMemGetInfo, cudaPointerAttributes, + cudaPointerGetAttributes, cudaSetDevice, + }, error::{CudaResult, CudaResultWrap}, stream::CudaStream, }; @@ -19,6 +22,23 @@ pub fn get_device() -> CudaResult { unsafe { cudaGetDevice(&mut device_id) }.wrap_value(device_id as usize) } +pub fn get_device_from_pointer(ptr: *const ::std::os::raw::c_void) -> CudaResult { + let mut ptr_attributes = MaybeUninit::::uninit(); + unsafe { + cudaPointerGetAttributes(ptr_attributes.as_mut_ptr(), ptr).wrap()?; + Ok(ptr_attributes + .assume_init() + .device as usize) + } +} + +pub fn check_device(device_id: usize) { + match device_id == get_device().unwrap() { + true => (), + false => panic!("Attempt to use on a different device"), + } +} + // This function pre-allocates default memory pool and warms the GPU up // so that subsequent memory allocations and other calls are not slowed down pub fn warmup(stream: &CudaStream) -> CudaResult<()> { diff --git a/wrappers/rust/icicle-cuda-runtime/src/device_context.rs b/wrappers/rust/icicle-cuda-runtime/src/device_context.rs index cfcae98f..f5d6f8a3 100644 --- a/wrappers/rust/icicle-cuda-runtime/src/device_context.rs +++ b/wrappers/rust/icicle-cuda-runtime/src/device_context.rs @@ -3,8 +3,6 @@ use crate::stream::CudaStream; pub const DEFAULT_DEVICE_ID: usize = 0; -use crate::device::get_device; - /// Properties of the device used in Icicle functions. #[repr(C)] #[derive(Debug, Clone)] @@ -38,10 +36,3 @@ impl DeviceContext<'_> { } } } - -pub fn check_device(device_id: i32) { - match device_id == get_device().unwrap() as i32 { - true => (), - false => panic!("Attempt to use on a different device"), - } -} diff --git a/wrappers/rust/icicle-cuda-runtime/src/lib.rs b/wrappers/rust/icicle-cuda-runtime/src/lib.rs index 103fd56b..81661897 100644 --- a/wrappers/rust/icicle-cuda-runtime/src/lib.rs +++ b/wrappers/rust/icicle-cuda-runtime/src/lib.rs @@ -1,7 +1,7 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] -#[allow(dead_code)] +#[allow(dead_code, non_snake_case)] mod bindings; pub mod device; pub mod device_context; diff --git a/wrappers/rust/icicle-cuda-runtime/src/memory.rs b/wrappers/rust/icicle-cuda-runtime/src/memory.rs index 4596585b..4501e746 100644 --- a/wrappers/rust/icicle-cuda-runtime/src/memory.rs +++ b/wrappers/rust/icicle-cuda-runtime/src/memory.rs @@ -1,87 +1,233 @@ use crate::bindings::{ cudaFree, cudaMalloc, cudaMallocAsync, cudaMemPool_t, cudaMemcpy, cudaMemcpyAsync, cudaMemcpyKind, }; -use crate::device::get_device; -use crate::device_context::check_device; +use crate::device::{check_device, get_device_from_pointer}; use crate::error::{CudaError, CudaResult, CudaResultWrap}; use crate::stream::CudaStream; -use std::mem::{size_of, MaybeUninit}; -use std::ops::{Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; +use std::mem::{size_of, ManuallyDrop, MaybeUninit}; +use std::ops::{ + Deref, DerefMut, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, +}; use std::os::raw::c_void; use std::slice::from_raw_parts_mut; +use std::slice::SliceIndex; -pub enum HostOrDeviceSlice<'a, T> { - Host(Vec), - Device(&'a mut [T], i32), +#[derive(Debug)] +pub struct HostSlice([T]); +pub struct DeviceVec(ManuallyDrop>); +pub struct DeviceSlice([T]); + +pub trait HostOrDeviceSlice { + fn is_on_device(&self) -> bool; + fn device_id(&self) -> Option; + unsafe fn as_ptr(&self) -> *const T; + unsafe fn as_mut_ptr(&mut self) -> *mut T; + fn len(&self) -> usize; + fn is_empty(&self) -> bool; } -impl<'a, T> HostOrDeviceSlice<'a, T> { - // Function to get the device_id for Device variant - pub fn get_device_id(&self) -> Option { - match self { - HostOrDeviceSlice::Device(_, device_id) => Some(*device_id), - HostOrDeviceSlice::Host(_) => None, - } +impl HostOrDeviceSlice for HostSlice { + fn is_on_device(&self) -> bool { + false } - pub fn len(&self) -> usize { - match self { - Self::Device(s, _) => s.len(), - Self::Host(v) => v.len(), - } + fn device_id(&self) -> Option { + None } - pub fn is_empty(&self) -> bool { - match self { - Self::Device(s, _) => s.is_empty(), - Self::Host(v) => v.is_empty(), - } + unsafe fn as_ptr(&self) -> *const T { + self.0 + .as_ptr() } - pub fn is_on_device(&self) -> bool { - match self { - Self::Device(_, _) => true, - Self::Host(_) => false, - } + unsafe fn as_mut_ptr(&mut self) -> *mut T { + self.0 + .as_mut_ptr() } - pub fn as_mut_slice(&mut self) -> &mut [T] { - match self { - Self::Device(_, _) => { - panic!("Use copy_to_host and copy_to_host_async to move device data to a slice") - } - Self::Host(v) => v.as_mut_slice(), - } + fn len(&self) -> usize { + self.0 + .len() + } + + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl HostOrDeviceSlice for DeviceSlice { + fn is_on_device(&self) -> bool { + true + } + + fn device_id(&self) -> Option { + Some( + get_device_from_pointer(unsafe { self.as_ptr() as *const ::std::os::raw::c_void }) + .expect("Invalid pointer. Maybe host pointer was used here?"), + ) + } + + unsafe fn as_ptr(&self) -> *const T { + self.0 + .as_ptr() + } + + unsafe fn as_mut_ptr(&mut self) -> *mut T { + self.0 + .as_mut_ptr() + } + + fn len(&self) -> usize { + self.0 + .len() + } + + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl HostSlice { + // Currently this function just transmutes types. However it is not guaranteed that this function + // will always be cheap as it might at some point e.g. pin the memory which takes some time. + pub fn from_slice(slice: &[T]) -> &Self { + unsafe { &*(slice as *const [T] as *const Self) } + } + + // Currently this function just transmutes types. However it is not guaranteed that this function + // will always be cheap as it might at some point e.g. pin the memory which takes some time. + pub fn from_mut_slice(slice: &mut [T]) -> &mut Self { + unsafe { &mut *(slice as *mut [T] as *mut Self) } } pub fn as_slice(&self) -> &[T] { - match self { - Self::Device(_, _) => { - panic!("Use copy_to_host and copy_to_host_async to move device data to a slice") + &self.0 + } + + pub fn as_mut_slice(&mut self) -> &mut [T] { + &mut self.0 + } + + pub fn iter(&self) -> impl Iterator { + self.0 + .iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.0 + .iter_mut() + } +} + +impl DeviceSlice { + pub unsafe fn from_slice(slice: &[T]) -> &Self { + &*(slice as *const [T] as *const Self) + } + + pub unsafe fn from_mut_slice(slice: &mut [T]) -> &mut Self { + &mut *(slice as *mut [T] as *mut Self) + } + + pub fn copy_from_host(&mut self, val: &HostSlice) -> CudaResult<()> { + assert!( + self.len() == val.len(), + "In copy from host, destination and source slices have different lengths" + ); + check_device( + self.device_id() + .unwrap(), + ); + let size = size_of::() * self.len(); + if size != 0 { + unsafe { + cudaMemcpy( + self.as_mut_ptr() as *mut c_void, + val.as_ptr() as *const c_void, + size, + cudaMemcpyKind::cudaMemcpyHostToDevice, + ) + .wrap()? } - Self::Host(v) => v.as_slice(), } + Ok(()) } - pub fn as_ptr(&self) -> *const T { - match self { - Self::Device(s, _) => s.as_ptr(), - Self::Host(v) => v.as_ptr(), + pub fn copy_to_host(&self, val: &mut HostSlice) -> CudaResult<()> { + assert!( + self.len() == val.len(), + "In copy to host, destination and source slices have different lengths" + ); + check_device( + self.device_id() + .unwrap(), + ); + let size = size_of::() * self.len(); + if size != 0 { + unsafe { + cudaMemcpy( + val.as_mut_ptr() as *mut c_void, + self.as_ptr() as *const c_void, + size, + cudaMemcpyKind::cudaMemcpyDeviceToHost, + ) + .wrap()? + } } + Ok(()) } - pub fn as_mut_ptr(&mut self) -> *mut T { - match self { - Self::Device(s, _) => s.as_mut_ptr(), - Self::Host(v) => v.as_mut_ptr(), + pub fn copy_from_host_async(&mut self, val: &HostSlice, stream: &CudaStream) -> CudaResult<()> { + assert!( + self.len() == val.len(), + "In copy from host async, destination and source slices have different lengths" + ); + check_device( + self.device_id() + .unwrap(), + ); + let size = size_of::() * self.len(); + if size != 0 { + unsafe { + cudaMemcpyAsync( + self.as_mut_ptr() as *mut c_void, + val.as_ptr() as *const c_void, + size, + cudaMemcpyKind::cudaMemcpyHostToDevice, + stream.handle, + ) + .wrap()? + } } + Ok(()) } - pub fn on_host(src: Vec) -> Self { - //TODO: HostOrDeviceSlice on_host() with slice input without actually copying the data - Self::Host(src) + pub fn copy_to_host_async(&self, val: &mut HostSlice, stream: &CudaStream) -> CudaResult<()> { + assert!( + self.len() == val.len(), + "In copy to host async, destination and source slices have different lengths" + ); + check_device( + self.device_id() + .unwrap(), + ); + let size = size_of::() * self.len(); + if size != 0 { + unsafe { + cudaMemcpyAsync( + val.as_mut_ptr() as *mut c_void, + self.as_ptr() as *const c_void, + size, + cudaMemcpyKind::cudaMemcpyDeviceToHost, + stream.handle, + ) + .wrap()? + } + } + Ok(()) } +} +impl DeviceVec { pub fn cuda_malloc(count: usize) -> CudaResult { let size = count .checked_mul(size_of::()) @@ -93,10 +239,11 @@ impl<'a, T> HostOrDeviceSlice<'a, T> { let mut device_ptr = MaybeUninit::<*mut c_void>::uninit(); unsafe { cudaMalloc(device_ptr.as_mut_ptr(), size).wrap()?; - Ok(Self::Device( - from_raw_parts_mut(device_ptr.assume_init() as *mut T, count), - get_device().unwrap() as i32, - )) + let res = Self(ManuallyDrop::new(Box::from_raw(from_raw_parts_mut( + device_ptr.assume_init() as *mut T, + count, + )))); + Ok(res) } } @@ -105,146 +252,58 @@ impl<'a, T> HostOrDeviceSlice<'a, T> { .checked_mul(size_of::()) .unwrap_or(0); if size == 0 { - return Err(CudaError::cudaErrorMemoryAllocation); + return Err(CudaError::cudaErrorMemoryAllocation); //TODO: only CUDA backend should return CudaError } let mut device_ptr = MaybeUninit::<*mut c_void>::uninit(); unsafe { - cudaMallocAsync(device_ptr.as_mut_ptr(), size, stream.handle as *mut _ as *mut _).wrap()?; - Ok(Self::Device( - from_raw_parts_mut(device_ptr.assume_init() as *mut T, count), - get_device().unwrap() as i32, - )) + cudaMallocAsync(device_ptr.as_mut_ptr(), size, stream.handle).wrap()?; + Ok(Self(ManuallyDrop::new(Box::from_raw(from_raw_parts_mut( + device_ptr.assume_init() as *mut T, + count, + ))))) } } - pub fn copy_from_host(&mut self, val: &[T]) -> CudaResult<()> { - match self { - Self::Device(_, device_id) => check_device(*device_id), - Self::Host(_) => panic!("Need device memory to copy into, and not host"), - }; - assert!( - self.len() == val.len(), - "destination and source slices have different lengths" - ); - let size = size_of::() * self.len(); - if size != 0 { - unsafe { - cudaMemcpy( - self.as_mut_ptr() as *mut c_void, - val.as_ptr() as *const c_void, - size, - cudaMemcpyKind::cudaMemcpyHostToDevice, - ) - .wrap()? - } - } - Ok(()) + pub fn cuda_malloc_for_device(count: usize, device_id: usize) -> CudaResult { + check_device(device_id); + Self::cuda_malloc(count) } - pub fn copy_to_host(&self, val: &mut [T]) -> CudaResult<()> { - match self { - Self::Device(_, device_id) => check_device(*device_id), - Self::Host(_) => panic!("Need device memory to copy from, and not host"), - }; - assert!( - self.len() == val.len(), - "destination and source slices have different lengths" - ); - let size = size_of::() * self.len(); - if size != 0 { - unsafe { - cudaMemcpy( - val.as_mut_ptr() as *mut c_void, - self.as_ptr() as *const c_void, - size, - cudaMemcpyKind::cudaMemcpyDeviceToHost, - ) - .wrap()? - } - } - Ok(()) - } - - pub fn copy_from_host_async(&mut self, val: &[T], stream: &CudaStream) -> CudaResult<()> { - match self { - Self::Device(_, device_id) => check_device(*device_id), - Self::Host(_) => panic!("Need device memory to copy into, and not host"), - }; - assert!( - self.len() == val.len(), - "destination and source slices have different lengths" - ); - let size = size_of::() * self.len(); - if size != 0 { - unsafe { - cudaMemcpyAsync( - self.as_mut_ptr() as *mut c_void, - val.as_ptr() as *const c_void, - size, - cudaMemcpyKind::cudaMemcpyHostToDevice, - stream.handle as *mut _ as *mut _, - ) - .wrap()? - } - } - Ok(()) - } - - pub fn copy_to_host_async(&self, val: &mut [T], stream: &CudaStream) -> CudaResult<()> { - match self { - Self::Device(_, device_id) => check_device(*device_id), - Self::Host(_) => panic!("Need device memory to copy from, and not host"), - }; - assert!( - self.len() == val.len(), - "destination and source slices have different lengths" - ); - let size = size_of::() * self.len(); - if size != 0 { - unsafe { - cudaMemcpyAsync( - val.as_mut_ptr() as *mut c_void, - self.as_ptr() as *const c_void, - size, - cudaMemcpyKind::cudaMemcpyDeviceToHost, - stream.handle as *mut _ as *mut _, - ) - .wrap()? - } - } - Ok(()) + pub fn cuda_malloc_async_for_device(count: usize, stream: &CudaStream, device_id: usize) -> CudaResult { + check_device(device_id); + Self::cuda_malloc_async(count, stream) } } -macro_rules! impl_index { +macro_rules! impl_host_index { ($($t:ty)*) => { $( - impl<'a, T> Index<$t> for HostOrDeviceSlice<'a, T> + impl Index<$t> for HostSlice { - type Output = [T]; + type Output = Self; fn index(&self, index: $t) -> &Self::Output { - match self { - Self::Device(s, _) => s.index(index), - Self::Host(v) => v.index(index), - } + Self::from_slice( + self.0 + .index(index), + ) } } - impl<'a, T> IndexMut<$t> for HostOrDeviceSlice<'a, T> + impl IndexMut<$t> for HostSlice { fn index_mut(&mut self, index: $t) -> &mut Self::Output { - match self { - Self::Device(s,_) => s.index_mut(index), - Self::Host(v) => v.index_mut(index), - } + Self::from_mut_slice( + self.0 + .index_mut(index), + ) } } )* } } -impl_index! { +impl_host_index! { Range RangeFull RangeFrom @@ -253,22 +312,112 @@ impl_index! { RangeToInclusive } -impl<'a, T> Drop for HostOrDeviceSlice<'a, T> { - fn drop(&mut self) { - match self { - Self::Device(s, device_id) => { - check_device(*device_id); - if s.is_empty() { - return; - } +impl Index for HostSlice { + type Output = T; - unsafe { - cudaFree(s.as_mut_ptr() as *mut c_void) - .wrap() - .unwrap(); - } - } - Self::Host(_) => {} + fn index(&self, index: usize) -> &Self::Output { + self.0 + .index(index) + } +} + +impl IndexMut for HostSlice { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.0 + .index_mut(index) + } +} + +impl Index for DeviceVec +where + Idx: SliceIndex<[T], Output = [T]>, +{ + type Output = DeviceSlice; + + fn index(&self, index: Idx) -> &Self::Output { + unsafe { + Self::Output::from_slice( + self.0 + .index(index), + ) + } + } +} + +impl IndexMut for DeviceVec +where + Idx: SliceIndex<[T], Output = [T]>, +{ + fn index_mut(&mut self, index: Idx) -> &mut Self::Output { + unsafe { + Self::Output::from_mut_slice( + self.0 + .index_mut(index), + ) + } + } +} + +impl Index for DeviceSlice +where + Idx: SliceIndex<[T], Output = [T]>, +{ + type Output = Self; + + fn index(&self, index: Idx) -> &Self::Output { + unsafe { + Self::from_slice( + self.0 + .index(index), + ) + } + } +} + +impl IndexMut for DeviceSlice +where + Idx: SliceIndex<[T], Output = [T]>, +{ + fn index_mut(&mut self, index: Idx) -> &mut Self::Output { + unsafe { + Self::from_mut_slice( + self.0 + .index_mut(index), + ) + } + } +} + +impl Deref for DeviceVec { + type Target = DeviceSlice; + + fn deref(&self) -> &Self::Target { + &self[..] + } +} + +impl DerefMut for DeviceVec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self[..] + } +} + +impl Drop for DeviceVec { + fn drop(&mut self) { + if self + .0 + .is_empty() + { + return; + } + + unsafe { + let ptr = self + .0 + .as_mut_ptr() as *mut c_void; + cudaFree(ptr) + .wrap() + .unwrap(); } } } diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/Cargo.toml b/wrappers/rust/icicle-curves/icicle-bls12-377/Cargo.toml index ac0eb83f..941385f8 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/Cargo.toml @@ -11,11 +11,13 @@ repository.workspace = true icicle-core = { workspace = true } icicle-cuda-runtime = { workspace = true } ark-bls12-377 = { version = "0.4.0", optional = true } +criterion = "0.3" [build-dependencies] cmake = "0.1.50" [dev-dependencies] +criterion = "0.3" ark-bls12-377 = "0.4.0" ark-std = "0.4.0" ark-ff = "0.4.0" @@ -23,6 +25,7 @@ ark-ec = "0.4.0" ark-poly = "0.4.0" icicle-core = { path = "../../icicle-core", features = ["arkworks"] } icicle-bls12-377 = { path = ".", features = ["arkworks"] } +serial_test = "3.0.0" [features] default = [] @@ -32,3 +35,7 @@ g2 = ["icicle-core/g2"] ec_ntt = ["icicle-core/ec_ntt"] devmode = ["icicle-core/devmode"] arkworks = ["ark-bls12-377", "icicle-core/arkworks"] + +[[bench]] +name = "ecntt" +harness = false # Criterion provides own harness diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/benches/ecntt.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/benches/ecntt.rs new file mode 100644 index 00000000..de1782cf --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/benches/ecntt.rs @@ -0,0 +1,10 @@ +#[cfg(feature = "ec_ntt")] +use icicle_bls12_377::curve::{CurveCfg, ScalarField}; + +#[cfg(feature = "ec_ntt")] +use icicle_core::impl_ecntt_bench; +#[cfg(feature = "ec_ntt")] +impl_ecntt_bench!("BLS12_377", ScalarField, CurveCfg); + +#[cfg(not(feature = "ec_ntt"))] +fn main() {} diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/build.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/build.rs index 1f0e36aa..cbd95909 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/build.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/build.rs @@ -13,47 +13,50 @@ fn main() { // Optional Features #[cfg(feature = "g2")] - config.define("G2_DEFINED", "ON"); + config.define("G2", "ON"); #[cfg(feature = "ec_ntt")] - config.define("ECNTT_DEFINED", "ON"); + config.define("ECNTT", "ON"); #[cfg(feature = "devmode")] config.define("DEVMODE", "ON"); // Build let out_dir = config - .build_target("icicle") + .build_target("icicle_curve") .build(); - println!("cargo:rustc-link-search={}/build", out_dir.display()); - println!("cargo:rustc-link-lib=ingo_bls12_377"); + println!("cargo:rustc-link-search={}/build/lib", out_dir.display()); + + println!("cargo:rustc-link-lib=ingo_field_bls12_377"); + println!("cargo:rustc-link-lib=ingo_curve_bls12_377"); if cfg!(feature = "bw6-761") { // Base config let mut config = Config::new("../../../../icicle"); config - .define("BUILD_TESTS", "OFF") .define("CURVE", "bw6_761") .define("CMAKE_BUILD_TYPE", "Release"); // Optional Features #[cfg(feature = "bw6-761-g2")] - config.define("G2_DEFINED", "ON"); + config.define("G2", "ON"); #[cfg(feature = "ec_ntt")] - config.define("ECNTT_DEFINED", "OFF"); + config.define("ECNTT", "OFF"); #[cfg(feature = "devmode")] config.define("DEVMODE", "ON"); // Build let out_dir = config - .build_target("icicle") + .build_target("icicle_curve") .build(); - println!("cargo:rustc-link-search={}/build", out_dir.display()); - println!("cargo:rustc-link-lib=ingo_bw6_761"); + println!("cargo:rustc-link-search={}/build/lib/", out_dir.display()); + + println!("cargo:rustc-link-lib=ingo_field_bw6_761"); + println!("cargo:rustc-link-lib=ingo_curve_bw6_761"); } println!("cargo:rustc-link-lib=stdc++"); diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/src/curve.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/src/curve.rs index afbc72aa..84b0934f 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/src/curve.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/src/curve.rs @@ -6,14 +6,15 @@ use icicle_core::curve::{Affine, Curve, Projective}; use icicle_core::field::{Field, MontgomeryConvertibleField}; use icicle_core::traits::{FieldConfig, FieldImpl, GenerateRandom}; use icicle_core::{impl_curve, impl_field, impl_scalar_field}; +use icicle_cuda_runtime::device::check_device; use icicle_cuda_runtime::device_context::DeviceContext; use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{DeviceSlice, HostOrDeviceSlice}; -pub(crate) const SCALAR_LIMBS: usize = 4; -pub(crate) const BASE_LIMBS: usize = 6; +pub(crate) const SCALAR_LIMBS: usize = 8; +pub(crate) const BASE_LIMBS: usize = 12; #[cfg(feature = "g2")] -pub(crate) const G2_BASE_LIMBS: usize = 12; +pub(crate) const G2_BASE_LIMBS: usize = 24; impl_scalar_field!("bls12_377", bls12_377_sf, SCALAR_LIMBS, ScalarField, ScalarCfg, Fr); #[cfg(feature = "bw6-761")] @@ -34,7 +35,7 @@ impl_curve!( ); #[cfg(feature = "g2")] impl_curve!( - "bls12_377G2", + "bls12_377_g2", bls12_377_g2, G2CurveCfg, ScalarField, diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/src/ecntt/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/src/ecntt/mod.rs new file mode 100644 index 00000000..504199f0 --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/src/ecntt/mod.rs @@ -0,0 +1,23 @@ +#![cfg(feature = "ec_ntt")] +use icicle_core::error::IcicleResult; +use icicle_core::impl_ecntt; +use icicle_core::ntt::{NTTConfig, NTTDir}; +use icicle_cuda_runtime::device_context::DeviceContext; +use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; +use icicle_cuda_runtime::error::CudaError; + +use crate::curve::{CurveCfg, ScalarCfg, ScalarField}; +use icicle_core::ecntt::Projective; + +impl_ecntt!("bls12_377", bls12_377, ScalarField, ScalarCfg, CurveCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::curve::{CurveCfg, ScalarField}; + + use icicle_core::ecntt::tests::*; + use icicle_core::impl_ecntt_tests; + use std::sync::OnceLock; + + impl_ecntt_tests!(ScalarField, CurveCfg); +} diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/src/lib.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/src/lib.rs index 2bda6aec..71a597c7 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/src/lib.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/src/lib.rs @@ -1,6 +1,8 @@ pub mod curve; +pub mod ecntt; pub mod msm; pub mod ntt; +pub mod polynomials; pub mod poseidon; pub mod tree; pub mod vec_ops; diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/src/msm/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/src/msm/mod.rs index 3cbfe3e8..ea8acf38 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/src/msm/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/src/msm/mod.rs @@ -8,11 +8,15 @@ use icicle_core::{ msm::{MSMConfig, MSM}, traits::IcicleResultWrap, }; -use icicle_cuda_runtime::{device_context::DeviceContext, error::CudaError, memory::HostOrDeviceSlice}; +use icicle_cuda_runtime::{ + device_context::DeviceContext, + error::CudaError, + memory::{DeviceSlice, HostOrDeviceSlice}, +}; impl_msm!("bls12_377", bls12_377, CurveCfg); #[cfg(feature = "g2")] -impl_msm!("bls12_377G2", bls12_377_g2, G2CurveCfg); +impl_msm!("bls12_377_g2", bls12_377_g2, G2CurveCfg); #[cfg(test)] pub(crate) mod tests { diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/src/ntt/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/src/ntt/mod.rs index bf8dfbd3..1142178a 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/src/ntt/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/src/ntt/mod.rs @@ -3,11 +3,10 @@ use crate::curve::{BaseCfg, BaseField}; use crate::curve::{ScalarCfg, ScalarField}; use icicle_core::error::IcicleResult; -use icicle_core::impl_ntt; -use icicle_core::ntt::{NTTConfig, NTTDir, NTT}; +use icicle_core::ntt::{NTTConfig, NTTDir, NTTDomain, NTT}; use icicle_core::traits::IcicleResultWrap; +use icicle_core::{impl_ntt, impl_ntt_without_domain}; use icicle_cuda_runtime::device_context::DeviceContext; -use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; use icicle_cuda_runtime::error::CudaError; use icicle_cuda_runtime::memory::HostOrDeviceSlice; @@ -18,9 +17,10 @@ impl_ntt!("bw6_761", bw6_761, BaseField, BaseCfg); #[cfg(test)] pub(crate) mod tests { use crate::curve::ScalarField; - use crate::ntt::DEFAULT_DEVICE_ID; use icicle_core::impl_ntt_tests; use icicle_core::ntt::tests::*; + use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; + use serial_test::{parallel, serial}; use std::sync::OnceLock; impl_ntt_tests!(ScalarField); diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/src/polynomials/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/src/polynomials/mod.rs new file mode 100644 index 00000000..2ae89fda --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/src/polynomials/mod.rs @@ -0,0 +1,10 @@ +use crate::curve::{ScalarCfg, ScalarField}; +use icicle_core::impl_univariate_polynomial_api; + +impl_univariate_polynomial_api!("bls12_377", bls12_377, ScalarField, ScalarCfg); + +#[cfg(test)] +mod tests { + use icicle_core::impl_polynomial_tests; + impl_polynomial_tests!(bls12_377, ScalarField); +} diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/Cargo.toml b/wrappers/rust/icicle-curves/icicle-bls12-381/Cargo.toml index b067e022..79110989 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/Cargo.toml @@ -11,11 +11,13 @@ repository.workspace = true icicle-core = { workspace = true } icicle-cuda-runtime = { workspace = true } ark-bls12-381 = { version = "0.4.0", optional = true } +criterion = "0.3" [build-dependencies] cmake = "0.1.50" [dev-dependencies] +criterion = "0.3" ark-bls12-381 = "0.4.0" ark-std = "0.4.0" ark-ff = "0.4.0" @@ -23,6 +25,7 @@ ark-ec = "0.4.0" ark-poly = "0.4.0" icicle-core = { path = "../../icicle-core", features = ["arkworks"] } icicle-bls12-381 = { path = ".", features = ["arkworks"] } +serial_test = "3.0.0" [features] default = [] @@ -30,3 +33,7 @@ g2 = ["icicle-core/g2"] ec_ntt = ["icicle-core/ec_ntt"] devmode = ["icicle-core/devmode"] arkworks = ["ark-bls12-381", "icicle-core/arkworks"] + +[[bench]] +name = "ecntt" +harness = false # Criterion provides own harness diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/benches/ecntt.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/benches/ecntt.rs new file mode 100644 index 00000000..962e82ad --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/benches/ecntt.rs @@ -0,0 +1,10 @@ +#[cfg(feature = "ec_ntt")] +use icicle_bls12_381::curve::{CurveCfg, ScalarField}; + +#[cfg(feature = "ec_ntt")] +use icicle_core::impl_ecntt_bench; +#[cfg(feature = "ec_ntt")] +impl_ecntt_bench!("BLS12_381", ScalarField, CurveCfg); + +#[cfg(not(feature = "ec_ntt"))] +fn main() {} diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/build.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/build.rs index b59dabae..ea5a2b9b 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/build.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/build.rs @@ -7,28 +7,29 @@ fn main() { // Base config let mut config = Config::new("../../../../icicle"); config - .define("BUILD_TESTS", "OFF") .define("CURVE", "bls12_381") .define("CMAKE_BUILD_TYPE", "Release"); // Optional Features #[cfg(feature = "g2")] - config.define("G2_DEFINED", "ON"); + config.define("G2", "ON"); #[cfg(feature = "ec_ntt")] - config.define("ECNTT_DEFINED", "ON"); + config.define("ECNTT", "ON"); #[cfg(feature = "devmode")] config.define("DEVMODE", "ON"); // Build let out_dir = config - .build_target("icicle") + .build_target("icicle_curve") .build(); - println!("cargo:rustc-link-search={}/build", out_dir.display()); + println!("cargo:rustc-link-search={}/build/lib", out_dir.display()); + + println!("cargo:rustc-link-lib=ingo_field_bls12_381"); + println!("cargo:rustc-link-lib=ingo_curve_bls12_381"); - println!("cargo:rustc-link-lib=ingo_bls12_381"); println!("cargo:rustc-link-lib=stdc++"); println!("cargo:rustc-link-lib=cudart"); } diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/src/curve.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/src/curve.rs index c227b631..be594d9d 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/src/curve.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/src/curve.rs @@ -6,14 +6,15 @@ use icicle_core::curve::{Affine, Curve, Projective}; use icicle_core::field::{Field, MontgomeryConvertibleField}; use icicle_core::traits::{FieldConfig, FieldImpl, GenerateRandom}; use icicle_core::{impl_curve, impl_field, impl_scalar_field}; +use icicle_cuda_runtime::device::check_device; use icicle_cuda_runtime::device_context::DeviceContext; use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{DeviceSlice, HostOrDeviceSlice}; -pub(crate) const SCALAR_LIMBS: usize = 4; -pub(crate) const BASE_LIMBS: usize = 6; +pub(crate) const SCALAR_LIMBS: usize = 8; +pub(crate) const BASE_LIMBS: usize = 12; #[cfg(feature = "g2")] -pub(crate) const G2_BASE_LIMBS: usize = 12; +pub(crate) const G2_BASE_LIMBS: usize = 24; impl_scalar_field!("bls12_381", bls12_381_sf, SCALAR_LIMBS, ScalarField, ScalarCfg, Fr); impl_field!(BASE_LIMBS, BaseField, BaseCfg, Fq); @@ -31,7 +32,7 @@ impl_curve!( ); #[cfg(feature = "g2")] impl_curve!( - "bls12_381G2", + "bls12_381_g2", bls12_381_g2, G2CurveCfg, ScalarField, diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/src/ecntt/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/src/ecntt/mod.rs new file mode 100644 index 00000000..706f3069 --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/src/ecntt/mod.rs @@ -0,0 +1,23 @@ +#![cfg(feature = "ec_ntt")] +use icicle_core::error::IcicleResult; +use icicle_core::impl_ecntt; +use icicle_core::ntt::{NTTConfig, NTTDir}; +use icicle_cuda_runtime::device_context::DeviceContext; +use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; +use icicle_cuda_runtime::error::CudaError; + +use crate::curve::{CurveCfg, ScalarCfg, ScalarField}; +use icicle_core::ecntt::Projective; + +impl_ecntt!("bls12_381", bls12_381, ScalarField, ScalarCfg, CurveCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::curve::{CurveCfg, ScalarField}; + + use icicle_core::ecntt::tests::*; + use icicle_core::impl_ecntt_tests; + use std::sync::OnceLock; + + impl_ecntt_tests!(ScalarField, CurveCfg); +} diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/src/lib.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/src/lib.rs index 2bda6aec..71a597c7 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/src/lib.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/src/lib.rs @@ -1,6 +1,8 @@ pub mod curve; +pub mod ecntt; pub mod msm; pub mod ntt; +pub mod polynomials; pub mod poseidon; pub mod tree; pub mod vec_ops; diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/src/msm/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/src/msm/mod.rs index 8bf0887e..5dcb6a3b 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/src/msm/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/src/msm/mod.rs @@ -8,11 +8,15 @@ use icicle_core::{ msm::{MSMConfig, MSM}, traits::IcicleResultWrap, }; -use icicle_cuda_runtime::{device_context::DeviceContext, error::CudaError, memory::HostOrDeviceSlice}; +use icicle_cuda_runtime::{ + device_context::DeviceContext, + error::CudaError, + memory::{DeviceSlice, HostOrDeviceSlice}, +}; impl_msm!("bls12_381", bls12_381, CurveCfg); #[cfg(feature = "g2")] -impl_msm!("bls12_381G2", bls12_381_g2, G2CurveCfg); +impl_msm!("bls12_381_g2", bls12_381_g2, G2CurveCfg); #[cfg(test)] pub(crate) mod tests { diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/src/ntt/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/src/ntt/mod.rs index 9e2e28c6..17c69326 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/src/ntt/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/src/ntt/mod.rs @@ -1,11 +1,10 @@ use crate::curve::{ScalarCfg, ScalarField}; use icicle_core::error::IcicleResult; -use icicle_core::impl_ntt; -use icicle_core::ntt::{NTTConfig, NTTDir, NTT}; +use icicle_core::ntt::{NTTConfig, NTTDir, NTTDomain, NTT}; use icicle_core::traits::IcicleResultWrap; +use icicle_core::{impl_ntt, impl_ntt_without_domain}; use icicle_cuda_runtime::device_context::DeviceContext; -use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; use icicle_cuda_runtime::error::CudaError; use icicle_cuda_runtime::memory::HostOrDeviceSlice; @@ -14,9 +13,10 @@ impl_ntt!("bls12_381", bls12_381, ScalarField, ScalarCfg); #[cfg(test)] pub(crate) mod tests { use crate::curve::ScalarField; - use crate::ntt::DEFAULT_DEVICE_ID; use icicle_core::impl_ntt_tests; use icicle_core::ntt::tests::*; + use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; + use serial_test::{parallel, serial}; use std::sync::OnceLock; impl_ntt_tests!(ScalarField); diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/src/polynomials/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/src/polynomials/mod.rs new file mode 100644 index 00000000..7e96b355 --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/src/polynomials/mod.rs @@ -0,0 +1,10 @@ +use crate::curve::{ScalarCfg, ScalarField}; +use icicle_core::impl_univariate_polynomial_api; + +impl_univariate_polynomial_api!("bls12_381", bls12_381, ScalarField, ScalarCfg); + +#[cfg(test)] +mod tests { + use icicle_core::impl_polynomial_tests; + impl_polynomial_tests!(bls12_381, ScalarField); +} diff --git a/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml b/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml index 3f6abe02..8946d901 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml @@ -11,11 +11,13 @@ repository.workspace = true icicle-core = { workspace = true } icicle-cuda-runtime = { workspace = true } ark-bn254 = { version = "0.4.0", optional = true } +criterion = "0.3" [build-dependencies] cmake = "0.1.50" [dev-dependencies] +criterion = "0.3" ark-bn254 = "0.4.0" ark-std = "0.4.0" ark-ff = "0.4.0" @@ -23,6 +25,7 @@ ark-ec = "0.4.0" ark-poly = "0.4.0" icicle-core = { path = "../../icicle-core", features = ["arkworks"] } icicle-bn254 = { path = ".", features = ["arkworks"] } +serial_test = "3.0.0" [features] default = [] @@ -30,3 +33,7 @@ g2 = ["icicle-core/g2"] ec_ntt = ["icicle-core/ec_ntt"] devmode = ["icicle-core/devmode"] arkworks = ["ark-bn254", "icicle-core/arkworks"] + +[[bench]] +name = "ecntt" +harness = false # Criterion provides own harness \ No newline at end of file diff --git a/wrappers/rust/icicle-curves/icicle-bn254/benches/ecntt.rs b/wrappers/rust/icicle-curves/icicle-bn254/benches/ecntt.rs new file mode 100644 index 00000000..37b5b80e --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bn254/benches/ecntt.rs @@ -0,0 +1,10 @@ +#[cfg(feature = "ec_ntt")] +use icicle_bn254::curve::{CurveCfg, ScalarField}; + +#[cfg(feature = "ec_ntt")] +use icicle_core::impl_ecntt_bench; +#[cfg(feature = "ec_ntt")] +impl_ecntt_bench!("bn254", ScalarField, CurveCfg); + +#[cfg(not(feature = "ec_ntt"))] +fn main() {} diff --git a/wrappers/rust/icicle-curves/icicle-bn254/build.rs b/wrappers/rust/icicle-curves/icicle-bn254/build.rs index 1b8a71c7..b0094f72 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/build.rs +++ b/wrappers/rust/icicle-curves/icicle-bn254/build.rs @@ -5,30 +5,30 @@ fn main() { println!("cargo:rerun-if-changed=../../../../icicle"); // Base config - let mut config = Config::new("../../../../icicle"); + let mut config = Config::new("../../../../icicle/"); config - .define("BUILD_TESTS", "OFF") .define("CURVE", "bn254") .define("CMAKE_BUILD_TYPE", "Release"); // Optional Features #[cfg(feature = "g2")] - config.define("G2_DEFINED", "ON"); + config.define("G2", "ON"); #[cfg(feature = "ec_ntt")] - config.define("ECNTT_DEFINED", "ON"); + config.define("ECNTT", "ON"); #[cfg(feature = "devmode")] config.define("DEVMODE", "ON"); // Build let out_dir = config - .build_target("icicle") + .build_target("icicle_curve") .build(); - println!("cargo:rustc-link-search={}/build", out_dir.display()); + println!("cargo:rustc-link-search={}/build/lib/", out_dir.display()); - println!("cargo:rustc-link-lib=ingo_bn254"); + println!("cargo:rustc-link-lib=ingo_field_bn254"); + println!("cargo:rustc-link-lib=ingo_curve_bn254"); println!("cargo:rustc-link-lib=stdc++"); println!("cargo:rustc-link-lib=cudart"); } diff --git a/wrappers/rust/icicle-curves/icicle-bn254/src/curve.rs b/wrappers/rust/icicle-curves/icicle-bn254/src/curve.rs index b53264fb..1c4eba46 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/src/curve.rs +++ b/wrappers/rust/icicle-curves/icicle-bn254/src/curve.rs @@ -6,14 +6,15 @@ use icicle_core::curve::{Affine, Curve, Projective}; use icicle_core::field::{Field, MontgomeryConvertibleField}; use icicle_core::traits::{FieldConfig, FieldImpl, GenerateRandom}; use icicle_core::{impl_curve, impl_field, impl_scalar_field}; +use icicle_cuda_runtime::device::check_device; use icicle_cuda_runtime::device_context::DeviceContext; use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{DeviceSlice, HostOrDeviceSlice}; -pub(crate) const SCALAR_LIMBS: usize = 4; -pub(crate) const BASE_LIMBS: usize = 4; +pub(crate) const SCALAR_LIMBS: usize = 8; +pub(crate) const BASE_LIMBS: usize = 8; #[cfg(feature = "g2")] -pub(crate) const G2_BASE_LIMBS: usize = 8; +pub(crate) const G2_BASE_LIMBS: usize = 16; impl_scalar_field!("bn254", bn254_sf, SCALAR_LIMBS, ScalarField, ScalarCfg, Fr); impl_field!(BASE_LIMBS, BaseField, BaseCfg, Fq); @@ -31,7 +32,7 @@ impl_curve!( ); #[cfg(feature = "g2")] impl_curve!( - "bn254G2", + "bn254_g2", bn254_g2, G2CurveCfg, ScalarField, diff --git a/wrappers/rust/icicle-curves/icicle-bn254/src/ecntt/mod.rs b/wrappers/rust/icicle-curves/icicle-bn254/src/ecntt/mod.rs new file mode 100644 index 00000000..b530630a --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bn254/src/ecntt/mod.rs @@ -0,0 +1,23 @@ +#![cfg(feature = "ec_ntt")] +use icicle_core::error::IcicleResult; +use icicle_core::impl_ecntt; +use icicle_core::ntt::{NTTConfig, NTTDir}; +use icicle_cuda_runtime::device_context::DeviceContext; +use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; +use icicle_cuda_runtime::error::CudaError; + +use crate::curve::{CurveCfg, ScalarCfg, ScalarField}; +use icicle_core::ecntt::Projective; + +impl_ecntt!("bn254", bn254, ScalarField, ScalarCfg, CurveCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::curve::{CurveCfg, ScalarField}; + + use icicle_core::ecntt::tests::*; + use icicle_core::impl_ecntt_tests; + use std::sync::OnceLock; + + impl_ecntt_tests!(ScalarField, CurveCfg); +} diff --git a/wrappers/rust/icicle-curves/icicle-bn254/src/lib.rs b/wrappers/rust/icicle-curves/icicle-bn254/src/lib.rs index 2bda6aec..71a597c7 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/src/lib.rs +++ b/wrappers/rust/icicle-curves/icicle-bn254/src/lib.rs @@ -1,6 +1,8 @@ pub mod curve; +pub mod ecntt; pub mod msm; pub mod ntt; +pub mod polynomials; pub mod poseidon; pub mod tree; pub mod vec_ops; diff --git a/wrappers/rust/icicle-curves/icicle-bn254/src/msm/mod.rs b/wrappers/rust/icicle-curves/icicle-bn254/src/msm/mod.rs index f60628c3..3af49895 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/src/msm/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-bn254/src/msm/mod.rs @@ -8,11 +8,15 @@ use icicle_core::{ msm::{MSMConfig, MSM}, traits::IcicleResultWrap, }; -use icicle_cuda_runtime::{device_context::DeviceContext, error::CudaError, memory::HostOrDeviceSlice}; +use icicle_cuda_runtime::{ + device_context::DeviceContext, + error::CudaError, + memory::{DeviceSlice, HostOrDeviceSlice}, +}; impl_msm!("bn254", bn254, CurveCfg); #[cfg(feature = "g2")] -impl_msm!("bn254G2", bn254_g2, G2CurveCfg); +impl_msm!("bn254_g2", bn254_g2, G2CurveCfg); #[cfg(test)] pub(crate) mod tests { diff --git a/wrappers/rust/icicle-curves/icicle-bn254/src/ntt/mod.rs b/wrappers/rust/icicle-curves/icicle-bn254/src/ntt/mod.rs index f91deaf6..10d9297b 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/src/ntt/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-bn254/src/ntt/mod.rs @@ -1,11 +1,10 @@ use crate::curve::{ScalarCfg, ScalarField}; use icicle_core::error::IcicleResult; -use icicle_core::impl_ntt; -use icicle_core::ntt::{NTTConfig, NTTDir, NTT}; +use icicle_core::ntt::{NTTConfig, NTTDir, NTTDomain, NTT}; use icicle_core::traits::IcicleResultWrap; +use icicle_core::{impl_ntt, impl_ntt_without_domain}; use icicle_cuda_runtime::device_context::DeviceContext; -use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; use icicle_cuda_runtime::error::CudaError; use icicle_cuda_runtime::memory::HostOrDeviceSlice; @@ -14,9 +13,10 @@ impl_ntt!("bn254", bn254, ScalarField, ScalarCfg); #[cfg(test)] pub(crate) mod tests { use crate::curve::ScalarField; - use crate::ntt::DEFAULT_DEVICE_ID; use icicle_core::impl_ntt_tests; use icicle_core::ntt::tests::*; + use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; + use serial_test::{parallel, serial}; use std::sync::OnceLock; impl_ntt_tests!(ScalarField); diff --git a/wrappers/rust/icicle-curves/icicle-bn254/src/polynomials/mod.rs b/wrappers/rust/icicle-curves/icicle-bn254/src/polynomials/mod.rs new file mode 100644 index 00000000..7ac153e6 --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bn254/src/polynomials/mod.rs @@ -0,0 +1,10 @@ +use crate::curve::{ScalarCfg, ScalarField}; +use icicle_core::impl_univariate_polynomial_api; + +impl_univariate_polynomial_api!("bn254", bn254, ScalarField, ScalarCfg); + +#[cfg(test)] +mod tests { + use icicle_core::impl_polynomial_tests; + impl_polynomial_tests!(bn254, ScalarField); +} diff --git a/wrappers/rust/icicle-curves/icicle-bw6-761/Cargo.toml b/wrappers/rust/icicle-curves/icicle-bw6-761/Cargo.toml index 205c418d..486296b8 100644 --- a/wrappers/rust/icicle-curves/icicle-bw6-761/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-bw6-761/Cargo.toml @@ -12,11 +12,13 @@ icicle-core = { workspace = true } icicle-cuda-runtime = { workspace = true } icicle-bls12-377 = { path = "../../icicle-curves/icicle-bls12-377", features = ["bw6-761"] } ark-bw6-761 = { version = "0.4.0", optional = true } +criterion = "0.3" [build-dependencies] cmake = "0.1.50" [dev-dependencies] +criterion = "0.3" ark-bw6-761 = "0.4.0" ark-std = "0.4.0" ark-ff = "0.4.0" @@ -24,9 +26,10 @@ ark-ec = "0.4.0" ark-poly = "0.4.0" icicle-core = { path = "../../icicle-core", features = ["arkworks"] } icicle-bw6-761 = { path = ".", features = ["arkworks"] } +serial_test = "3.0.0" [features] default = [] g2 = ["icicle-bls12-377/bw6-761-g2"] devmode = ["icicle-core/devmode"] -arkworks = ["ark-bw6-761", "icicle-core/arkworks", "icicle-bls12-377/arkworks"] +arkworks = ["ark-bw6-761", "icicle-core/arkworks", "icicle-bls12-377/arkworks"] \ No newline at end of file diff --git a/wrappers/rust/icicle-curves/icicle-bw6-761/src/curve.rs b/wrappers/rust/icicle-curves/icicle-bw6-761/src/curve.rs index 0500f1a9..fd063360 100644 --- a/wrappers/rust/icicle-curves/icicle-bw6-761/src/curve.rs +++ b/wrappers/rust/icicle-curves/icicle-bw6-761/src/curve.rs @@ -9,9 +9,8 @@ use icicle_core::traits::FieldConfig; use icicle_core::{impl_curve, impl_field}; use icicle_cuda_runtime::device_context::DeviceContext; use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; -pub(crate) const BASE_LIMBS: usize = 12; +pub(crate) const BASE_LIMBS: usize = 24; impl_field!(BASE_LIMBS, BaseField, BaseCfg, Fq); pub type ScalarField = bls12_377BaseField; @@ -27,7 +26,7 @@ impl_curve!( ); #[cfg(feature = "g2")] impl_curve!( - "bw6_761G2", + "bw6_761_g2", bw6_761_g2, G2CurveCfg, ScalarField, diff --git a/wrappers/rust/icicle-curves/icicle-bw6-761/src/msm/mod.rs b/wrappers/rust/icicle-curves/icicle-bw6-761/src/msm/mod.rs index b1c4185a..5b87e08b 100644 --- a/wrappers/rust/icicle-curves/icicle-bw6-761/src/msm/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-bw6-761/src/msm/mod.rs @@ -8,11 +8,15 @@ use icicle_core::{ msm::{MSMConfig, MSM}, traits::IcicleResultWrap, }; -use icicle_cuda_runtime::{device_context::DeviceContext, error::CudaError, memory::HostOrDeviceSlice}; +use icicle_cuda_runtime::{ + device_context::DeviceContext, + error::CudaError, + memory::{DeviceSlice, HostOrDeviceSlice}, +}; impl_msm!("bw6_761", bw6_761, CurveCfg); #[cfg(feature = "g2")] -impl_msm!("bw6_761G2", bw6_761_g2, G2CurveCfg); +impl_msm!("bw6_761_g2", bw6_761_g2, G2CurveCfg); #[cfg(test)] pub(crate) mod tests { diff --git a/wrappers/rust/icicle-curves/icicle-bw6-761/src/ntt/mod.rs b/wrappers/rust/icicle-curves/icicle-bw6-761/src/ntt/mod.rs index fb673824..f8c9a7d5 100644 --- a/wrappers/rust/icicle-curves/icicle-bw6-761/src/ntt/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-bw6-761/src/ntt/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod tests { use icicle_core::impl_ntt_tests; use icicle_core::ntt::tests::*; use icicle_cuda_runtime::device_context::DEFAULT_DEVICE_ID; + use serial_test::{parallel, serial}; use std::sync::OnceLock; impl_ntt_tests!(ScalarField); diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/Cargo.toml b/wrappers/rust/icicle-curves/icicle-curve-template/Cargo.toml deleted file mode 100644 index ba0d6688..00000000 --- a/wrappers/rust/icicle-curves/icicle-curve-template/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "icicle-" -version.workspace = true -edition.workspace = true -authors.workspace = true -description = "Rust wrapper for the CUDA implementation of elliptic curve by Ingonyama" -homepage.workspace = true -repository.workspace = true - -[dependencies] -icicle-core = { workspace = true } -icicle-cuda-runtime = { workspace = true } -ark- = { version = "0.4.0", optional = true } - -[build-dependencies] -cmake = "0.1.50" - -[dev-dependencies] -ark- = "0.4.0" -ark-std = "0.4.0" -ark-ff = "0.4.0" -ark-ec = "0.4.0" -ark-poly = "0.4.0" -icicle-core = { path = "../../icicle-core", features = ["arkworks"] } -icicle- = { path = ".", features = ["arkworks"] } - -[features] -default = [] -g2 = ["icicle-core/g2"] -arkworks = ["ark-", "icicle-core/arkworks"] diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/build.rs b/wrappers/rust/icicle-curves/icicle-curve-template/build.rs deleted file mode 100644 index 08817195..00000000 --- a/wrappers/rust/icicle-curves/icicle-curve-template/build.rs +++ /dev/null @@ -1,28 +0,0 @@ -use cmake::Config; - -fn main() { - println!("cargo:rerun-if-env-changed=CXXFLAGS"); - println!("cargo:rerun-if-changed=../../../../icicle"); - - // Base config - let mut config = Config::new("../../../../icicle"); - config - .define("BUILD_TESTS", "OFF") - .define("CURVE", "") - .define("CMAKE_BUILD_TYPE", "Release"); - - // Optional Features - #[cfg(feature = "g2")] - config.define("G2_DEFINED", "ON"); - - // Build - let out_dir = config - .build_target("icicle") - .build(); - - println!("cargo:rustc-link-search={}/build", out_dir.display()); - - println!("cargo:rustc-link-lib=ingo_"); - println!("cargo:rustc-link-lib=stdc++"); - println!("cargo:rustc-link-lib=cudart"); -} diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/src/curve.rs b/wrappers/rust/icicle-curves/icicle-curve-template/src/curve.rs deleted file mode 100644 index 327efd2b..00000000 --- a/wrappers/rust/icicle-curves/icicle-curve-template/src/curve.rs +++ /dev/null @@ -1,61 +0,0 @@ -#[cfg(feature = "arkworks")] -use ark_::{g1::Config as ArkG1Config, Fq, Fr}; -#[cfg(all(feature = "arkworks", feature = "g2"))] -use ark_::{g2::Config as ArkG2Config, Fq2}; -use icicle_core::curve::{Affine, Curve, Projective}; -use icicle_core::field::{Field, MontgomeryConvertibleField}; -use icicle_core::traits::{FieldConfig, FieldImpl, GenerateRandom}; -use icicle_core::{impl_curve, impl_field, impl_scalar_field}; -use icicle_cuda_runtime::device_context::DeviceContext; -use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; - -pub(crate) const SCALAR_LIMBS: usize = ; -pub(crate) const BASE_LIMBS: usize = ; -#[cfg(feature = "g2")] -pub(crate) const G2_BASE_LIMBS: usize = ; - -impl_scalar_field!("", _sf, SCALAR_LIMBS, ScalarField, ScalarCfg, Fr); -impl_field!(BASE_LIMBS, BaseField, BaseCfg, Fq); -#[cfg(feature = "g2")] -impl_field!(G2_BASE_LIMBS, G2BaseField, G2BaseCfg, Fq2); -impl_curve!( - "", - , - CurveCfg, - ScalarField, - BaseField, - ArkG1Config, - G1Affine, - G1Projective -); -#[cfg(feature = "g2")] -impl_curve!( - "G2", - _g2, - G2CurveCfg, - ScalarField, - G2BaseField, - ArkG2Config, - G2Affine, - G2Projective -); - -#[cfg(test)] -mod tests { - use super::{CurveCfg, ScalarField, BASE_LIMBS}; - #[cfg(feature = "g2")] - use super::{G2CurveCfg, G2_BASE_LIMBS}; - use icicle_core::curve::Curve; - use icicle_core::tests::*; - use icicle_core::traits::FieldImpl; - use icicle_core::{impl_curve_tests, impl_field_tests}; - - impl_field_tests!(ScalarField); - impl_curve_tests!(BASE_LIMBS, CurveCfg); - #[cfg(feature = "g2")] - mod g2 { - use super::*; - impl_curve_tests!(G2_BASE_LIMBS, G2CurveCfg); - } -} diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/src/lib.rs b/wrappers/rust/icicle-curves/icicle-curve-template/src/lib.rs deleted file mode 100644 index 2bda6aec..00000000 --- a/wrappers/rust/icicle-curves/icicle-curve-template/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod curve; -pub mod msm; -pub mod ntt; -pub mod poseidon; -pub mod tree; -pub mod vec_ops; - -impl icicle_core::SNARKCurve for curve::CurveCfg {} diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/src/msm/mod.rs b/wrappers/rust/icicle-curves/icicle-curve-template/src/msm/mod.rs deleted file mode 100644 index 487926bc..00000000 --- a/wrappers/rust/icicle-curves/icicle-curve-template/src/msm/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::curve::CurveCfg; -#[cfg(feature = "g2")] -use crate::curve::G2CurveCfg; -use icicle_core::{ - curve::{Affine, Curve, Projective}, - error::IcicleResult, - impl_msm, - msm::{MSMConfig, MSM}, - traits::IcicleResultWrap, -}; -use icicle_cuda_runtime::{error::CudaError, memory::HostOrDeviceSlice}; - -impl_msm!("", , CurveCfg); -#[cfg(feature = "g2")] -impl_msm!("G2", _g2, G2CurveCfg); - -#[cfg(test)] -pub(crate) mod tests { - use crate::curve::CurveCfg; - #[cfg(feature = "g2")] - use crate::curve::G2CurveCfg; - use icicle_core::impl_msm_tests; - use icicle_core::msm::tests::*; - - impl_msm_tests!(CurveCfg); - #[cfg(feature = "g2")] - mod g2 { - use super::*; - impl_msm_tests!(G2CurveCfg); - } -} diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/src/ntt/mod.rs b/wrappers/rust/icicle-curves/icicle-curve-template/src/ntt/mod.rs deleted file mode 100644 index 3243fdf0..00000000 --- a/wrappers/rust/icicle-curves/icicle-curve-template/src/ntt/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::curve::{ScalarCfg, ScalarField}; - -use icicle_core::error::IcicleResult; -use icicle_core::impl_ntt; -use icicle_core::ntt::{NTTConfig, NTTDir, NTT}; -use icicle_core::traits::IcicleResultWrap; -use icicle_cuda_runtime::device_context::{DeviceContext, DEFAULT_DEVICE_ID}; -use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; - -impl_ntt!("", , ScalarField, ScalarCfg); - -#[cfg(test)] -pub(crate) mod tests { - use crate::curve::ScalarField; - use crate::ntt::DEFAULT_DEVICE_ID; - use icicle_core::impl_ntt_tests; - use icicle_core::ntt::tests::*; - use std::sync::OnceLock; - - impl_ntt_tests!(ScalarField); -} diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/src/poseidon/mod.rs b/wrappers/rust/icicle-curves/icicle-curve-template/src/poseidon/mod.rs deleted file mode 100644 index f6bfc198..00000000 --- a/wrappers/rust/icicle-curves/icicle-curve-template/src/poseidon/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::curve::{ScalarCfg, ScalarField}; - -use icicle_core::error::IcicleResult; -use icicle_core::impl_poseidon; -use icicle_core::poseidon::{Poseidon, PoseidonConfig, PoseidonConstants}; -use icicle_core::traits::IcicleResultWrap; -use icicle_cuda_runtime::device_context::DeviceContext; -use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; - -use core::mem::MaybeUninit; - -impl_poseidon!("", , ScalarField, ScalarCfg); - -#[cfg(test)] -pub(crate) mod tests { - use crate::curve::ScalarField; - use icicle_core::impl_poseidon_tests; - use icicle_core::poseidon::tests::*; - - impl_poseidon_tests!(ScalarField); -} diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/src/tree/mod.rs b/wrappers/rust/icicle-curves/icicle-curve-template/src/tree/mod.rs deleted file mode 100644 index f5f00b6c..00000000 --- a/wrappers/rust/icicle-curves/icicle-curve-template/src/tree/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::curve::{ScalarCfg, ScalarField}; - -use icicle_core::error::IcicleResult; -use icicle_core::impl_tree_builder; -use icicle_core::poseidon::PoseidonConstants; -use icicle_core::traits::IcicleResultWrap; -use icicle_core::tree::{TreeBuilder, TreeBuilderConfig}; -use icicle_cuda_runtime::device_context::DeviceContext; -use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; - -impl_tree_builder!("", , ScalarField, ScalarCfg); - -#[cfg(test)] -pub(crate) mod tests { - use crate::curve::ScalarField; - use icicle_core::impl_tree_builder_tests; - use icicle_core::tree::tests::*; - - impl_tree_builder_tests!(ScalarField); -} diff --git a/wrappers/rust/icicle-curves/icicle-grumpkin/build.rs b/wrappers/rust/icicle-curves/icicle-grumpkin/build.rs index b137e09d..e71e0624 100644 --- a/wrappers/rust/icicle-curves/icicle-grumpkin/build.rs +++ b/wrappers/rust/icicle-curves/icicle-grumpkin/build.rs @@ -4,24 +4,22 @@ fn main() { println!("cargo:rerun-if-env-changed=CXXFLAGS"); println!("cargo:rerun-if-changed=../../../../icicle"); - // Base config let mut config = Config::new("../../../../icicle"); config - .define("BUILD_TESTS", "OFF") .define("CURVE", "grumpkin") .define("CMAKE_BUILD_TYPE", "Release"); #[cfg(feature = "devmode")] config.define("DEVMODE", "ON"); - // Build let out_dir = config - .build_target("icicle") + .build_target("icicle_curve") .build(); - println!("cargo:rustc-link-search={}/build", out_dir.display()); + println!("cargo:rustc-link-search={}/build/lib", out_dir.display()); - println!("cargo:rustc-link-lib=ingo_grumpkin"); + println!("cargo:rustc-link-lib=ingo_field_grumpkin"); + println!("cargo:rustc-link-lib=ingo_curve_grumpkin"); println!("cargo:rustc-link-lib=stdc++"); println!("cargo:rustc-link-lib=cudart"); } diff --git a/wrappers/rust/icicle-curves/icicle-grumpkin/src/curve.rs b/wrappers/rust/icicle-curves/icicle-grumpkin/src/curve.rs index 4af89147..8b2ea826 100644 --- a/wrappers/rust/icicle-curves/icicle-grumpkin/src/curve.rs +++ b/wrappers/rust/icicle-curves/icicle-grumpkin/src/curve.rs @@ -4,12 +4,13 @@ use icicle_core::curve::{Affine, Curve, Projective}; use icicle_core::field::{Field, MontgomeryConvertibleField}; use icicle_core::traits::{FieldConfig, FieldImpl, GenerateRandom}; use icicle_core::{impl_curve, impl_field, impl_scalar_field}; +use icicle_cuda_runtime::device::check_device; use icicle_cuda_runtime::device_context::DeviceContext; use icicle_cuda_runtime::error::CudaError; -use icicle_cuda_runtime::memory::HostOrDeviceSlice; +use icicle_cuda_runtime::memory::{DeviceSlice, HostOrDeviceSlice}; -pub(crate) const SCALAR_LIMBS: usize = 4; -pub(crate) const BASE_LIMBS: usize = 4; +pub(crate) const SCALAR_LIMBS: usize = 8; +pub(crate) const BASE_LIMBS: usize = 8; impl_scalar_field!("grumpkin", grumpkin_sf, SCALAR_LIMBS, ScalarField, ScalarCfg, Fr); impl_field!(BASE_LIMBS, BaseField, BaseCfg, Fq); diff --git a/wrappers/rust/icicle-curves/icicle-grumpkin/src/msm/mod.rs b/wrappers/rust/icicle-curves/icicle-grumpkin/src/msm/mod.rs index e1a538c0..a0c7c9f3 100644 --- a/wrappers/rust/icicle-curves/icicle-grumpkin/src/msm/mod.rs +++ b/wrappers/rust/icicle-curves/icicle-grumpkin/src/msm/mod.rs @@ -6,7 +6,11 @@ use icicle_core::{ msm::{MSMConfig, MSM}, traits::IcicleResultWrap, }; -use icicle_cuda_runtime::{device_context::DeviceContext, error::CudaError, memory::HostOrDeviceSlice}; +use icicle_cuda_runtime::{ + device_context::DeviceContext, + error::CudaError, + memory::{DeviceSlice, HostOrDeviceSlice}, +}; impl_msm!("grumpkin", grumpkin, CurveCfg); diff --git a/wrappers/rust/icicle-fields/icicle-babybear/Cargo.toml b/wrappers/rust/icicle-fields/icicle-babybear/Cargo.toml new file mode 100644 index 00000000..3d8fd6f9 --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-babybear/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "icicle-babybear" +version.workspace = true +edition.workspace = true +authors.workspace = true +description = "Rust wrapper for the CUDA implementation of baby bear prime field by Ingonyama" +homepage.workspace = true +repository.workspace = true + +[dependencies] +icicle-core = { workspace = true } +icicle-cuda-runtime = { workspace = true } + +[build-dependencies] +cmake = "0.1.50" + +[dev-dependencies] +risc0-core = "0.21.0" +risc0-zkp = "0.21.0" +p3-baby-bear = { git = "https://github.com/Plonky3/Plonky3", rev = "83590121c8c28011cffa7e73cb71cf9bf94b8477" } +p3-field = { git = "https://github.com/Plonky3/Plonky3", rev = "83590121c8c28011cffa7e73cb71cf9bf94b8477" } +p3-dft = { git = "https://github.com/Plonky3/Plonky3", rev = "83590121c8c28011cffa7e73cb71cf9bf94b8477" } +p3-matrix = { git = "https://github.com/Plonky3/Plonky3", rev = "83590121c8c28011cffa7e73cb71cf9bf94b8477" } +serial_test = "3.0.0" + +[features] +default = [] +devmode = ["icicle-core/devmode"] diff --git a/wrappers/rust/icicle-fields/icicle-babybear/build.rs b/wrappers/rust/icicle-fields/icicle-babybear/build.rs new file mode 100644 index 00000000..f170751a --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-babybear/build.rs @@ -0,0 +1,24 @@ +use cmake::Config; + +fn main() { + println!("cargo:rerun-if-env-changed=CXXFLAGS"); + println!("cargo:rerun-if-changed=../../../../icicle"); + + // Base config + let mut config = Config::new("../../../../icicle/"); + config + .define("FIELD", "babybear") + .define("CMAKE_BUILD_TYPE", "Release") + .define("EXT_FIELD", "ON"); + + // Build + let out_dir = config + .build_target("icicle_field") + .build(); + + println!("cargo:rustc-link-search={}/build/lib", out_dir.display()); + + println!("cargo:rustc-link-lib=ingo_field_babybear"); + println!("cargo:rustc-link-lib=stdc++"); + println!("cargo:rustc-link-lib=cudart"); +} diff --git a/wrappers/rust/icicle-fields/icicle-babybear/src/field.rs b/wrappers/rust/icicle-fields/icicle-babybear/src/field.rs new file mode 100644 index 00000000..439dda31 --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-babybear/src/field.rs @@ -0,0 +1,34 @@ +use icicle_core::field::{Field, MontgomeryConvertibleField}; +use icicle_core::traits::{FieldConfig, FieldImpl, GenerateRandom}; +use icicle_core::{impl_field, impl_scalar_field}; +use icicle_cuda_runtime::device::check_device; +use icicle_cuda_runtime::device_context::DeviceContext; +use icicle_cuda_runtime::error::CudaError; +use icicle_cuda_runtime::memory::{DeviceSlice, HostOrDeviceSlice}; + +pub(crate) const SCALAR_LIMBS: usize = 1; +pub(crate) const EXTENSION_LIMBS: usize = 4; + +impl_scalar_field!("babybear", babybear, SCALAR_LIMBS, ScalarField, ScalarCfg, Fr); +impl_scalar_field!( + "babybear_extension", + babybear_extension, + EXTENSION_LIMBS, + ExtensionField, + ExtensionCfg, + Fr +); + +#[cfg(test)] +mod tests { + use super::{ExtensionField, ScalarField}; + use icicle_core::impl_field_tests; + use icicle_core::tests::*; + + impl_field_tests!(ScalarField); + mod extension { + use super::*; + + impl_field_tests!(ExtensionField); + } +} diff --git a/wrappers/rust/icicle-fields/icicle-babybear/src/lib.rs b/wrappers/rust/icicle-fields/icicle-babybear/src/lib.rs new file mode 100644 index 00000000..a95ac089 --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-babybear/src/lib.rs @@ -0,0 +1,4 @@ +pub mod field; +pub mod ntt; +pub mod polynomials; +pub mod vec_ops; diff --git a/wrappers/rust/icicle-fields/icicle-babybear/src/ntt/mod.rs b/wrappers/rust/icicle-fields/icicle-babybear/src/ntt/mod.rs new file mode 100644 index 00000000..a1747815 --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-babybear/src/ntt/mod.rs @@ -0,0 +1,174 @@ +use crate::field::{ExtensionField, ScalarCfg, ScalarField}; + +use icicle_core::error::IcicleResult; +use icicle_core::ntt::{NTTConfig, NTTDir, NTTDomain, NTT}; +use icicle_core::traits::IcicleResultWrap; +use icicle_core::{impl_ntt, impl_ntt_without_domain}; +use icicle_cuda_runtime::device_context::DeviceContext; +use icicle_cuda_runtime::error::CudaError; +use icicle_cuda_runtime::memory::HostOrDeviceSlice; + +impl_ntt!("babybear", babybear, ScalarField, ScalarCfg); +impl_ntt_without_domain!( + "babybear_extension", + ScalarField, + ScalarCfg, + NTT, + "_ntt", + ExtensionField +); + +#[cfg(test)] +pub(crate) mod tests { + use super::{ExtensionField, ScalarField}; + use icicle_core::{ + ntt::{initialize_domain, ntt_inplace, release_domain, NTTConfig, NTTDir}, + traits::{FieldImpl, GenerateRandom}, + }; + use icicle_cuda_runtime::{device_context::DeviceContext, memory::HostSlice}; + use p3_baby_bear::BabyBear; + use p3_dft::{Radix2Dit, TwoAdicSubgroupDft}; + use p3_field::{ + extension::BinomialExtensionField, AbstractExtensionField, AbstractField, PrimeField32, TwoAdicField, + }; + use p3_matrix::dense::RowMajorMatrix; + use risc0_core::field::{ + baby_bear::{Elem, ExtElem}, + Elem as FieldElem, RootsOfUnity, + }; + use serial_test::serial; + + // Note that risc0 and plonky3 tests shouldn't be ran simultaneously in parallel as they use different roots of unity + #[test] + #[serial] + fn test_against_risc0() { + let log_sizes = [15, 20]; + let ctx = DeviceContext::default(); + let risc0_rou = Elem::ROU_FWD[log_sizes[1]]; + initialize_domain(ScalarField::from([risc0_rou.as_u32()]), &ctx, false).unwrap(); + for log_size in log_sizes { + let ntt_size = 1 << log_size; + + let mut scalars: Vec = ::Config::generate_random(ntt_size); + let mut scalars_risc0: Vec = scalars + .iter() + .map(|x| Elem::new(Into::<[u32; 1]>::into(*x)[0])) + .collect(); + + let ntt_cfg: NTTConfig<'_, ScalarField> = NTTConfig::default(); + ntt_inplace(HostSlice::from_mut_slice(&mut scalars[..]), NTTDir::kForward, &ntt_cfg).unwrap(); + + risc0_zkp::core::ntt::bit_reverse(&mut scalars_risc0[..]); + risc0_zkp::core::ntt::evaluate_ntt::(&mut scalars_risc0[..], ntt_size); + + for (s1, s2) in scalars + .iter() + .zip(scalars_risc0) + { + assert_eq!(Into::<[u32; 1]>::into(*s1)[0], s2.as_u32()); + } + + let mut ext_scalars: Vec = ::Config::generate_random(ntt_size); + let mut ext_scalars_risc0: Vec = ext_scalars + .iter() + .map(|x| ExtElem::from_u32_words(&Into::<[u32; 4]>::into(*x)[..])) + .collect(); + + ntt_inplace( + HostSlice::from_mut_slice(&mut ext_scalars[..]), + NTTDir::kForward, + &ntt_cfg, + ) + .unwrap(); + + risc0_zkp::core::ntt::bit_reverse(&mut ext_scalars_risc0[..]); + risc0_zkp::core::ntt::evaluate_ntt::(&mut ext_scalars_risc0[..], ntt_size); + + for (s1, s2) in ext_scalars + .iter() + .zip(ext_scalars_risc0) + { + assert_eq!(Into::<[u32; 4]>::into(*s1)[..], s2.to_u32_words()[..]); + } + } + + release_domain::(&ctx).unwrap(); + } + + #[test] + #[serial] + fn test_against_plonky3() { + let log_ncols = [15, 18]; + let nrows = 4; + let ctx = DeviceContext::default(); + let plonky3_rou = BabyBear::two_adic_generator(log_ncols[1]); + // To compute FFTs using icicle, we first need to initialize it using plonky3's "two adic generator" + initialize_domain(ScalarField::from([plonky3_rou.as_canonical_u32()]), &ctx, false).unwrap(); + for log_ncol in log_ncols { + let ntt_size = 1 << log_ncol; + + let mut scalars: Vec = ::Config::generate_random(nrows * ntt_size); + let scalars_p3: Vec = scalars + .iter() + .map(|x| BabyBear::from_wrapped_u32(Into::<[u32; 1]>::into(*x)[0])) + .collect(); + let matrix_p3 = RowMajorMatrix::new(scalars_p3, nrows); + + let mut ntt_cfg: NTTConfig<'_, ScalarField> = NTTConfig::default(); + // Next two lines signalize that we want to compute `nrows` FFTs in column-ordered fashion + ntt_cfg.batch_size = nrows as i32; + ntt_cfg.columns_batch = true; + ntt_inplace(HostSlice::from_mut_slice(&mut scalars[..]), NTTDir::kForward, &ntt_cfg).unwrap(); + + let result_p3 = Radix2Dit.dft_batch(matrix_p3); + + for i in 0..nrows { + for j in 0..ntt_size { + assert_eq!( + Into::<[u32; 1]>::into(scalars[i + j * nrows])[0], + result_p3.values[i + j * nrows].as_canonical_u32() + ); + } + } + + type Plonky3Extension = BinomialExtensionField; + + let mut ext_scalars: Vec = + ::Config::generate_random(nrows * ntt_size); + let ext_scalars_p3: Vec = ext_scalars + .iter() + .map(|x| { + let arr: [u32; 4] = (*x).into(); + Plonky3Extension::from_base_slice( + &(arr + .iter() + .map(|y| BabyBear::from_wrapped_u32(*y)) + .collect::>())[..], + ) + }) + .collect(); + let ext_matrix_p3 = RowMajorMatrix::new(ext_scalars_p3, nrows); + + ntt_inplace( + HostSlice::from_mut_slice(&mut ext_scalars[..]), + NTTDir::kForward, + &ntt_cfg, + ) + .unwrap(); + + let ext_result_p3 = Radix2Dit.dft_batch(ext_matrix_p3); + + for i in 0..nrows { + for j in 0..ntt_size { + let arr: [u32; 4] = ext_scalars[i + j * nrows].into(); + let base_slice: &[BabyBear] = ext_result_p3.values[i + j * nrows].as_base_slice(); + for k in 0..4 { + assert_eq!(arr[k], base_slice[k].as_canonical_u32()); + } + } + } + } + + release_domain::(&ctx).unwrap(); + } +} diff --git a/wrappers/rust/icicle-fields/icicle-babybear/src/polynomials/mod.rs b/wrappers/rust/icicle-fields/icicle-babybear/src/polynomials/mod.rs new file mode 100644 index 00000000..727d36da --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-babybear/src/polynomials/mod.rs @@ -0,0 +1,10 @@ +use crate::field::{ScalarCfg, ScalarField}; +use icicle_core::impl_univariate_polynomial_api; + +impl_univariate_polynomial_api!("babybear", babybear, ScalarField, ScalarCfg); + +#[cfg(test)] +mod tests { + use icicle_core::impl_polynomial_tests; + impl_polynomial_tests!(babybear, ScalarField); +} diff --git a/wrappers/rust/icicle-curves/icicle-curve-template/src/vec_ops/mod.rs b/wrappers/rust/icicle-fields/icicle-babybear/src/vec_ops/mod.rs similarity index 55% rename from wrappers/rust/icicle-curves/icicle-curve-template/src/vec_ops/mod.rs rename to wrappers/rust/icicle-fields/icicle-babybear/src/vec_ops/mod.rs index 791f9abd..8df5fbfb 100644 --- a/wrappers/rust/icicle-curves/icicle-curve-template/src/vec_ops/mod.rs +++ b/wrappers/rust/icicle-fields/icicle-babybear/src/vec_ops/mod.rs @@ -1,4 +1,4 @@ -use crate::curve::{ScalarCfg, ScalarField}; +use crate::field::{ExtensionCfg, ExtensionField, ScalarCfg, ScalarField}; use icicle_core::error::IcicleResult; use icicle_core::impl_vec_ops_field; @@ -8,13 +8,19 @@ use icicle_cuda_runtime::device_context::DeviceContext; use icicle_cuda_runtime::error::CudaError; use icicle_cuda_runtime::memory::HostOrDeviceSlice; -impl_vec_ops_field!("", , ScalarField, ScalarCfg); +impl_vec_ops_field!("babybear", babybear, ScalarField, ScalarCfg); +impl_vec_ops_field!("babybear_extension", babybear_extension, ExtensionField, ExtensionCfg); #[cfg(test)] pub(crate) mod tests { - use crate::curve::ScalarField; + use crate::field::{ExtensionField, ScalarField}; use icicle_core::impl_vec_add_tests; use icicle_core::vec_ops::tests::*; impl_vec_add_tests!(ScalarField); + mod extension { + use super::*; + + impl_vec_add_tests!(ExtensionField); + } } diff --git a/wrappers/rust/icicle-hash/Cargo.toml b/wrappers/rust/icicle-hash/Cargo.toml new file mode 100644 index 00000000..dd384155 --- /dev/null +++ b/wrappers/rust/icicle-hash/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "icicle-hash" +version.workspace = true +edition.workspace = true +authors.workspace = true +description = "Rust wrapper for the CUDA implementation of hash functions by Ingonyama" +homepage.workspace = true +repository.workspace = true + +[dependencies] +icicle-core = { workspace = true } +icicle-cuda-runtime = { workspace = true } + +[build-dependencies] +cmake = "0.1.50" + +[features] +default = [] \ No newline at end of file diff --git a/wrappers/rust/icicle-hash/build.rs b/wrappers/rust/icicle-hash/build.rs new file mode 100644 index 00000000..f4b827e9 --- /dev/null +++ b/wrappers/rust/icicle-hash/build.rs @@ -0,0 +1,21 @@ +use cmake::Config; + +fn main() { + println!("cargo:rerun-if-env-changed=CXXFLAGS"); + println!("cargo:rerun-if-changed=../../../icicle"); + + // Base config + let mut config = Config::new("../../../icicle/"); + config.define("CMAKE_BUILD_TYPE", "Release"); + config.define("BUILD_HASH", "ON"); + + // Build + let out_dir = config + .build_target("icicle_hash") + .build(); + + println!("cargo:rustc-link-search={}/build/lib", out_dir.display()); + println!("cargo:rustc-link-lib=ingo_hash"); + println!("cargo:rustc-link-lib=stdc++"); + println!("cargo:rustc-link-lib=cudart"); +} diff --git a/wrappers/rust/icicle-hash/src/keccak/mod.rs b/wrappers/rust/icicle-hash/src/keccak/mod.rs new file mode 100644 index 00000000..2fbd52ce --- /dev/null +++ b/wrappers/rust/icicle-hash/src/keccak/mod.rs @@ -0,0 +1,102 @@ +use icicle_cuda_runtime::error::CudaError; +use icicle_cuda_runtime::{ + device_context::{DeviceContext, DEFAULT_DEVICE_ID}, + memory::HostOrDeviceSlice, +}; + +use icicle_core::error::IcicleResult; +use icicle_core::traits::IcicleResultWrap; + +pub mod tests; + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct KeccakConfig<'a> { + /// Details related to the device such as its id and stream id. See [DeviceContext](@ref device_context::DeviceContext). + pub ctx: DeviceContext<'a>, + + /// True if inputs are on device and false if they're on host. Default value: false. + are_inputs_on_device: bool, + + /// If true, output is preserved on device, otherwise on host. Default value: false. + are_outputs_on_device: bool, + + /// Whether to run the Keccak asynchronously. If set to `true`, the keccak_hash function will be + /// non-blocking and you'd need to synchronize it explicitly by running + /// `cudaStreamSynchronize` or `cudaDeviceSynchronize`. If set to false, keccak_hash + /// function will block the current CPU thread. + is_async: bool, +} + +impl<'a> Default for KeccakConfig<'a> { + fn default() -> Self { + Self::default_for_device(DEFAULT_DEVICE_ID) + } +} + +impl<'a> KeccakConfig<'a> { + pub fn default_for_device(device_id: usize) -> Self { + KeccakConfig { + ctx: DeviceContext::default_for_device(device_id), + are_inputs_on_device: false, + are_outputs_on_device: false, + is_async: false, + } + } +} + +extern "C" { + pub(crate) fn keccak256_cuda( + input: *const u8, + input_block_size: i32, + number_of_blocks: i32, + output: *mut u8, + config: KeccakConfig, + ) -> CudaError; + + pub(crate) fn keccak512_cuda( + input: *const u8, + input_block_size: i32, + number_of_blocks: i32, + output: *mut u8, + config: KeccakConfig, + ) -> CudaError; +} + +pub fn keccak256( + input: &(impl HostOrDeviceSlice + ?Sized), + input_block_size: i32, + number_of_blocks: i32, + output: &mut (impl HostOrDeviceSlice + ?Sized), + config: KeccakConfig, +) -> IcicleResult<()> { + unsafe { + keccak256_cuda( + input.as_ptr(), + input_block_size, + number_of_blocks, + output.as_mut_ptr(), + config, + ) + .wrap() + } +} + +pub fn keccak512( + input: &(impl HostOrDeviceSlice + ?Sized), + input_block_size: i32, + number_of_blocks: i32, + output: &mut (impl HostOrDeviceSlice + ?Sized), + config: KeccakConfig, +) -> IcicleResult<()> { + unsafe { + keccak512_cuda( + input.as_ptr(), + input_block_size, + number_of_blocks, + output.as_mut_ptr(), + config, + ) + .wrap() + } +} diff --git a/wrappers/rust/icicle-hash/src/keccak/tests.rs b/wrappers/rust/icicle-hash/src/keccak/tests.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/wrappers/rust/icicle-hash/src/keccak/tests.rs @@ -0,0 +1 @@ + diff --git a/wrappers/rust/icicle-hash/src/lib.rs b/wrappers/rust/icicle-hash/src/lib.rs new file mode 100644 index 00000000..ebcb6d4d --- /dev/null +++ b/wrappers/rust/icicle-hash/src/lib.rs @@ -0,0 +1 @@ +pub mod keccak;