diff --git a/.github/workflows/v3_golang.yml b/.github/workflows/v3_golang.yml new file mode 100644 index 000000000..d1f7b7aae --- /dev/null +++ b/.github/workflows/v3_golang.yml @@ -0,0 +1,205 @@ +name: GoLang + +on: + pull_request: + branches: + - V3 + - yshekel/V3 # TODO remove when merged to V3 + push: + branches: + - V3 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-changed-files: + uses: ./.github/workflows/check-changed-files.yml + + check-format: + name: Check Code Format + runs-on: ubuntu-22.04 + needs: check-changed-files + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.20.0' + - name: Check gofmt + if: needs.check-changed-files.outputs.golang == 'true' + run: if [[ $(go list ./... | xargs go fmt) ]]; then echo "Please run go fmt"; exit 1; fi + + extract-cuda-backend-branch: + name: Extract cuda branch name + runs-on: ubuntu-22.04 + outputs: + cuda-backend-branch: ${{ steps.extract.outputs.cuda-backend-branch }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Extract Private Branch from PR Description + id: extract + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + DESCRIPTION=$(gh pr view ${{ github.event.pull_request.number }} --json body -q '.body') + echo "PR Description: $DESCRIPTION" + CUDA_BE_BRANCH=$(echo "$DESCRIPTION" | grep -oP 'cuda-backend-branch:\s*\K[^\s]+') || true + if [ -z "$CUDA_BE_BRANCH" ]; then + CUDA_BE_BRANCH="main" # Default branch if not specified + fi + echo "Extracted CUDA Backend Branch: $CUDA_BE_BRANCH" + echo "::set-output name=cuda-backend-branch::$CUDA_BE_BRANCH" + + build-curves-linux: + name: Build and test curves on Linux + runs-on: [self-hosted, Linux, X64, icicle] + needs: [check-changed-files, check-format, extract-cuda-backend-branch] + strategy: + matrix: + 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 + - name: Checkout CUDA Backend + uses: actions/checkout@v4 + with: + repository: ingonyama-zk/icicle-cuda-backend + path: ./icicle_v3/backend/cuda + token: ${{ secrets.GITHUB_TOKEN }} + ssh-key: ${{ secrets.CUDA_PULL_KEY }} + ref: ${{ needs.extract-branch.outputs.cuda-backend-branch }} + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.20.0' + - name: Build + working-directory: ./wrappers/golang_v3 + if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + # builds a single curve with the curve's specified build args + run: | + export DEFAULT_BACKEND_INSTALL_DIR=$PWD/../../build/ + ./build.sh -curve=${{ matrix.curve.name }} ${{ matrix.curve.build_args }} -cuda_backend=local + - name: Test + working-directory: ./wrappers/golang_v3/curves + if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + run: | + CURVE=$(echo ${{ matrix.curve.name }} | sed -e 's/_//g') + export CPATH=$CPATH:/usr/local/cuda/include + export DEFAULT_BACKEND_INSTALL_DIR=$PWD/../../../build/ + go test ./$CURVE/tests -count=1 -failfast -p 2 -timeout 60m -v + + build-fields-linux: + name: Build and test fields on Linux + runs-on: [self-hosted, Linux, X64, icicle] + needs: [check-changed-files, check-format, extract-cuda-backend-branch] + strategy: + matrix: + field: + - name: babybear + build_args: -field-ext + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + - name: Checkout CUDA Backend + uses: actions/checkout@v4 + with: + repository: ingonyama-zk/icicle-cuda-backend + path: ./icicle_v3/backend/cuda + token: ${{ secrets.GITHUB_TOKEN }} + ssh-key: ${{ secrets.CUDA_PULL_KEY }} + ref: ${{ needs.extract-branch.outputs.cuda-backend-branch }} + - name: Setup go + uses: actions/setup-go@v5 + with: + go-version: '1.20.0' + - name: Build + working-directory: ./wrappers/golang_v3 + if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + # builds a single field with the fields specified build args + run: | + export DEFAULT_BACKEND_INSTALL_DIR=$PWD/../../build/ + ./build.sh -field=${{ matrix.field.name }} ${{ matrix.field.build_args }} -cuda_backend=local + - name: Test + working-directory: ./wrappers/golang_v3/fields + if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + run: | + FIELD=$(echo ${{ matrix.field.name }} | sed -e 's/_//g') + export CPATH=$CPATH:/usr/local/cuda/include + export DEFAULT_BACKEND_INSTALL_DIR=$PWD/../../../build/ + go test ./$FIELD/tests -count=1 -failfast -p 2 -timeout 60m -v + + # build-hashes-linux: + # name: Build and test hashes on Linux + # runs-on: [self-hosted, Linux, X64, icicle] + # needs: [check-changed-files, check-format] + # strategy: + # matrix: + # hash: + # - name: keccak + # build_args: + # 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_v3 + # if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + # # builds a single hash algorithm with the hash's specified build args + # run: ./build.sh -hash=${{ matrix.hash.name }} ${{ matrix.hash.build_args }} + # - name: Test + # working-directory: ./wrappers/golang_v3/hash + # if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + # run: | + # HASH=$(echo ${{ matrix.hash.name }} | sed -e 's/_//g') + # export CPATH=$CPATH:/usr/local/cuda/include + # go test ./$HASH/tests -count=1 -failfast -p 2 -timeout 60m -v + + # TODO: bw6 on windows requires more memory than the standard runner has + # Add a large runner and then enable this job + # build-windows: + # name: Build on Windows + # runs-on: windows-2022 + # needs: [check-changed-files, check-format] + # strategy: + # matrix: + # curve: [bn254, bls12_381, bls12_377, bw6_761] + # steps: + # - name: Checkout Repo + # uses: actions/checkout@v4 + # - name: Setup go + # uses: actions/setup-go@v5 + # with: + # go-version: '1.20.0' + # - name: Download and Install Cuda + # if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + # id: cuda-toolkit + # uses: Jimver/cuda-toolkit@v0.2.11 + # with: + # cuda: '12.0.0' + # method: 'network' + # # https://docs.nvidia.com/cuda/archive/12.0.0/cuda-installation-guide-microsoft-windows/index.html + # sub-packages: '["cudart", "nvcc", "thrust", "visual_studio_integration"]' + # - name: Build libs + # if: needs.check-changed-files.outputs.golang == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' + # working-directory: ./wrappers/golang + # env: + # CUDA_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }} + # shell: pwsh + # run: ./build.ps1 ${{ matrix.curve }} ON # builds a single curve with G2 enabled diff --git a/.github/workflows/v3_rust.yml b/.github/workflows/v3_rust.yml index 4482cee56..04941810f 100644 --- a/.github/workflows/v3_rust.yml +++ b/.github/workflows/v3_rust.yml @@ -26,7 +26,7 @@ jobs: uses: actions/checkout@v4 - name: Check rustfmt if: needs.check-changed-files.outputs.rust == 'true' || needs.check-changed-files.outputs.cpp_cuda == 'true' - working-directory: ./wrappers/rust + working-directory: ./wrappers/rust_v3 # "-name target -prune" removes searching in any directory named "target" # Formatting by single file is necessary due to generated files not being present # before building the project. diff --git a/docs/docs/contributor-guide.md b/docs/docs/contributor-guide.md index ffd82cd9c..fbbf8b569 100644 --- a/docs/docs/contributor-guide.md +++ b/docs/docs/contributor-guide.md @@ -1,6 +1,6 @@ # Contributor's Guide -We welcome all contributions with open arms. At Ingonyama we take a village approach, believing it takes many hands and minds to build a ecosystem. +We welcome all contributions with open arms. At Ingonyama we take a village approach, believing it takes many hands and minds to build an ecosystem. ## Contributing to ICICLE @@ -14,9 +14,9 @@ We welcome all contributions with open arms. At Ingonyama we take a village appr When opening a [pull request](https://github.com/ingonyama-zk/icicle/pulls) please keep the following in mind. - `Clear Purpose` - The pull request should solve a single issue and be clean of any unrelated changes. -- `Clear description` - If the pull request is for a new feature describe what you built, why you added it and how its best that we test it. For bug fixes please describe the issue and the solution. +- `Clear description` - If the pull request is for a new feature describe what you built, why you added it and how it's best that we test it. For bug fixes please describe the issue and the solution. - `Consistent style` - Rust and Golang code should be linted by the official linters (golang fmt and rust fmt) and maintain a proper style. For CUDA and C++ code we use [`clang-format`](https://github.com/ingonyama-zk/icicle/blob/main/.clang-format), [here](https://github.com/ingonyama-zk/icicle/blob/605c25f9d22135c54ac49683b710fe2ce06e2300/.github/workflows/main-format.yml#L46) you can see how we run it. -- `Minimal Tests` - please add test which cover basic usage of your changes . +- `Minimal Tests` - please add test which cover basic usage of your changes. ## Questions? diff --git a/docs/docs/icicle/golang-bindings/ecntt.md b/docs/docs/icicle/golang-bindings/ecntt.md index 90b0b9617..13a985855 100644 --- a/docs/docs/icicle/golang-bindings/ecntt.md +++ b/docs/docs/icicle/golang-bindings/ecntt.md @@ -49,7 +49,7 @@ type NTTConfig[T any] struct { - **`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. +- **`NttAlgorithm`**: Explicitly select the NTT algorithm. ECNTT supports running on `Radix2` algorithm. ### Default Configuration diff --git a/docs/docs/icicle/golang-bindings/msm.md b/docs/docs/icicle/golang-bindings/msm.md index 7463e9f1e..5262949fd 100644 --- a/docs/docs/icicle/golang-bindings/msm.md +++ b/docs/docs/icicle/golang-bindings/msm.md @@ -141,7 +141,7 @@ cfg.Ctx.IsBigTriangle = true Toggling between MSM modes occurs automatically based on the number of results you are expecting from the `MSM` function. -The number of results is interpreted from the size of `var out core.DeviceSlice`. Thus its important when allocating memory for `var out core.DeviceSlice` to make sure that you are allocating ` X `. +The number of results is interpreted from the size of `var out core.DeviceSlice`. Thus it's important when allocating memory for `var out core.DeviceSlice` to make sure that you are allocating ` X `. ```go ... @@ -170,7 +170,7 @@ import ( ) ``` -This package include `G2Projective` and `G2Affine` points as well as a `G2Msm` method. +This package includes `G2Projective` and `G2Affine` points as well as a `G2Msm` method. ```go package main diff --git a/docs/docs/icicle/polynomials/overview.md b/docs/docs/icicle/polynomials/overview.md index 881eeef88..aae933146 100644 --- a/docs/docs/icicle/polynomials/overview.md +++ b/docs/docs/icicle/polynomials/overview.md @@ -159,7 +159,7 @@ 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. +The ability to add or subtract monomials directly and in-place is an efficient way to manipulate polynomials. Example: diff --git a/docs/docs/icicle/primitives/keccak.md b/docs/docs/icicle/primitives/keccak.md index 0d2a0dc90..2af6c3bb0 100644 --- a/docs/docs/icicle/primitives/keccak.md +++ b/docs/docs/icicle/primitives/keccak.md @@ -14,6 +14,10 @@ At its core, Keccak consists of a permutation function operating on a state arra - **Chi:** This step applies a nonlinear mixing operation to each lane of the state array. - **Iota:** This step introduces a round constant to the state array. +## Keccak vs Sha3 + +There exists a [confusion](https://www.cybertest.com/blog/keccak-vs-sha3) between what is called `Keccak` and `Sha3`. In ICICLE we support both. `Keccak256` relates to the old hash function used in Ethereum, and `Sha3-256` relates to the modern hash function. + ## Using Keccak ICICLE Keccak supports batch hashing, which can be utilized for constructing a merkle tree or running multiple hashes in parallel. @@ -37,7 +41,7 @@ let input_block_len = 136; let number_of_hashes = 1024; let preimages = vec![1u8; number_of_hashes * input_block_len]; -let mut digests = vec![0u8; number_of_hashes * 64]; +let mut digests = vec![0u8; number_of_hashes * 32]; let preimages_slice = HostSlice::from_slice(&preimages); let digests_slice = HostSlice::from_mut_slice(&mut digests); diff --git a/docs/docs/icicle/primitives/msm.md b/docs/docs/icicle/primitives/msm.md index 6bd7ded03..0809cd57d 100644 --- a/docs/docs/icicle/primitives/msm.md +++ b/docs/docs/icicle/primitives/msm.md @@ -200,4 +200,4 @@ Here are the parameters and the results for the different cases: | 24 | 1 | 1 | 18 | 6.58 | 6.61 | 232 | | 24 | 1 | 7 | 21 | 12.4 | 13.4 | 199 | -The optimal values can vary per GPU and per curve. It is best to try a few combinations until you get the best results for your specific case. +The optimal values can vary per GPU and per curve. It is best to try a few combinations until you get the best results for your specific case. \ No newline at end of file diff --git a/docs/docs/icicle/primitives/poseidon.md b/docs/docs/icicle/primitives/poseidon.md index df2af0930..61e0f82a2 100644 --- a/docs/docs/icicle/primitives/poseidon.md +++ b/docs/docs/icicle/primitives/poseidon.md @@ -215,4 +215,4 @@ The benchmarks include copying data from and to the device. | ----------- | ----------- | ----------- | 10 | 9.4 seconds | 13.6 seconds | 20 | 9.5 seconds | 13.6 seconds -| 29 | 13.7 seconds | 13.6 seconds +| 29 | 13.7 seconds | 13.6 seconds \ No newline at end of file diff --git a/go.sum b/go.sum index a3e38a8d1..da0837081 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,7 @@ golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/icicle/include/hash/keccak/keccak.cuh b/icicle/include/hash/keccak/keccak.cuh index d95c22a40..01bed87ea 100644 --- a/icicle/include/hash/keccak/keccak.cuh +++ b/icicle/include/hash/keccak/keccak.cuh @@ -22,9 +22,14 @@ namespace keccak { // Number of state elements in u64 const int KECCAK_STATE_SIZE = 25; + const int KECCAK_PADDING_CONST = 1; + const int SHA3_PADDING_CONST = 6; + class Keccak : public Hasher { public: + const int PADDING_CONST; + cudaError_t run_hash_many_kernel( const uint8_t* input, uint64_t* output, @@ -33,7 +38,34 @@ namespace keccak { unsigned int output_len, const device_context::DeviceContext& ctx) const override; - Keccak(unsigned int rate) : Hasher(KECCAK_STATE_SIZE, KECCAK_STATE_SIZE, rate, 0) {} + Keccak(unsigned int rate, unsigned int padding_const) + : Hasher(KECCAK_STATE_SIZE, KECCAK_STATE_SIZE, rate, 0), PADDING_CONST(padding_const) + { + } + }; + + class Keccak256 : public Keccak + { + public: + Keccak256() : Keccak(KECCAK_256_RATE, KECCAK_PADDING_CONST) {} + }; + + class Keccak512 : public Keccak + { + public: + Keccak512() : Keccak(KECCAK_512_RATE, KECCAK_PADDING_CONST) {} + }; + + class Sha3_256 : public Keccak + { + public: + Sha3_256() : Keccak(KECCAK_256_RATE, SHA3_PADDING_CONST) {} + }; + + class Sha3_512 : public Keccak + { + public: + Sha3_512() : Keccak(KECCAK_512_RATE, SHA3_PADDING_CONST) {} }; } // namespace keccak diff --git a/icicle/src/curves/extern.cu b/icicle/src/curves/extern.cu index 8ea5bce2f..a40d24ffd 100644 --- a/icicle/src/curves/extern.cu +++ b/icicle/src/curves/extern.cu @@ -20,6 +20,11 @@ extern "C" void CONCAT_EXPAND(CURVE, to_affine)(projective_t* point, affine_t* p *point_out = projective_t::to_affine(*point); } +extern "C" void CONCAT_EXPAND(CURVE, from_affine)(affine_t* point, projective_t* point_out) +{ + *point_out = projective_t::from_affine(*point); +} + extern "C" void CONCAT_EXPAND(CURVE, generate_projective_points)(projective_t* points, int size) { projective_t::rand_host_many(points, size); diff --git a/icicle/src/curves/extern_g2.cu b/icicle/src/curves/extern_g2.cu index e8daa5e11..6d1e44146 100644 --- a/icicle/src/curves/extern_g2.cu +++ b/icicle/src/curves/extern_g2.cu @@ -20,6 +20,11 @@ extern "C" void CONCAT_EXPAND(CURVE, g2_to_affine)(g2_projective_t* point, g2_af *point_out = g2_projective_t::to_affine(*point); } +extern "C" void CONCAT_EXPAND(CURVE, g2_from_affine)(g2_affine_t* point, g2_projective_t* point_out) +{ + *point_out = g2_projective_t::from_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); diff --git a/icicle/src/hash/keccak/extern.cu b/icicle/src/hash/keccak/extern.cu index b1e6d6aaa..519a0e14f 100644 --- a/icicle/src/hash/keccak/extern.cu +++ b/icicle/src/hash/keccak/extern.cu @@ -11,15 +11,29 @@ namespace keccak { extern "C" cudaError_t keccak256_cuda(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, HashConfig& config) { - return Keccak(KECCAK_256_RATE) - .hash_many(input, (uint64_t*)output, number_of_blocks, input_block_size, KECCAK_256_DIGEST, config); + return Keccak256().hash_many( + input, (uint64_t*)output, number_of_blocks, input_block_size, KECCAK_256_DIGEST, config); } extern "C" cudaError_t keccak512_cuda(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, HashConfig& config) { - return Keccak(KECCAK_512_RATE) - .hash_many(input, (uint64_t*)output, number_of_blocks, input_block_size, KECCAK_512_DIGEST, config); + return Keccak512().hash_many( + input, (uint64_t*)output, number_of_blocks, input_block_size, KECCAK_512_DIGEST, config); + } + + extern "C" cudaError_t + sha3_256_cuda(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, HashConfig& config) + { + return Sha3_256().hash_many( + input, (uint64_t*)output, number_of_blocks, input_block_size, KECCAK_256_DIGEST, config); + } + + extern "C" cudaError_t + sha3_512_cuda(uint8_t* input, int input_block_size, int number_of_blocks, uint8_t* output, HashConfig& config) + { + return Sha3_512().hash_many( + input, (uint64_t*)output, number_of_blocks, input_block_size, KECCAK_512_DIGEST, config); } extern "C" cudaError_t build_keccak256_merkle_tree_cuda( @@ -29,7 +43,7 @@ namespace keccak { unsigned int input_block_len, const merkle_tree::TreeBuilderConfig& tree_config) { - Keccak keccak(KECCAK_256_RATE); + Keccak256 keccak; return merkle_tree::build_merkle_tree( leaves, digests, height, input_block_len, keccak, keccak, tree_config); } @@ -41,7 +55,31 @@ namespace keccak { unsigned int input_block_len, const merkle_tree::TreeBuilderConfig& tree_config) { - Keccak keccak(KECCAK_512_RATE); + Keccak512 keccak; + return merkle_tree::build_merkle_tree( + leaves, digests, height, input_block_len, keccak, keccak, tree_config); + } + + extern "C" cudaError_t build_sha3_256_merkle_tree_cuda( + const uint8_t* leaves, + uint64_t* digests, + unsigned int height, + unsigned int input_block_len, + const merkle_tree::TreeBuilderConfig& tree_config) + { + Sha3_256 keccak; + return merkle_tree::build_merkle_tree( + leaves, digests, height, input_block_len, keccak, keccak, tree_config); + } + + extern "C" cudaError_t build_sha3_512_merkle_tree_cuda( + const uint8_t* leaves, + uint64_t* digests, + unsigned int height, + unsigned int input_block_len, + const merkle_tree::TreeBuilderConfig& tree_config) + { + Sha3_512 keccak; return merkle_tree::build_merkle_tree( leaves, digests, height, input_block_len, keccak, keccak, tree_config); } diff --git a/icicle/src/hash/keccak/keccak.cu b/icicle/src/hash/keccak/keccak.cu index e805bcf63..542da24ae 100644 --- a/icicle/src/hash/keccak/keccak.cu +++ b/icicle/src/hash/keccak/keccak.cu @@ -180,8 +180,13 @@ namespace keccak { } template - __global__ void - keccak_hash_blocks(const uint8_t* input, int input_block_size, int output_len, int number_of_blocks, uint64_t* output) + __global__ void keccak_hash_blocks( + const uint8_t* input, + int input_block_size, + int output_len, + int number_of_blocks, + uint64_t* output, + int padding_const) { int sid = (blockIdx.x * blockDim.x) + threadIdx.x; if (sid >= number_of_blocks) { return; } @@ -209,7 +214,7 @@ namespace keccak { } // pad 10*1 - last_block[input_len] = 1; + last_block[input_len] = padding_const; for (int i = 0; i < R - input_len - 1; i++) { last_block[input_len + i + 1] = 0; } @@ -240,11 +245,11 @@ namespace keccak { switch (rate) { case KECCAK_256_RATE: keccak_hash_blocks<<>>( - input, input_len, output_len, number_of_states, output); + input, input_len, output_len, number_of_states, output, PADDING_CONST); break; case KECCAK_512_RATE: keccak_hash_blocks<<>>( - input, input_len, output_len, number_of_states, output); + input, input_len, output_len, number_of_states, output, PADDING_CONST); break; default: THROW_ICICLE_ERR(IcicleError_t::InvalidArgument, "KeccakHash: #rate must be one of [136, 72]"); diff --git a/icicle/src/merkle-tree/merkle.cu b/icicle/src/merkle-tree/merkle.cu index 53f3b8f6f..2fe171634 100644 --- a/icicle/src/merkle-tree/merkle.cu +++ b/icicle/src/merkle-tree/merkle.cu @@ -129,8 +129,9 @@ namespace merkle_tree { while (number_of_states > 0) { CHK_IF_RETURN(compression.run_hash_many_kernel( - (L*)prev_layer, next_layer, number_of_states, tree_config.digest_elements * tree_config.arity, - tree_config.digest_elements, hash_config.ctx)); + (L*)prev_layer, next_layer, number_of_states, + tree_config.digest_elements * tree_config.arity * (sizeof(D) / sizeof(L)), tree_config.digest_elements, + hash_config.ctx)); if (!keep_rows || subtree_height < keep_rows) { D* digests_with_offset = @@ -298,8 +299,9 @@ namespace merkle_tree { size_t segment_offset = start_segment_offset; while (number_of_states > 0) { CHK_IF_RETURN(compression.run_hash_many_kernel( - (L*)prev_layer, next_layer, number_of_states, tree_config.digest_elements * tree_config.arity, - tree_config.digest_elements, tree_config.ctx)); + (L*)prev_layer, next_layer, number_of_states, + tree_config.digest_elements * tree_config.arity * (sizeof(D) / sizeof(L)), tree_config.digest_elements, + tree_config.ctx)); if (!tree_config.keep_rows || cap_height < tree_config.keep_rows + (int)caps_mode) { D* digests_with_offset = digests + segment_offset; CHK_IF_RETURN(cudaMemcpyAsync( diff --git a/icicle_v3/backend/cpu/src/curve/cpu_msm.cpp b/icicle_v3/backend/cpu/src/curve/cpu_msm.cpp index df171388c..be0a4b6e9 100644 --- a/icicle_v3/backend/cpu/src/curve/cpu_msm.cpp +++ b/icicle_v3/backend/cpu/src/curve/cpu_msm.cpp @@ -18,7 +18,9 @@ cpu_msm(const Device& device, const S* scalars, const A* bases, int msm_size, co const S* batch_scalars = scalars + msm_size * batch_idx; const A* batch_bases = config.are_bases_shared ? bases : bases + msm_size * batch_idx; for (auto i = 0; i < msm_size; ++i) { - res = res + P::from_affine(batch_bases[i]) * batch_scalars[i]; + res = + res + P::from_affine(config.are_points_montgomery_form ? A::from_montgomery(batch_bases[i]) : batch_bases[i]) * + (config.are_scalars_montgomery_form ? S::from_montgomery(batch_scalars[i]) : batch_scalars[i]); } results[batch_idx] = res; } diff --git a/wrappers/golang/core/internal/mock_curve.go b/wrappers/golang/core/internal/mock_curve.go index de21b3d92..dddbddee7 100644 --- a/wrappers/golang/core/internal/mock_curve.go +++ b/wrappers/golang/core/internal/mock_curve.go @@ -28,21 +28,6 @@ func (p *MockProjective) FromLimbs(x, y, z []uint32) MockProjective { return *p } -func (p *MockProjective) FromAffine(a MockAffine) MockProjective { - z := MockBaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } - - return *p -} - type MockAffine struct { X, Y MockBaseField } @@ -68,18 +53,3 @@ func (a *MockAffine) FromLimbs(x, y []uint32) MockAffine { return *a } - -func (a MockAffine) ToProjective() MockProjective { - var z MockBaseField - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p MockProjective - return p.Zero() - } - - return MockProjective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } -} diff --git a/wrappers/golang/curves/bls12377/curve.go b/wrappers/golang/curves/bls12377/curve.go index 8083669ab..43184ff56 100644 --- a/wrappers/golang/curves/bls12377/curve.go +++ b/wrappers/golang/curves/bls12377/curve.go @@ -40,17 +40,10 @@ func (p *Projective) FromLimbs(x, y, z []uint32) Projective { } func (p *Projective) FromAffine(a Affine) Projective { - z := BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(p)) + C.bls12_377_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *Projective) ProjectiveToAffine() Affine { var a Affine cA := (*C.affine_t)(unsafe.Pointer(&a)) - cP := (*C.projective_t)(unsafe.Pointer(&p)) + cP := (*C.projective_t)(unsafe.Pointer(p)) C.bls12_377_to_affine(cP, cA) return a } @@ -111,18 +104,12 @@ func (a *Affine) FromLimbs(x, y []uint32) Affine { } func (a Affine) ToProjective() Projective { - var z BaseField - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p Projective - return p.Zero() - } + var p Projective - return Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(&p)) + C.bls12_377_from_affine(cA, cP) + return p } func AffineFromProjective(p *Projective) Affine { diff --git a/wrappers/golang/curves/bls12377/g2/curve.go b/wrappers/golang/curves/bls12377/g2/curve.go index 7da1d2717..4544e9735 100644 --- a/wrappers/golang/curves/bls12377/g2/curve.go +++ b/wrappers/golang/curves/bls12377/g2/curve.go @@ -40,17 +40,10 @@ func (p *G2Projective) FromLimbs(x, y, z []uint32) G2Projective { } func (p *G2Projective) FromAffine(a G2Affine) G2Projective { - z := G2BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(p)) + C.bls12_377_g2_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *G2Projective) ProjectiveToAffine() G2Affine { var a G2Affine cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) - cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + cP := (*C.g2_projective_t)(unsafe.Pointer(p)) C.bls12_377_g2_to_affine(cP, cA) return a } @@ -111,18 +104,12 @@ func (a *G2Affine) FromLimbs(x, y []uint32) G2Affine { } func (a G2Affine) ToProjective() G2Projective { - var z G2BaseField - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p G2Projective - return p.Zero() - } + var p G2Projective - return G2Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + C.bls12_377_g2_from_affine(cA, cP) + return p } func G2AffineFromProjective(p *G2Projective) G2Affine { diff --git a/wrappers/golang/curves/bls12377/g2/include/curve.h b/wrappers/golang/curves/bls12377/g2/include/curve.h index b136de9a8..57d5e148f 100644 --- a/wrappers/golang/curves/bls12377/g2/include/curve.h +++ b/wrappers/golang/curves/bls12377/g2/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(g2_affine_t* point, g2_projective_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); diff --git a/wrappers/golang/curves/bls12377/include/curve.h b/wrappers/golang/curves/bls12377/include/curve.h index 87a0229b6..6f32d1c36 100644 --- a/wrappers/golang/curves/bls12377/include/curve.h +++ b/wrappers/golang/curves/bls12377/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(affine_t* point, projective_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); diff --git a/wrappers/golang/curves/bls12381/curve.go b/wrappers/golang/curves/bls12381/curve.go index 02cee7b64..3b1b59ea2 100644 --- a/wrappers/golang/curves/bls12381/curve.go +++ b/wrappers/golang/curves/bls12381/curve.go @@ -40,17 +40,10 @@ func (p *Projective) FromLimbs(x, y, z []uint32) Projective { } func (p *Projective) FromAffine(a Affine) Projective { - z := BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(p)) + C.bls12_381_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *Projective) ProjectiveToAffine() Affine { var a Affine cA := (*C.affine_t)(unsafe.Pointer(&a)) - cP := (*C.projective_t)(unsafe.Pointer(&p)) + cP := (*C.projective_t)(unsafe.Pointer(p)) C.bls12_381_to_affine(cP, cA) return a } @@ -111,18 +104,12 @@ func (a *Affine) FromLimbs(x, y []uint32) Affine { } func (a Affine) ToProjective() Projective { - var z BaseField - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p Projective - return p.Zero() - } + var p Projective - return Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(&p)) + C.bls12_381_from_affine(cA, cP) + return p } func AffineFromProjective(p *Projective) Affine { diff --git a/wrappers/golang/curves/bls12381/g2/curve.go b/wrappers/golang/curves/bls12381/g2/curve.go index d4fced658..b711be658 100644 --- a/wrappers/golang/curves/bls12381/g2/curve.go +++ b/wrappers/golang/curves/bls12381/g2/curve.go @@ -40,17 +40,10 @@ func (p *G2Projective) FromLimbs(x, y, z []uint32) G2Projective { } func (p *G2Projective) FromAffine(a G2Affine) G2Projective { - z := G2BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(p)) + C.bls12_381_g2_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *G2Projective) ProjectiveToAffine() G2Affine { var a G2Affine cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) - cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + cP := (*C.g2_projective_t)(unsafe.Pointer(p)) C.bls12_381_g2_to_affine(cP, cA) return a } @@ -111,18 +104,13 @@ func (a *G2Affine) FromLimbs(x, y []uint32) G2Affine { } func (a G2Affine) ToProjective() G2Projective { - var z G2BaseField + var p G2Projective - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p G2Projective - return p.Zero() - } + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + C.bls12_381_g2_from_affine(cA, cP) + return p - return G2Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } } func G2AffineFromProjective(p *G2Projective) G2Affine { diff --git a/wrappers/golang/curves/bls12381/g2/include/curve.h b/wrappers/golang/curves/bls12381/g2/include/curve.h index b7710244d..274f3ec16 100644 --- a/wrappers/golang/curves/bls12381/g2/include/curve.h +++ b/wrappers/golang/curves/bls12381/g2/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(g2_affine_t* point, g2_projective_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); diff --git a/wrappers/golang/curves/bls12381/include/curve.h b/wrappers/golang/curves/bls12381/include/curve.h index 1cb3bd61e..b9a50675c 100644 --- a/wrappers/golang/curves/bls12381/include/curve.h +++ b/wrappers/golang/curves/bls12381/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(affine_t* point, projective_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); diff --git a/wrappers/golang/curves/bn254/curve.go b/wrappers/golang/curves/bn254/curve.go index 3bc94a8f0..f78e02840 100644 --- a/wrappers/golang/curves/bn254/curve.go +++ b/wrappers/golang/curves/bn254/curve.go @@ -40,17 +40,10 @@ func (p *Projective) FromLimbs(x, y, z []uint32) Projective { } func (p *Projective) FromAffine(a Affine) Projective { - z := BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(p)) + C.bn254_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *Projective) ProjectiveToAffine() Affine { var a Affine cA := (*C.affine_t)(unsafe.Pointer(&a)) - cP := (*C.projective_t)(unsafe.Pointer(&p)) + cP := (*C.projective_t)(unsafe.Pointer(p)) C.bn254_to_affine(cP, cA) return a } @@ -111,18 +104,13 @@ func (a *Affine) FromLimbs(x, y []uint32) Affine { } func (a Affine) ToProjective() Projective { - var z BaseField + var p Projective - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p Projective - return p.Zero() - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(&p)) + C.bn254_from_affine(cA, cP) + return p - return Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } } func AffineFromProjective(p *Projective) Affine { diff --git a/wrappers/golang/curves/bn254/g2/curve.go b/wrappers/golang/curves/bn254/g2/curve.go index 7ee0ec7f9..72245e02b 100644 --- a/wrappers/golang/curves/bn254/g2/curve.go +++ b/wrappers/golang/curves/bn254/g2/curve.go @@ -40,17 +40,10 @@ func (p *G2Projective) FromLimbs(x, y, z []uint32) G2Projective { } func (p *G2Projective) FromAffine(a G2Affine) G2Projective { - z := G2BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(p)) + C.bn254_g2_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *G2Projective) ProjectiveToAffine() G2Affine { var a G2Affine cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) - cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + cP := (*C.g2_projective_t)(unsafe.Pointer(p)) C.bn254_g2_to_affine(cP, cA) return a } @@ -111,18 +104,12 @@ func (a *G2Affine) FromLimbs(x, y []uint32) G2Affine { } func (a G2Affine) ToProjective() G2Projective { - var z G2BaseField - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p G2Projective - return p.Zero() - } + var p G2Projective - return G2Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + C.bn254_g2_from_affine(cA, cP) + return p } func G2AffineFromProjective(p *G2Projective) G2Affine { diff --git a/wrappers/golang/curves/bn254/g2/include/curve.h b/wrappers/golang/curves/bn254/g2/include/curve.h index e8863f1ef..c24deb307 100644 --- a/wrappers/golang/curves/bn254/g2/include/curve.h +++ b/wrappers/golang/curves/bn254/g2/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(g2_affine_t* point, g2_projective_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); diff --git a/wrappers/golang/curves/bn254/include/curve.h b/wrappers/golang/curves/bn254/include/curve.h index 069600aa4..ce29f587a 100644 --- a/wrappers/golang/curves/bn254/include/curve.h +++ b/wrappers/golang/curves/bn254/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(affine_t* point, projective_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); diff --git a/wrappers/golang/curves/bw6761/curve.go b/wrappers/golang/curves/bw6761/curve.go index 2e8518be9..38a68fd94 100644 --- a/wrappers/golang/curves/bw6761/curve.go +++ b/wrappers/golang/curves/bw6761/curve.go @@ -40,17 +40,10 @@ func (p *Projective) FromLimbs(x, y, z []uint32) Projective { } func (p *Projective) FromAffine(a Affine) Projective { - z := BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(p)) + C.bw6_761_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *Projective) ProjectiveToAffine() Affine { var a Affine cA := (*C.affine_t)(unsafe.Pointer(&a)) - cP := (*C.projective_t)(unsafe.Pointer(&p)) + cP := (*C.projective_t)(unsafe.Pointer(p)) C.bw6_761_to_affine(cP, cA) return a } @@ -111,18 +104,13 @@ func (a *Affine) FromLimbs(x, y []uint32) Affine { } func (a Affine) ToProjective() Projective { - var z BaseField + var p Projective - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p Projective - return p.Zero() - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(&p)) + C.bw6_761_from_affine(cA, cP) + return p - return Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } } func AffineFromProjective(p *Projective) Affine { diff --git a/wrappers/golang/curves/bw6761/g2/curve.go b/wrappers/golang/curves/bw6761/g2/curve.go index 1433602a2..f38bf134f 100644 --- a/wrappers/golang/curves/bw6761/g2/curve.go +++ b/wrappers/golang/curves/bw6761/g2/curve.go @@ -40,17 +40,10 @@ func (p *G2Projective) FromLimbs(x, y, z []uint32) G2Projective { } func (p *G2Projective) FromAffine(a G2Affine) G2Projective { - z := G2BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(p)) + C.bw6_761_g2_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *G2Projective) ProjectiveToAffine() G2Affine { var a G2Affine cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) - cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + cP := (*C.g2_projective_t)(unsafe.Pointer(p)) C.bw6_761_g2_to_affine(cP, cA) return a } @@ -111,18 +104,12 @@ func (a *G2Affine) FromLimbs(x, y []uint32) G2Affine { } func (a G2Affine) ToProjective() G2Projective { - var z G2BaseField - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p G2Projective - return p.Zero() - } + var p G2Projective - return G2Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + C.bw6_761_g2_from_affine(cA, cP) + return p } func G2AffineFromProjective(p *G2Projective) G2Affine { diff --git a/wrappers/golang/curves/bw6761/g2/include/curve.h b/wrappers/golang/curves/bw6761/g2/include/curve.h index b57b55cfc..8d113fc90 100644 --- a/wrappers/golang/curves/bw6761/g2/include/curve.h +++ b/wrappers/golang/curves/bw6761/g2/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(g2_affine_t* point, g2_projective_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); diff --git a/wrappers/golang/curves/bw6761/include/curve.h b/wrappers/golang/curves/bw6761/include/curve.h index cc6e401cb..ba243029c 100644 --- a/wrappers/golang/curves/bw6761/include/curve.h +++ b/wrappers/golang/curves/bw6761/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(affine_t* point, projective_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); diff --git a/wrappers/golang/curves/grumpkin/curve.go b/wrappers/golang/curves/grumpkin/curve.go index 1079ce2e4..4ad6e2321 100644 --- a/wrappers/golang/curves/grumpkin/curve.go +++ b/wrappers/golang/curves/grumpkin/curve.go @@ -40,17 +40,10 @@ func (p *Projective) FromLimbs(x, y, z []uint32) Projective { } func (p *Projective) FromAffine(a Affine) Projective { - z := BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - } else { - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(p)) + C.grumpkin_from_affine(cA, cP) return *p } @@ -65,7 +58,7 @@ func (p *Projective) ProjectiveToAffine() Affine { var a Affine cA := (*C.affine_t)(unsafe.Pointer(&a)) - cP := (*C.projective_t)(unsafe.Pointer(&p)) + cP := (*C.projective_t)(unsafe.Pointer(p)) C.grumpkin_to_affine(cP, cA) return a } @@ -111,18 +104,13 @@ func (a *Affine) FromLimbs(x, y []uint32) Affine { } func (a Affine) ToProjective() Projective { - var z BaseField + var p Projective - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p Projective - return p.Zero() - } + cA := (*C.affine_t)(unsafe.Pointer(&a)) + cP := (*C.projective_t)(unsafe.Pointer(&p)) + C.grumpkin_from_affine(cA, cP) + return p - return Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } } func AffineFromProjective(p *Projective) Affine { diff --git a/wrappers/golang/curves/grumpkin/include/curve.h b/wrappers/golang/curves/grumpkin/include/curve.h index 8466982ed..6005b280c 100644 --- a/wrappers/golang/curves/grumpkin/include/curve.h +++ b/wrappers/golang/curves/grumpkin/include/curve.h @@ -14,6 +14,7 @@ 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_from_affine(affine_t* point, projective_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); diff --git a/wrappers/golang/internal/generator/curves/templates/curve.go.tmpl b/wrappers/golang/internal/generator/curves/templates/curve.go.tmpl index d02fe6a96..c411f63c4 100644 --- a/wrappers/golang/internal/generator/curves/templates/curve.go.tmpl +++ b/wrappers/golang/internal/generator/curves/templates/curve.go.tmpl @@ -39,21 +39,17 @@ func (p *{{.CurvePrefix}}Projective) FromLimbs(x, y, z []uint32) {{.CurvePrefix} return *p } -func (p *{{.CurvePrefix}}Projective) FromAffine(a {{.CurvePrefix}}Affine) {{.CurvePrefix}}Projective { - z := {{.CurvePrefix}}BaseField{} - z.One() - - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - p.Zero() - }else{ - p.X = a.X - p.Y = a.Y - p.Z = z.One() - } + +{{if ne .CurvePrefix "Mock"}} +func (p *{{.CurvePrefix}}Projective) FromAffine(a {{.CurvePrefix}}Affine) {{.CurvePrefix}}Projective { + + cA := (*C.{{toCName .CurvePrefix}}affine_t)(unsafe.Pointer(&a)) + cP := (*C.{{toCName .CurvePrefix}}projective_t)(unsafe.Pointer(p)) + C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_from_affine(cA, cP) 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)) @@ -65,7 +61,7 @@ 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)) + cP := (*C.{{toCName .CurvePrefix}}projective_t)(unsafe.Pointer(p)) C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_to_affine(cP, cA) return a } @@ -110,21 +106,17 @@ func (a *{{.CurvePrefix}}Affine) FromLimbs(x, y []uint32) {{.CurvePrefix}}Affine return *a } -func (a {{.CurvePrefix}}Affine) ToProjective() {{.CurvePrefix}}Projective { - var z {{.CurvePrefix}}BaseField - if (a.X == z.Zero()) && (a.Y == z.Zero()) { - var p {{.CurvePrefix}}Projective - return p.Zero() - } +{{if ne .CurvePrefix "Mock"}} +func (a {{.CurvePrefix}}Affine) ToProjective() {{.CurvePrefix}}Projective { + var p {{.CurvePrefix}}Projective - return {{.CurvePrefix}}Projective{ - X: a.X, - Y: a.Y, - Z: z.One(), - } + cA := (*C.{{toCName .CurvePrefix}}affine_t)(unsafe.Pointer(&a)) + cP := (*C.{{toCName .CurvePrefix}}projective_t)(unsafe.Pointer(&p)) + C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_from_affine(cA, cP) + return p } -{{if ne .CurvePrefix "Mock"}} + func {{.CurvePrefix}}AffineFromProjective(p *{{.CurvePrefix}}Projective) {{.CurvePrefix}}Affine { return p.ProjectiveToAffine() } diff --git a/wrappers/golang/internal/generator/curves/templates/curve.h.tmpl b/wrappers/golang/internal/generator/curves/templates/curve.h.tmpl index 22179e6f6..0dffd240f 100644 --- a/wrappers/golang/internal/generator/curves/templates/curve.h.tmpl +++ b/wrappers/golang/internal/generator/curves/templates/curve.h.tmpl @@ -14,6 +14,7 @@ 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}}_from_affine({{toCName .CurvePrefix}}affine_t* point, {{toCName .CurvePrefix}}projective_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); diff --git a/wrappers/golang_v3/README.md b/wrappers/golang_v3/README.md new file mode 100644 index 000000000..5f3c2ea0e --- /dev/null +++ b/wrappers/golang_v3/README.md @@ -0,0 +1,121 @@ +# Golang Bindings + +In order to build the underlying ICICLE libraries you should run the build script found [here](./build.sh). + +Build script USAGE + +```bash +./build.sh [-curve= | -field=] [-cuda_version=] [-g2] [-ecntt] [-devmode] + +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 +``` + +To build ICICLE libraries for all supported curves with G2 and ECNTT enabled. + +```sh +./build.sh -curve=all -g2 -ecntt +``` + +If you wish to build for a specific curve, for example bn254, without G2 or ECNTT enabled. + +```sh +./build.sh -curve=bn254 +``` + +## Supported curves, fields and operations + +### Supported curves and operations + +| Operation\Curve | bn254 | bls12_377 | bls12_381 | bw6-761 | grumpkin | +| --- | :---: | :---: | :---: | :---: | :---: | +| MSM | ✅ | ✅ | ✅ | ✅ | ✅ | +| G2 | ✅ | ✅ | ✅ | ✅ | ❌ | +| NTT | ✅ | ✅ | ✅ | ✅ | ❌ | +| ECNTT | ✅ | ✅ | ✅ | ✅ | ❌ | +| VecOps | ✅ | ✅ | ✅ | ✅ | ✅ | +| Polynomials | ✅ | ✅ | ✅ | ✅ | ❌ | + +### Supported fields and operations + +| Operation\Field | babybear | +| --- | :---: | +| VecOps | ✅ | +| Polynomials | ✅ | +| NTT | ✅ | +| Extension Field | ✅ | + +## Running golang tests + +To run the tests for curve bn254. + +```sh +go test ./wrappers/golang_v3/curves/bn254/tests -count=1 -v +``` + +To run all the tests in the golang bindings + +```sh +go test ./... -count=1 -v +``` + +## 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_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_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" + +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. +} +``` + +Replace `/path/to/shared/libs` with the actual path where the shared libraries are located on your system. + +## Common issues + +### Cannot find shared library + +In some cases you may encounter the following error, despite exporting the correct `LD_LIBRARY_PATH`. + +```sh +/usr/local/go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1 +/usr/bin/ld: cannot find -lbn254: No such file or directory +/usr/bin/ld: cannot find -lbn254: No such file or directory +/usr/bin/ld: cannot find -lbn254: No such file or directory +/usr/bin/ld: cannot find -lbn254: No such file or directory +/usr/bin/ld: cannot find -lbn254: No such file or directory +collect2: error: ld returned 1 exit status +``` + +This is normally fixed by exporting the path to the shared library location in the following way: `export CGO_LDFLAGS="-L//"` + +### cuda_runtime.h: No such file or directory + +```sh +# github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381 +In file included from wrappers/golang_v3/curves/bls12381/curve.go:5: +wrappers/golang_v3/curves/bls12381/include/curve.h:1:10: fatal error: cuda_runtime.h: No such file or directory + 1 | #include + | ^~~~~~~~~~~~~~~~ +compilation terminated. +``` + +Our golang bindings rely on cuda headers and require that they can be found as system headers. Make sure to add the `cuda/include` of your cuda installation to your CPATH + +```sh +export CPATH=$CPATH: +``` diff --git a/wrappers/golang_v3/build.ps1 b/wrappers/golang_v3/build.ps1 new file mode 100644 index 000000000..d64e54bf6 --- /dev/null +++ b/wrappers/golang_v3/build.ps1 @@ -0,0 +1,23 @@ +$G2_DEFINED = "OFF" + +if ($args.Count -gt 1) { + $G2_DEFINED = "ON" +} + +$BUILD_DIR = (Get-Location).Path + "\..\icicle\build" +$SUPPORTED_CURVES = @("bn254", "bls12_377", "bls12_381", "bw6_761") + +if ($args[0] -eq "all") { + $BUILD_CURVES = $SUPPORTED_CURVES +} else { + $BUILD_CURVES = @($args[0]) +} + +Set-Location "../../icicle" + +New-Item -ItemType Directory -Path "build" -Force + +foreach ($CURVE in $BUILD_CURVES) { + cmake -DCURVE:STRING=$CURVE -DG2_DEFINED:STRING=$G2_DEFINED -DCMAKE_BUILD_TYPE:STRING=Release -S . -B build + cmake --build build +} diff --git a/wrappers/golang_v3/build.sh b/wrappers/golang_v3/build.sh new file mode 100755 index 000000000..9bd53c232 --- /dev/null +++ b/wrappers/golang_v3/build.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +G2_DEFINED=OFF +ECNTT_DEFINED=OFF +CUDA_COMPILER_PATH=/usr/local/cuda/bin/nvcc + +DEVMODE=OFF +EXT_FIELD=OFF +BUILD_CURVES=( ) +BUILD_FIELDS=( ) +BUILD_HASHES=( ) + +SUPPORTED_CURVES=("bn254" "bls12_377" "bls12_381" "bw6_761", "grumpkin") +SUPPORTED_FIELDS=("babybear") +# SUPPORTED_HASHES=("keccak") +CUDA_BACKEND=OFF + +BUILD_DIR="${ICICLE_BUILD_DIR:-$(realpath "$PWD/../../icicle_v3/build")}" +DEFAULT_BACKEND_INSTALL_DIR="${DEFAULT_BACKEND_INSTALL_DIR:="/usr/local/"}" + +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 " -install_dir Path to the folder where libraries will be installed" + echo " -cuda_backend " + echo " -devmode Enables devmode debugging and fast build times" + echo " -cuda_version= The version of cuda to use for compiling" + echo "" + exit 0 +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 + ;; + -cuda_backend=*) + CUDA_BACKEND=$(echo "$arg_lower" | cut -d'=' -f2) + ;; + -install_dir=*) + DEFAULT_BACKEND_INSTALL_DIR=$(echo "$arg_lower" | cut -d'=' -f2) + ;; + -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 + ;; + -hash*) + hash=$(echo "$arg_lower" | cut -d'=' -f2) + if [[ $hash == "all" ]] + then + BUILD_HASHES=("${SUPPORTED_HASHES[@]}") + else + BUILD_HASHES=( $hash ) + fi + ;; + -devmode) + DEVMODE=ON + ;; + *) + echo "Unknown argument: $arg" + exit 1 + ;; + esac +done + +cd ../../icicle_v3 +mkdir -p build +rm -f "$BUILD_DIR/CMakeCache.txt" + +for CURVE in "${BUILD_CURVES[@]}" +do + 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 + echo "DEFAULT_BACKEND_INSTALL_DIR=${DEFAULT_BACKEND_INSTALL_DIR}" >> build_config.txt + cmake -DCMAKE_CUDA_COMPILER=$CUDA_COMPILER_PATH -DCMAKE_INSTALL_PREFIX=$DEFAULT_BACKEND_INSTALL_DIR -DCUDA_BACKEND=$CUDA_BACKEND -DCURVE=$CURVE -DG2=$G2_DEFINED -DECNTT=$ECNTT_DEFINED -DDEVMODE=$DEVMODE -DCMAKE_BUILD_TYPE=Release -S . -B build + cmake --build build --target install -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 + echo "DEFAULT_BACKEND_INSTALL_DIR=${DEFAULT_BACKEND_INSTALL_DIR}" >> build_config.txt + cmake -DCMAKE_CUDA_COMPILER=$CUDA_COMPILER_PATH -DCMAKE_INSTALL_PREFIX=$DEFAULT_BACKEND_INSTALL_DIR -DCUDA_BACKEND=$CUDA_BACKEND -DFIELD=$FIELD -DEXT_FIELD=$EXT_FIELD -DDEVMODE=$DEVMODE -DCMAKE_BUILD_TYPE=Release -S . -B build + cmake --build build --target install -j8 && rm build_config.txt +done + +# for HASH in "${BUILD_HASHES[@]}" +# do +# echo "HASH=${HASH_DEFINED}" > build_config.txt +# echo "DEVMODE=${DEVMODE}" >> build_config.txt +# cmake -DCMAKE_CUDA_COMPILER=$CUDA_COMPILER_PATH -DBUILD_HASH=$HASH -DDEVMODE=$DEVMODE -DCMAKE_BUILD_TYPE=Release -S . -B build +# cmake --build build -j8 && rm build_config.txt +# done diff --git a/wrappers/golang_v3/core/internal/mock_curve.go b/wrappers/golang_v3/core/internal/mock_curve.go new file mode 100644 index 000000000..0a6fe8be4 --- /dev/null +++ b/wrappers/golang_v3/core/internal/mock_curve.go @@ -0,0 +1,76 @@ +package internal + +type MockProjective struct { + X, Y, Z MockBaseField +} + +func (p MockProjective) Size() int { + return p.X.Size() * 3 +} + +func (p MockProjective) AsPointer() *uint32 { + return p.X.AsPointer() +} + +func (p *MockProjective) Zero() MockProjective { + p.X.Zero() + p.Y.One() + p.Z.Zero() + + return *p +} + +func (p *MockProjective) FromLimbs(x, y, z []uint32) MockProjective { + p.X.FromLimbs(x) + p.Y.FromLimbs(y) + p.Z.FromLimbs(z) + + return *p +} + +func (p *MockProjective) FromAffine(a MockAffine) MockProjective { + z := MockBaseField{} + z.One() + + p.X = a.X + p.Y = a.Y + p.Z = z + + return *p +} + +type MockAffine struct { + X, Y MockBaseField +} + +func (a MockAffine) Size() int { + return a.X.Size() * 2 +} + +func (a MockAffine) AsPointer() *uint32 { + return a.X.AsPointer() +} + +func (a *MockAffine) Zero() MockAffine { + a.X.Zero() + a.Y.Zero() + + return *a +} + +func (a *MockAffine) FromLimbs(x, y []uint32) MockAffine { + a.X.FromLimbs(x) + a.Y.FromLimbs(y) + + return *a +} + +func (a MockAffine) ToProjective() MockProjective { + var z MockBaseField + + return MockProjective{ + X: a.X, + Y: a.Y, + Z: z.One(), + } +} diff --git a/wrappers/golang_v3/core/internal/mock_field.go b/wrappers/golang_v3/core/internal/mock_field.go new file mode 100644 index 000000000..ae5ce68a7 --- /dev/null +++ b/wrappers/golang_v3/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_v3/core/msm.go b/wrappers/golang_v3/core/msm.go new file mode 100644 index 000000000..0c023d59d --- /dev/null +++ b/wrappers/golang_v3/core/msm.go @@ -0,0 +1,134 @@ +package core + +import ( + "fmt" + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +type MSMConfig struct { + StreamHandle runtime.Stream + /// The number of extra bases to pre-compute for each point. See the `precompute_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). + /// + PrecomputeFactor int32 + + /// `c` value, or "window bitsize" which is the main parameter of the "bucket method" + /// that we use to solve the MSM problem. As a rule of thumb, larger value 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 `c`, however in the future value of `c` here and the one passed into the + /// `precompute_bases` function will need to be identical. Default value: 0 (the optimal value of `c` is chosen automatically). + C int32 + + /// 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 variable. Default value: 0 (set to the bitsize of scalar field). + Bitsize int32 + + BatchSize int32 + AreBasesShared bool + areScalarsOnDevice bool + AreScalarsMontgomeryForm bool + areBasesOnDevice bool + AreBasesMontgomeryForm bool + areResultsOnDevice bool + + /// Whether to run the MSM asynchronously. If set to `true`, the MSM function will be non-blocking + /// and you'd need to synchronize it explicitly by running `cudaStreamSynchronize` or `cudaDeviceSynchronize`. + /// If set to `false`, the MSM function will block the current CPU thread. + IsAsync bool + Ext runtime.ConfigExtensionHandler +} + +func GetDefaultMSMConfig() MSMConfig { + return MSMConfig{ + StreamHandle: nil, + PrecomputeFactor: 1, + C: 0, + Bitsize: 0, + BatchSize: 1, + AreBasesShared: true, + areScalarsOnDevice: false, + AreScalarsMontgomeryForm: false, + areBasesOnDevice: false, + AreBasesMontgomeryForm: false, + areResultsOnDevice: false, + IsAsync: false, + Ext: nil, + } +} + +func MsmCheck(scalars HostOrDeviceSlice, bases HostOrDeviceSlice, cfg *MSMConfig, results HostOrDeviceSlice) (unsafe.Pointer, unsafe.Pointer, unsafe.Pointer, int) { + if bases.Len()%int(cfg.PrecomputeFactor) != 0 { + errorString := fmt.Sprintf( + "Precompute factor %d does not divide the number of bases %d", + cfg.PrecomputeFactor, + bases.Len(), + ) + panic(errorString) + } + scalarsLength, basesLength, resultsLength := scalars.Len(), bases.Len()/int(cfg.PrecomputeFactor), results.Len() + if scalarsLength%basesLength != 0 { + errorString := fmt.Sprintf( + "Number of bases %d does not divide the number of scalars %d", + basesLength, + scalarsLength, + ) + panic(errorString) + } + if scalarsLength%resultsLength != 0 { + errorString := fmt.Sprintf( + "Number of results %d does not divide the number of scalars %d", + resultsLength, + scalarsLength, + ) + panic(errorString) + } + + // cfg.basesSize = int32(basesLength) + cfg.AreBasesShared = basesLength < scalarsLength + cfg.BatchSize = int32(resultsLength) + cfg.areScalarsOnDevice = scalars.IsOnDevice() + cfg.areBasesOnDevice = bases.IsOnDevice() + cfg.areResultsOnDevice = results.IsOnDevice() + + if scalars.IsOnDevice() { + scalars.(DeviceSlice).CheckDevice() + } + + if bases.IsOnDevice() { + bases.(DeviceSlice).CheckDevice() + } + + if results.IsOnDevice() { + results.(DeviceSlice).CheckDevice() + } + + size := scalars.Len() / results.Len() + return scalars.AsUnsafePointer(), bases.AsUnsafePointer(), results.AsUnsafePointer(), size +} + +func PrecomputeBasesCheck(bases HostOrDeviceSlice, cfg *MSMConfig, outputBases DeviceSlice) (unsafe.Pointer, unsafe.Pointer) { + outputBasesLength, basesLength := outputBases.Len(), bases.Len() + if outputBasesLength != basesLength*int(cfg.PrecomputeFactor) { + errorString := fmt.Sprintf( + "Precompute factor is probably incorrect: expected %d but got %d", + outputBasesLength/basesLength, + cfg.PrecomputeFactor, + ) + panic(errorString) + } + + if bases.IsOnDevice() { + bases.(DeviceSlice).CheckDevice() + } + outputBases.CheckDevice() + + // cfg.basesSize = int32(basesLength) + // cfg.areBasesShared = cfg.BatchSize > 1 + cfg.areBasesOnDevice = bases.IsOnDevice() + cfg.areResultsOnDevice = bases.IsOnDevice() + + return bases.AsUnsafePointer(), outputBases.AsUnsafePointer() +} diff --git a/wrappers/golang_v3/core/msm_test.go b/wrappers/golang_v3/core/msm_test.go new file mode 100644 index 000000000..a71b314a8 --- /dev/null +++ b/wrappers/golang_v3/core/msm_test.go @@ -0,0 +1,102 @@ +package core + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core/internal" + "github.com/stretchr/testify/assert" +) + +func TestMSMDefaultConfig(t *testing.T) { + actual := GetDefaultMSMConfig() + + expected := MSMConfig{ + StreamHandle: nil, + PrecomputeFactor: 1, + C: 0, + Bitsize: 0, + BatchSize: 1, + AreBasesShared: true, + areScalarsOnDevice: false, + AreScalarsMontgomeryForm: false, + areBasesOnDevice: false, + AreBasesMontgomeryForm: false, + areResultsOnDevice: false, + IsAsync: false, + Ext: actual.Ext, + } + + assert.EqualValues(t, expected, actual) +} + +func TestMSMCheckHostSlices(t *testing.T) { + cfg := GetDefaultMSMConfig() + + rawScalars := make([]internal.MockBaseField, 10) + for i := range rawScalars { + var emptyField internal.MockBaseField + emptyField.One() + + rawScalars[i] = emptyField + } + scalars := HostSliceFromElements[internal.MockBaseField](rawScalars) + + affine := internal.MockAffine{} + var emptyField internal.MockBaseField + emptyField.One() + affine.FromLimbs(emptyField.GetLimbs(), emptyField.GetLimbs()) + rawAffinePoints := make([]internal.MockAffine, 10) + for i := range rawAffinePoints { + rawAffinePoints[i] = affine + } + points := HostSliceFromElements[internal.MockAffine](rawAffinePoints) + + output := make(HostSlice[internal.MockProjective], 1) + assert.NotPanics(t, func() { MsmCheck(scalars, points, &cfg, output) }) + assert.False(t, cfg.areScalarsOnDevice) + assert.False(t, cfg.areBasesOnDevice) + assert.False(t, cfg.areResultsOnDevice) + assert.Equal(t, int32(1), cfg.BatchSize) + + output2 := make(HostSlice[internal.MockProjective], 3) + assert.Panics(t, func() { MsmCheck(scalars, points, &cfg, output2) }) +} + +func TestMSMCheckDeviceSlices(t *testing.T) { + cfg := GetDefaultMSMConfig() + + rawScalars := make([]internal.MockBaseField, 10) + for i := range rawScalars { + var emptyField internal.MockBaseField + emptyField.One() + + rawScalars[i] = emptyField + } + scalars := HostSliceFromElements[internal.MockBaseField](rawScalars) + var scalarsOnDevice DeviceSlice + scalars.CopyToDevice(&scalarsOnDevice, true) + + affine := internal.MockAffine{} + var emptyField internal.MockBaseField + emptyField.One() + affine.FromLimbs(emptyField.GetLimbs(), emptyField.GetLimbs()) + rawAffinePoints := make([]internal.MockAffine, 10) + for i := range rawAffinePoints { + rawAffinePoints[i] = affine + } + points := HostSliceFromElements[internal.MockAffine](rawAffinePoints) + var pointsOnDevice DeviceSlice + points.CopyToDevice(&pointsOnDevice, true) + + output := make(HostSlice[internal.MockProjective], 1) + assert.NotPanics(t, func() { MsmCheck(scalarsOnDevice, pointsOnDevice, &cfg, output) }) + assert.True(t, cfg.areScalarsOnDevice) + assert.True(t, cfg.areBasesOnDevice) + assert.False(t, cfg.areResultsOnDevice) + assert.Equal(t, int32(1), cfg.BatchSize) + + output2 := make(HostSlice[internal.MockProjective], 3) + assert.Panics(t, func() { MsmCheck(scalarsOnDevice, pointsOnDevice, &cfg, output2) }) +} + +// TODO add check for batches and batchSize diff --git a/wrappers/golang_v3/core/ntt.go b/wrappers/golang_v3/core/ntt.go new file mode 100644 index 000000000..1e1157438 --- /dev/null +++ b/wrappers/golang_v3/core/ntt.go @@ -0,0 +1,111 @@ +package core + +import ( + "fmt" + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +type NTTDir int8 + +const ( + KForward NTTDir = iota + KInverse +) + +type Ordering uint32 + +const ( + KNN Ordering = iota + KNR + KRN + KRR + KNM + KMN +) + +type NttAlgorithm uint32 + +const ( + Auto NttAlgorithm = iota + Radix2 + MixedRadix +) + +const CUDA_NTT_FAST_TWIDDLES_MODE = "fast_twiddles" +const CUDA_NTT_ALGORITHM = "ntt_algorithm" + +type NTTConfig[T any] struct { + /// Details related to the device such as its id and stream id. See [DeviceContext](@ref device_context::DeviceContext). + StreamHandle runtime.Stream + /// Coset generator. Used to perform coset (i)NTTs. Default value: `S::one()` (corresponding to no coset being used). + CosetGen T + /// The number of NTTs to compute. Default value: 1. + BatchSize int32 + /// If true the function will compute the NTTs over the columns of the input matrix and not over the rows. + ColumnsBatch bool + /// Ordering of inputs and outputs. See [Ordering](@ref Ordering). Default value: `Ordering::kNN`. + Ordering Ordering + areInputsOnDevice bool + areOutputsOnDevice 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. + IsAsync bool + Ext runtime.ConfigExtensionHandler +} + +func GetDefaultNTTConfig[T any](cosetGen T) NTTConfig[T] { + return NTTConfig[T]{ + nil, // StreamHandle + cosetGen, // CosetGen + 1, // BatchSize + false, // ColumnsBatch + KNN, // Ordering + false, // areInputsOnDevice + false, // areOutputsOnDevice + false, // IsAsync + nil, // Ext + } +} + +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( + "input and output capacities %d; %d are not equal", + inputLen, + outputLen, + ) + panic(errorString) + } + 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 +} + +type NTTInitDomainConfig struct { + StreamHandle runtime.Stream + IsAsync bool + Ext runtime.ConfigExtensionHandler +} + +func GetDefaultNTTInitDomainConfig() NTTInitDomainConfig { + return NTTInitDomainConfig{ + nil, // StreamHandle + false, // IsAsync + nil, // Ext + } +} diff --git a/wrappers/golang_v3/core/ntt_test.go b/wrappers/golang_v3/core/ntt_test.go new file mode 100644 index 000000000..dbdcc4d86 --- /dev/null +++ b/wrappers/golang_v3/core/ntt_test.go @@ -0,0 +1,92 @@ +package core + +import ( + "testing" + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core/internal" + "github.com/stretchr/testify/assert" +) + +func TestNTTDefaultConfig(t *testing.T) { + var cosetGenField internal.MockBaseField + cosetGenField.One() + var cosetGen [1]uint32 + copy(cosetGen[:], cosetGenField.GetLimbs()) + + actual := GetDefaultNTTConfig(cosetGen) + expected := NTTConfig[[1]uint32]{ + unsafe.Pointer(nil), // Ctx + cosetGen, // CosetGen + 1, // BatchSize + false, // ColumnsBatch + KNN, // Ordering + false, // areInputsOnDevice + false, // areOutputsOnDevice + false, // IsAsync + actual.Ext, // ExtensionConfig + } + + assert.Equal(t, expected, actual) +} + +func TestNTTCheckHostScalars(t *testing.T) { + var cosetGen internal.MockBaseField + cosetGen.One() + cfg := GetDefaultNTTConfig(&cosetGen) + + rawInput := make([]internal.MockBaseField, 10) + var emptyField internal.MockBaseField + emptyField.One() + + for i := range rawInput { + rawInput[i] = emptyField + } + + 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.MockBaseField, 11) + for i := range rawInputLarger { + rawInputLarger[i] = emptyField + } + output2 := HostSliceFromElements[internal.MockBaseField](rawInputLarger) + assert.Panics(t, func() { NttCheck(input, &cfg, output2) }) +} + +func TestNTTCheckDeviceScalars(t *testing.T) { + var cosetGen internal.MockBaseField + cosetGen.One() + cfg := GetDefaultNTTConfig(cosetGen) + + numFields := 10 + rawInput := make([]internal.MockBaseField, numFields) + for i := range rawInput { + var emptyField internal.MockBaseField + emptyField.One() + + rawInput[i] = emptyField + } + + hostElements := HostSliceFromElements[internal.MockBaseField](rawInput) + + var input DeviceSlice + hostElements.CopyToDevice(&input, true) + + fieldBytesSize := hostElements.SizeOfElement() + var output DeviceSlice + output.Malloc(numFields*fieldBytesSize, fieldBytesSize) + + assert.NotPanics(t, func() { NttCheck(input, &cfg, output) }) + assert.True(t, cfg.areInputsOnDevice) + assert.True(t, cfg.areOutputsOnDevice) + + var output2 DeviceSlice + output2.Malloc((numFields+1)*fieldBytesSize, fieldBytesSize) + assert.Panics(t, func() { NttCheck(input, &cfg, output2) }) +} + +// TODO add check for batches and batchSize diff --git a/wrappers/golang_v3/core/slice.go b/wrappers/golang_v3/core/slice.go new file mode 100644 index 000000000..886660026 --- /dev/null +++ b/wrappers/golang_v3/core/slice.go @@ -0,0 +1,275 @@ +package core + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +type HostOrDeviceSlice interface { + Len() int + Cap() int + IsEmpty() bool + IsOnDevice() bool + AsUnsafePointer() unsafe.Pointer +} + +type DevicePointer = unsafe.Pointer + +type DeviceSlice struct { + inner unsafe.Pointer + // capacity is the number of bytes that have been allocated + capacity int + // length is the number of elements that have been written + length int +} + +func (d DeviceSlice) Len() int { + return d.length +} + +func (d DeviceSlice) Cap() int { + return d.capacity +} + +func (d DeviceSlice) IsEmpty() bool { + return d.length == 0 +} + +func (d DeviceSlice) AsUnsafePointer() unsafe.Pointer { + return d.inner +} + +func (d DeviceSlice) IsOnDevice() bool { + return true +} + +// func (d DeviceSlice) GetDeviceId() int { +// return runtime.GetDeviceFromPointer(d.inner) +// } + +// CheckDevice is used to ensure that the DeviceSlice about to be used resides on the currently set device +func (d DeviceSlice) CheckDevice() { + if !runtime.IsActiveDeviceMemory(d.AsUnsafePointer()) { + panic("Attempt to use DeviceSlice on a different device") + } +} + +func (d *DeviceSlice) Range(start, end int, endInclusive bool) DeviceSlice { + if end <= start { + panic("Cannot have negative or zero size slices") + } + + if end >= d.length { + panic("Cannot increase slice size from Range") + } + + var newSlice DeviceSlice + switch { + case start < 0: + panic("Negative value for start is not supported") + case start == 0: + newSlice = d.RangeTo(end, endInclusive) + case start > 0: + tempSlice := d.RangeFrom(start) + newSlice = tempSlice.RangeTo(end-start, endInclusive) + } + return newSlice +} + +func (d *DeviceSlice) RangeTo(end int, inclusive bool) DeviceSlice { + if end <= 0 { + panic("Cannot have negative or zero size slices") + } + + if end >= d.length { + panic("Cannot increase slice size from Range") + } + + var newSlice DeviceSlice + sizeOfElement := d.capacity / d.length + newSlice.length = end + if inclusive { + newSlice.length += 1 + } + newSlice.capacity = newSlice.length * sizeOfElement + newSlice.inner = d.inner + return newSlice +} + +func (d *DeviceSlice) RangeFrom(start int) DeviceSlice { + if start >= d.length { + panic("Cannot have negative or zero size slices") + } + + if start < 0 { + panic("Negative value for start is not supported") + } + + var newSlice DeviceSlice + sizeOfElement := d.capacity / d.length + + newSlice.inner = unsafe.Pointer(uintptr(d.inner) + uintptr(start)*uintptr(sizeOfElement)) + newSlice.length = d.length - start + newSlice.capacity = d.capacity - start*sizeOfElement + + return newSlice +} + +// TODO: change signature to be Malloc(element, numElements) +// calc size internally +func (d *DeviceSlice) Malloc(size, sizeOfElement int) (DeviceSlice, runtime.EIcicleError) { + dp, err := runtime.Malloc(uint(size)) + d.inner = dp + d.capacity = size + d.length = size / sizeOfElement + return *d, err +} + +func (d *DeviceSlice) MallocAsync(size, sizeOfElement int, stream runtime.Stream) (DeviceSlice, runtime.EIcicleError) { + dp, err := runtime.MallocAsync(uint(size), stream) + d.inner = dp + d.capacity = size + d.length = size / sizeOfElement + return *d, err +} + +func (d *DeviceSlice) Free() runtime.EIcicleError { + d.CheckDevice() + err := runtime.Free(d.inner) + if err == runtime.Success { + d.length, d.capacity = 0, 0 + d.inner = nil + } + return err +} + +func (d *DeviceSlice) FreeAsync(stream runtime.Stream) runtime.EIcicleError { + d.CheckDevice() + err := runtime.FreeAsync(d.inner, stream) + if err == runtime.Success { + d.length, d.capacity = 0, 0 + d.inner = nil + } + return err +} + +type HostSlice[T any] []T + +func HostSliceFromElements[T any](elements []T) HostSlice[T] { + return elements +} + +func HostSliceWithValue[T any](underlyingValue T, size int) HostSlice[T] { + slice := make(HostSlice[T], size) + for i := range slice { + slice[i] = underlyingValue + } + + return slice +} + +func (h HostSlice[T]) Len() int { + return len(h) +} + +func (h HostSlice[T]) Cap() int { + return cap(h) +} + +func (h HostSlice[T]) IsEmpty() bool { + return len(h) == 0 +} + +func (h HostSlice[T]) IsOnDevice() bool { + return false +} + +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]) +} + +// Registers host memory as pinned, allowing the GPU to read data from the host quicker and save GPU memory space. +// Memory pinned using this function should be unpinned using [Unpin] +// func (h HostSlice[T]) Pin(flags runtime.RegisterPinnedFlags) runtime.EIcicleError { +// _, err := runtime.RegisterPinned(h.AsUnsafePointer(), h.SizeOfElement()*h.Len(), flags) +// return err +// } + +// Unregisters host memory as pinned +// func (h HostSlice[T]) Unpin() runtime.EIcicleError { +// return runtime.FreeRegisteredPinned(h.AsUnsafePointer()) +// } + +// Allocates new host memory as pinned and copies the HostSlice data to the newly allocated area +// Memory pinned using this function should be unpinned using [FreePinned] +// func (h HostSlice[T]) AllocPinned(flags runtime.AllocPinnedFlags) (HostSlice[T], runtime.EIcicleError) { +// pinnedMemPointer, err := runtime.AllocPinned(h.SizeOfElement()*h.Len(), flags) +// if err != runtime.Success { +// return nil, err +// } +// pinnedMem := unsafe.Slice((*T)(pinnedMemPointer), h.Len()) +// copy(pinnedMem, h) +// return pinnedMem, runtime.Success +// } + +// Unpins host memory that was pinned using [AllocPinned] +// func (h HostSlice[T]) FreePinned() runtime.EIcicleError { +// return runtime.FreeAllocPinned(h.AsUnsafePointer()) +// } + +func (h HostSlice[T]) CopyToDevice(dst *DeviceSlice, shouldAllocate bool) *DeviceSlice { + size := h.Len() * h.SizeOfElement() + if shouldAllocate { + dst.Malloc(size, h.SizeOfElement()) + } + dst.CheckDevice() + if size > dst.Cap() { + panic("Number of bytes to copy is too large for destination") + } + + runtime.CopyToDevice(dst.inner, h.AsUnsafePointer(), uint(size)) + dst.length = h.Len() + return dst +} + +func (h HostSlice[T]) CopyToDeviceAsync(dst *DeviceSlice, stream runtime.Stream, shouldAllocate bool) *DeviceSlice { + size := h.Len() * h.SizeOfElement() + if shouldAllocate { + dst.MallocAsync(size, h.SizeOfElement(), stream) + } + dst.CheckDevice() + if size > dst.Cap() { + panic("Number of bytes to copy is too large for destination") + } + + runtime.CopyToDeviceAsync(dst.inner, h.AsUnsafePointer(), uint(size), stream) + dst.length = h.Len() + return dst +} + +func (h HostSlice[T]) CopyFromDevice(src *DeviceSlice) { + src.CheckDevice() + if h.Len() != src.Len() { + panic("destination and source slices have different lengths") + } + bytesSize := src.Len() * h.SizeOfElement() + runtime.CopyFromDevice(h.AsUnsafePointer(), src.inner, uint(bytesSize)) +} + +func (h HostSlice[T]) CopyFromDeviceAsync(src *DeviceSlice, stream runtime.Stream) { + src.CheckDevice() + if h.Len() != src.Len() { + panic("destination and source slices have different lengths") + } + bytesSize := src.Len() * h.SizeOfElement() + runtime.CopyFromDeviceAsync(h.AsUnsafePointer(), src.inner, uint(bytesSize), stream) +} diff --git a/wrappers/golang_v3/core/slice_test.go b/wrappers/golang_v3/core/slice_test.go new file mode 100644 index 000000000..f3a696779 --- /dev/null +++ b/wrappers/golang_v3/core/slice_test.go @@ -0,0 +1,250 @@ +package core + +import ( + "math/rand" + "testing" + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core/internal" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/stretchr/testify/assert" +) + +func randomField(size int) internal.MockBaseField { + limbs := make([]uint32, size) + for i := range limbs { + limbs[i] = rand.Uint32() + } + + var field internal.MockBaseField + field.FromLimbs(limbs) + + return field +} + +func randomFields(numFields, fieldSize int) []internal.MockBaseField { + var randFields []internal.MockBaseField + + for i := 0; i < numFields; i++ { + randFields = append(randFields, randomField(fieldSize)) + } + + return randFields +} + +// This function is solely for the purpose of testing HostDeviceSlice +// It can produce invalid points and should not be used to test curve operations +func randomProjectivePoints(numPoints, fieldSize int) []internal.MockProjective { + var randProjectives []internal.MockProjective + + for i := 0; i < numPoints; i++ { + projective := internal.MockProjective{ + X: randomField(fieldSize), + Y: randomField(fieldSize), + Z: randomField(fieldSize), + } + randProjectives = append(randProjectives, projective) + } + + return randProjectives +} + +// This function is solely for the purpose of testing HostDeviceSlice +// It can produce invalid points and should not be used to test curve operations +func randomAffinePoints(numPoints, fieldSize int) []internal.MockAffine { + var randAffines []internal.MockAffine + + for i := 0; i < numPoints; i++ { + affine := internal.MockAffine{ + X: randomField(fieldSize), + Y: randomField(fieldSize), + } + randAffines = append(randAffines, affine) + } + + return randAffines +} + +const ( + numPoints = 4 + numFields = 4 + fieldSize = 4 + fieldBytesSize = fieldSize * 4 +) + +func TestHostSlice(t *testing.T) { + var emptyHostSlice HostSlice[internal.MockBaseField] + assert.Equal(t, emptyHostSlice.Len(), 0) + assert.Equal(t, emptyHostSlice.Cap(), 0) + + randFields := randomFields(numFields, fieldSize) + + hostSlice := HostSliceFromElements(randFields) + assert.Equal(t, hostSlice.Len(), 4) + assert.Equal(t, hostSlice.Cap(), 4) + + 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.MockBaseField] + assert.True(t, emptyHostSlice.IsEmpty()) + + randFields := randomFields(numFields, fieldSize) + + hostSlice := HostSliceFromElements(randFields) + assert.False(t, hostSlice.IsEmpty()) +} + +func TestHostSliceIsOnDevice(t *testing.T) { + var emptyHostSlice HostSlice[*internal.MockBaseField] + assert.False(t, emptyHostSlice.IsOnDevice()) +} + +func TestHostSliceSizeOf(t *testing.T) { + randFields := randomFields(numFields, fieldSize) + hostSlice := HostSliceFromElements(randFields) + assert.Equal(t, hostSlice.SizeOfElement(), fieldSize*4) +} + +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.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.AsUnsafePointer()) + + emptyDeviceSlice.Free() + assert.Equal(t, 0, emptyDeviceSlice.Len()) + assert.Equal(t, 0, emptyDeviceSlice.Cap()) + assert.Equal(t, unsafe.Pointer(nil), emptyDeviceSlice.AsUnsafePointer()) +} + +func TestDeviceSliceIsEmpty(t *testing.T) { + var emptyDeviceSlice DeviceSlice + assert.True(t, emptyDeviceSlice.IsEmpty()) + + const bytes = numFields * fieldBytesSize + emptyDeviceSlice.Malloc(bytes, fieldBytesSize) + + randFields := randomFields(numFields, fieldSize) + hostSlice := HostSliceFromElements(randFields) + + hostSlice.CopyToDevice(&emptyDeviceSlice, false) + assert.False(t, emptyDeviceSlice.IsEmpty()) +} + +func TestDeviceSliceIsOnDevice(t *testing.T) { + var deviceSlice DeviceSlice + assert.True(t, deviceSlice.IsOnDevice()) +} + +func TestCopyToFromHostDeviceField(t *testing.T) { + var emptyDeviceSlice DeviceSlice + + numFields := 1 << 10 + randFields := randomFields(numFields, fieldSize) + hostSlice := HostSliceFromElements(randFields) + hostSlice.CopyToDevice(&emptyDeviceSlice, true) + + randFields2 := randomFields(numFields, fieldSize) + hostSlice2 := HostSliceFromElements(randFields2) + + assert.NotEqual(t, hostSlice, hostSlice2) + hostSlice2.CopyFromDevice(&emptyDeviceSlice) + assert.Equal(t, hostSlice, hostSlice2) +} + +func TestCopyToFromHostDeviceAffinePoints(t *testing.T) { + runtime.LoadBackendFromEnv() + runtime.CreateDevice("CUDA", 0) + + var emptyDeviceSlice DeviceSlice + + numPoints := 1 << 10 + randAffines := randomAffinePoints(numPoints, fieldSize) + hostSlice := HostSliceFromElements(randAffines) + hostSlice.CopyToDevice(&emptyDeviceSlice, true) + + randAffines2 := randomAffinePoints(numPoints, fieldSize) + hostSlice2 := HostSliceFromElements(randAffines2) + + assert.NotEqual(t, hostSlice, hostSlice2) + hostSlice2.CopyFromDevice(&emptyDeviceSlice) + emptyDeviceSlice.Free() + assert.Equal(t, hostSlice, hostSlice2) +} + +func TestCopyToFromHostDeviceProjectivePoints(t *testing.T) { + var emptyDeviceSlice DeviceSlice + + numPoints := 1 << 15 + randProjectives := randomProjectivePoints(numPoints, fieldSize) + hostSlice := HostSliceFromElements(randProjectives) + hostSlice.CopyToDevice(&emptyDeviceSlice, true) + + randProjectives2 := randomProjectivePoints(numPoints, fieldSize) + hostSlice2 := HostSliceFromElements(randProjectives2) + + assert.NotEqual(t, hostSlice, hostSlice2) + hostSlice2.CopyFromDevice(&emptyDeviceSlice) + + assert.Equal(t, hostSlice, hostSlice2) +} + +func TestSliceRanges(t *testing.T) { + var deviceSlice DeviceSlice + + numPoints := 1 << 3 + randProjectives := randomProjectivePoints(numPoints, fieldSize) + hostSlice := (HostSlice[internal.MockProjective])(randProjectives) + hostSlice.CopyToDevice(&deviceSlice, true) + + // RangeFrom + var zeroProj internal.MockProjective + hostSliceRet := HostSliceWithValue[internal.MockProjective](zeroProj, numPoints-2) + + deviceSliceRangeFrom := deviceSlice.RangeFrom(2) + hostSliceRet.CopyFromDevice(&deviceSliceRangeFrom) + assert.Equal(t, hostSlice[2:], hostSliceRet) + + // RangeTo + deviceSliceRangeTo := deviceSlice.RangeTo(numPoints-3, true) + hostSliceRet.CopyFromDevice(&deviceSliceRangeTo) + assert.Equal(t, hostSlice[:6], hostSliceRet) + + // Range + hostSliceRange := HostSliceWithValue[internal.MockProjective](zeroProj, numPoints-4) + deviceSliceRange := deviceSlice.Range(2, numPoints-3, true) + hostSliceRange.CopyFromDevice(&deviceSliceRange) + assert.Equal(t, hostSlice[2:6], hostSliceRange) +} + +// func TestHostSlicePinning(t *testing.T) { +// data := []int{1, 2, 3, 4, 5, 7, 8, 9} +// dataHostSlice := HostSliceFromElements(data) +// err := dataHostSlice.Pin(cuda_runtime.CudaHostRegisterDefault) +// assert.Equal(t, cuda_runtime.CudaSuccess, err) +// err = dataHostSlice.Pin(cuda_runtime.CudaHostRegisterDefault) +// assert.Equal(t, cuda_runtime.CudaErrorHostMemoryAlreadyRegistered, err) + +// err = dataHostSlice.Unpin() +// assert.Equal(t, cuda_runtime.CudaSuccess, err) +// err = dataHostSlice.Unpin() +// assert.Equal(t, cuda_runtime.CudaErrorHostMemoryNotRegistered, err) + +// pinnedMem, err := dataHostSlice.AllocPinned(cuda_runtime.CudaHostAllocDefault) +// assert.Equal(t, cuda_runtime.CudaSuccess, err) +// assert.ElementsMatch(t, dataHostSlice, pinnedMem) + +// err = pinnedMem.FreePinned() +// assert.Equal(t, cuda_runtime.CudaSuccess, err) +// err = pinnedMem.FreePinned() +// assert.Equal(t, cuda_runtime.CudaErrorInvalidValue, err) +// } diff --git a/wrappers/golang_v3/core/utils.go b/wrappers/golang_v3/core/utils.go new file mode 100644 index 000000000..d98ec775f --- /dev/null +++ b/wrappers/golang_v3/core/utils.go @@ -0,0 +1,26 @@ +package core + +import ( + "encoding/binary" +) + +func ConvertUint32ArrToUint64Arr(arr32 []uint32) []uint64 { + arr64 := make([]uint64, len(arr32)/2) + for i := 0; i < len(arr32); i += 2 { + arr64[i/2] = (uint64(arr32[i]) << 32) | uint64(arr32[i+1]) + } + return arr64 +} + +func ConvertUint64ArrToUint32Arr(arr64 []uint64) []uint32 { + arr32 := make([]uint32, len(arr64)*2) + for i, v := range arr64 { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, v) + + arr32[i*2] = binary.LittleEndian.Uint32(b[0:4]) + arr32[i*2+1] = binary.LittleEndian.Uint32(b[4:8]) + } + + return arr32 +} diff --git a/wrappers/golang_v3/core/utils_test.go b/wrappers/golang_v3/core/utils_test.go new file mode 100644 index 000000000..5b5e9ef33 --- /dev/null +++ b/wrappers/golang_v3/core/utils_test.go @@ -0,0 +1,78 @@ +package core + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestConvertUint32ArrToUint64Arr(t *testing.T) { + testCases := []struct { + name string + input []uint32 + expected []uint64 + }{ + { + name: "Test with incremental array", + input: []uint32{1, 2, 3, 4, 5, 6, 7, 8}, + expected: []uint64{4294967298, 12884901892, 21474836486, 30064771080}, + }, + { + name: "Test with all zeros", + input: []uint32{0, 0, 0, 0, 0, 0, 0, 0}, + expected: []uint64{0, 0, 0, 0}, + }, + { + name: "Test with maximum uint32 values", + input: []uint32{4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295}, + expected: []uint64{18446744073709551615, 18446744073709551615, 18446744073709551615, 18446744073709551615}, + }, + { + name: "Test with alternating min and max uint32 values", + input: []uint32{0, 4294967295, 0, 4294967295, 0, 4294967295, 0, 4294967295}, + expected: []uint64{4294967295, 4294967295, 4294967295, 4294967295}, + }, + { + name: "Test with alternating max and min uint32 values", + input: []uint32{4294967295, 0, 4294967295, 0, 4294967295, 0, 4294967295, 0}, + expected: []uint64{18446744069414584320, 18446744069414584320, 18446744069414584320, 18446744069414584320}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := ConvertUint32ArrToUint64Arr(tc.input) + assert.Equal(t, tc.expected, got, "Got %v, %v", got, tc.expected) + }) + } +} + +func TestConvertUint64ArrToUint32Arr(t *testing.T) { + testCases := []struct { + name string + input []uint64 + expected []uint32 + }{ + { + name: "test one", + input: []uint64{1, 2, 3, 4}, + expected: []uint32{1, 0, 2, 0, 3, 0, 4, 0}, + }, + { + name: "test two", + input: []uint64{100, 200, 300, 400}, + expected: []uint32{100, 0, 200, 0, 300, 0, 400, 0}, + }, + { + name: "test three", + input: []uint64{1000, 2000, 3000, 4000}, + expected: []uint32{1000, 0, 2000, 0, 3000, 0, 4000, 0}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := ConvertUint64ArrToUint32Arr(tc.input) + assert.Equal(t, tc.expected, got, "Got %v, %v", got, tc.expected) + }) + } +} diff --git a/wrappers/golang_v3/core/vec_ops.go b/wrappers/golang_v3/core/vec_ops.go new file mode 100644 index 000000000..8ac3afd6b --- /dev/null +++ b/wrappers/golang_v3/core/vec_ops.go @@ -0,0 +1,110 @@ +package core + +import ( + "fmt" + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +type VecOps int + +const ( + Sub VecOps = iota + Add + Mul +) + +type VecOpsConfig struct { + StreamHandle runtime.Stream + /* True if `a` is on device and false if it is not. Default value: false. */ + isAOnDevice bool + /* True if `b` is on device and false if it is not. Default value: false. */ + isBOnDevice bool + /* If true, output is preserved on device, otherwise on host. Default value: false. */ + isResultOnDevice 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. */ + IsAsync bool + Ext runtime.ConfigExtensionHandler +} + +/** + * A function that returns the default value of [VecOpsConfig](@ref VecOpsConfig). + * @return Default value of [VecOpsConfig](@ref VecOpsConfig). + */ +func DefaultVecOpsConfig() VecOpsConfig { + config := VecOpsConfig{ + nil, // StreamHandle + false, // isAOnDevice + false, // isBOnDevice + false, // isResultOnDevice + false, // IsAsync + nil, // Ext + } + + return config +} + +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( + "a and b vector lengths %d; %d are not equal", + aLen, + bLen, + ) + panic(errorString) + } + if aLen != outLen { + errorString := fmt.Sprintf( + "a and out vector lengths %d; %d are not equal", + aLen, + outLen, + ) + 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_v3/core/vec_ops_test.go b/wrappers/golang_v3/core/vec_ops_test.go new file mode 100644 index 000000000..e7dec49a2 --- /dev/null +++ b/wrappers/golang_v3/core/vec_ops_test.go @@ -0,0 +1,21 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVecOpsDefaultConfig(t *testing.T) { + actual := DefaultVecOpsConfig() + expected := VecOpsConfig{ + actual.StreamHandle, // Ctx + false, // isAOnDevice + false, // isBOnDevice + false, // isResultOnDevice + false, // IsAsync + actual.Ext, // Ext + } + + assert.Equal(t, expected, actual) +} diff --git a/wrappers/golang_v3/curves/bls12377/base_field.go b/wrappers/golang_v3/curves/bls12377/base_field.go new file mode 100644 index 000000000..247482119 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/base_field.go @@ -0,0 +1,84 @@ +package bls12377 + +import ( + "encoding/binary" + "fmt" +) + +const ( + BASE_LIMBS int = 12 +) + +type BaseField struct { + limbs [BASE_LIMBS]uint32 +} + +func (f BaseField) Len() int { + return int(BASE_LIMBS) +} + +func (f BaseField) Size() int { + return int(BASE_LIMBS * 4) +} + +func (f BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *BaseField) Zero() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *BaseField) One() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +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))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f BaseField) 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_v3/curves/bls12377/curve.go b/wrappers/golang_v3/curves/bls12377/curve.go new file mode 100644 index 000000000..50a0346f2 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/curve.go @@ -0,0 +1,171 @@ +package bls12377 + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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.bls12_377_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.bls12_377_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.bls12_377_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.bls12_377_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bls12_377_affine_convert_montgomery((*C.affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, true) +} + +func AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, false) +} + +func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bls12_377_projective_convert_montgomery((*C.projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, true) +} + +func ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/bls12377/ecntt/ecntt.go b/wrappers/golang_v3/curves/bls12377/ecntt/ecntt.go new file mode 100644 index 000000000..d00e29702 --- /dev/null +++ b/wrappers/golang_v3/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/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cPoints, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bls12377/ecntt/include/ecntt.h b/wrappers/golang_v3/curves/bls12377/ecntt/include/ecntt.h new file mode 100644 index 000000000..b54ac83d2 --- /dev/null +++ b/wrappers/golang_v3/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; + +int bls12_377_ecntt(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_v3/curves/bls12377/g2/curve.go b/wrappers/golang_v3/curves/bls12377/g2/curve.go new file mode 100644 index 000000000..8a568b9d9 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/g2/curve.go @@ -0,0 +1,171 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +type G2Projective struct { + X, Y, Z G2BaseField +} + +func (p G2Projective) Size() int { + return p.X.Size() * 3 +} + +func (p G2Projective) AsPointer() *uint32 { + return p.X.AsPointer() +} + +func (p *G2Projective) Zero() G2Projective { + p.X.Zero() + p.Y.One() + p.Z.Zero() + + return *p +} + +func (p *G2Projective) FromLimbs(x, y, z []uint32) G2Projective { + p.X.FromLimbs(x) + p.Y.FromLimbs(y) + p.Z.FromLimbs(z) + + return *p +} + +func (p *G2Projective) FromAffine(a G2Affine) G2Projective { + z := G2BaseField{} + z.One() + + p.X = a.X + p.Y = a.Y + p.Z = z + + return *p +} + +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_377_g2_eq(cP, cP2) + return __ret == (C._Bool)(true) +} + +func (p *G2Projective) ProjectiveToAffine() G2Affine { + var a G2Affine + + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + C.bls12_377_g2_to_affine(cP, cA) + return a +} + +func G2GenerateProjectivePoints(size int) core.HostSlice[G2Projective] { + points := make([]G2Projective, size) + for i := range points { + points[i] = G2Projective{} + } + + pointsSlice := core.HostSliceFromElements[G2Projective](points) + pPoints := (*C.g2_projective_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.bls12_377_g2_generate_projective_points(pPoints, cSize) + + return pointsSlice +} + +type G2Affine struct { + X, Y G2BaseField +} + +func (a G2Affine) Size() int { + return a.X.Size() * 2 +} + +func (a G2Affine) AsPointer() *uint32 { + return a.X.AsPointer() +} + +func (a *G2Affine) Zero() G2Affine { + a.X.Zero() + a.Y.Zero() + + return *a +} + +func (a *G2Affine) FromLimbs(x, y []uint32) G2Affine { + a.X.FromLimbs(x) + a.Y.FromLimbs(y) + + return *a +} + +func (a G2Affine) ToProjective() G2Projective { + var z G2BaseField + + return G2Projective{ + X: a.X, + Y: a.Y, + Z: z.One(), + } +} + +func G2AffineFromProjective(p *G2Projective) G2Affine { + return p.ProjectiveToAffine() +} + +func G2GenerateAffinePoints(size int) core.HostSlice[G2Affine] { + points := make([]G2Affine, size) + for i := range points { + points[i] = G2Affine{} + } + + pointsSlice := core.HostSliceFromElements[G2Affine](points) + cPoints := (*C.g2_affine_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.bls12_377_g2_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertG2AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bls12_377_g2_affine_convert_montgomery((*C.g2_affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.g2_affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func G2AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2AffinePointsMontgomery(points, true) +} + +func G2AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2AffinePointsMontgomery(points, false) +} + +func convertG2ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bls12_377_g2_projective_convert_montgomery((*C.g2_projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.g2_projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func G2ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2ProjectivePointsMontgomery(points, true) +} + +func G2ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2ProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/bls12377/g2/g2base_field.go b/wrappers/golang_v3/curves/bls12377/g2/g2base_field.go new file mode 100644 index 000000000..c4073af97 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/g2/g2base_field.go @@ -0,0 +1,84 @@ +package g2 + +import ( + "encoding/binary" + "fmt" +) + +const ( + G2BASE_LIMBS int = 24 +) + +type G2BaseField struct { + limbs [G2BASE_LIMBS]uint32 +} + +func (f G2BaseField) Len() int { + return int(G2BASE_LIMBS) +} + +func (f G2BaseField) Size() int { + return int(G2BASE_LIMBS * 4) +} + +func (f G2BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *G2BaseField) Zero() G2BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *G2BaseField) One() G2BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *G2BaseField) FromBytesLittleEndian(bytes []byte) G2BaseField { + 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 G2BaseField) 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_v3/curves/bls12377/g2/include/curve.h b/wrappers/golang_v3/curves/bls12377/g2/include/curve.h new file mode 100644 index 000000000..759adbd7a --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/g2/include/curve.h @@ -0,0 +1,25 @@ +#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 VecOpsConfig VecOpsConfig; + +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); +int bls12_377_g2_affine_convert_montgomery(const g2_affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, g2_affine_t* d_out); +int bls12_377_g2_projective_convert_montgomery(const g2_projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, g2_projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12377/g2/include/msm.h b/wrappers/golang_v3/curves/bls12377/g2/include/msm.h new file mode 100644 index 000000000..b94d8e0af --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/g2/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int bls12_377_g2_msm(const scalar_t* scalars, const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); +int bls12_377_g2_msm_precompute_bases(g2_affine_t* input_bases, int bases_size, MSMConfig* config, g2_affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12377/g2/include/scalar_field.h b/wrappers/golang_v3/curves/bls12377/g2/include/scalar_field.h new file mode 100644 index 000000000..97de9e414 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/g2/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BLS12_377_FIELD_H +#define _BLS12_377_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void bls12_377_generate_scalars(scalar_t* scalars, int size); +int bls12_377_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12377/g2/msm.go b/wrappers/golang_v3/curves/bls12377/g2/msm.go new file mode 100644 index 000000000..3dfd47f1a --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/g2/msm.go @@ -0,0 +1,48 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func G2GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.bls12_377_g2_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func G2PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.g2_affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.g2_affine_t)(outputBasesPointer) + + __ret := C.bls12_377_g2_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bls12377/include/curve.h b/wrappers/golang_v3/curves/bls12377/include/curve.h new file mode 100644 index 000000000..63719e238 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/include/curve.h @@ -0,0 +1,25 @@ +#include + +#ifndef _BLS12_377_CURVE_H +#define _BLS12_377_CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct VecOpsConfig VecOpsConfig; + +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); +int bls12_377_affine_convert_montgomery(const affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, affine_t* d_out); +int bls12_377_projective_convert_montgomery(const projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12377/include/scalar_field.h b/wrappers/golang_v3/curves/bls12377/include/scalar_field.h new file mode 100644 index 000000000..97de9e414 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BLS12_377_FIELD_H +#define _BLS12_377_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void bls12_377_generate_scalars(scalar_t* scalars, int size); +int bls12_377_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12377/main.go b/wrappers/golang_v3/curves/bls12377/main.go new file mode 100644 index 000000000..bf86376cb --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/main.go @@ -0,0 +1,4 @@ +package bls12377 + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../build/lib -licicle_field_bls12_377 -licicle_curve_bls12_377 -lstdc++ -Wl,-rpath=${SRCDIR}/../../../../build/lib +import "C" diff --git a/wrappers/golang_v3/curves/bls12377/msm/include/msm.h b/wrappers/golang_v3/curves/bls12377/msm/include/msm.h new file mode 100644 index 000000000..fa9604cbf --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/msm/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int bls12_377_msm(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); +int bls12_377_msm_precompute_bases(affine_t* input_bases, int bases_size, MSMConfig* config, affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12377/msm/msm.go b/wrappers/golang_v3/curves/bls12377/msm/msm.go new file mode 100644 index 000000000..2492645ba --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/msm/msm.go @@ -0,0 +1,48 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.bls12_377_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.bls12_377_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bls12377/ntt/include/ntt.h b/wrappers/golang_v3/curves/bls12377/ntt/include/ntt.h new file mode 100644 index 000000000..146d0c311 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#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 NTTInitDomainConfig NTTInitDomainConfig; + +int bls12_377_ntt(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +int bls12_377_ntt_init_domain(scalar_t* primitive_root, NTTInitDomainConfig* ctx); +int bls12_377_ntt_release_domain(); +int* bls12_377_get_root_of_unity(size_t size); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang_v3/curves/bls12377/ntt/ntt.go b/wrappers/golang_v3/curves/bls12377/ntt/ntt.go new file mode 100644 index 000000000..fa59ee0b0 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/ntt/ntt.go @@ -0,0 +1,59 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cScalars, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return 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 GetRootOfUnity(size uint64) bls12_377.ScalarField { + cRes := C.bls12_377_get_root_of_unity((C.size_t)(size)) + var res bls12_377.ScalarField + res.FromLimbs(*(*[]uint32)(unsafe.Pointer(cRes))) + return res +} + +func InitDomain(primitiveRoot bls12_377.ScalarField, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCfg := (*C.NTTInitDomainConfig)(unsafe.Pointer(&cfg)) + __ret := C.bls12_377_ntt_init_domain(cPrimitiveRoot, cCfg) + err := runtime.EIcicleError(__ret) + return err +} + +func ReleaseDomain() runtime.EIcicleError { + __ret := C.bls12_377_ntt_release_domain() + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bls12377/polynomial/include/polynomial.h b/wrappers/golang_v3/curves/bls12377/polynomial/include/polynomial.h new file mode 100644 index 000000000..967bcdad5 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/polynomial/include/polynomial.h @@ -0,0 +1,50 @@ +#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; + +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_v3/curves/bls12377/polynomial/polynomial.go b/wrappers/golang_v3/curves/bls12377/polynomial/polynomial.go new file mode 100644 index 000000000..de12e0be8 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/polynomial/polynomial.go @@ -0,0 +1,172 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +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_v3/curves/bls12377/scalar_field.go b/wrappers/golang_v3/curves/bls12377/scalar_field.go new file mode 100644 index 000000000..4734ac7d7 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/scalar_field.go @@ -0,0 +1,118 @@ +package bls12377 + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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.bls12_377_generate_scalars(cScalars, cSize) + + return scalarSlice +} + +func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*scalars, *scalars, *scalars, &defaultCfg) + cErr := C.bls12_377_scalar_convert_montgomery((*C.scalar_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.scalar_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang_v3/curves/bls12377/tests/base_field_test.go b/wrappers/golang_v3/curves/bls12377/tests/base_field_test.go new file mode 100644 index 000000000..8de4b0f28 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + BASE_LIMBS = bls12_377.BASE_LIMBS +) + +func TestBaseFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestBaseFieldGetLimbs(t *testing.T) { + 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 bls12_377.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_377.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_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 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 bls12_377.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_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_v3/curves/bls12377/tests/curve_test.go b/wrappers/golang_v3/curves/bls12377/tests/curve_test.go new file mode 100644 index 000000000..b894dbe39 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAffineZero(t *testing.T) { + var fieldZero = bls12_377.BaseField{} + + var affineZero bls12_377.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_377.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_377.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_377.BaseField + fieldOne.One() + + var expected bls12_377.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine bls12_377.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestProjectiveZero(t *testing.T) { + var projectiveZero bls12_377.Projective + projectiveZero.Zero() + 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 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective bls12_377.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_377.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_377.BaseField + fieldOne.One() + + var expected bls12_377.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine bls12_377.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint bls12_377.Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang_v3/curves/bls12377/tests/ecntt_test.go b/wrappers/golang_v3/curves/bls12377/tests/ecntt_test.go new file mode 100644 index 000000000..8ebb3cb37 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/ecntt_test.go @@ -0,0 +1,36 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + ecntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/ecntt" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + ext := runtime.CreateConfigExtension() + ext.SetInt(core.CUDA_NTT_ALGORITHM, int(core.Radix2)) + cfg.Ext = ext.AsUnsafePointer() + + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[bls12_377.Projective](points[:testSize]) + cfg.Ordering = v + + output := make(core.HostSlice[bls12_377.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, runtime.Success, e, "ECNtt failed") + } + } +} diff --git a/wrappers/golang_v3/curves/bls12377/tests/g2_curve_test.go b/wrappers/golang_v3/curves/bls12377/tests/g2_curve_test.go new file mode 100644 index 000000000..ef17b15d9 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/g2_curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestG2AffineZero(t *testing.T) { + var fieldZero = g2.G2BaseField{} + + var affineZero g2.G2Affine + assert.Equal(t, affineZero.X, fieldZero) + assert.Equal(t, affineZero.Y, fieldZero) + + x := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var affine g2.G2Affine + affine.FromLimbs(x, y) + + affine.Zero() + assert.Equal(t, affine.X, fieldZero) + assert.Equal(t, affine.Y, fieldZero) +} + +func TestG2AffineFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) + assert.ElementsMatch(t, randLimbs2, affine.Y.GetLimbs()) +} + +func TestG2AffineToProjective(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField + fieldOne.One() + + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestG2ProjectiveZero(t *testing.T) { + var projectiveZero g2.G2Projective + projectiveZero.Zero() + 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 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var projective g2.G2Projective + 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 TestG2ProjectiveFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + + var projective g2.G2Projective + 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 TestG2ProjectiveFromAffine(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField + fieldOne.One() + + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint g2.G2Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang_v3/curves/bls12377/tests/g2_g2base_field_test.go b/wrappers/golang_v3/curves/bls12377/tests/g2_g2base_field_test.go new file mode 100644 index 000000000..49eaad307 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/g2_g2base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + G2BASE_LIMBS = bls12_377.G2BASE_LIMBS +) + +func TestG2BaseFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestG2BaseFieldGetLimbs(t *testing.T) { + 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 bls12_377.G2BaseField + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int(G2BASE_LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "G2BaseField with limbs to field one did not work") +} + +func TestG2BaseFieldZero(t *testing.T) { + var emptyField bls12_377.G2BaseField + emptyField.Zero() + limbsZero := make([]uint32, G2BASE_LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "G2BaseField with limbs to field zero failed") +} + +func TestG2BaseFieldSize(t *testing.T) { + 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 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 bls12_377.G2BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func TestG2BaseFieldToBytes(t *testing.T) { + 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_v3/curves/bls12377/tests/g2_msm_test.go b/wrappers/golang_v3/curves/bls12377/tests/g2_msm_test.go new file mode 100644 index 000000000..cd036d2f2 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/g2_msm_test.go @@ -0,0 +1,431 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "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/v2/wrappers/golang_v3/core" + icicleBls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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:])) + x := bls12377.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 := bls12377.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 := bls12377.E2{ + A0: pzA0, + A1: pzA1, + } + + var zSquared bls12377.E2 + zSquared.Mul(&z, &z) + + var X bls12377.E2 + X.Mul(&x, &z) + + var Y bls12377.E2 + Y.Mul(&y, &zSquared) + + g2Jac := bls12377.G2Jac{ + X: X, + Y: Y, + Z: z, + } + + var g2Affine bls12377.G2Affine + return *g2Affine.FromJacobian(&g2Jac) +} + +func testAgainstGnarkCryptoMsmG2(t *testing.T, scalars core.HostSlice[icicleBls12_377.ScalarField], points core.HostSlice[g2.G2Affine], out g2.G2Projective) { + 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([]bls12377.G2Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffineG2(v.ToProjective()) + } + + testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t *testing.T, scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bls12377.G2Affine], out g2.G2Projective) { + var msmRes bls12377.G2Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine bls12377.G2Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffineG2(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +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 := g2.G2GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleBls12_377.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } +} + +// func TestMSMG2PinnedHostMemory(t *testing.T) { +// cfg := g2.G2GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleBls12_377.GenerateScalars(size) +// points := g2.G2GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[g2.G2Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p g2.G2Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[g2.G2Projective], 1) +// +// e = g2.G2Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) +// +// +// if pinnableAndLockable { +// e = g2.G2Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, pinnedPoints, outHost[0])) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +func TestMSMG2GnarkCryptoTypes(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{3} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = g2.G2Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t, scalarsHost, pointsHost, outHost[0]) + } +} + +func TestMSMG2Batch(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + size := 1 << power + totalSize := size * batchSize + scalars := icicleBls12_377.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsG2(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := g2.G2GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = g2.G2PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p g2.G2Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsSharedBasesG2(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleBls12_377.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = g2.G2PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p g2.G2Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[0:size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestMSMG2SkewedDistribution(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + + scalars := icicleBls12_377.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := g2.G2GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } +} + +func TestMSMG2MultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := g2.G2GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleBls12_377.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/bls12377/tests/main_test.go b/wrappers/golang_v3/curves/bls12377/tests/main_test.go new file mode 100644 index 000000000..3402658db --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/main_test.go @@ -0,0 +1,66 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft" +) + +const ( + largestTestSize = 20 +) + +var DEVICE runtime.Device + +func initDomain(largestTestSize int, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + 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) + return e +} + +func TestMain(m *testing.M) { + runtime.LoadBackendFromEnv() + devices, e := runtime.GetRegisteredDevices() + if e != runtime.Success { + panic("Failed to load registered devices") + } + for _, deviceType := range devices { + DEVICE = runtime.CreateDevice(deviceType, 0) + runtime.SetDevice(&DEVICE) + + // setup domain + cfg := core.GetDefaultNTTInitDomainConfig() + e = initDomain(largestTestSize, cfg) + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("initDomain is not implemented for ", deviceType, " device type") + } else { + panic("initDomain failed") + } + } + + // execute tests + m.Run() + + // release domain + e = ntt.ReleaseDomain() + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("ReleaseDomain is not implemented for ", deviceType, " device type") + } else { + panic("ReleaseDomain failed") + } + } + } +} diff --git a/wrappers/golang_v3/curves/bls12377/tests/msm_test.go b/wrappers/golang_v3/curves/bls12377/tests/msm_test.go new file mode 100644 index 000000000..bab33b04d --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/msm_test.go @@ -0,0 +1,391 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "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/v2/wrappers/golang_v3/core" + icicleBls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/msm" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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())) + + zInv := new(fp.Element) + x := new(fp.Element) + y := new(fp.Element) + + zInv.Inverse(&pz) + + x.Mul(&px, zInv) + y.Mul(&py, zInv) + + return bls12377.G1Affine{X: *x, Y: *y} +} + +func testAgainstGnarkCryptoMsm(t *testing.T, scalars core.HostSlice[icicleBls12_377.ScalarField], points core.HostSlice[icicleBls12_377.Affine], out icicleBls12_377.Projective) { + 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([]bls12377.G1Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffine(v.ToProjective()) + } + + testAgainstGnarkCryptoMsmGnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmGnarkCryptoTypes(t *testing.T, scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bls12377.G1Affine], out icicleBls12_377.Projective) { + var msmRes bls12377.G1Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine bls12377.G1Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffine(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +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 := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleBls12_377.GenerateScalars(size) + points := icicleBls12_377.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleBls12_377.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } +} + +// func TestMSMPinnedHostMemory(t *testing.T) { +// cfg := msm.GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleBls12_377.GenerateScalars(size) +// points := icicleBls12_377.GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[icicleBls12_377.Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p icicleBls12_377.Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) +// +// e = msm.Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) +// +// +// if pinnableAndLockable { +// e = msm.Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, pinnedPoints, outHost[0])) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +func TestMSMGnarkCryptoTypes(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{3} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = msm.Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + testAgainstGnarkCryptoMsmGnarkCryptoTypes(t, scalarsHost, pointsHost, outHost[0]) + } +} + +func TestMSMBatch(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + size := 1 << power + totalSize := size * batchSize + scalars := icicleBls12_377.GenerateScalars(totalSize) + points := icicleBls12_377.GenerateAffinePoints(totalSize) + + var p icicleBls12_377.Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePoints(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleBls12_377.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsSharedBases(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleBls12_377.GenerateScalars(totalSize) + points := icicleBls12_377.GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleBls12_377.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[0:size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestMSMSkewedDistribution(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + + scalars := icicleBls12_377.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := icicleBls12_377.GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p icicleBls12_377.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } +} + +func TestMSMMultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleBls12_377.GenerateScalars(size) + points := icicleBls12_377.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleBls12_377.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_377.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/bls12377/tests/ntt_test.go b/wrappers/golang_v3/curves/bls12377/tests/ntt_test.go new file mode 100644 index 000000000..ef5ce0b1b --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/ntt_test.go @@ -0,0 +1,270 @@ +package tests + +import ( + "reflect" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-377/fr/fft" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" +) + +func testAgainstGnarkCryptoNtt(t *testing.T, size int, scalars core.HostSlice[bls12_377.ScalarField], output core.HostSlice[bls12_377.ScalarField], order core.Ordering, direction core.NTTDir) { + scalarsFr := make([]fr.Element, size) + for i, v := range scalars { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + outputAsFr := make([]fr.Element, size) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + testAgainstGnarkCryptoNttGnarkTypes(t, size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(t *testing.T, size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) { + domainWithPrecompute := fft.NewDomain(uint64(size)) + // DIT + BitReverse == Ordering.kRR + // DIT == Ordering.kRN + // DIF + BitReverse == Ordering.kNN + // DIF == Ordering.kNR + var decimation fft.Decimation + if order == core.KRN || order == core.KRR { + decimation = fft.DIT + } else { + decimation = fft.DIF + } + + if direction == core.KForward { + domainWithPrecompute.FFT(scalarsFr, decimation) + } else { + domainWithPrecompute.FFTInverse(scalarsFr, decimation) + } + + if order == core.KNN || order == core.KRR { + fft.BitReverse(scalarsFr) + } + assert.Equal(t, scalarsFr, outputAsFr) +} +func TestNTTGetDefaultConfig(t *testing.T) { + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(bls12_377.SCALAR_LIMBS)) + assert.Equal(t, expected, actual.CosetGen[:]) + + 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 := core.GetDefaultNTTInitDomainConfig() + assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) +} + +func TestNtt(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + scalarsCopy := core.HostSliceFromElements[bls12_377.ScalarField](scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[bls12_377.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, 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 + } + + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + runtime.SetDevice(&DEVICE) + + testSize := size + + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNttGnarkTypes(t, testSize, scalarsCopy, output, v, core.KForward) + } + } +} + +func TestNttDeviceAsync(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[bls12_377.ScalarField](scalars[:testSize]) + + stream, _ := runtime.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.StreamHandle = 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[bls12_377.ScalarField], testSize) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + runtime.SynchronizeStream(stream) + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, testSize, scalarsCopy, output, v, direction) + } + } + } +} + +func TestNttBatch(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestTestSize := 10 + largestBatchSize := 20 + scalars := bls12_377.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{2, 16, largestBatchSize} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + totalSize := testSize * batchSize + + scalarsCopy := core.HostSliceFromElements[bls12_377.ScalarField](scalars[:totalSize]) + + cfg.Ordering = core.KNN + cfg.BatchSize = int32(batchSize) + // run ntt + output := make(core.HostSlice[bls12_377.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + domainWithPrecompute := fft.NewDomain(uint64(testSize)) + outputAsFr := make([]fr.Element, totalSize) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + for i := 0; i < batchSize; i++ { + scalarsFr := make([]fr.Element, testSize) + for i, v := range scalarsCopy[i*testSize : (i+1)*testSize] { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + + domainWithPrecompute.FFT(scalarsFr, fft.DIF) + fft.BitReverse(scalarsFr) + if !assert.True(t, reflect.DeepEqual(scalarsFr, outputAsFr[i*testSize:(i+1)*testSize])) { + t.FailNow() + } + } + } + } +} + +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") + e := ntt.ReleaseDomain() + assert.Equal(t, runtime.Success, e, "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_v3/curves/bls12377/tests/polynomial_test.go b/wrappers/golang_v3/curves/bls12377/tests/polynomial_test.go new file mode 100644 index 000000000..1effcfb0c --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + // "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377/polynomial" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/curves/bls12377/tests/scalar_field_test.go b/wrappers/golang_v3/curves/bls12377/tests/scalar_field_test.go new file mode 100644 index 000000000..a96e7e7a6 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/scalar_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 << 20 + scalars := bls12_377.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + bls12_377.ToMontgomery(&deviceScalars) + + scalarsMontHost := make(core.HostSlice[bls12_377.ScalarField], 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_v3/curves/bls12377/tests/vec_ops_test.go b/wrappers/golang_v3/curves/bls12377/tests/vec_ops_test.go new file mode 100644 index 000000000..fb789a87e --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/tests/vec_ops_test.go @@ -0,0 +1,65 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_377 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12377" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 + + matrix := bls12_377.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[bls12_377.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[bls12_377.ScalarField], rowSize*columnSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, cfg) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, cfg) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + + 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, cfg) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, cfg) + output := make(core.HostSlice[bls12_377.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang_v3/curves/bls12377/vecOps/include/vec_ops.h b/wrappers/golang_v3/curves/bls12377/vecOps/include/vec_ops.h new file mode 100644 index 000000000..ac62d3e5d --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/vecOps/include/vec_ops.h @@ -0,0 +1,50 @@ +#include + +#ifndef _BLS12_377_VEC_OPS_H +#define _BLS12_377_VEC_OPS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +int bls12_377_vector_mul( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bls12_377_vector_add( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bls12_377_vector_sub( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bls12_377_matrix_transpose( + scalar_t* mat_in, + int row_size, + int column_size, + VecOpsConfig* config, + scalar_t* mat_out +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12377/vecOps/vec_ops.go b/wrappers/golang_v3/curves/bls12377/vecOps/vec_ops.go new file mode 100644 index 000000000..d592bc9b9 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12377/vecOps/vec_ops.go @@ -0,0 +1,44 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret runtime.EIcicleError) { + 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 = (runtime.EIcicleError)(C.bls12_377_vector_sub(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (runtime.EIcicleError)(C.bls12_377_vector_add(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (runtime.EIcicleError)(C.bls12_377_vector_mul(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, config core.VecOpsConfig) runtime.EIcicleError { + inPointer, _, outPointer, cfgPointer, _ := core.VecOpCheck(in, in, out, &config) + + cIn := (*C.scalar_t)(inPointer) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cOut := (*C.scalar_t)(outPointer) + + err := (C.bls12_377_matrix_transpose(cIn, cRowSize, cColumnSize, cConfig, cOut)) + return runtime.EIcicleError(err) +} diff --git a/wrappers/golang_v3/curves/bls12381/base_field.go b/wrappers/golang_v3/curves/bls12381/base_field.go new file mode 100644 index 000000000..724c8bca9 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/base_field.go @@ -0,0 +1,84 @@ +package bls12381 + +import ( + "encoding/binary" + "fmt" +) + +const ( + BASE_LIMBS int = 12 +) + +type BaseField struct { + limbs [BASE_LIMBS]uint32 +} + +func (f BaseField) Len() int { + return int(BASE_LIMBS) +} + +func (f BaseField) Size() int { + return int(BASE_LIMBS * 4) +} + +func (f BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *BaseField) Zero() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *BaseField) One() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +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))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f BaseField) 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_v3/curves/bls12381/curve.go b/wrappers/golang_v3/curves/bls12381/curve.go new file mode 100644 index 000000000..ea1c4e88a --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/curve.go @@ -0,0 +1,171 @@ +package bls12381 + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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.bls12_381_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.bls12_381_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.bls12_381_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.bls12_381_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bls12_381_affine_convert_montgomery((*C.affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, true) +} + +func AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, false) +} + +func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bls12_381_projective_convert_montgomery((*C.projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, true) +} + +func ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/bls12381/ecntt/ecntt.go b/wrappers/golang_v3/curves/bls12381/ecntt/ecntt.go new file mode 100644 index 000000000..ad0794d14 --- /dev/null +++ b/wrappers/golang_v3/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/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cPoints, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bls12381/ecntt/include/ecntt.h b/wrappers/golang_v3/curves/bls12381/ecntt/include/ecntt.h new file mode 100644 index 000000000..8a0ce0971 --- /dev/null +++ b/wrappers/golang_v3/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; + +int bls12_381_ecntt(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_v3/curves/bls12381/g2/curve.go b/wrappers/golang_v3/curves/bls12381/g2/curve.go new file mode 100644 index 000000000..6abe81b79 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/g2/curve.go @@ -0,0 +1,171 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +type G2Projective struct { + X, Y, Z G2BaseField +} + +func (p G2Projective) Size() int { + return p.X.Size() * 3 +} + +func (p G2Projective) AsPointer() *uint32 { + return p.X.AsPointer() +} + +func (p *G2Projective) Zero() G2Projective { + p.X.Zero() + p.Y.One() + p.Z.Zero() + + return *p +} + +func (p *G2Projective) FromLimbs(x, y, z []uint32) G2Projective { + p.X.FromLimbs(x) + p.Y.FromLimbs(y) + p.Z.FromLimbs(z) + + return *p +} + +func (p *G2Projective) FromAffine(a G2Affine) G2Projective { + z := G2BaseField{} + z.One() + + p.X = a.X + p.Y = a.Y + p.Z = z + + return *p +} + +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_381_g2_eq(cP, cP2) + return __ret == (C._Bool)(true) +} + +func (p *G2Projective) ProjectiveToAffine() G2Affine { + var a G2Affine + + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + C.bls12_381_g2_to_affine(cP, cA) + return a +} + +func G2GenerateProjectivePoints(size int) core.HostSlice[G2Projective] { + points := make([]G2Projective, size) + for i := range points { + points[i] = G2Projective{} + } + + pointsSlice := core.HostSliceFromElements[G2Projective](points) + pPoints := (*C.g2_projective_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.bls12_381_g2_generate_projective_points(pPoints, cSize) + + return pointsSlice +} + +type G2Affine struct { + X, Y G2BaseField +} + +func (a G2Affine) Size() int { + return a.X.Size() * 2 +} + +func (a G2Affine) AsPointer() *uint32 { + return a.X.AsPointer() +} + +func (a *G2Affine) Zero() G2Affine { + a.X.Zero() + a.Y.Zero() + + return *a +} + +func (a *G2Affine) FromLimbs(x, y []uint32) G2Affine { + a.X.FromLimbs(x) + a.Y.FromLimbs(y) + + return *a +} + +func (a G2Affine) ToProjective() G2Projective { + var z G2BaseField + + return G2Projective{ + X: a.X, + Y: a.Y, + Z: z.One(), + } +} + +func G2AffineFromProjective(p *G2Projective) G2Affine { + return p.ProjectiveToAffine() +} + +func G2GenerateAffinePoints(size int) core.HostSlice[G2Affine] { + points := make([]G2Affine, size) + for i := range points { + points[i] = G2Affine{} + } + + pointsSlice := core.HostSliceFromElements[G2Affine](points) + cPoints := (*C.g2_affine_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.bls12_381_g2_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertG2AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bls12_381_g2_affine_convert_montgomery((*C.g2_affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.g2_affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func G2AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2AffinePointsMontgomery(points, true) +} + +func G2AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2AffinePointsMontgomery(points, false) +} + +func convertG2ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bls12_381_g2_projective_convert_montgomery((*C.g2_projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.g2_projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func G2ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2ProjectivePointsMontgomery(points, true) +} + +func G2ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2ProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/bls12381/g2/g2base_field.go b/wrappers/golang_v3/curves/bls12381/g2/g2base_field.go new file mode 100644 index 000000000..c4073af97 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/g2/g2base_field.go @@ -0,0 +1,84 @@ +package g2 + +import ( + "encoding/binary" + "fmt" +) + +const ( + G2BASE_LIMBS int = 24 +) + +type G2BaseField struct { + limbs [G2BASE_LIMBS]uint32 +} + +func (f G2BaseField) Len() int { + return int(G2BASE_LIMBS) +} + +func (f G2BaseField) Size() int { + return int(G2BASE_LIMBS * 4) +} + +func (f G2BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *G2BaseField) Zero() G2BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *G2BaseField) One() G2BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *G2BaseField) FromBytesLittleEndian(bytes []byte) G2BaseField { + 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 G2BaseField) 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_v3/curves/bls12381/g2/include/curve.h b/wrappers/golang_v3/curves/bls12381/g2/include/curve.h new file mode 100644 index 000000000..09f334012 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/g2/include/curve.h @@ -0,0 +1,25 @@ +#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 VecOpsConfig VecOpsConfig; + +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); +int bls12_381_g2_affine_convert_montgomery(const g2_affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, g2_affine_t* d_out); +int bls12_381_g2_projective_convert_montgomery(const g2_projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, g2_projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12381/g2/include/msm.h b/wrappers/golang_v3/curves/bls12381/g2/include/msm.h new file mode 100644 index 000000000..37067ca11 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/g2/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int bls12_381_g2_msm(const scalar_t* scalars, const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); +int bls12_381_g2_msm_precompute_bases(g2_affine_t* input_bases, int bases_size, MSMConfig* config, g2_affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12381/g2/include/scalar_field.h b/wrappers/golang_v3/curves/bls12381/g2/include/scalar_field.h new file mode 100644 index 000000000..f0807d9e9 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/g2/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BLS12_381_FIELD_H +#define _BLS12_381_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void bls12_381_generate_scalars(scalar_t* scalars, int size); +int bls12_381_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12381/g2/msm.go b/wrappers/golang_v3/curves/bls12381/g2/msm.go new file mode 100644 index 000000000..16fe4fdd7 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/g2/msm.go @@ -0,0 +1,48 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func G2GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.bls12_381_g2_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func G2PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.g2_affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.g2_affine_t)(outputBasesPointer) + + __ret := C.bls12_381_g2_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bls12381/include/curve.h b/wrappers/golang_v3/curves/bls12381/include/curve.h new file mode 100644 index 000000000..88ec9e04b --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/include/curve.h @@ -0,0 +1,25 @@ +#include + +#ifndef _BLS12_381_CURVE_H +#define _BLS12_381_CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct VecOpsConfig VecOpsConfig; + +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); +int bls12_381_affine_convert_montgomery(const affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, affine_t* d_out); +int bls12_381_projective_convert_montgomery(const projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12381/include/scalar_field.h b/wrappers/golang_v3/curves/bls12381/include/scalar_field.h new file mode 100644 index 000000000..f0807d9e9 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BLS12_381_FIELD_H +#define _BLS12_381_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void bls12_381_generate_scalars(scalar_t* scalars, int size); +int bls12_381_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12381/main.go b/wrappers/golang_v3/curves/bls12381/main.go new file mode 100644 index 000000000..81ca46886 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/main.go @@ -0,0 +1,4 @@ +package bls12381 + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../build/lib -licicle_field_bls12_381 -licicle_curve_bls12_381 -lstdc++ -Wl,-rpath=${SRCDIR}/../../../../build/lib +import "C" diff --git a/wrappers/golang_v3/curves/bls12381/msm/include/msm.h b/wrappers/golang_v3/curves/bls12381/msm/include/msm.h new file mode 100644 index 000000000..9518b1aa8 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/msm/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int bls12_381_msm(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); +int bls12_381_msm_precompute_bases(affine_t* input_bases, int bases_size, MSMConfig* config, affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12381/msm/msm.go b/wrappers/golang_v3/curves/bls12381/msm/msm.go new file mode 100644 index 000000000..09362e36e --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/msm/msm.go @@ -0,0 +1,48 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.bls12_381_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.bls12_381_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bls12381/ntt/include/ntt.h b/wrappers/golang_v3/curves/bls12381/ntt/include/ntt.h new file mode 100644 index 000000000..b305a181d --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#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 NTTInitDomainConfig NTTInitDomainConfig; + +int bls12_381_ntt(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +int bls12_381_ntt_init_domain(scalar_t* primitive_root, NTTInitDomainConfig* ctx); +int bls12_381_ntt_release_domain(); +int* bls12_381_get_root_of_unity(size_t size); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang_v3/curves/bls12381/ntt/ntt.go b/wrappers/golang_v3/curves/bls12381/ntt/ntt.go new file mode 100644 index 000000000..683e6e12a --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/ntt/ntt.go @@ -0,0 +1,59 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cScalars, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return 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 GetRootOfUnity(size uint64) bls12_381.ScalarField { + cRes := C.bls12_381_get_root_of_unity((C.size_t)(size)) + var res bls12_381.ScalarField + res.FromLimbs(*(*[]uint32)(unsafe.Pointer(cRes))) + return res +} + +func InitDomain(primitiveRoot bls12_381.ScalarField, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCfg := (*C.NTTInitDomainConfig)(unsafe.Pointer(&cfg)) + __ret := C.bls12_381_ntt_init_domain(cPrimitiveRoot, cCfg) + err := runtime.EIcicleError(__ret) + return err +} + +func ReleaseDomain() runtime.EIcicleError { + __ret := C.bls12_381_ntt_release_domain() + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bls12381/polynomial/include/polynomial.h b/wrappers/golang_v3/curves/bls12381/polynomial/include/polynomial.h new file mode 100644 index 000000000..52476bdfa --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/polynomial/include/polynomial.h @@ -0,0 +1,50 @@ +#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; + +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_v3/curves/bls12381/polynomial/polynomial.go b/wrappers/golang_v3/curves/bls12381/polynomial/polynomial.go new file mode 100644 index 000000000..bc2c4d839 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/polynomial/polynomial.go @@ -0,0 +1,172 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +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_v3/curves/bls12381/scalar_field.go b/wrappers/golang_v3/curves/bls12381/scalar_field.go new file mode 100644 index 000000000..65e16782a --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/scalar_field.go @@ -0,0 +1,118 @@ +package bls12381 + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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.bls12_381_generate_scalars(cScalars, cSize) + + return scalarSlice +} + +func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*scalars, *scalars, *scalars, &defaultCfg) + cErr := C.bls12_381_scalar_convert_montgomery((*C.scalar_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.scalar_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang_v3/curves/bls12381/tests/base_field_test.go b/wrappers/golang_v3/curves/bls12381/tests/base_field_test.go new file mode 100644 index 000000000..5bd6aaa49 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/curves/bls12381/tests/curve_test.go b/wrappers/golang_v3/curves/bls12381/tests/curve_test.go new file mode 100644 index 000000000..65558e943 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/curves/bls12381/tests/ecntt_test.go b/wrappers/golang_v3/curves/bls12381/tests/ecntt_test.go new file mode 100644 index 000000000..119b30a6d --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/ecntt_test.go @@ -0,0 +1,36 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + ecntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/ecntt" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + ext := runtime.CreateConfigExtension() + ext.SetInt(core.CUDA_NTT_ALGORITHM, int(core.Radix2)) + cfg.Ext = ext.AsUnsafePointer() + + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[bls12_381.Projective](points[:testSize]) + cfg.Ordering = v + + output := make(core.HostSlice[bls12_381.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, runtime.Success, e, "ECNtt failed") + } + } +} diff --git a/wrappers/golang_v3/curves/bls12381/tests/g2_curve_test.go b/wrappers/golang_v3/curves/bls12381/tests/g2_curve_test.go new file mode 100644 index 000000000..462d18bb5 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/g2_curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestG2AffineZero(t *testing.T) { + var fieldZero = g2.G2BaseField{} + + var affineZero g2.G2Affine + assert.Equal(t, affineZero.X, fieldZero) + assert.Equal(t, affineZero.Y, fieldZero) + + x := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var affine g2.G2Affine + affine.FromLimbs(x, y) + + affine.Zero() + assert.Equal(t, affine.X, fieldZero) + assert.Equal(t, affine.Y, fieldZero) +} + +func TestG2AffineFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) + assert.ElementsMatch(t, randLimbs2, affine.Y.GetLimbs()) +} + +func TestG2AffineToProjective(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField + fieldOne.One() + + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestG2ProjectiveZero(t *testing.T) { + var projectiveZero g2.G2Projective + projectiveZero.Zero() + 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 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var projective g2.G2Projective + 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 TestG2ProjectiveFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + + var projective g2.G2Projective + 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 TestG2ProjectiveFromAffine(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField + fieldOne.One() + + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint g2.G2Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang_v3/curves/bls12381/tests/g2_g2base_field_test.go b/wrappers/golang_v3/curves/bls12381/tests/g2_g2base_field_test.go new file mode 100644 index 000000000..e5796f313 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/g2_g2base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + G2BASE_LIMBS = bls12_381.G2BASE_LIMBS +) + +func TestG2BaseFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestG2BaseFieldGetLimbs(t *testing.T) { + 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 bls12_381.G2BaseField + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int(G2BASE_LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "G2BaseField with limbs to field one did not work") +} + +func TestG2BaseFieldZero(t *testing.T) { + var emptyField bls12_381.G2BaseField + emptyField.Zero() + limbsZero := make([]uint32, G2BASE_LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "G2BaseField with limbs to field zero failed") +} + +func TestG2BaseFieldSize(t *testing.T) { + 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 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 bls12_381.G2BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func TestG2BaseFieldToBytes(t *testing.T) { + 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_v3/curves/bls12381/tests/g2_msm_test.go b/wrappers/golang_v3/curves/bls12381/tests/g2_msm_test.go new file mode 100644 index 000000000..d8bf14750 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/g2_msm_test.go @@ -0,0 +1,431 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "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/v2/wrappers/golang_v3/core" + icicleBls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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:])) + x := bls12381.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 := bls12381.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 := bls12381.E2{ + A0: pzA0, + A1: pzA1, + } + + var zSquared bls12381.E2 + zSquared.Mul(&z, &z) + + var X bls12381.E2 + X.Mul(&x, &z) + + var Y bls12381.E2 + Y.Mul(&y, &zSquared) + + g2Jac := bls12381.G2Jac{ + X: X, + Y: Y, + Z: z, + } + + var g2Affine bls12381.G2Affine + return *g2Affine.FromJacobian(&g2Jac) +} + +func testAgainstGnarkCryptoMsmG2(t *testing.T, scalars core.HostSlice[icicleBls12_381.ScalarField], points core.HostSlice[g2.G2Affine], out g2.G2Projective) { + 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([]bls12381.G2Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffineG2(v.ToProjective()) + } + + testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t *testing.T, scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bls12381.G2Affine], out g2.G2Projective) { + var msmRes bls12381.G2Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine bls12381.G2Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffineG2(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +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 := g2.G2GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleBls12_381.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } +} + +// func TestMSMG2PinnedHostMemory(t *testing.T) { +// cfg := g2.G2GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleBls12_381.GenerateScalars(size) +// points := g2.G2GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[g2.G2Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p g2.G2Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[g2.G2Projective], 1) +// +// e = g2.G2Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) +// +// +// if pinnableAndLockable { +// e = g2.G2Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, pinnedPoints, outHost[0])) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +func TestMSMG2GnarkCryptoTypes(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{3} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = g2.G2Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t, scalarsHost, pointsHost, outHost[0]) + } +} + +func TestMSMG2Batch(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + size := 1 << power + totalSize := size * batchSize + scalars := icicleBls12_381.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsG2(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := g2.G2GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = g2.G2PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p g2.G2Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsSharedBasesG2(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleBls12_381.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = g2.G2PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p g2.G2Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[0:size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestMSMG2SkewedDistribution(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + + scalars := icicleBls12_381.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := g2.G2GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } +} + +func TestMSMG2MultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := g2.G2GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleBls12_381.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/bls12381/tests/main_test.go b/wrappers/golang_v3/curves/bls12381/tests/main_test.go new file mode 100644 index 000000000..05a24834d --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/main_test.go @@ -0,0 +1,66 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fft" +) + +const ( + largestTestSize = 20 +) + +var DEVICE runtime.Device + +func initDomain(largestTestSize int, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + 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) + return e +} + +func TestMain(m *testing.M) { + runtime.LoadBackendFromEnv() + devices, e := runtime.GetRegisteredDevices() + if e != runtime.Success { + panic("Failed to load registered devices") + } + for _, deviceType := range devices { + DEVICE = runtime.CreateDevice(deviceType, 0) + runtime.SetDevice(&DEVICE) + + // setup domain + cfg := core.GetDefaultNTTInitDomainConfig() + e = initDomain(largestTestSize, cfg) + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("initDomain is not implemented for ", deviceType, " device type") + } else { + panic("initDomain failed") + } + } + + // execute tests + m.Run() + + // release domain + e = ntt.ReleaseDomain() + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("ReleaseDomain is not implemented for ", deviceType, " device type") + } else { + panic("ReleaseDomain failed") + } + } + } +} diff --git a/wrappers/golang_v3/curves/bls12381/tests/msm_test.go b/wrappers/golang_v3/curves/bls12381/tests/msm_test.go new file mode 100644 index 000000000..c1520e315 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/msm_test.go @@ -0,0 +1,391 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "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/v2/wrappers/golang_v3/core" + icicleBls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/msm" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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())) + + zInv := new(fp.Element) + x := new(fp.Element) + y := new(fp.Element) + + zInv.Inverse(&pz) + + x.Mul(&px, zInv) + y.Mul(&py, zInv) + + return bls12381.G1Affine{X: *x, Y: *y} +} + +func testAgainstGnarkCryptoMsm(t *testing.T, scalars core.HostSlice[icicleBls12_381.ScalarField], points core.HostSlice[icicleBls12_381.Affine], out icicleBls12_381.Projective) { + 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([]bls12381.G1Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffine(v.ToProjective()) + } + + testAgainstGnarkCryptoMsmGnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmGnarkCryptoTypes(t *testing.T, scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bls12381.G1Affine], out icicleBls12_381.Projective) { + var msmRes bls12381.G1Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine bls12381.G1Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffine(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +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 := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleBls12_381.GenerateScalars(size) + points := icicleBls12_381.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleBls12_381.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } +} + +// func TestMSMPinnedHostMemory(t *testing.T) { +// cfg := msm.GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleBls12_381.GenerateScalars(size) +// points := icicleBls12_381.GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[icicleBls12_381.Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p icicleBls12_381.Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) +// +// e = msm.Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) +// +// +// if pinnableAndLockable { +// e = msm.Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, pinnedPoints, outHost[0])) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +func TestMSMGnarkCryptoTypes(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{3} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = msm.Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + testAgainstGnarkCryptoMsmGnarkCryptoTypes(t, scalarsHost, pointsHost, outHost[0]) + } +} + +func TestMSMBatch(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + size := 1 << power + totalSize := size * batchSize + scalars := icicleBls12_381.GenerateScalars(totalSize) + points := icicleBls12_381.GenerateAffinePoints(totalSize) + + var p icicleBls12_381.Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePoints(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleBls12_381.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsSharedBases(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleBls12_381.GenerateScalars(totalSize) + points := icicleBls12_381.GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleBls12_381.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[0:size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestMSMSkewedDistribution(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + + scalars := icicleBls12_381.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := icicleBls12_381.GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p icicleBls12_381.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } +} + +func TestMSMMultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleBls12_381.GenerateScalars(size) + points := icicleBls12_381.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleBls12_381.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBls12_381.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/bls12381/tests/ntt_test.go b/wrappers/golang_v3/curves/bls12381/tests/ntt_test.go new file mode 100644 index 000000000..a3dc3cf57 --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/ntt_test.go @@ -0,0 +1,270 @@ +package tests + +import ( + "reflect" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr" + "github.com/consensys/gnark-crypto/ecc/bls12-381/fr/fft" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" +) + +func testAgainstGnarkCryptoNtt(t *testing.T, size int, scalars core.HostSlice[bls12_381.ScalarField], output core.HostSlice[bls12_381.ScalarField], order core.Ordering, direction core.NTTDir) { + scalarsFr := make([]fr.Element, size) + for i, v := range scalars { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + outputAsFr := make([]fr.Element, size) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + testAgainstGnarkCryptoNttGnarkTypes(t, size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(t *testing.T, size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) { + domainWithPrecompute := fft.NewDomain(uint64(size)) + // DIT + BitReverse == Ordering.kRR + // DIT == Ordering.kRN + // DIF + BitReverse == Ordering.kNN + // DIF == Ordering.kNR + var decimation fft.Decimation + if order == core.KRN || order == core.KRR { + decimation = fft.DIT + } else { + decimation = fft.DIF + } + + if direction == core.KForward { + domainWithPrecompute.FFT(scalarsFr, decimation) + } else { + domainWithPrecompute.FFTInverse(scalarsFr, decimation) + } + + if order == core.KNN || order == core.KRR { + fft.BitReverse(scalarsFr) + } + assert.Equal(t, scalarsFr, outputAsFr) +} +func TestNTTGetDefaultConfig(t *testing.T) { + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(bls12_381.SCALAR_LIMBS)) + assert.Equal(t, expected, actual.CosetGen[:]) + + 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 := core.GetDefaultNTTInitDomainConfig() + assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) +} + +func TestNtt(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + scalarsCopy := core.HostSliceFromElements[bls12_381.ScalarField](scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[bls12_381.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, 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 + } + + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + runtime.SetDevice(&DEVICE) + + testSize := size + + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNttGnarkTypes(t, testSize, scalarsCopy, output, v, core.KForward) + } + } +} + +func TestNttDeviceAsync(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[bls12_381.ScalarField](scalars[:testSize]) + + stream, _ := runtime.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.StreamHandle = 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[bls12_381.ScalarField], testSize) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + runtime.SynchronizeStream(stream) + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, testSize, scalarsCopy, output, v, direction) + } + } + } +} + +func TestNttBatch(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestTestSize := 10 + largestBatchSize := 20 + scalars := bls12_381.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{2, 16, largestBatchSize} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + totalSize := testSize * batchSize + + scalarsCopy := core.HostSliceFromElements[bls12_381.ScalarField](scalars[:totalSize]) + + cfg.Ordering = core.KNN + cfg.BatchSize = int32(batchSize) + // run ntt + output := make(core.HostSlice[bls12_381.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + domainWithPrecompute := fft.NewDomain(uint64(testSize)) + outputAsFr := make([]fr.Element, totalSize) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + for i := 0; i < batchSize; i++ { + scalarsFr := make([]fr.Element, testSize) + for i, v := range scalarsCopy[i*testSize : (i+1)*testSize] { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + + domainWithPrecompute.FFT(scalarsFr, fft.DIF) + fft.BitReverse(scalarsFr) + if !assert.True(t, reflect.DeepEqual(scalarsFr, outputAsFr[i*testSize:(i+1)*testSize])) { + t.FailNow() + } + } + } + } +} + +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") + e := ntt.ReleaseDomain() + assert.Equal(t, runtime.Success, e, "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_v3/curves/bls12381/tests/polynomial_test.go b/wrappers/golang_v3/curves/bls12381/tests/polynomial_test.go new file mode 100644 index 000000000..376a18e4e --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + // "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381/polynomial" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/curves/bls12381/tests/scalar_field_test.go b/wrappers/golang_v3/curves/bls12381/tests/scalar_field_test.go new file mode 100644 index 000000000..b0617c3bf --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/scalar_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 << 20 + scalars := bls12_381.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + bls12_381.ToMontgomery(&deviceScalars) + + scalarsMontHost := make(core.HostSlice[bls12_381.ScalarField], 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_v3/curves/bls12381/tests/vec_ops_test.go b/wrappers/golang_v3/curves/bls12381/tests/vec_ops_test.go new file mode 100644 index 000000000..872e9169e --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/tests/vec_ops_test.go @@ -0,0 +1,65 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bls12_381 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bls12381" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 + + matrix := bls12_381.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[bls12_381.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[bls12_381.ScalarField], rowSize*columnSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, cfg) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, cfg) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + + 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, cfg) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, cfg) + output := make(core.HostSlice[bls12_381.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang_v3/curves/bls12381/vecOps/include/vec_ops.h b/wrappers/golang_v3/curves/bls12381/vecOps/include/vec_ops.h new file mode 100644 index 000000000..d82f8375f --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/vecOps/include/vec_ops.h @@ -0,0 +1,50 @@ +#include + +#ifndef _BLS12_381_VEC_OPS_H +#define _BLS12_381_VEC_OPS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +int bls12_381_vector_mul( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bls12_381_vector_add( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bls12_381_vector_sub( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bls12_381_matrix_transpose( + scalar_t* mat_in, + int row_size, + int column_size, + VecOpsConfig* config, + scalar_t* mat_out +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bls12381/vecOps/vec_ops.go b/wrappers/golang_v3/curves/bls12381/vecOps/vec_ops.go new file mode 100644 index 000000000..f27c6fe6a --- /dev/null +++ b/wrappers/golang_v3/curves/bls12381/vecOps/vec_ops.go @@ -0,0 +1,44 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret runtime.EIcicleError) { + 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 = (runtime.EIcicleError)(C.bls12_381_vector_sub(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (runtime.EIcicleError)(C.bls12_381_vector_add(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (runtime.EIcicleError)(C.bls12_381_vector_mul(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, config core.VecOpsConfig) runtime.EIcicleError { + inPointer, _, outPointer, cfgPointer, _ := core.VecOpCheck(in, in, out, &config) + + cIn := (*C.scalar_t)(inPointer) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cOut := (*C.scalar_t)(outPointer) + + err := (C.bls12_381_matrix_transpose(cIn, cRowSize, cColumnSize, cConfig, cOut)) + return runtime.EIcicleError(err) +} diff --git a/wrappers/golang_v3/curves/bn254/base_field.go b/wrappers/golang_v3/curves/bn254/base_field.go new file mode 100644 index 000000000..0fbf3aeee --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/base_field.go @@ -0,0 +1,84 @@ +package bn254 + +import ( + "encoding/binary" + "fmt" +) + +const ( + BASE_LIMBS int = 8 +) + +type BaseField struct { + limbs [BASE_LIMBS]uint32 +} + +func (f BaseField) Len() int { + return int(BASE_LIMBS) +} + +func (f BaseField) Size() int { + return int(BASE_LIMBS * 4) +} + +func (f BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *BaseField) Zero() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *BaseField) One() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +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))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f BaseField) 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_v3/curves/bn254/curve.go b/wrappers/golang_v3/curves/bn254/curve.go new file mode 100644 index 000000000..8ad65ba10 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/curve.go @@ -0,0 +1,171 @@ +package bn254 + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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.bn254_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.bn254_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.bn254_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.bn254_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bn254_affine_convert_montgomery((*C.affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, true) +} + +func AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, false) +} + +func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bn254_projective_convert_montgomery((*C.projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, true) +} + +func ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/bn254/ecntt/ecntt.go b/wrappers/golang_v3/curves/bn254/ecntt/ecntt.go new file mode 100644 index 000000000..d04227034 --- /dev/null +++ b/wrappers/golang_v3/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/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cPoints, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bn254/ecntt/include/ecntt.h b/wrappers/golang_v3/curves/bn254/ecntt/include/ecntt.h new file mode 100644 index 000000000..7809610c7 --- /dev/null +++ b/wrappers/golang_v3/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; + +int bn254_ecntt(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_v3/curves/bn254/g2/curve.go b/wrappers/golang_v3/curves/bn254/g2/curve.go new file mode 100644 index 000000000..c3b4c3ad5 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/g2/curve.go @@ -0,0 +1,171 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +type G2Projective struct { + X, Y, Z G2BaseField +} + +func (p G2Projective) Size() int { + return p.X.Size() * 3 +} + +func (p G2Projective) AsPointer() *uint32 { + return p.X.AsPointer() +} + +func (p *G2Projective) Zero() G2Projective { + p.X.Zero() + p.Y.One() + p.Z.Zero() + + return *p +} + +func (p *G2Projective) FromLimbs(x, y, z []uint32) G2Projective { + p.X.FromLimbs(x) + p.Y.FromLimbs(y) + p.Z.FromLimbs(z) + + return *p +} + +func (p *G2Projective) FromAffine(a G2Affine) G2Projective { + z := G2BaseField{} + z.One() + + p.X = a.X + p.Y = a.Y + p.Z = z + + return *p +} + +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.bn254_g2_eq(cP, cP2) + return __ret == (C._Bool)(true) +} + +func (p *G2Projective) ProjectiveToAffine() G2Affine { + var a G2Affine + + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + C.bn254_g2_to_affine(cP, cA) + return a +} + +func G2GenerateProjectivePoints(size int) core.HostSlice[G2Projective] { + points := make([]G2Projective, size) + for i := range points { + points[i] = G2Projective{} + } + + pointsSlice := core.HostSliceFromElements[G2Projective](points) + pPoints := (*C.g2_projective_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.bn254_g2_generate_projective_points(pPoints, cSize) + + return pointsSlice +} + +type G2Affine struct { + X, Y G2BaseField +} + +func (a G2Affine) Size() int { + return a.X.Size() * 2 +} + +func (a G2Affine) AsPointer() *uint32 { + return a.X.AsPointer() +} + +func (a *G2Affine) Zero() G2Affine { + a.X.Zero() + a.Y.Zero() + + return *a +} + +func (a *G2Affine) FromLimbs(x, y []uint32) G2Affine { + a.X.FromLimbs(x) + a.Y.FromLimbs(y) + + return *a +} + +func (a G2Affine) ToProjective() G2Projective { + var z G2BaseField + + return G2Projective{ + X: a.X, + Y: a.Y, + Z: z.One(), + } +} + +func G2AffineFromProjective(p *G2Projective) G2Affine { + return p.ProjectiveToAffine() +} + +func G2GenerateAffinePoints(size int) core.HostSlice[G2Affine] { + points := make([]G2Affine, size) + for i := range points { + points[i] = G2Affine{} + } + + pointsSlice := core.HostSliceFromElements[G2Affine](points) + cPoints := (*C.g2_affine_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.bn254_g2_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertG2AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bn254_g2_affine_convert_montgomery((*C.g2_affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.g2_affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func G2AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2AffinePointsMontgomery(points, true) +} + +func G2AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2AffinePointsMontgomery(points, false) +} + +func convertG2ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bn254_g2_projective_convert_montgomery((*C.g2_projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.g2_projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func G2ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2ProjectivePointsMontgomery(points, true) +} + +func G2ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2ProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/bn254/g2/g2base_field.go b/wrappers/golang_v3/curves/bn254/g2/g2base_field.go new file mode 100644 index 000000000..409a7d5ad --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/g2/g2base_field.go @@ -0,0 +1,84 @@ +package g2 + +import ( + "encoding/binary" + "fmt" +) + +const ( + G2BASE_LIMBS int = 16 +) + +type G2BaseField struct { + limbs [G2BASE_LIMBS]uint32 +} + +func (f G2BaseField) Len() int { + return int(G2BASE_LIMBS) +} + +func (f G2BaseField) Size() int { + return int(G2BASE_LIMBS * 4) +} + +func (f G2BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *G2BaseField) Zero() G2BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *G2BaseField) One() G2BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *G2BaseField) FromBytesLittleEndian(bytes []byte) G2BaseField { + 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 G2BaseField) 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_v3/curves/bn254/g2/include/curve.h b/wrappers/golang_v3/curves/bn254/g2/include/curve.h new file mode 100644 index 000000000..147e4bc9a --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/g2/include/curve.h @@ -0,0 +1,25 @@ +#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 VecOpsConfig VecOpsConfig; + +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); +int bn254_g2_affine_convert_montgomery(const g2_affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, g2_affine_t* d_out); +int bn254_g2_projective_convert_montgomery(const g2_projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, g2_projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bn254/g2/include/msm.h b/wrappers/golang_v3/curves/bn254/g2/include/msm.h new file mode 100644 index 000000000..d0907aac3 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/g2/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int bn254_g2_msm(const scalar_t* scalars, const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); +int bn254_g2_msm_precompute_bases(g2_affine_t* input_bases, int bases_size, MSMConfig* config, g2_affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bn254/g2/include/scalar_field.h b/wrappers/golang_v3/curves/bn254/g2/include/scalar_field.h new file mode 100644 index 000000000..9101faa80 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/g2/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BN254_FIELD_H +#define _BN254_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void bn254_generate_scalars(scalar_t* scalars, int size); +int bn254_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bn254/g2/msm.go b/wrappers/golang_v3/curves/bn254/g2/msm.go new file mode 100644 index 000000000..9af57f53a --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/g2/msm.go @@ -0,0 +1,48 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func G2GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.bn254_g2_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func G2PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.g2_affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.g2_affine_t)(outputBasesPointer) + + __ret := C.bn254_g2_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bn254/include/curve.h b/wrappers/golang_v3/curves/bn254/include/curve.h new file mode 100644 index 000000000..ee78b4658 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/include/curve.h @@ -0,0 +1,25 @@ +#include + +#ifndef _BN254_CURVE_H +#define _BN254_CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct VecOpsConfig VecOpsConfig; + +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); +int bn254_affine_convert_montgomery(const affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, affine_t* d_out); +int bn254_projective_convert_montgomery(const projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bn254/include/scalar_field.h b/wrappers/golang_v3/curves/bn254/include/scalar_field.h new file mode 100644 index 000000000..9101faa80 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BN254_FIELD_H +#define _BN254_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void bn254_generate_scalars(scalar_t* scalars, int size); +int bn254_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bn254/main.go b/wrappers/golang_v3/curves/bn254/main.go new file mode 100644 index 000000000..6b381a42e --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/main.go @@ -0,0 +1,4 @@ +package bn254 + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../build/lib -licicle_field_bn254 -licicle_curve_bn254 -lstdc++ -Wl,-rpath=${SRCDIR}/../../../../build/lib +import "C" diff --git a/wrappers/golang_v3/curves/bn254/msm/include/msm.h b/wrappers/golang_v3/curves/bn254/msm/include/msm.h new file mode 100644 index 000000000..5d35c59da --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/msm/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int bn254_msm(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); +int bn254_msm_precompute_bases(affine_t* input_bases, int bases_size, MSMConfig* config, affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bn254/msm/msm.go b/wrappers/golang_v3/curves/bn254/msm/msm.go new file mode 100644 index 000000000..b6358d44a --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/msm/msm.go @@ -0,0 +1,48 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.bn254_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.bn254_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bn254/ntt/include/ntt.h b/wrappers/golang_v3/curves/bn254/ntt/include/ntt.h new file mode 100644 index 000000000..92d468fd6 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#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 NTTInitDomainConfig NTTInitDomainConfig; + +int bn254_ntt(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +int bn254_ntt_init_domain(scalar_t* primitive_root, NTTInitDomainConfig* ctx); +int bn254_ntt_release_domain(); +int* bn254_get_root_of_unity(size_t size); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang_v3/curves/bn254/ntt/ntt.go b/wrappers/golang_v3/curves/bn254/ntt/ntt.go new file mode 100644 index 000000000..7c9c92652 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/ntt/ntt.go @@ -0,0 +1,59 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cScalars, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return 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 GetRootOfUnity(size uint64) bn254.ScalarField { + cRes := C.bn254_get_root_of_unity((C.size_t)(size)) + var res bn254.ScalarField + res.FromLimbs(*(*[]uint32)(unsafe.Pointer(cRes))) + return res +} + +func InitDomain(primitiveRoot bn254.ScalarField, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCfg := (*C.NTTInitDomainConfig)(unsafe.Pointer(&cfg)) + __ret := C.bn254_ntt_init_domain(cPrimitiveRoot, cCfg) + err := runtime.EIcicleError(__ret) + return err +} + +func ReleaseDomain() runtime.EIcicleError { + __ret := C.bn254_ntt_release_domain() + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bn254/polynomial/include/polynomial.h b/wrappers/golang_v3/curves/bn254/polynomial/include/polynomial.h new file mode 100644 index 000000000..fe8e9a1af --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/polynomial/include/polynomial.h @@ -0,0 +1,50 @@ +#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; + +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_v3/curves/bn254/polynomial/polynomial.go b/wrappers/golang_v3/curves/bn254/polynomial/polynomial.go new file mode 100644 index 000000000..64bf9bda1 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/polynomial/polynomial.go @@ -0,0 +1,172 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +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_v3/curves/bn254/scalar_field.go b/wrappers/golang_v3/curves/bn254/scalar_field.go new file mode 100644 index 000000000..8c9019290 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/scalar_field.go @@ -0,0 +1,118 @@ +package bn254 + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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.bn254_generate_scalars(cScalars, cSize) + + return scalarSlice +} + +func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*scalars, *scalars, *scalars, &defaultCfg) + cErr := C.bn254_scalar_convert_montgomery((*C.scalar_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.scalar_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang_v3/curves/bn254/tests/base_field_test.go b/wrappers/golang_v3/curves/bn254/tests/base_field_test.go new file mode 100644 index 000000000..07d4f84c4 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + BASE_LIMBS = bn254.BASE_LIMBS +) + +func TestBaseFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestBaseFieldGetLimbs(t *testing.T) { + 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 bn254.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 bn254.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 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 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 bn254.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 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_v3/curves/bn254/tests/curve_test.go b/wrappers/golang_v3/curves/bn254/tests/curve_test.go new file mode 100644 index 000000000..b59b857e6 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAffineZero(t *testing.T) { + var fieldZero = bn254.BaseField{} + + var affineZero bn254.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 bn254.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 bn254.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 bn254.BaseField + fieldOne.One() + + var expected bn254.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine bn254.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestProjectiveZero(t *testing.T) { + var projectiveZero bn254.Projective + projectiveZero.Zero() + 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 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective bn254.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 bn254.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 bn254.BaseField + fieldOne.One() + + var expected bn254.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine bn254.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint bn254.Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang_v3/curves/bn254/tests/ecntt_test.go b/wrappers/golang_v3/curves/bn254/tests/ecntt_test.go new file mode 100644 index 000000000..9d2cbdaf2 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/ecntt_test.go @@ -0,0 +1,36 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + ecntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/ecntt" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + ext := runtime.CreateConfigExtension() + ext.SetInt(core.CUDA_NTT_ALGORITHM, int(core.Radix2)) + cfg.Ext = ext.AsUnsafePointer() + + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[bn254.Projective](points[:testSize]) + cfg.Ordering = v + + output := make(core.HostSlice[bn254.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, runtime.Success, e, "ECNtt failed") + } + } +} diff --git a/wrappers/golang_v3/curves/bn254/tests/g2_curve_test.go b/wrappers/golang_v3/curves/bn254/tests/g2_curve_test.go new file mode 100644 index 000000000..6529ca483 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/g2_curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestG2AffineZero(t *testing.T) { + var fieldZero = g2.G2BaseField{} + + var affineZero g2.G2Affine + assert.Equal(t, affineZero.X, fieldZero) + assert.Equal(t, affineZero.Y, fieldZero) + + x := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var affine g2.G2Affine + affine.FromLimbs(x, y) + + affine.Zero() + assert.Equal(t, affine.X, fieldZero) + assert.Equal(t, affine.Y, fieldZero) +} + +func TestG2AffineFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) + assert.ElementsMatch(t, randLimbs2, affine.Y.GetLimbs()) +} + +func TestG2AffineToProjective(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField + fieldOne.One() + + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestG2ProjectiveZero(t *testing.T) { + var projectiveZero g2.G2Projective + projectiveZero.Zero() + 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 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var projective g2.G2Projective + 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 TestG2ProjectiveFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + + var projective g2.G2Projective + 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 TestG2ProjectiveFromAffine(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField + fieldOne.One() + + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint g2.G2Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang_v3/curves/bn254/tests/g2_g2base_field_test.go b/wrappers/golang_v3/curves/bn254/tests/g2_g2base_field_test.go new file mode 100644 index 000000000..08b3cba5d --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/g2_g2base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + G2BASE_LIMBS = bn254.G2BASE_LIMBS +) + +func TestG2BaseFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestG2BaseFieldGetLimbs(t *testing.T) { + 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 bn254.G2BaseField + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int(G2BASE_LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "G2BaseField with limbs to field one did not work") +} + +func TestG2BaseFieldZero(t *testing.T) { + var emptyField bn254.G2BaseField + emptyField.Zero() + limbsZero := make([]uint32, G2BASE_LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "G2BaseField with limbs to field zero failed") +} + +func TestG2BaseFieldSize(t *testing.T) { + 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 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 bn254.G2BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func TestG2BaseFieldToBytes(t *testing.T) { + 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_v3/curves/bn254/tests/g2_msm_test.go b/wrappers/golang_v3/curves/bn254/tests/g2_msm_test.go new file mode 100644 index 000000000..aa4625770 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/g2_msm_test.go @@ -0,0 +1,431 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "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/v2/wrappers/golang_v3/core" + icicleBn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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:])) + x := bn254.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 := bn254.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 := bn254.E2{ + A0: pzA0, + A1: pzA1, + } + + var zSquared bn254.E2 + zSquared.Mul(&z, &z) + + var X bn254.E2 + X.Mul(&x, &z) + + var Y bn254.E2 + Y.Mul(&y, &zSquared) + + g2Jac := bn254.G2Jac{ + X: X, + Y: Y, + Z: z, + } + + var g2Affine bn254.G2Affine + return *g2Affine.FromJacobian(&g2Jac) +} + +func testAgainstGnarkCryptoMsmG2(t *testing.T, scalars core.HostSlice[icicleBn254.ScalarField], points core.HostSlice[g2.G2Affine], out g2.G2Projective) { + 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([]bn254.G2Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffineG2(v.ToProjective()) + } + + testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t *testing.T, scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bn254.G2Affine], out g2.G2Projective) { + var msmRes bn254.G2Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine bn254.G2Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffineG2(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +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 := g2.G2GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleBn254.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } +} + +// func TestMSMG2PinnedHostMemory(t *testing.T) { +// cfg := g2.G2GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleBn254.GenerateScalars(size) +// points := g2.G2GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[g2.G2Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p g2.G2Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[g2.G2Projective], 1) +// +// e = g2.G2Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) +// +// +// if pinnableAndLockable { +// e = g2.G2Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, pinnedPoints, outHost[0])) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +func TestMSMG2GnarkCryptoTypes(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{3} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = g2.G2Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t, scalarsHost, pointsHost, outHost[0]) + } +} + +func TestMSMG2Batch(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + size := 1 << power + totalSize := size * batchSize + scalars := icicleBn254.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsG2(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := g2.G2GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = g2.G2PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p g2.G2Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsSharedBasesG2(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleBn254.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = g2.G2PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p g2.G2Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[0:size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestMSMG2SkewedDistribution(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + + scalars := icicleBn254.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := g2.G2GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } +} + +func TestMSMG2MultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := g2.G2GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleBn254.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/bn254/tests/main_test.go b/wrappers/golang_v3/curves/bn254/tests/main_test.go new file mode 100644 index 000000000..7df490853 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/main_test.go @@ -0,0 +1,66 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" +) + +const ( + largestTestSize = 20 +) + +var DEVICE runtime.Device + +func initDomain(largestTestSize int, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + rouMont, _ := fft.Generator(uint64(1 << largestTestSize)) + rou := rouMont.Bits() + rouIcicle := bn254.ScalarField{} + limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) + + rouIcicle.FromLimbs(limbs) + e := ntt.InitDomain(rouIcicle, cfg) + return e +} + +func TestMain(m *testing.M) { + runtime.LoadBackendFromEnv() + devices, e := runtime.GetRegisteredDevices() + if e != runtime.Success { + panic("Failed to load registered devices") + } + for _, deviceType := range devices { + DEVICE = runtime.CreateDevice(deviceType, 0) + runtime.SetDevice(&DEVICE) + + // setup domain + cfg := core.GetDefaultNTTInitDomainConfig() + e = initDomain(largestTestSize, cfg) + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("initDomain is not implemented for ", deviceType, " device type") + } else { + panic("initDomain failed") + } + } + + // execute tests + m.Run() + + // release domain + e = ntt.ReleaseDomain() + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("ReleaseDomain is not implemented for ", deviceType, " device type") + } else { + panic("ReleaseDomain failed") + } + } + } +} diff --git a/wrappers/golang_v3/curves/bn254/tests/msm_test.go b/wrappers/golang_v3/curves/bn254/tests/msm_test.go new file mode 100644 index 000000000..9c1520b03 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/msm_test.go @@ -0,0 +1,391 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "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/v2/wrappers/golang_v3/core" + icicleBn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/msm" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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())) + + zInv := new(fp.Element) + x := new(fp.Element) + y := new(fp.Element) + + zInv.Inverse(&pz) + + x.Mul(&px, zInv) + y.Mul(&py, zInv) + + return bn254.G1Affine{X: *x, Y: *y} +} + +func testAgainstGnarkCryptoMsm(t *testing.T, scalars core.HostSlice[icicleBn254.ScalarField], points core.HostSlice[icicleBn254.Affine], out icicleBn254.Projective) { + 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([]bn254.G1Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffine(v.ToProjective()) + } + + testAgainstGnarkCryptoMsmGnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmGnarkCryptoTypes(t *testing.T, scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bn254.G1Affine], out icicleBn254.Projective) { + var msmRes bn254.G1Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine bn254.G1Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffine(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +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 := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleBn254.GenerateScalars(size) + points := icicleBn254.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleBn254.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBn254.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } +} + +// func TestMSMPinnedHostMemory(t *testing.T) { +// cfg := msm.GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleBn254.GenerateScalars(size) +// points := icicleBn254.GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[icicleBn254.Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p icicleBn254.Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[icicleBn254.Projective], 1) +// +// e = msm.Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) +// +// +// if pinnableAndLockable { +// e = msm.Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, pinnedPoints, outHost[0])) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +func TestMSMGnarkCryptoTypes(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{3} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = msm.Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBn254.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + testAgainstGnarkCryptoMsmGnarkCryptoTypes(t, scalarsHost, pointsHost, outHost[0]) + } +} + +func TestMSMBatch(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + size := 1 << power + totalSize := size * batchSize + scalars := icicleBn254.GenerateScalars(totalSize) + points := icicleBn254.GenerateAffinePoints(totalSize) + + var p icicleBn254.Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePoints(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleBn254.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsSharedBases(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleBn254.GenerateScalars(totalSize) + points := icicleBn254.GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleBn254.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[0:size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestMSMSkewedDistribution(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + + scalars := icicleBn254.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := icicleBn254.GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p icicleBn254.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBn254.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } +} + +func TestMSMMultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleBn254.GenerateScalars(size) + points := icicleBn254.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleBn254.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBn254.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/bn254/tests/ntt_test.go b/wrappers/golang_v3/curves/bn254/tests/ntt_test.go new file mode 100644 index 000000000..29ff29c24 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/ntt_test.go @@ -0,0 +1,270 @@ +package tests + +import ( + "reflect" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bn254/fr" + "github.com/consensys/gnark-crypto/ecc/bn254/fr/fft" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" +) + +func testAgainstGnarkCryptoNtt(t *testing.T, size int, scalars core.HostSlice[bn254.ScalarField], output core.HostSlice[bn254.ScalarField], order core.Ordering, direction core.NTTDir) { + scalarsFr := make([]fr.Element, size) + for i, v := range scalars { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + outputAsFr := make([]fr.Element, size) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + testAgainstGnarkCryptoNttGnarkTypes(t, size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(t *testing.T, size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) { + domainWithPrecompute := fft.NewDomain(uint64(size)) + // DIT + BitReverse == Ordering.kRR + // DIT == Ordering.kRN + // DIF + BitReverse == Ordering.kNN + // DIF == Ordering.kNR + var decimation fft.Decimation + if order == core.KRN || order == core.KRR { + decimation = fft.DIT + } else { + decimation = fft.DIF + } + + if direction == core.KForward { + domainWithPrecompute.FFT(scalarsFr, decimation) + } else { + domainWithPrecompute.FFTInverse(scalarsFr, decimation) + } + + if order == core.KNN || order == core.KRR { + fft.BitReverse(scalarsFr) + } + assert.Equal(t, scalarsFr, outputAsFr) +} +func TestNTTGetDefaultConfig(t *testing.T) { + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(bn254.SCALAR_LIMBS)) + assert.Equal(t, expected, actual.CosetGen[:]) + + 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 := core.GetDefaultNTTInitDomainConfig() + assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) +} + +func TestNtt(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + scalarsCopy := core.HostSliceFromElements[bn254.ScalarField](scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[bn254.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, 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 + } + + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + runtime.SetDevice(&DEVICE) + + testSize := size + + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNttGnarkTypes(t, testSize, scalarsCopy, output, v, core.KForward) + } + } +} + +func TestNttDeviceAsync(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[bn254.ScalarField](scalars[:testSize]) + + stream, _ := runtime.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.StreamHandle = 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[bn254.ScalarField], testSize) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + runtime.SynchronizeStream(stream) + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, testSize, scalarsCopy, output, v, direction) + } + } + } +} + +func TestNttBatch(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestTestSize := 10 + largestBatchSize := 20 + scalars := bn254.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{2, 16, largestBatchSize} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + totalSize := testSize * batchSize + + scalarsCopy := core.HostSliceFromElements[bn254.ScalarField](scalars[:totalSize]) + + cfg.Ordering = core.KNN + cfg.BatchSize = int32(batchSize) + // run ntt + output := make(core.HostSlice[bn254.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + domainWithPrecompute := fft.NewDomain(uint64(testSize)) + outputAsFr := make([]fr.Element, totalSize) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + for i := 0; i < batchSize; i++ { + scalarsFr := make([]fr.Element, testSize) + for i, v := range scalarsCopy[i*testSize : (i+1)*testSize] { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + + domainWithPrecompute.FFT(scalarsFr, fft.DIF) + fft.BitReverse(scalarsFr) + if !assert.True(t, reflect.DeepEqual(scalarsFr, outputAsFr[i*testSize:(i+1)*testSize])) { + t.FailNow() + } + } + } + } +} + +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") + e := ntt.ReleaseDomain() + assert.Equal(t, runtime.Success, e, "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_v3/curves/bn254/tests/polynomial_test.go b/wrappers/golang_v3/curves/bn254/tests/polynomial_test.go new file mode 100644 index 000000000..cf2a88473 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + // "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254/polynomial" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/curves/bn254/tests/scalar_field_test.go b/wrappers/golang_v3/curves/bn254/tests/scalar_field_test.go new file mode 100644 index 000000000..5bda6159a --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/scalar_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + SCALAR_LIMBS = bn254.SCALAR_LIMBS +) + +func TestScalarFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestScalarFieldGetLimbs(t *testing.T) { + 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 bn254.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 bn254.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 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 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 bn254.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 bn254.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) + emptyField.FromLimbs(limbs) + + assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") +} + +func TestBn254GenerateScalars(t *testing.T) { + const numScalars = 8 + scalars := bn254.GenerateScalars(numScalars) + + assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) + + assert.Equal(t, numScalars, scalars.Len()) + zeroScalar := bn254.ScalarField{} + assert.NotContains(t, scalars, zeroScalar) +} + +func TestBn254MongtomeryConversion(t *testing.T) { + size := 1 << 20 + scalars := bn254.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + bn254.ToMontgomery(&deviceScalars) + + scalarsMontHost := make(core.HostSlice[bn254.ScalarField], size) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.NotEqual(t, scalars, scalarsMontHost) + + bn254.FromMontgomery(&deviceScalars) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.Equal(t, scalars, scalarsMontHost) +} diff --git a/wrappers/golang_v3/curves/bn254/tests/vec_ops_test.go b/wrappers/golang_v3/curves/bn254/tests/vec_ops_test.go new file mode 100644 index 000000000..7e67fafba --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/tests/vec_ops_test.go @@ -0,0 +1,65 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bn254 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bn254" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 + + matrix := bn254.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[bn254.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[bn254.ScalarField], rowSize*columnSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, cfg) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, cfg) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + + 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, cfg) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, cfg) + output := make(core.HostSlice[bn254.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang_v3/curves/bn254/vecOps/include/vec_ops.h b/wrappers/golang_v3/curves/bn254/vecOps/include/vec_ops.h new file mode 100644 index 000000000..a22c3fe53 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/vecOps/include/vec_ops.h @@ -0,0 +1,50 @@ +#include + +#ifndef _BN254_VEC_OPS_H +#define _BN254_VEC_OPS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +int bn254_vector_mul( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bn254_vector_add( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bn254_vector_sub( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bn254_matrix_transpose( + scalar_t* mat_in, + int row_size, + int column_size, + VecOpsConfig* config, + scalar_t* mat_out +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bn254/vecOps/vec_ops.go b/wrappers/golang_v3/curves/bn254/vecOps/vec_ops.go new file mode 100644 index 000000000..eda97f939 --- /dev/null +++ b/wrappers/golang_v3/curves/bn254/vecOps/vec_ops.go @@ -0,0 +1,44 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret runtime.EIcicleError) { + 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 = (runtime.EIcicleError)(C.bn254_vector_sub(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (runtime.EIcicleError)(C.bn254_vector_add(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (runtime.EIcicleError)(C.bn254_vector_mul(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, config core.VecOpsConfig) runtime.EIcicleError { + inPointer, _, outPointer, cfgPointer, _ := core.VecOpCheck(in, in, out, &config) + + cIn := (*C.scalar_t)(inPointer) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cOut := (*C.scalar_t)(outPointer) + + err := (C.bn254_matrix_transpose(cIn, cRowSize, cColumnSize, cConfig, cOut)) + return runtime.EIcicleError(err) +} diff --git a/wrappers/golang_v3/curves/bw6761/base_field.go b/wrappers/golang_v3/curves/bw6761/base_field.go new file mode 100644 index 000000000..b401e49d7 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/base_field.go @@ -0,0 +1,84 @@ +package bw6761 + +import ( + "encoding/binary" + "fmt" +) + +const ( + BASE_LIMBS int = 24 +) + +type BaseField struct { + limbs [BASE_LIMBS]uint32 +} + +func (f BaseField) Len() int { + return int(BASE_LIMBS) +} + +func (f BaseField) Size() int { + return int(BASE_LIMBS * 4) +} + +func (f BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *BaseField) Zero() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *BaseField) One() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +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))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f BaseField) 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_v3/curves/bw6761/curve.go b/wrappers/golang_v3/curves/bw6761/curve.go new file mode 100644 index 000000000..9d12d46de --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/curve.go @@ -0,0 +1,171 @@ +package bw6761 + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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.bw6_761_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.bw6_761_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.bw6_761_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.bw6_761_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertAffinePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bw6_761_affine_convert_montgomery((*C.affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, true) +} + +func AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, false) +} + +func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bw6_761_projective_convert_montgomery((*C.projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, true) +} + +func ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/bw6761/ecntt/ecntt.go b/wrappers/golang_v3/curves/bw6761/ecntt/ecntt.go new file mode 100644 index 000000000..949cd945c --- /dev/null +++ b/wrappers/golang_v3/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/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cPoints, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bw6761/ecntt/include/ecntt.h b/wrappers/golang_v3/curves/bw6761/ecntt/include/ecntt.h new file mode 100644 index 000000000..dc1cea361 --- /dev/null +++ b/wrappers/golang_v3/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; + +int bw6_761_ecntt(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_v3/curves/bw6761/g2/curve.go b/wrappers/golang_v3/curves/bw6761/g2/curve.go new file mode 100644 index 000000000..8e3bca532 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/g2/curve.go @@ -0,0 +1,171 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +type G2Projective struct { + X, Y, Z G2BaseField +} + +func (p G2Projective) Size() int { + return p.X.Size() * 3 +} + +func (p G2Projective) AsPointer() *uint32 { + return p.X.AsPointer() +} + +func (p *G2Projective) Zero() G2Projective { + p.X.Zero() + p.Y.One() + p.Z.Zero() + + return *p +} + +func (p *G2Projective) FromLimbs(x, y, z []uint32) G2Projective { + p.X.FromLimbs(x) + p.Y.FromLimbs(y) + p.Z.FromLimbs(z) + + return *p +} + +func (p *G2Projective) FromAffine(a G2Affine) G2Projective { + z := G2BaseField{} + z.One() + + p.X = a.X + p.Y = a.Y + p.Z = z + + return *p +} + +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_761_g2_eq(cP, cP2) + return __ret == (C._Bool)(true) +} + +func (p *G2Projective) ProjectiveToAffine() G2Affine { + var a G2Affine + + cA := (*C.g2_affine_t)(unsafe.Pointer(&a)) + cP := (*C.g2_projective_t)(unsafe.Pointer(&p)) + C.bw6_761_g2_to_affine(cP, cA) + return a +} + +func G2GenerateProjectivePoints(size int) core.HostSlice[G2Projective] { + points := make([]G2Projective, size) + for i := range points { + points[i] = G2Projective{} + } + + pointsSlice := core.HostSliceFromElements[G2Projective](points) + pPoints := (*C.g2_projective_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.bw6_761_g2_generate_projective_points(pPoints, cSize) + + return pointsSlice +} + +type G2Affine struct { + X, Y G2BaseField +} + +func (a G2Affine) Size() int { + return a.X.Size() * 2 +} + +func (a G2Affine) AsPointer() *uint32 { + return a.X.AsPointer() +} + +func (a *G2Affine) Zero() G2Affine { + a.X.Zero() + a.Y.Zero() + + return *a +} + +func (a *G2Affine) FromLimbs(x, y []uint32) G2Affine { + a.X.FromLimbs(x) + a.Y.FromLimbs(y) + + return *a +} + +func (a G2Affine) ToProjective() G2Projective { + var z G2BaseField + + return G2Projective{ + X: a.X, + Y: a.Y, + Z: z.One(), + } +} + +func G2AffineFromProjective(p *G2Projective) G2Affine { + return p.ProjectiveToAffine() +} + +func G2GenerateAffinePoints(size int) core.HostSlice[G2Affine] { + points := make([]G2Affine, size) + for i := range points { + points[i] = G2Affine{} + } + + pointsSlice := core.HostSliceFromElements[G2Affine](points) + cPoints := (*C.g2_affine_t)(unsafe.Pointer(&pointsSlice[0])) + cSize := (C.int)(size) + C.bw6_761_g2_generate_affine_points(cPoints, cSize) + + return pointsSlice +} + +func convertG2AffinePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bw6_761_g2_affine_convert_montgomery((*C.g2_affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.g2_affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func G2AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2AffinePointsMontgomery(points, true) +} + +func G2AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2AffinePointsMontgomery(points, false) +} + +func convertG2ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.bw6_761_g2_projective_convert_montgomery((*C.g2_projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.g2_projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func G2ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2ProjectivePointsMontgomery(points, true) +} + +func G2ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertG2ProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/bw6761/g2/g2base_field.go b/wrappers/golang_v3/curves/bw6761/g2/g2base_field.go new file mode 100644 index 000000000..c4073af97 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/g2/g2base_field.go @@ -0,0 +1,84 @@ +package g2 + +import ( + "encoding/binary" + "fmt" +) + +const ( + G2BASE_LIMBS int = 24 +) + +type G2BaseField struct { + limbs [G2BASE_LIMBS]uint32 +} + +func (f G2BaseField) Len() int { + return int(G2BASE_LIMBS) +} + +func (f G2BaseField) Size() int { + return int(G2BASE_LIMBS * 4) +} + +func (f G2BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *G2BaseField) Zero() G2BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *G2BaseField) One() G2BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +func (f *G2BaseField) FromBytesLittleEndian(bytes []byte) G2BaseField { + 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 G2BaseField) 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_v3/curves/bw6761/g2/include/curve.h b/wrappers/golang_v3/curves/bw6761/g2/include/curve.h new file mode 100644 index 000000000..ae2fa409f --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/g2/include/curve.h @@ -0,0 +1,25 @@ +#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 VecOpsConfig VecOpsConfig; + +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); +int bw6_761_g2_affine_convert_montgomery(const g2_affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, g2_affine_t* d_out); +int bw6_761_g2_projective_convert_montgomery(const g2_projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, g2_projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bw6761/g2/include/msm.h b/wrappers/golang_v3/curves/bw6761/g2/include/msm.h new file mode 100644 index 000000000..710918202 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/g2/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int bw6_761_g2_msm(const scalar_t* scalars, const g2_affine_t* points, int count, MSMConfig* config, g2_projective_t* out); +int bw6_761_g2_msm_precompute_bases(g2_affine_t* input_bases, int bases_size, MSMConfig* config, g2_affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bw6761/g2/include/scalar_field.h b/wrappers/golang_v3/curves/bw6761/g2/include/scalar_field.h new file mode 100644 index 000000000..29ff19f4e --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/g2/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BW6_761_FIELD_H +#define _BW6_761_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void bw6_761_generate_scalars(scalar_t* scalars, int size); +int bw6_761_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bw6761/g2/msm.go b/wrappers/golang_v3/curves/bw6761/g2/msm.go new file mode 100644 index 000000000..ee829a8c4 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/g2/msm.go @@ -0,0 +1,48 @@ +package g2 + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func G2GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func G2Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.bw6_761_g2_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func G2PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.g2_affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.g2_affine_t)(outputBasesPointer) + + __ret := C.bw6_761_g2_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bw6761/include/curve.h b/wrappers/golang_v3/curves/bw6761/include/curve.h new file mode 100644 index 000000000..e568210a3 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/include/curve.h @@ -0,0 +1,25 @@ +#include + +#ifndef _BW6_761_CURVE_H +#define _BW6_761_CURVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct projective_t projective_t; +typedef struct affine_t affine_t; +typedef struct VecOpsConfig VecOpsConfig; + +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); +int bw6_761_affine_convert_montgomery(const affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, affine_t* d_out); +int bw6_761_projective_convert_montgomery(const projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bw6761/include/scalar_field.h b/wrappers/golang_v3/curves/bw6761/include/scalar_field.h new file mode 100644 index 000000000..29ff19f4e --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BW6_761_FIELD_H +#define _BW6_761_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void bw6_761_generate_scalars(scalar_t* scalars, int size); +int bw6_761_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bw6761/main.go b/wrappers/golang_v3/curves/bw6761/main.go new file mode 100644 index 000000000..70daf3898 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/main.go @@ -0,0 +1,4 @@ +package bw6761 + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../build/lib -licicle_field_bw6_761 -licicle_curve_bw6_761 -lstdc++ -Wl,-rpath=${SRCDIR}/../../../../build/lib +import "C" diff --git a/wrappers/golang_v3/curves/bw6761/msm/include/msm.h b/wrappers/golang_v3/curves/bw6761/msm/include/msm.h new file mode 100644 index 000000000..e2eef31c4 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/msm/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int bw6_761_msm(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); +int bw6_761_msm_precompute_bases(affine_t* input_bases, int bases_size, MSMConfig* config, affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bw6761/msm/msm.go b/wrappers/golang_v3/curves/bw6761/msm/msm.go new file mode 100644 index 000000000..39334c2f9 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/msm/msm.go @@ -0,0 +1,48 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.bw6_761_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.bw6_761_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bw6761/ntt/include/ntt.h b/wrappers/golang_v3/curves/bw6761/ntt/include/ntt.h new file mode 100644 index 000000000..62f593f86 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#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 NTTInitDomainConfig NTTInitDomainConfig; + +int bw6_761_ntt(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +int bw6_761_ntt_init_domain(scalar_t* primitive_root, NTTInitDomainConfig* ctx); +int bw6_761_ntt_release_domain(); +int* bw6_761_get_root_of_unity(size_t size); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang_v3/curves/bw6761/ntt/ntt.go b/wrappers/golang_v3/curves/bw6761/ntt/ntt.go new file mode 100644 index 000000000..1f46b519f --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/ntt/ntt.go @@ -0,0 +1,59 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cScalars, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return 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 GetRootOfUnity(size uint64) bw6_761.ScalarField { + cRes := C.bw6_761_get_root_of_unity((C.size_t)(size)) + var res bw6_761.ScalarField + res.FromLimbs(*(*[]uint32)(unsafe.Pointer(cRes))) + return res +} + +func InitDomain(primitiveRoot bw6_761.ScalarField, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCfg := (*C.NTTInitDomainConfig)(unsafe.Pointer(&cfg)) + __ret := C.bw6_761_ntt_init_domain(cPrimitiveRoot, cCfg) + err := runtime.EIcicleError(__ret) + return err +} + +func ReleaseDomain() runtime.EIcicleError { + __ret := C.bw6_761_ntt_release_domain() + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/bw6761/polynomial/include/polynomial.h b/wrappers/golang_v3/curves/bw6761/polynomial/include/polynomial.h new file mode 100644 index 000000000..e62424faf --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/polynomial/include/polynomial.h @@ -0,0 +1,50 @@ +#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; + +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_v3/curves/bw6761/polynomial/polynomial.go b/wrappers/golang_v3/curves/bw6761/polynomial/polynomial.go new file mode 100644 index 000000000..f29f5a341 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/polynomial/polynomial.go @@ -0,0 +1,172 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +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_v3/curves/bw6761/scalar_field.go b/wrappers/golang_v3/curves/bw6761/scalar_field.go new file mode 100644 index 000000000..811fe0a26 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/scalar_field.go @@ -0,0 +1,118 @@ +package bw6761 + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "unsafe" +) + +const ( + SCALAR_LIMBS int = 12 +) + +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.bw6_761_generate_scalars(cScalars, cSize) + + return scalarSlice +} + +func convertScalarsMontgomery(scalars *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*scalars, *scalars, *scalars, &defaultCfg) + cErr := C.bw6_761_scalar_convert_montgomery((*C.scalar_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.scalar_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang_v3/curves/bw6761/tests/base_field_test.go b/wrappers/golang_v3/curves/bw6761/tests/base_field_test.go new file mode 100644 index 000000000..81d4be94b --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + BASE_LIMBS = bw6_761.BASE_LIMBS +) + +func TestBaseFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestBaseFieldGetLimbs(t *testing.T) { + 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 bw6_761.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 bw6_761.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 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 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 bw6_761.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 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_v3/curves/bw6761/tests/curve_test.go b/wrappers/golang_v3/curves/bw6761/tests/curve_test.go new file mode 100644 index 000000000..7903df6c1 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAffineZero(t *testing.T) { + var fieldZero = bw6_761.BaseField{} + + var affineZero bw6_761.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 bw6_761.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 bw6_761.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 bw6_761.BaseField + fieldOne.One() + + var expected bw6_761.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine bw6_761.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestProjectiveZero(t *testing.T) { + var projectiveZero bw6_761.Projective + projectiveZero.Zero() + 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 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective bw6_761.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 bw6_761.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 bw6_761.BaseField + fieldOne.One() + + var expected bw6_761.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine bw6_761.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint bw6_761.Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang_v3/curves/bw6761/tests/ecntt_test.go b/wrappers/golang_v3/curves/bw6761/tests/ecntt_test.go new file mode 100644 index 000000000..4708b484b --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/ecntt_test.go @@ -0,0 +1,36 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + ecntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/ecntt" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + ext := runtime.CreateConfigExtension() + ext.SetInt(core.CUDA_NTT_ALGORITHM, int(core.Radix2)) + cfg.Ext = ext.AsUnsafePointer() + + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[bw6_761.Projective](points[:testSize]) + cfg.Ordering = v + + output := make(core.HostSlice[bw6_761.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, runtime.Success, e, "ECNtt failed") + } + } +} diff --git a/wrappers/golang_v3/curves/bw6761/tests/g2_curve_test.go b/wrappers/golang_v3/curves/bw6761/tests/g2_curve_test.go new file mode 100644 index 000000000..017e753cc --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/g2_curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestG2AffineZero(t *testing.T) { + var fieldZero = g2.G2BaseField{} + + var affineZero g2.G2Affine + assert.Equal(t, affineZero.X, fieldZero) + assert.Equal(t, affineZero.Y, fieldZero) + + x := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + y := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var affine g2.G2Affine + affine.FromLimbs(x, y) + + affine.Zero() + assert.Equal(t, affine.X, fieldZero) + assert.Equal(t, affine.Y, fieldZero) +} + +func TestG2AffineFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + assert.ElementsMatch(t, randLimbs, affine.X.GetLimbs()) + assert.ElementsMatch(t, randLimbs2, affine.Y.GetLimbs()) +} + +func TestG2AffineToProjective(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField + fieldOne.One() + + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestG2ProjectiveZero(t *testing.T) { + var projectiveZero g2.G2Projective + projectiveZero.Zero() + 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 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var projective g2.G2Projective + 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 TestG2ProjectiveFromLimbs(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs3 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + + var projective g2.G2Projective + 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 TestG2ProjectiveFromAffine(t *testing.T) { + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + randLimbs2 := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + var fieldOne g2.G2BaseField + fieldOne.One() + + var expected g2.G2Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine g2.G2Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint g2.G2Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang_v3/curves/bw6761/tests/g2_g2base_field_test.go b/wrappers/golang_v3/curves/bw6761/tests/g2_g2base_field_test.go new file mode 100644 index 000000000..3d79fce3d --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/g2_g2base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + G2BASE_LIMBS = bw6_761.G2BASE_LIMBS +) + +func TestG2BaseFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestG2BaseFieldGetLimbs(t *testing.T) { + 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 bw6_761.G2BaseField + emptyField.One() + limbOne := test_helpers.GenerateLimbOne(int(G2BASE_LIMBS)) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "Empty field to field one did not work") + + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.One() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbOne, "G2BaseField with limbs to field one did not work") +} + +func TestG2BaseFieldZero(t *testing.T) { + var emptyField bw6_761.G2BaseField + emptyField.Zero() + limbsZero := make([]uint32, G2BASE_LIMBS) + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "Empty field to field zero failed") + + randLimbs := test_helpers.GenerateRandomLimb(int(G2BASE_LIMBS)) + emptyField.FromLimbs(randLimbs[:]) + + emptyField.Zero() + assert.ElementsMatch(t, emptyField.GetLimbs(), limbsZero, "G2BaseField with limbs to field zero failed") +} + +func TestG2BaseFieldSize(t *testing.T) { + 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 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 bw6_761.G2BaseField + bytes, expected := test_helpers.GenerateBytesArray(int(G2BASE_LIMBS)) + + emptyField.FromBytesLittleEndian(bytes) + + assert.ElementsMatch(t, emptyField.GetLimbs(), expected, "FromBytes returned incorrect values") +} + +func TestG2BaseFieldToBytes(t *testing.T) { + 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_v3/curves/bw6761/tests/g2_msm_test.go b/wrappers/golang_v3/curves/bw6761/tests/g2_msm_test.go new file mode 100644 index 000000000..0c1fa0623 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/g2_msm_test.go @@ -0,0 +1,391 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "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/v2/wrappers/golang_v3/core" + icicleBw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/g2" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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())) + + zInv := new(fp.Element) + x := new(fp.Element) + y := new(fp.Element) + + zInv.Inverse(&pz) + + x.Mul(&px, zInv) + y.Mul(&py, zInv) + + return bw6761.G2Affine{X: *x, Y: *y} +} + +func testAgainstGnarkCryptoMsmG2(t *testing.T, scalars core.HostSlice[icicleBw6_761.ScalarField], points core.HostSlice[g2.G2Affine], out g2.G2Projective) { + 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([]bw6761.G2Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffineG2(v.ToProjective()) + } + + testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t *testing.T, scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bw6761.G2Affine], out g2.G2Projective) { + var msmRes bw6761.G2Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine bw6761.G2Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffineG2(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +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 := g2.G2GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleBw6_761.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } +} + +// func TestMSMG2PinnedHostMemory(t *testing.T) { +// cfg := g2.G2GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleBw6_761.GenerateScalars(size) +// points := g2.G2GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[g2.G2Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p g2.G2Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[g2.G2Projective], 1) +// +// e = g2.G2Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, points, outHost[0])) +// +// +// if pinnableAndLockable { +// e = g2.G2Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsmG2(scalars, pinnedPoints, outHost[0])) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +func TestMSMG2GnarkCryptoTypes(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{3} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = g2.G2Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2GnarkCryptoTypes(t, scalarsHost, pointsHost, outHost[0]) + } +} + +func TestMSMG2Batch(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + size := 1 << power + totalSize := size * batchSize + scalars := icicleBw6_761.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(totalSize) + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsG2(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := g2.G2GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = g2.G2PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p g2.G2Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsSharedBasesG2(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleBw6_761.GenerateScalars(totalSize) + points := g2.G2GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = g2.G2PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p g2.G2Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[0:size] + out := outHost[i] + testAgainstGnarkCryptoMsmG2(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestMSMG2SkewedDistribution(t *testing.T) { + cfg := g2.G2GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + + scalars := icicleBw6_761.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := g2.G2GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } +} + +func TestMSMG2MultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := g2.G2GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleBw6_761.GenerateScalars(size) + points := g2.G2GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p g2.G2Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = g2.G2Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[g2.G2Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsmG2(t, scalars, points, outHost[0]) + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/bw6761/tests/main_test.go b/wrappers/golang_v3/curves/bw6761/tests/main_test.go new file mode 100644 index 000000000..05d182a42 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/main_test.go @@ -0,0 +1,66 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fft" +) + +const ( + largestTestSize = 20 +) + +var DEVICE runtime.Device + +func initDomain(largestTestSize int, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + 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) + return e +} + +func TestMain(m *testing.M) { + runtime.LoadBackendFromEnv() + devices, e := runtime.GetRegisteredDevices() + if e != runtime.Success { + panic("Failed to load registered devices") + } + for _, deviceType := range devices { + DEVICE = runtime.CreateDevice(deviceType, 0) + runtime.SetDevice(&DEVICE) + + // setup domain + cfg := core.GetDefaultNTTInitDomainConfig() + e = initDomain(largestTestSize, cfg) + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("initDomain is not implemented for ", deviceType, " device type") + } else { + panic("initDomain failed") + } + } + + // execute tests + m.Run() + + // release domain + e = ntt.ReleaseDomain() + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("ReleaseDomain is not implemented for ", deviceType, " device type") + } else { + panic("ReleaseDomain failed") + } + } + } +} diff --git a/wrappers/golang_v3/curves/bw6761/tests/msm_test.go b/wrappers/golang_v3/curves/bw6761/tests/msm_test.go new file mode 100644 index 000000000..4ef230b42 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/msm_test.go @@ -0,0 +1,391 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "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/v2/wrappers/golang_v3/core" + icicleBw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/msm" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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())) + + zInv := new(fp.Element) + x := new(fp.Element) + y := new(fp.Element) + + zInv.Inverse(&pz) + + x.Mul(&px, zInv) + y.Mul(&py, zInv) + + return bw6761.G1Affine{X: *x, Y: *y} +} + +func testAgainstGnarkCryptoMsm(t *testing.T, scalars core.HostSlice[icicleBw6_761.ScalarField], points core.HostSlice[icicleBw6_761.Affine], out icicleBw6_761.Projective) { + 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([]bw6761.G1Affine, len(points)) + for i, v := range points { + pointsFp[i] = projectiveToGnarkAffine(v.ToProjective()) + } + + testAgainstGnarkCryptoMsmGnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsmGnarkCryptoTypes(t *testing.T, scalarsFr core.HostSlice[fr.Element], pointsFp core.HostSlice[bw6761.G1Affine], out icicleBw6_761.Projective) { + var msmRes bw6761.G1Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine bw6761.G1Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffine(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +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 := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleBw6_761.GenerateScalars(size) + points := icicleBw6_761.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleBw6_761.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } +} + +// func TestMSMPinnedHostMemory(t *testing.T) { +// cfg := msm.GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleBw6_761.GenerateScalars(size) +// points := icicleBw6_761.GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[icicleBw6_761.Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p icicleBw6_761.Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) +// +// e = msm.Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) +// +// +// if pinnableAndLockable { +// e = msm.Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// // // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, pinnedPoints, outHost[0])) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +func TestMSMGnarkCryptoTypes(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{3} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = msm.Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + // Check with gnark-crypto + testAgainstGnarkCryptoMsmGnarkCryptoTypes(t, scalarsHost, pointsHost, outHost[0]) + } +} + +func TestMSMBatch(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + size := 1 << power + totalSize := size * batchSize + scalars := icicleBw6_761.GenerateScalars(totalSize) + points := icicleBw6_761.GenerateAffinePoints(totalSize) + + var p icicleBw6_761.Projective + var out core.DeviceSlice + _, e := out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePoints(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleBw6_761.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[i*size : (i+1)*size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestPrecomputePointsSharedBases(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleBw6_761.GenerateScalars(totalSize) + points := icicleBw6_761.GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleBw6_761.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + 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] + pointsSlice := points[0:size] + out := outHost[i] + testAgainstGnarkCryptoMsm(t, scalarsSlice, pointsSlice, out) + } + } + } +} + +func TestMSMSkewedDistribution(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{2, 3, 4, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + + scalars := icicleBw6_761.GenerateScalars(size) + for i := size / 4; i < size; i++ { + scalars[i].One() + } + points := icicleBw6_761.GenerateAffinePoints(size) + for i := 0; i < size/4; i++ { + points[i].Zero() + } + + var p icicleBw6_761.Projective + var out core.DeviceSlice + _, e := out.Malloc(p.Size(), p.Size()) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } +} + +func TestMSMMultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleBw6_761.GenerateScalars(size) + points := icicleBw6_761.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleBw6_761.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleBw6_761.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + // Check with gnark-crypto + testAgainstGnarkCryptoMsm(t, scalars, points, outHost[0]) + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/bw6761/tests/ntt_test.go b/wrappers/golang_v3/curves/bw6761/tests/ntt_test.go new file mode 100644 index 000000000..122cb3ff1 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/ntt_test.go @@ -0,0 +1,270 @@ +package tests + +import ( + "reflect" + "testing" + + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr" + "github.com/consensys/gnark-crypto/ecc/bw6-761/fr/fft" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" +) + +func testAgainstGnarkCryptoNtt(t *testing.T, size int, scalars core.HostSlice[bw6_761.ScalarField], output core.HostSlice[bw6_761.ScalarField], order core.Ordering, direction core.NTTDir) { + scalarsFr := make([]fr.Element, size) + for i, v := range scalars { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + outputAsFr := make([]fr.Element, size) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + testAgainstGnarkCryptoNttGnarkTypes(t, size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(t *testing.T, size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) { + domainWithPrecompute := fft.NewDomain(uint64(size)) + // DIT + BitReverse == Ordering.kRR + // DIT == Ordering.kRN + // DIF + BitReverse == Ordering.kNN + // DIF == Ordering.kNR + var decimation fft.Decimation + if order == core.KRN || order == core.KRR { + decimation = fft.DIT + } else { + decimation = fft.DIF + } + + if direction == core.KForward { + domainWithPrecompute.FFT(scalarsFr, decimation) + } else { + domainWithPrecompute.FFTInverse(scalarsFr, decimation) + } + + if order == core.KNN || order == core.KRR { + fft.BitReverse(scalarsFr) + } + assert.Equal(t, scalarsFr, outputAsFr) +} +func TestNTTGetDefaultConfig(t *testing.T) { + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int(bw6_761.SCALAR_LIMBS)) + assert.Equal(t, expected, actual.CosetGen[:]) + + 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 := core.GetDefaultNTTInitDomainConfig() + assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) +} + +func TestNtt(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + scalarsCopy := core.HostSliceFromElements[bw6_761.ScalarField](scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[bw6_761.ScalarField], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, 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 + } + + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + runtime.SetDevice(&DEVICE) + + testSize := size + + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNttGnarkTypes(t, testSize, scalarsCopy, output, v, core.KForward) + } + } +} + +func TestNttDeviceAsync(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[bw6_761.ScalarField](scalars[:testSize]) + + stream, _ := runtime.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.StreamHandle = 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[bw6_761.ScalarField], testSize) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + runtime.SynchronizeStream(stream) + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, testSize, scalarsCopy, output, v, direction) + } + } + } +} + +func TestNttBatch(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestTestSize := 10 + largestBatchSize := 20 + scalars := bw6_761.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{2, 16, largestBatchSize} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + totalSize := testSize * batchSize + + scalarsCopy := core.HostSliceFromElements[bw6_761.ScalarField](scalars[:totalSize]) + + cfg.Ordering = core.KNN + cfg.BatchSize = int32(batchSize) + // run ntt + output := make(core.HostSlice[bw6_761.ScalarField], totalSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + domainWithPrecompute := fft.NewDomain(uint64(testSize)) + outputAsFr := make([]fr.Element, totalSize) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + for i := 0; i < batchSize; i++ { + scalarsFr := make([]fr.Element, testSize) + for i, v := range scalarsCopy[i*testSize : (i+1)*testSize] { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + + domainWithPrecompute.FFT(scalarsFr, fft.DIF) + fft.BitReverse(scalarsFr) + if !assert.True(t, reflect.DeepEqual(scalarsFr, outputAsFr[i*testSize:(i+1)*testSize])) { + t.FailNow() + } + } + } + } +} + +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") + e := ntt.ReleaseDomain() + assert.Equal(t, runtime.Success, e, "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_v3/curves/bw6761/tests/polynomial_test.go b/wrappers/golang_v3/curves/bw6761/tests/polynomial_test.go new file mode 100644 index 000000000..c017ddf56 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + // "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761/polynomial" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/curves/bw6761/tests/scalar_field_test.go b/wrappers/golang_v3/curves/bw6761/tests/scalar_field_test.go new file mode 100644 index 000000000..5e375ff94 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/scalar_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + SCALAR_LIMBS = bw6_761.SCALAR_LIMBS +) + +func TestScalarFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestScalarFieldGetLimbs(t *testing.T) { + 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 bw6_761.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 bw6_761.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 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 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 bw6_761.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 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 TestBw6_761GenerateScalars(t *testing.T) { + const numScalars = 8 + scalars := bw6_761.GenerateScalars(numScalars) + + assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) + + assert.Equal(t, numScalars, scalars.Len()) + zeroScalar := bw6_761.ScalarField{} + assert.NotContains(t, scalars, zeroScalar) +} + +func TestBw6_761MongtomeryConversion(t *testing.T) { + size := 1 << 20 + scalars := bw6_761.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + bw6_761.ToMontgomery(&deviceScalars) + + scalarsMontHost := make(core.HostSlice[bw6_761.ScalarField], size) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.NotEqual(t, scalars, scalarsMontHost) + + bw6_761.FromMontgomery(&deviceScalars) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.Equal(t, scalars, scalarsMontHost) +} diff --git a/wrappers/golang_v3/curves/bw6761/tests/vec_ops_test.go b/wrappers/golang_v3/curves/bw6761/tests/vec_ops_test.go new file mode 100644 index 000000000..6f62dcd76 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/tests/vec_ops_test.go @@ -0,0 +1,65 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + bw6_761 "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/bw6761" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 + + matrix := bw6_761.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[bw6_761.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[bw6_761.ScalarField], rowSize*columnSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, cfg) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, cfg) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + + 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, cfg) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, cfg) + output := make(core.HostSlice[bw6_761.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang_v3/curves/bw6761/vecOps/include/vec_ops.h b/wrappers/golang_v3/curves/bw6761/vecOps/include/vec_ops.h new file mode 100644 index 000000000..a7a2bab74 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/vecOps/include/vec_ops.h @@ -0,0 +1,50 @@ +#include + +#ifndef _BW6_761_VEC_OPS_H +#define _BW6_761_VEC_OPS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; +typedef struct DeviceContext DeviceContext; + +int bw6_761_vector_mul( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bw6_761_vector_add( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bw6_761_vector_sub( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int bw6_761_matrix_transpose( + scalar_t* mat_in, + int row_size, + int column_size, + VecOpsConfig* config, + scalar_t* mat_out +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/bw6761/vecOps/vec_ops.go b/wrappers/golang_v3/curves/bw6761/vecOps/vec_ops.go new file mode 100644 index 000000000..60b5cdc61 --- /dev/null +++ b/wrappers/golang_v3/curves/bw6761/vecOps/vec_ops.go @@ -0,0 +1,44 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret runtime.EIcicleError) { + 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 = (runtime.EIcicleError)(C.bw6_761_vector_sub(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (runtime.EIcicleError)(C.bw6_761_vector_add(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (runtime.EIcicleError)(C.bw6_761_vector_mul(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, config core.VecOpsConfig) runtime.EIcicleError { + inPointer, _, outPointer, cfgPointer, _ := core.VecOpCheck(in, in, out, &config) + + cIn := (*C.scalar_t)(inPointer) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cOut := (*C.scalar_t)(outPointer) + + err := (C.bw6_761_matrix_transpose(cIn, cRowSize, cColumnSize, cConfig, cOut)) + return runtime.EIcicleError(err) +} diff --git a/wrappers/golang_v3/curves/grumpkin/base_field.go b/wrappers/golang_v3/curves/grumpkin/base_field.go new file mode 100644 index 000000000..cacd27674 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/base_field.go @@ -0,0 +1,84 @@ +package grumpkin + +import ( + "encoding/binary" + "fmt" +) + +const ( + BASE_LIMBS int = 8 +) + +type BaseField struct { + limbs [BASE_LIMBS]uint32 +} + +func (f BaseField) Len() int { + return int(BASE_LIMBS) +} + +func (f BaseField) Size() int { + return int(BASE_LIMBS * 4) +} + +func (f BaseField) GetLimbs() []uint32 { + return f.limbs[:] +} + +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") + } + for i := range f.limbs { + f.limbs[i] = limbs[i] + } + + return *f +} + +func (f *BaseField) Zero() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + + return *f +} + +func (f *BaseField) One() BaseField { + for i := range f.limbs { + f.limbs[i] = 0 + } + f.limbs[0] = 1 + + return *f +} + +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))) + } + + for i := range f.limbs { + f.limbs[i] = binary.LittleEndian.Uint32(bytes[i*4 : i*4+4]) + } + + return *f +} + +func (f BaseField) 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_v3/curves/grumpkin/curve.go b/wrappers/golang_v3/curves/grumpkin/curve.go new file mode 100644 index 000000000..80a57316b --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/curve.go @@ -0,0 +1,171 @@ +package grumpkin + +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.grumpkin_affine_convert_montgomery((*C.affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, true) +} + +func AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertAffinePointsMontgomery(points, false) +} + +func convertProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.grumpkin_projective_convert_montgomery((*C.projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, true) +} + +func ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convertProjectivePointsMontgomery(points, false) +} diff --git a/wrappers/golang_v3/curves/grumpkin/include/curve.h b/wrappers/golang_v3/curves/grumpkin/include/curve.h new file mode 100644 index 000000000..710dc4553 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/include/curve.h @@ -0,0 +1,25 @@ +#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 VecOpsConfig VecOpsConfig; + +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); +int grumpkin_affine_convert_montgomery(const affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, affine_t* d_out); +int grumpkin_projective_convert_montgomery(const projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/grumpkin/include/scalar_field.h b/wrappers/golang_v3/curves/grumpkin/include/scalar_field.h new file mode 100644 index 000000000..f9708c3db --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _GRUMPKIN_FIELD_H +#define _GRUMPKIN_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void grumpkin_generate_scalars(scalar_t* scalars, int size); +int grumpkin_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/grumpkin/main.go b/wrappers/golang_v3/curves/grumpkin/main.go new file mode 100644 index 000000000..2282e7210 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/main.go @@ -0,0 +1,4 @@ +package grumpkin + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../build/lib -licicle_field_grumpkin -licicle_curve_grumpkin -lstdc++ -Wl,-rpath=${SRCDIR}/../../../../build/lib +import "C" diff --git a/wrappers/golang_v3/curves/grumpkin/msm/include/msm.h b/wrappers/golang_v3/curves/grumpkin/msm/include/msm.h new file mode 100644 index 000000000..58529b6b9 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/msm/include/msm.h @@ -0,0 +1,22 @@ +#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; + +int grumpkin_msm(const scalar_t* scalars, const affine_t* points, int count, MSMConfig* config, projective_t* out); +int grumpkin_msm_precompute_bases(affine_t* input_bases, int bases_size, MSMConfig* config, affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/grumpkin/msm/msm.go b/wrappers/golang_v3/curves/grumpkin/msm/msm.go new file mode 100644 index 000000000..e7a1d1810 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/msm/msm.go @@ -0,0 +1,48 @@ +package msm + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.grumpkin_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.affine_t)(outputBasesPointer) + + __ret := C.grumpkin_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/curves/grumpkin/scalar_field.go b/wrappers/golang_v3/curves/grumpkin/scalar_field.go new file mode 100644 index 000000000..b097d093e --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/scalar_field.go @@ -0,0 +1,118 @@ +package grumpkin + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*scalars, *scalars, *scalars, &defaultCfg) + cErr := C.grumpkin_scalar_convert_montgomery((*C.scalar_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.scalar_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang_v3/curves/grumpkin/tests/base_field_test.go b/wrappers/golang_v3/curves/grumpkin/tests/base_field_test.go new file mode 100644 index 000000000..282240b72 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/tests/base_field_test.go @@ -0,0 +1,88 @@ +package tests + +import ( + grumpkin "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/grumpkin" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + BASE_LIMBS = grumpkin.BASE_LIMBS +) + +func TestBaseFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestBaseFieldGetLimbs(t *testing.T) { + 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 grumpkin.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 grumpkin.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 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 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 grumpkin.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 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_v3/curves/grumpkin/tests/curve_test.go b/wrappers/golang_v3/curves/grumpkin/tests/curve_test.go new file mode 100644 index 000000000..93ee02d13 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/tests/curve_test.go @@ -0,0 +1,103 @@ +package tests + +import ( + grumpkin "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/grumpkin" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAffineZero(t *testing.T) { + var fieldZero = grumpkin.BaseField{} + + var affineZero grumpkin.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 grumpkin.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 grumpkin.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 grumpkin.BaseField + fieldOne.One() + + var expected grumpkin.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine grumpkin.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + projectivePoint := affine.ToProjective() + assert.Equal(t, expected, projectivePoint) +} + +func TestProjectiveZero(t *testing.T) { + var projectiveZero grumpkin.Projective + projectiveZero.Zero() + 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 := test_helpers.GenerateRandomLimb(int(BASE_LIMBS)) + var projective grumpkin.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 grumpkin.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 grumpkin.BaseField + fieldOne.One() + + var expected grumpkin.Projective + expected.FromLimbs(randLimbs, randLimbs2, fieldOne.GetLimbs()[:]) + + var affine grumpkin.Affine + affine.FromLimbs(randLimbs, randLimbs2) + + var projectivePoint grumpkin.Projective + projectivePoint.FromAffine(affine) + assert.Equal(t, expected, projectivePoint) +} diff --git a/wrappers/golang_v3/curves/grumpkin/tests/main_test.go b/wrappers/golang_v3/curves/grumpkin/tests/main_test.go new file mode 100644 index 000000000..5ef2f80b3 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/tests/main_test.go @@ -0,0 +1,29 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +const ( + largestTestSize = 20 +) + +var DEVICE runtime.Device + +func TestMain(m *testing.M) { + runtime.LoadBackendFromEnv() + devices, e := runtime.GetRegisteredDevices() + if e != runtime.Success { + panic("Failed to load registered devices") + } + for _, deviceType := range devices { + DEVICE = runtime.CreateDevice(deviceType, 0) + runtime.SetDevice(&DEVICE) + + // execute tests + m.Run() + + } +} diff --git a/wrappers/golang_v3/curves/grumpkin/tests/msm_test.go b/wrappers/golang_v3/curves/grumpkin/tests/msm_test.go new file mode 100644 index 000000000..d594c539b --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/tests/msm_test.go @@ -0,0 +1,266 @@ +package tests + +import ( + "fmt" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + icicleGrumpkin "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/grumpkin" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/grumpkin/msm" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func TestMSM(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicleGrumpkin.GenerateScalars(size) + points := icicleGrumpkin.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + + } +} + +// func TestMSMPinnedHostMemory(t *testing.T) { +// cfg := msm.GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicleGrumpkin.GenerateScalars(size) +// points := icicleGrumpkin.GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[icicleGrumpkin.Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// var p icicleGrumpkin.Projective +// var out core.DeviceSlice +// _, e := out.Malloc(p.Size(), p.Size()) +// assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[icicleGrumpkin.Projective], 1) +// +// e = msm.Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// +// +// if pinnableAndLockable { +// e = msm.Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } + +func TestMSMBatch(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + for _, power := range []int{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], batchSize) + outHost.CopyFromDevice(&out) + out.Free() + + } + } +} + +func TestPrecomputePoints(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], batchSize) + outHost.CopyFromDevice(&out) + out.Free() + precomputeOut.Free() + + } + } +} + +func TestPrecomputePointsSharedBases(t *testing.T) { + cfg := msm.GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + size := 1 << power + totalSize := size * batchSize + scalars := icicleGrumpkin.GenerateScalars(totalSize) + points := icicleGrumpkin.GenerateAffinePoints(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = msm.PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "PrecomputeBases failed") + + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e = out.Malloc(batchSize*p.Size(), p.Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "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} { + runtime.SetDevice(&DEVICE) + + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], 1) + outHost.CopyFromDevice(&out) + out.Free() + + } +} + +func TestMSMMultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := msm.GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicleGrumpkin.GenerateScalars(size) + points := icicleGrumpkin.GenerateAffinePoints(size) + + stream, _ := runtime.CreateStream() + var p icicleGrumpkin.Projective + var out core.DeviceSlice + _, e := out.MallocAsync(p.Size(), p.Size(), stream) + assert.Equal(t, e, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = msm.Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "Msm failed") + outHost := make(core.HostSlice[icicleGrumpkin.Projective], 1) + outHost.CopyFromDeviceAsync(&out, stream) + out.FreeAsync(stream) + + runtime.SynchronizeStream(stream) + + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/curves/grumpkin/tests/scalar_field_test.go b/wrappers/golang_v3/curves/grumpkin/tests/scalar_field_test.go new file mode 100644 index 000000000..152441f05 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/tests/scalar_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + grumpkin "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/grumpkin" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + SCALAR_LIMBS = grumpkin.SCALAR_LIMBS +) + +func TestScalarFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestScalarFieldGetLimbs(t *testing.T) { + 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 grumpkin.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 grumpkin.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 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 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 grumpkin.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 grumpkin.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) + emptyField.FromLimbs(limbs) + + assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") +} + +func TestGrumpkinGenerateScalars(t *testing.T) { + const numScalars = 8 + scalars := grumpkin.GenerateScalars(numScalars) + + assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) + + assert.Equal(t, numScalars, scalars.Len()) + zeroScalar := grumpkin.ScalarField{} + assert.NotContains(t, scalars, zeroScalar) +} + +func TestGrumpkinMongtomeryConversion(t *testing.T) { + size := 1 << 20 + scalars := grumpkin.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + grumpkin.ToMontgomery(&deviceScalars) + + scalarsMontHost := make(core.HostSlice[grumpkin.ScalarField], size) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.NotEqual(t, scalars, scalarsMontHost) + + grumpkin.FromMontgomery(&deviceScalars) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.Equal(t, scalars, scalarsMontHost) +} diff --git a/wrappers/golang_v3/curves/grumpkin/tests/vec_ops_test.go b/wrappers/golang_v3/curves/grumpkin/tests/vec_ops_test.go new file mode 100644 index 000000000..50e06dda0 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/tests/vec_ops_test.go @@ -0,0 +1,65 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + grumpkin "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/curves/grumpkin" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 + + matrix := grumpkin.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[grumpkin.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[grumpkin.ScalarField], rowSize*columnSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, cfg) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, cfg) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + + 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, cfg) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, cfg) + output := make(core.HostSlice[grumpkin.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang_v3/curves/grumpkin/vecOps/include/vec_ops.h b/wrappers/golang_v3/curves/grumpkin/vecOps/include/vec_ops.h new file mode 100644 index 000000000..64a6e33a5 --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/vecOps/include/vec_ops.h @@ -0,0 +1,50 @@ +#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; + +int grumpkin_vector_mul( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int grumpkin_vector_add( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int grumpkin_vector_sub( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int grumpkin_matrix_transpose( + scalar_t* mat_in, + int row_size, + int column_size, + VecOpsConfig* config, + scalar_t* mat_out +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/curves/grumpkin/vecOps/vec_ops.go b/wrappers/golang_v3/curves/grumpkin/vecOps/vec_ops.go new file mode 100644 index 000000000..f49039f3c --- /dev/null +++ b/wrappers/golang_v3/curves/grumpkin/vecOps/vec_ops.go @@ -0,0 +1,44 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret runtime.EIcicleError) { + 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 = (runtime.EIcicleError)(C.grumpkin_vector_sub(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (runtime.EIcicleError)(C.grumpkin_vector_add(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (runtime.EIcicleError)(C.grumpkin_vector_mul(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, config core.VecOpsConfig) runtime.EIcicleError { + inPointer, _, outPointer, cfgPointer, _ := core.VecOpCheck(in, in, out, &config) + + cIn := (*C.scalar_t)(inPointer) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cOut := (*C.scalar_t)(outPointer) + + err := (C.grumpkin_matrix_transpose(cIn, cRowSize, cColumnSize, cConfig, cOut)) + return runtime.EIcicleError(err) +} diff --git a/wrappers/golang_v3/fields/babybear/extension/extension_field.go b/wrappers/golang_v3/fields/babybear/extension/extension_field.go new file mode 100644 index 000000000..bea36165f --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/extension/extension_field.go @@ -0,0 +1,118 @@ +package extension + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*scalars, *scalars, *scalars, &defaultCfg) + cErr := C.babybear_extension_scalar_convert_montgomery((*C.scalar_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.scalar_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang_v3/fields/babybear/extension/include/scalar_field.h b/wrappers/golang_v3/fields/babybear/extension/include/scalar_field.h new file mode 100644 index 000000000..447ac2f1f --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/extension/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BABYBEAR_EXTENSION_FIELD_H +#define _BABYBEAR_EXTENSION_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void babybear_extension_generate_scalars(scalar_t* scalars, int size); +int babybear_extension_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/fields/babybear/extension/ntt/include/ntt.h b/wrappers/golang_v3/fields/babybear/extension/ntt/include/ntt.h new file mode 100644 index 000000000..5a9af7b34 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/extension/ntt/include/ntt.h @@ -0,0 +1,21 @@ +#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; + + +int babybear_extension_ntt(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_v3/fields/babybear/extension/ntt/ntt.go b/wrappers/golang_v3/fields/babybear/extension/ntt/ntt.go new file mode 100644 index 000000000..574a81951 --- /dev/null +++ b/wrappers/golang_v3/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/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cScalars, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/fields/babybear/extension/vecOps/include/vec_ops.h b/wrappers/golang_v3/fields/babybear/extension/vecOps/include/vec_ops.h new file mode 100644 index 000000000..b9029ea7e --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/extension/vecOps/include/vec_ops.h @@ -0,0 +1,50 @@ +#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; + +int babybear_extension_vector_mul( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int babybear_extension_vector_add( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int babybear_extension_vector_sub( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int babybear_extension_matrix_transpose( + scalar_t* mat_in, + int row_size, + int column_size, + VecOpsConfig* config, + scalar_t* mat_out +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/fields/babybear/extension/vecOps/vec_ops.go b/wrappers/golang_v3/fields/babybear/extension/vecOps/vec_ops.go new file mode 100644 index 000000000..afde0e157 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/extension/vecOps/vec_ops.go @@ -0,0 +1,44 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret runtime.EIcicleError) { + 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 = (runtime.EIcicleError)(C.babybear_extension_vector_sub(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (runtime.EIcicleError)(C.babybear_extension_vector_add(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (runtime.EIcicleError)(C.babybear_extension_vector_mul(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, config core.VecOpsConfig) runtime.EIcicleError { + inPointer, _, outPointer, cfgPointer, _ := core.VecOpCheck(in, in, out, &config) + + cIn := (*C.scalar_t)(inPointer) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cOut := (*C.scalar_t)(outPointer) + + err := (C.babybear_extension_matrix_transpose(cIn, cRowSize, cColumnSize, cConfig, cOut)) + return runtime.EIcicleError(err) +} diff --git a/wrappers/golang_v3/fields/babybear/include/scalar_field.h b/wrappers/golang_v3/fields/babybear/include/scalar_field.h new file mode 100644 index 000000000..36d1aff10 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/include/scalar_field.h @@ -0,0 +1,20 @@ +#include + +#ifndef _BABYBEAR_FIELD_H +#define _BABYBEAR_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void babybear_generate_scalars(scalar_t* scalars, int size); +int babybear_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/fields/babybear/main.go b/wrappers/golang_v3/fields/babybear/main.go new file mode 100644 index 000000000..e2fd712f1 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/main.go @@ -0,0 +1,4 @@ +package babybear + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../build/lib -licicle_field_babybear -lstdc++ -Wl,-rpath=${SRCDIR}/../../../../build/lib +import "C" diff --git a/wrappers/golang_v3/fields/babybear/ntt/include/ntt.h b/wrappers/golang_v3/fields/babybear/ntt/include/ntt.h new file mode 100644 index 000000000..8df57bf5b --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/ntt/include/ntt.h @@ -0,0 +1,23 @@ +#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 NTTInitDomainConfig NTTInitDomainConfig; + +int babybear_ntt(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +int babybear_ntt_init_domain(scalar_t* primitive_root, NTTInitDomainConfig* ctx); +int babybear_ntt_release_domain(); +int* babybear_get_root_of_unity(size_t size); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang_v3/fields/babybear/ntt/ntt.go b/wrappers/golang_v3/fields/babybear/ntt/ntt.go new file mode 100644 index 000000000..2d4b1edb9 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/ntt/ntt.go @@ -0,0 +1,59 @@ +package ntt + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cScalars, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return 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 GetRootOfUnity(size uint64) babybear.ScalarField { + cRes := C.babybear_get_root_of_unity((C.size_t)(size)) + var res babybear.ScalarField + res.FromLimbs(*(*[]uint32)(unsafe.Pointer(cRes))) + return res +} + +func InitDomain(primitiveRoot babybear.ScalarField, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCfg := (*C.NTTInitDomainConfig)(unsafe.Pointer(&cfg)) + __ret := C.babybear_ntt_init_domain(cPrimitiveRoot, cCfg) + err := runtime.EIcicleError(__ret) + return err +} + +func ReleaseDomain() runtime.EIcicleError { + __ret := C.babybear_ntt_release_domain() + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/fields/babybear/polynomial/include/polynomial.h b/wrappers/golang_v3/fields/babybear/polynomial/include/polynomial.h new file mode 100644 index 000000000..dd5107cc7 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/polynomial/include/polynomial.h @@ -0,0 +1,50 @@ +#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; + +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_v3/fields/babybear/polynomial/polynomial.go b/wrappers/golang_v3/fields/babybear/polynomial/polynomial.go new file mode 100644 index 000000000..c83161151 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/polynomial/polynomial.go @@ -0,0 +1,172 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +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_v3/fields/babybear/scalar_field.go b/wrappers/golang_v3/fields/babybear/scalar_field.go new file mode 100644 index 000000000..3b8261944 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/scalar_field.go @@ -0,0 +1,118 @@ +package babybear + +// #cgo CFLAGS: -I./include/ +// #include "scalar_field.h" +import "C" +import ( + "encoding/binary" + "fmt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*scalars, *scalars, *scalars, &defaultCfg) + cErr := C.babybear_scalar_convert_montgomery((*C.scalar_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.scalar_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +} diff --git a/wrappers/golang_v3/fields/babybear/tests/extension_field_test.go b/wrappers/golang_v3/fields/babybear/tests/extension_field_test.go new file mode 100644 index 000000000..0a669b38e --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/tests/extension_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear_extension "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear/extension" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 << 20 + scalars := babybear_extension.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + babybear_extension.ToMontgomery(&deviceScalars) + + scalarsMontHost := make(core.HostSlice[babybear_extension.ExtensionField], 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_v3/fields/babybear/tests/extension_vec_ops_test.go b/wrappers/golang_v3/fields/babybear/tests/extension_vec_ops_test.go new file mode 100644 index 000000000..fc8e8fab5 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/tests/extension_vec_ops_test.go @@ -0,0 +1,65 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear_extension "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear/extension" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 + + matrix := babybear_extension.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[babybear_extension.ExtensionField], rowSize*columnSize) + out2 := make(core.HostSlice[babybear_extension.ExtensionField], rowSize*columnSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, cfg) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, cfg) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + + 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, cfg) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, cfg) + output := make(core.HostSlice[babybear_extension.ExtensionField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang_v3/fields/babybear/tests/main_test.go b/wrappers/golang_v3/fields/babybear/tests/main_test.go new file mode 100644 index 000000000..5c781649a --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/tests/main_test.go @@ -0,0 +1,60 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +const ( + largestTestSize = 20 +) + +var DEVICE runtime.Device + +func initDomain(largestTestSize int, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + rouIcicle := babybear.ScalarField{} + rouIcicle.FromUint32(1461624142) + e := ntt.InitDomain(rouIcicle, cfg) + return e +} + +func TestMain(m *testing.M) { + runtime.LoadBackendFromEnv() + devices, e := runtime.GetRegisteredDevices() + if e != runtime.Success { + panic("Failed to load registered devices") + } + for _, deviceType := range devices { + DEVICE = runtime.CreateDevice(deviceType, 0) + runtime.SetDevice(&DEVICE) + + // setup domain + cfg := core.GetDefaultNTTInitDomainConfig() + e = initDomain(largestTestSize, cfg) + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("initDomain is not implemented for ", deviceType, " device type") + } else { + panic("initDomain failed") + } + } + + // execute tests + m.Run() + + // release domain + e = ntt.ReleaseDomain() + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("ReleaseDomain is not implemented for ", deviceType, " device type") + } else { + panic("ReleaseDomain failed") + } + } + } +} diff --git a/wrappers/golang_v3/fields/babybear/tests/ntt_no_domain_test.go b/wrappers/golang_v3/fields/babybear/tests/ntt_no_domain_test.go new file mode 100644 index 000000000..0c16bbc28 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/tests/ntt_no_domain_test.go @@ -0,0 +1,82 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear_extension "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear/extension" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +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, _ := runtime.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.StreamHandle = 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) + + runtime.SynchronizeStream(stream) + } + } + } +} + +func TestNttBatchNoDomain(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestTestSize := 12 + largestBatchSize := 100 + scalars := babybear_extension.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{2, 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_v3/fields/babybear/tests/ntt_test.go b/wrappers/golang_v3/fields/babybear/tests/ntt_test.go new file mode 100644 index 000000000..c4be50beb --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/tests/ntt_test.go @@ -0,0 +1,176 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 := core.GetDefaultNTTInitDomainConfig() + 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} { + runtime.SetDevice(&DEVICE) + + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[babybear.ScalarField](scalars[:testSize]) + + stream, _ := runtime.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.StreamHandle = 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) + + runtime.SynchronizeStream(stream) + } + } + } +} + +func TestNttBatch(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestTestSize := 10 + largestBatchSize := 20 + scalars := babybear.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{2, 16, largestBatchSize} { + runtime.SetDevice(&DEVICE) + + 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") + e := ntt.ReleaseDomain() + assert.Equal(t, runtime.Success, e, "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_v3/fields/babybear/tests/polynomial_test.go b/wrappers/golang_v3/fields/babybear/tests/polynomial_test.go new file mode 100644 index 000000000..6851dd182 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/tests/polynomial_test.go @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear" + // "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear/polynomial" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/fields/babybear/tests/scalar_field_test.go b/wrappers/golang_v3/fields/babybear/tests/scalar_field_test.go new file mode 100644 index 000000000..3ae8c001d --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/tests/scalar_field_test.go @@ -0,0 +1,120 @@ +package tests + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + "github.com/stretchr/testify/assert" + "testing" +) + +const ( + SCALAR_LIMBS = babybear.SCALAR_LIMBS +) + +func TestScalarFieldFromLimbs(t *testing.T) { + 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") + randLimbs[0] = 100 + assert.NotEqual(t, randLimbs, emptyField.GetLimbs()) +} + +func TestScalarFieldGetLimbs(t *testing.T) { + 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 babybear.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 babybear.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 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 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 babybear.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 babybear.ScalarField + expected, limbs := test_helpers.GenerateBytesArray(int(SCALAR_LIMBS)) + emptyField.FromLimbs(limbs) + + assert.ElementsMatch(t, emptyField.ToBytesLittleEndian(), expected, "ToBytes returned incorrect values") +} + +func TestBabybearGenerateScalars(t *testing.T) { + const numScalars = 8 + scalars := babybear.GenerateScalars(numScalars) + + assert.Implements(t, (*core.HostOrDeviceSlice)(nil), &scalars) + + assert.Equal(t, numScalars, scalars.Len()) + zeroScalar := babybear.ScalarField{} + assert.NotContains(t, scalars, zeroScalar) +} + +func TestBabybearMongtomeryConversion(t *testing.T) { + size := 1 << 20 + scalars := babybear.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + babybear.ToMontgomery(&deviceScalars) + + scalarsMontHost := make(core.HostSlice[babybear.ScalarField], size) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.NotEqual(t, scalars, scalarsMontHost) + + babybear.FromMontgomery(&deviceScalars) + + scalarsMontHost.CopyFromDevice(&deviceScalars) + assert.Equal(t, scalars, scalarsMontHost) +} diff --git a/wrappers/golang_v3/fields/babybear/tests/vec_ops_test.go b/wrappers/golang_v3/fields/babybear/tests/vec_ops_test.go new file mode 100644 index 000000000..add94730e --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/tests/vec_ops_test.go @@ -0,0 +1,65 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + babybear "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/fields/babybear" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 + + matrix := babybear.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[babybear.ScalarField], rowSize*columnSize) + out2 := make(core.HostSlice[babybear.ScalarField], rowSize*columnSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, cfg) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, cfg) + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + + 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, cfg) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, cfg) + output := make(core.HostSlice[babybear.ScalarField], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang_v3/fields/babybear/vecOps/include/vec_ops.h b/wrappers/golang_v3/fields/babybear/vecOps/include/vec_ops.h new file mode 100644 index 000000000..585b3fc7c --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/vecOps/include/vec_ops.h @@ -0,0 +1,50 @@ +#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; + +int babybear_vector_mul( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int babybear_vector_add( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int babybear_vector_sub( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int babybear_matrix_transpose( + scalar_t* mat_in, + int row_size, + int column_size, + VecOpsConfig* config, + scalar_t* mat_out +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/fields/babybear/vecOps/vec_ops.go b/wrappers/golang_v3/fields/babybear/vecOps/vec_ops.go new file mode 100644 index 000000000..06da50dd1 --- /dev/null +++ b/wrappers/golang_v3/fields/babybear/vecOps/vec_ops.go @@ -0,0 +1,44 @@ +package vecOps + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret runtime.EIcicleError) { + 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 = (runtime.EIcicleError)(C.babybear_vector_sub(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (runtime.EIcicleError)(C.babybear_vector_add(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (runtime.EIcicleError)(C.babybear_vector_mul(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, config core.VecOpsConfig) runtime.EIcicleError { + inPointer, _, outPointer, cfgPointer, _ := core.VecOpCheck(in, in, out, &config) + + cIn := (*C.scalar_t)(inPointer) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cOut := (*C.scalar_t)(outPointer) + + err := (C.babybear_matrix_transpose(cIn, cRowSize, cColumnSize, cConfig, cOut)) + return runtime.EIcicleError(err) +} diff --git a/wrappers/golang_v3/internal/generator/config/babybear.go b/wrappers/golang_v3/internal/generator/config/babybear.go new file mode 100644 index 000000000..15b58b5c0 --- /dev/null +++ b/wrappers/golang_v3/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_v3/internal/generator/config/bls12377.go b/wrappers/golang_v3/internal/generator/config/bls12377.go new file mode 100644 index 000000000..ea44fd62d --- /dev/null +++ b/wrappers/golang_v3/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_v3/internal/generator/config/bls12381.go b/wrappers/golang_v3/internal/generator/config/bls12381.go new file mode 100644 index 000000000..3f3e1a4ef --- /dev/null +++ b/wrappers/golang_v3/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_v3/internal/generator/config/bn254.go b/wrappers/golang_v3/internal/generator/config/bn254.go new file mode 100644 index 000000000..cdc83afcb --- /dev/null +++ b/wrappers/golang_v3/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_v3/internal/generator/config/bw6761.go b/wrappers/golang_v3/internal/generator/config/bw6761.go new file mode 100644 index 000000000..90920c87f --- /dev/null +++ b/wrappers/golang_v3/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_v3/internal/generator/config/config.go b/wrappers/golang_v3/internal/generator/config/config.go new file mode 100644 index 000000000..bb1f422e0 --- /dev/null +++ b/wrappers/golang_v3/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_v3/internal/generator/config/grumpkin.go b/wrappers/golang_v3/internal/generator/config/grumpkin.go new file mode 100644 index 000000000..84279b98d --- /dev/null +++ b/wrappers/golang_v3/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_v3/internal/generator/curves/generate.go b/wrappers/golang_v3/internal/generator/curves/generate.go new file mode 100644 index 000000000..1a4982c86 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/curves/generate.go @@ -0,0 +1,42 @@ +package curves + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/internal/generator/curves/templates/curve.go.tmpl b/wrappers/golang_v3/internal/generator/curves/templates/curve.go.tmpl new file mode 100644 index 000000000..82f936223 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/curves/templates/curve.go.tmpl @@ -0,0 +1,172 @@ +package {{.PackageName}} +{{if ne .CurvePrefix "Mock"}} +// #cgo CFLAGS: -I./include/ +// #include "curve.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_affine_convert_montgomery((*C.{{toCName .CurvePrefix}}affine_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.{{toCName .CurvePrefix}}affine_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func {{.CurvePrefix}}AffineToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convert{{.CurvePrefix}}AffinePointsMontgomery(points, true) +} + +func {{.CurvePrefix}}AffineFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convert{{.CurvePrefix}}AffinePointsMontgomery(points, false) +} + +func convert{{.CurvePrefix}}ProjectivePointsMontgomery(points *core.DeviceSlice, isInto bool) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*points, *points, *points, &defaultCfg) + cErr := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_projective_convert_montgomery((*C.{{toCName .CurvePrefix}}projective_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.{{toCName .CurvePrefix}}projective_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func {{.CurvePrefix}}ProjectiveToMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convert{{.CurvePrefix}}ProjectivePointsMontgomery(points, true) +} + +func {{.CurvePrefix}}ProjectiveFromMontgomery(points *core.DeviceSlice) runtime.EIcicleError { + points.CheckDevice() + return convert{{.CurvePrefix}}ProjectivePointsMontgomery(points, false) +} +{{end}} \ No newline at end of file diff --git a/wrappers/golang_v3/internal/generator/curves/templates/curve.h.tmpl b/wrappers/golang_v3/internal/generator/curves/templates/curve.h.tmpl new file mode 100644 index 000000000..3dd3a2993 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/curves/templates/curve.h.tmpl @@ -0,0 +1,25 @@ +#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 VecOpsConfig VecOpsConfig; + +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); +int {{.Curve}}{{toCNameBackwards .CurvePrefix}}_affine_convert_montgomery(const {{toCName .CurvePrefix}}affine_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, {{toCName .CurvePrefix}}affine_t* d_out); +int {{.Curve}}{{toCNameBackwards .CurvePrefix}}_projective_convert_montgomery(const {{toCName .CurvePrefix}}projective_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, {{toCName .CurvePrefix}}projective_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/internal/generator/curves/templates/curve_test.go.tmpl b/wrappers/golang_v3/internal/generator/curves/templates/curve_test.go.tmpl new file mode 100644 index 000000000..0b2ca3832 --- /dev/null +++ b/wrappers/golang_v3/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/v2/wrappers/golang_v3/{{.BaseImportPath}}" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/internal/generator/curves/templates/main.go.tmpl b/wrappers/golang_v3/internal/generator/curves/templates/main.go.tmpl new file mode 100644 index 000000000..e8b9104c7 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/curves/templates/main.go.tmpl @@ -0,0 +1,4 @@ +package {{.PackageName}} + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../build/lib -licicle_field_{{.Field}} -licicle_curve_{{.Field}} -lstdc++ -Wl,-rpath=${SRCDIR}/../../../../build/lib +import "C" diff --git a/wrappers/golang_v3/internal/generator/ecntt/generate.go b/wrappers/golang_v3/internal/generator/ecntt/generate.go new file mode 100644 index 000000000..a557babee --- /dev/null +++ b/wrappers/golang_v3/internal/generator/ecntt/generate.go @@ -0,0 +1,29 @@ +package ntt + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/internal/generator/ecntt/templates/ecntt.go.tmpl b/wrappers/golang_v3/internal/generator/ecntt/templates/ecntt.go.tmpl new file mode 100644 index 000000000..dbfcd6600 --- /dev/null +++ b/wrappers/golang_v3/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/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func ECNtt[T any](points core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cPoints, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/internal/generator/ecntt/templates/ecntt.h.tmpl b/wrappers/golang_v3/internal/generator/ecntt/templates/ecntt.h.tmpl new file mode 100644 index 000000000..a31a75529 --- /dev/null +++ b/wrappers/golang_v3/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; + +int {{.Curve}}_ecntt(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_v3/internal/generator/ecntt/templates/ecntt_test.go.tmpl b/wrappers/golang_v3/internal/generator/ecntt/templates/ecntt_test.go.tmpl new file mode 100644 index 000000000..b6f758f55 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/ecntt/templates/ecntt_test.go.tmpl @@ -0,0 +1,36 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + {{.Curve}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}" + ecntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}/ecntt" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/stretchr/testify/assert" +) + +func TestECNtt(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + ext := runtime.CreateConfigExtension() + ext.SetInt(core.CUDA_NTT_ALGORITHM, int(core.Radix2)) + cfg.Ext = ext.AsUnsafePointer() + + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + pointsCopy := core.HostSliceFromElements[{{.Curve}}.Projective](points[:testSize]) + cfg.Ordering = v + + output := make(core.HostSlice[{{.Curve}}.Projective], testSize) + e := ecntt.ECNtt(pointsCopy, core.KForward, &cfg, output) + assert.Equal(t, runtime.Success, e, "ECNtt failed") + } + } +} diff --git a/wrappers/golang_v3/internal/generator/fields/generate.go b/wrappers/golang_v3/internal/generator/fields/generate.go new file mode 100644 index 000000000..6c52c6d5e --- /dev/null +++ b/wrappers/golang_v3/internal/generator/fields/generate.go @@ -0,0 +1,47 @@ +package fields + +import ( + "path" + "strings" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/generator_utils" +) + +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_v3/internal/generator/fields/templates/field.go.tmpl b/wrappers/golang_v3/internal/generator/fields/templates/field.go.tmpl new file mode 100644 index 000000000..cf602eb17 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/fields/templates/field.go.tmpl @@ -0,0 +1,121 @@ +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/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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) runtime.EIcicleError { + defaultCfg := core.DefaultVecOpsConfig() + cValues, _, _, cCfg, cSize := core.VecOpCheck(*scalars, *scalars, *scalars, &defaultCfg) + cErr := C.{{.Field}}_scalar_convert_montgomery((*C.scalar_t)(cValues), (C.size_t)(cSize), (C._Bool)(isInto), (*C.VecOpsConfig)(cCfg), (*C.scalar_t)(cValues)) + err := runtime.EIcicleError(cErr) + return err +} + +func ToMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, true) +} + +func FromMontgomery(scalars *core.DeviceSlice) runtime.EIcicleError { + scalars.CheckDevice() + return convertScalarsMontgomery(scalars, false) +}{{end}} diff --git a/wrappers/golang_v3/internal/generator/fields/templates/field_test.go.tmpl b/wrappers/golang_v3/internal/generator/fields/templates/field_test.go.tmpl new file mode 100644 index 000000000..0b8efc0e8 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/fields/templates/field_test.go.tmpl @@ -0,0 +1,121 @@ +package tests + +import ( + {{- if .IsScalar}} + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core"{{end}} + {{if ne .FieldPrefix "G2"}}{{.Field}}{{end}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}{{if eq .FieldPrefix "G2"}}/g2{{end}}" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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 << 20 + scalars := {{.Field}}.GenerateScalars(size) + + var deviceScalars core.DeviceSlice + scalars.CopyToDevice(&deviceScalars, true) + + {{.Field}}.ToMontgomery(&deviceScalars) + + scalarsMontHost := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], 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_v3/internal/generator/fields/templates/main.go.tmpl b/wrappers/golang_v3/internal/generator/fields/templates/main.go.tmpl new file mode 100644 index 000000000..2b4e9d934 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/fields/templates/main.go.tmpl @@ -0,0 +1,4 @@ +package {{.PackageName}} + +// #cgo LDFLAGS: -L${SRCDIR}/../../../../build/lib -licicle_field_{{.Field}} -lstdc++ -Wl,-rpath=${SRCDIR}/../../../../build/lib +import "C" diff --git a/wrappers/golang_v3/internal/generator/fields/templates/scalar_field.h.tmpl b/wrappers/golang_v3/internal/generator/fields/templates/scalar_field.h.tmpl new file mode 100644 index 000000000..74327f47e --- /dev/null +++ b/wrappers/golang_v3/internal/generator/fields/templates/scalar_field.h.tmpl @@ -0,0 +1,20 @@ +#include + +#ifndef _{{toUpper .Field}}_FIELD_H +#define _{{toUpper .Field}}_FIELD_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct VecOpsConfig VecOpsConfig; + +void {{.Field}}_generate_scalars(scalar_t* scalars, int size); +int {{.Field}}_scalar_convert_montgomery(const scalar_t* d_in, size_t n, bool is_into, const VecOpsConfig* ctx, scalar_t* d_out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/internal/generator/generator_utils/generate.go b/wrappers/golang_v3/internal/generator/generator_utils/generate.go new file mode 100644 index 000000000..0046ead86 --- /dev/null +++ b/wrappers/golang_v3/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_v3/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_v3/internal/generator/lib_linker/generate.go b/wrappers/golang_v3/internal/generator/lib_linker/generate.go new file mode 100644 index 000000000..24c6ba5ad --- /dev/null +++ b/wrappers/golang_v3/internal/generator/lib_linker/generate.go @@ -0,0 +1,33 @@ +package lib_linker + +import ( + "strings" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/generator_utils" +) + +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_v3/internal/generator/main.go b/wrappers/golang_v3/internal/generator/main.go new file mode 100644 index 000000000..128a5d6b4 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path" + + config "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/config" + curves "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/curves" + ecntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/ecntt" + fields "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/fields" + lib_linker "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/lib_linker" + mock "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/mock" + msm "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/msm" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/ntt" + poly "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/polynomial" + poseidon "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/poseidon" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/tests" + vecops "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/vecOps" +) + +func generateFiles() { + fmt.Println("Generating files") + + 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) + } + 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, "", "") + } + + if curve.SupportsECNTT { + ecntt.Generate(curveDir, curve.Curve, curve.GnarkImport) + } + + msm.Generate(curveDir, "msm", curve.Curve, "", curve.GnarkImport) + poseidon.Generate(curveDir, "", curve.Curve, scalarFieldPrefix) + 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) + } + + 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) + + 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) + } + + tests.Generate(fieldDir, field.Field, scalarFieldPrefix, field.GnarkImport, field.ROU, field.SupportsNTT, field.SupportsPoly) + } + + // 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_v3/internal/generator/mock/generate.go b/wrappers/golang_v3/internal/generator/mock/generate.go new file mode 100644 index 000000000..ec4c55413 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/mock/generate.go @@ -0,0 +1,38 @@ +package fields + +import ( + "strings" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/internal/generator/generator_utils" +) + +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_v3/internal/generator/msm/generate.go b/wrappers/golang_v3/internal/generator/msm/generate.go new file mode 100644 index 000000000..304c192af --- /dev/null +++ b/wrappers/golang_v3/internal/generator/msm/generate.go @@ -0,0 +1,39 @@ +package msm + +import ( + "path" + "path/filepath" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/internal/generator/msm/templates/msm.go.tmpl b/wrappers/golang_v3/internal/generator/msm/templates/msm.go.tmpl new file mode 100644 index 000000000..d519e6c80 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/msm/templates/msm.go.tmpl @@ -0,0 +1,48 @@ +package {{.PackageName}} + +// #cgo CFLAGS: -I./include/ +// #include "msm.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func {{.CurvePrefix}}GetDefaultMSMConfig() core.MSMConfig { + return core.GetDefaultMSMConfig() +} + +func {{.CurvePrefix}}Msm(scalars core.HostOrDeviceSlice, points core.HostOrDeviceSlice, cfg *core.MSMConfig, results core.HostOrDeviceSlice) runtime.EIcicleError { + scalarsPointer, pointsPointer, resultsPointer, size := 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)(unsafe.Pointer(cfg)) + + __ret := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_msm(cScalars, cPoints, cSize, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return err +} + +func {{.CurvePrefix}}PrecomputeBases(bases core.HostOrDeviceSlice, cfg *core.MSMConfig, outputBases core.DeviceSlice) runtime.EIcicleError { + basesPointer, outputBasesPointer := core.PrecomputeBasesCheck(bases, cfg, outputBases) + + cBases := (*C.{{toCName .CurvePrefix}}affine_t)(basesPointer) + var cBasesLen C.int + if cfg.AreBasesShared { + cBasesLen = (C.int)(bases.Len()) + } else { + cBasesLen = (C.int)(bases.Len() / int(cfg.BatchSize)) + } + cCfg := (*C.MSMConfig)(unsafe.Pointer(cfg)) + cOutputBases := (*C.{{toCName .CurvePrefix}}affine_t)(outputBasesPointer) + + __ret := C.{{.Curve}}{{toCNameBackwards .CurvePrefix}}_msm_precompute_bases(cBases, cBasesLen, cCfg, cOutputBases) + err := runtime.EIcicleError(__ret) + return err +} diff --git a/wrappers/golang_v3/internal/generator/msm/templates/msm.h.tmpl b/wrappers/golang_v3/internal/generator/msm/templates/msm.h.tmpl new file mode 100644 index 000000000..3d773e8e5 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/msm/templates/msm.h.tmpl @@ -0,0 +1,22 @@ +#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; + +int {{.Curve}}{{toCNameBackwards .CurvePrefix}}_msm(const scalar_t* scalars, const {{toCName .CurvePrefix}}affine_t* points, int count, MSMConfig* config, {{toCName .CurvePrefix}}projective_t* out); +int {{.Curve}}{{toCNameBackwards .CurvePrefix}}_msm_precompute_bases({{toCName .CurvePrefix}}affine_t* input_bases, int bases_size, MSMConfig* config, {{toCName .CurvePrefix}}affine_t* output_bases); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/internal/generator/msm/templates/msm_test.go.tmpl b/wrappers/golang_v3/internal/generator/msm/templates/msm_test.go.tmpl new file mode 100644 index 000000000..06d5cf25c --- /dev/null +++ b/wrappers/golang_v3/internal/generator/msm/templates/msm_test.go.tmpl @@ -0,0 +1,477 @@ +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/v2/wrappers/golang_v3/core" + icicle{{capitalize .Curve}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}/{{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) +{{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}}(t *testing.T, 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) { + 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()) + } + + testAgainstGnarkCryptoMsm{{.CurvePrefix}}GnarkCryptoTypes(t, scalarsFr, pointsFp, out) +} + +func testAgainstGnarkCryptoMsm{{.CurvePrefix}}GnarkCryptoTypes(t *testing.T, 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) { + var msmRes {{toPackage .GnarkImport}}.{{if eq .CurvePrefix "G2"}}G2{{else}}G1{{end}}Jac + msmRes.MultiExp(pointsFp, scalarsFr, ecc.MultiExpConfig{}) + + var msmResAffine {{toPackage .GnarkImport}}.{{if eq .CurvePrefix "G2"}}G2{{else}}G1{{end}}Affine + msmResAffine.FromJacobian(&msmRes) + + icicleResAffine := projectiveToGnarkAffine{{.CurvePrefix}}(out) + + assert.Equal(t, msmResAffine, icicleResAffine) +} + +{{$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} { + runtime.SetDevice(&DEVICE) + size := 1 << power + + scalars := icicle{{capitalize .Curve}}.GenerateScalars(size) + points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(size) + + stream, _ := runtime.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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "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) + + runtime.SynchronizeStream(stream) + {{if ne .GnarkImport "" -}} + // Check with gnark-crypto + testAgainstGnarkCryptoMsm{{.CurvePrefix}}(t, scalars, points, outHost[0]){{end}} + } +} + +// func TestMSM{{.CurvePrefix}}PinnedHostMemory(t *testing.T) { +// cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() +// for _, power := range []int{10} { +// size := 1 << power +// +// scalars := icicle{{capitalize .Curve}}.GenerateScalars(size) +// points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(size) +// +// pinnable := cr.GetDeviceAttribute(cr.CudaDevAttrHostRegisterSupported, 0) +// lockable := cr.GetDeviceAttribute(cr.CudaDevAttrPageableMemoryAccessUsesHostPageTables, 0) +// +// pinnableAndLockable := pinnable == 1 && lockable == 0 +// +// var pinnedPoints core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Affine] +// if pinnableAndLockable { +// points.Pin(cr.CudaHostRegisterDefault) +// pinnedPoints, _ = points.AllocPinned(cr.CudaHostAllocDefault) +// assert.Equal(t, points, pinnedPoints, "Allocating newly pinned memory resulted in bad points") +// } +// +// 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, runtime.Success, "Allocating bytes on device for Projective results failed") +// outHost := make(core.HostSlice[{{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}Projective], 1) +// +// e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm allocated pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// {{if ne .GnarkImport "" -}} +// // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm{{.CurvePrefix}}(scalars, points, outHost[0])) +// {{end}} +// +// if pinnableAndLockable { +// e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, pinnedPoints, &cfg, out) +// assert.Equal(t, e, runtime.Success, "Msm registered pinned host mem failed") +// +// outHost.CopyFromDevice(&out) +// {{if ne .GnarkImport "" -}} +// // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm{{.CurvePrefix}}(scalars, pinnedPoints, outHost[0])) +// {{end}} +// } +// +// out.Free() +// +// if pinnableAndLockable { +// points.Unpin() +// pinnedPoints.FreePinned() +// } +// } +// } +{{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} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.AreBasesMontgomeryForm = true + cfg.AreScalarsMontgomeryForm = true + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalarsHost, pointsHost, &cfg, out) + assert.Equal(t, e, runtime.Success, "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 + testAgainstGnarkCryptoMsm{{.CurvePrefix}}GnarkCryptoTypes(t, 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{5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + 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, runtime.Success, "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, runtime.Success, "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] + testAgainstGnarkCryptoMsm{{.CurvePrefix}}(t, scalarsSlice, pointsSlice, out) + }{{end}} + } + } +} + +func TestPrecomputePoints{{.CurvePrefix}}(t *testing.T) { + if DEVICE.GetDeviceType() == "CPU" { + t.Skip("Skipping cpu test") + } + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{7, 8} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + 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, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + cfg.BatchSize = int32(batchSize) + cfg.AreBasesShared = false + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "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, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "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] + testAgainstGnarkCryptoMsm{{.CurvePrefix}}(t, scalarsSlice, pointsSlice, out) + }{{end}} + } + } +} + +func TestPrecomputePointsSharedBases{{.CurvePrefix}}(t *testing.T) { + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + const precomputeFactor = 8 + cfg.PrecomputeFactor = precomputeFactor + + for _, power := range []int{4, 5, 6} { + for _, batchSize := range []int{1, 3, 5} { + runtime.SetDevice(&DEVICE) + + 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(size) + + var precomputeOut core.DeviceSlice + _, e := precomputeOut.Malloc(points[0].Size()*points.Len()*int(precomputeFactor), points[0].Size()) + assert.Equal(t, runtime.Success, e, "Allocating bytes on device for PrecomputeBases results failed") + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}PrecomputeBases(points, &cfg, precomputeOut) + assert.Equal(t, runtime.Success, e, "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, runtime.Success, e, "Allocating bytes on device for Projective results failed") + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, precomputeOut, &cfg, out) + assert.Equal(t, runtime.Success, e, "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[0 : size] + out := outHost[i] + testAgainstGnarkCryptoMsm{{.CurvePrefix}}(t, 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} { + runtime.SetDevice(&DEVICE) + + 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, runtime.Success, "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, runtime.Success, "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 + testAgainstGnarkCryptoMsm{{.CurvePrefix}}(t, scalars, points, outHost[0]){{end}} + } +} + +func TestMSM{{.CurvePrefix}}MultiDevice(t *testing.T) { + numDevices, _ := runtime.GetDeviceCount() + fmt.Println("There are ", numDevices, " ", DEVICE.GetDeviceType(), " devices available") + wg := sync.WaitGroup{} + + for i := 0; i < numDevices; i++ { + currentDevice := runtime.Device{DeviceType: DEVICE.DeviceType, Id: int32(i)} + wg.Add(1) + runtime.RunOnDevice(¤tDevice, func(args ...any) { + defer wg.Done() + + fmt.Println("Running on ", currentDevice.GetDeviceType(), " ", currentDevice.Id, " device") + + cfg := {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}GetDefaultMSMConfig() + cfg.IsAsync = true + for _, power := range []int{2, 3, 4, 5, 6} { + size := 1 << power + scalars := icicle{{capitalize .Curve}}.GenerateScalars(size) + points := {{if ne .CurvePrefix "G2"}}icicle{{capitalize .Curve}}{{else}}g2{{end}}.{{.CurvePrefix}}GenerateAffinePoints(size) + + stream, _ := runtime.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, runtime.Success, "Allocating bytes on device for Projective results failed") + cfg.StreamHandle = stream + + e = {{if eq .CurvePrefix "G2"}}g2{{else}}msm{{end}}.{{.CurvePrefix}}Msm(scalars, points, &cfg, out) + assert.Equal(t, e, runtime.Success, "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) + + runtime.SynchronizeStream(stream) + {{if ne .GnarkImport "" -}}// Check with gnark-crypto + testAgainstGnarkCryptoMsm{{.CurvePrefix}}(t, scalars, points, outHost[0]){{end}} + } + }) + } + wg.Wait() +} diff --git a/wrappers/golang_v3/internal/generator/ntt/generate.go b/wrappers/golang_v3/internal/generator/ntt/generate.go new file mode 100644 index 000000000..740a0f2a2 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/ntt/generate.go @@ -0,0 +1,54 @@ +package ntt + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/internal/generator/ntt/templates/ntt.go.tmpl b/wrappers/golang_v3/internal/generator/ntt/templates/ntt.go.tmpl new file mode 100644 index 000000000..1387a714d --- /dev/null +++ b/wrappers/golang_v3/internal/generator/ntt/templates/ntt.go.tmpl @@ -0,0 +1,61 @@ +package {{.PackageName}} + +// #cgo CFLAGS: -I./include/ +// #include "ntt.h" +import "C" + +import ( + {{if .WithDomain}} + "unsafe" + + {{.Field}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}"{{end}} + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func Ntt[T any](scalars core.HostOrDeviceSlice, dir core.NTTDir, cfg *core.NTTConfig[T], results core.HostOrDeviceSlice) runtime.EIcicleError { + 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(cScalars, cSize, cDir, cCfg, cResults) + err := runtime.EIcicleError(__ret) + return 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 GetRootOfUnity(size uint64) {{.Field}}.{{.FieldPrefix}}Field { + cRes := C.{{.Field}}_get_root_of_unity((C.size_t)(size)) + var res {{.Field}}.{{.FieldPrefix}}Field + res.FromLimbs(*(*[]uint32)(unsafe.Pointer(cRes))) + return res +} + +func InitDomain(primitiveRoot {{.Field}}.{{.FieldPrefix}}Field, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + cPrimitiveRoot := (*C.scalar_t)(unsafe.Pointer(primitiveRoot.AsPointer())) + cCfg := (*C.NTTInitDomainConfig)(unsafe.Pointer(&cfg)) + __ret := C.{{.Field}}_ntt_init_domain(cPrimitiveRoot, cCfg) + err := runtime.EIcicleError(__ret) + return err +} + +func ReleaseDomain() runtime.EIcicleError { + __ret := C.{{.Field}}_ntt_release_domain() + err := runtime.EIcicleError(__ret) + return err +} +{{end}} \ No newline at end of file diff --git a/wrappers/golang_v3/internal/generator/ntt/templates/ntt.h.tmpl b/wrappers/golang_v3/internal/generator/ntt/templates/ntt.h.tmpl new file mode 100644 index 000000000..42e47b470 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/ntt/templates/ntt.h.tmpl @@ -0,0 +1,24 @@ +#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 NTTInitDomainConfig NTTInitDomainConfig;{{end}} + +int {{if .WithDomain}}{{.Field}}{{else}}{{.FieldNoDomain}}{{end}}_ntt(const scalar_t* input, int size, int dir, NTTConfig* config, scalar_t* output); +{{if .WithDomain -}} +int {{.Field}}_ntt_init_domain(scalar_t* primitive_root, NTTInitDomainConfig* ctx); +int {{.Field}}_ntt_release_domain(); +int* {{.Field}}_get_root_of_unity(size_t size);{{end}} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang_v3/internal/generator/ntt/templates/ntt_no_domain_test.go.tmpl b/wrappers/golang_v3/internal/generator/ntt/templates/ntt_no_domain_test.go.tmpl new file mode 100644 index 000000000..dc0e0fe19 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/ntt/templates/ntt_no_domain_test.go.tmpl @@ -0,0 +1,85 @@ +package tests + +import ( + {{if ne .GnarkImport "" -}} + "reflect" + {{end -}} + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + {{.FieldNoDomain}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPathNoDomain}}" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.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, _ := runtime.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.StreamHandle = 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) + + runtime.SynchronizeStream(stream) + } + } + } +} + +func TestNttBatchNoDomain(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestTestSize := 12 + largestBatchSize := 100 + scalars := {{.FieldNoDomain}}.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{2, 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_v3/internal/generator/ntt/templates/ntt_test.go.tmpl b/wrappers/golang_v3/internal/generator/ntt/templates/ntt_test.go.tmpl new file mode 100644 index 000000000..d076c335c --- /dev/null +++ b/wrappers/golang_v3/internal/generator/ntt/templates/ntt_test.go.tmpl @@ -0,0 +1,284 @@ +package tests + +import ( + {{if ne .GnarkImport "" -}} + "reflect" + {{end -}} + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/test_helpers" + {{.Field}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}" + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.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" +) + +{{if ne .GnarkImport "" -}} +func testAgainstGnarkCryptoNtt(t *testing.T, size int, scalars core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], output core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], order core.Ordering, direction core.NTTDir) { + scalarsFr := make([]fr.Element, size) + for i, v := range scalars { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + outputAsFr := make([]fr.Element, size) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + testAgainstGnarkCryptoNttGnarkTypes(t, size, scalarsFr, outputAsFr, order, direction) +} + +func testAgainstGnarkCryptoNttGnarkTypes(t *testing.T, size int, scalarsFr core.HostSlice[fr.Element], outputAsFr core.HostSlice[fr.Element], order core.Ordering, direction core.NTTDir) { + domainWithPrecompute := fft.NewDomain(uint64(size)) + // DIT + BitReverse == Ordering.kRR + // DIT == Ordering.kRN + // DIF + BitReverse == Ordering.kNN + // DIF == Ordering.kNR + var decimation fft.Decimation + if order == core.KRN || order == core.KRR { + decimation = fft.DIT + } else { + decimation = fft.DIF + } + + if direction == core.KForward { + domainWithPrecompute.FFT(scalarsFr, decimation) + } else { + domainWithPrecompute.FFTInverse(scalarsFr, decimation) + } + + if order == core.KNN || order == core.KRR { + fft.BitReverse(scalarsFr) + } + assert.Equal(t, scalarsFr, outputAsFr) +} +{{end -}} + +func TestNTTGetDefaultConfig(t *testing.T) { + actual := ntt.GetDefaultNttConfig() + expected := test_helpers.GenerateLimbOne(int({{.Field}}.{{toConst .FieldPrefix}}LIMBS)) + assert.Equal(t, expected, actual.CosetGen[:]) + + 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 := core.GetDefaultNTTInitDomainConfig() + assert.NotPanics(t, func() { initDomain(largestTestSize, cfg) }) +} + +func TestNtt(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + + scalarsCopy := core.HostSliceFromElements[{{.Field}}.{{.FieldPrefix}}Field](scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + {{if ne .GnarkImport "" -}} + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, 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 + } + + for _, size := range []int{4} { + for _, v := range [1]core.Ordering{core.KNN} { + runtime.SetDevice(&DEVICE) + + testSize := size + + scalarsCopy := (core.HostSlice[fr.Element])(scalars[:testSize]) + cfg.Ordering = v + + // run ntt + output := make(core.HostSlice[fr.Element], testSize) + ntt.Ntt(scalarsCopy, core.KForward, &cfg, output) + + // Compare with gnark-crypto + testAgainstGnarkCryptoNttGnarkTypes(t, testSize, scalarsCopy, output, v, core.KForward) + } + } +} +{{end}} +func TestNttDeviceAsync(t *testing.T) { + 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} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + scalarsCopy := core.HostSliceFromElements[{{.Field}}.{{.FieldPrefix}}Field](scalars[:testSize]) + + stream, _ := runtime.CreateStream() + + cfg.Ordering = v + cfg.IsAsync = true + cfg.StreamHandle = 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[{{.Field}}.{{.FieldPrefix}}Field], testSize) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + runtime.SynchronizeStream(stream) + {{if ne .GnarkImport "" -}} + // Compare with gnark-crypto + testAgainstGnarkCryptoNtt(t, testSize, scalarsCopy, output, v, direction) + {{end -}} + } + } + } +} + +func TestNttBatch(t *testing.T) { + cfg := ntt.GetDefaultNttConfig() + largestTestSize := 10 + largestBatchSize := 20 + scalars := {{.Field}}.GenerateScalars(1 << largestTestSize * largestBatchSize) + + for _, size := range []int{4, largestTestSize} { + for _, batchSize := range []int{2, 16, largestBatchSize} { + runtime.SetDevice(&DEVICE) + + testSize := 1 << size + totalSize := testSize * batchSize + + scalarsCopy := core.HostSliceFromElements[{{.Field}}.{{.FieldPrefix}}Field](scalars[:totalSize]) + + cfg.Ordering = core.KNN + cfg.BatchSize = int32(batchSize) + // run ntt + 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) + for i, v := range output { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + outputAsFr[i] = slice64 + } + + for i := 0; i < batchSize; i++ { + scalarsFr := make([]fr.Element, testSize) + for i, v := range scalarsCopy[i*testSize : (i+1)*testSize] { + slice64, _ := fr.LittleEndian.Element((*[fr.Bytes]byte)(v.ToBytesLittleEndian())) + scalarsFr[i] = slice64 + } + + domainWithPrecompute.FFT(scalarsFr, fft.DIF) + fft.BitReverse(scalarsFr) + if !assert.True(t, reflect.DeepEqual(scalarsFr, outputAsFr[i*testSize:(i+1)*testSize])) { + 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") + e := ntt.ReleaseDomain() + assert.Equal(t, runtime.Success, e, "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[{{.FieldPrefix}}Field] +// for _, v := range scalars { +// var scalar {{.FieldPrefix}}Field +// scalarsCopy = append(scalarsCopy, scalar.FromLimbs(v.GetLimbs())) +// } + +// // init domain +// rouMont, _ := fft.Generator(1 << 20) +// rou := rouMont.Bits() +// rouIcicle := {{.FieldPrefix}}Field{} +// limbs := core.ConvertUint64ArrToUint32Arr(rou[:]) + +// rouIcicle.FromLimbs(limbs) +// InitDomain(rouIcicle, cfg.Ctx) +// cfg.Ordering = v + +// // run ntt +// output := make(core.HostSlice[{{.FieldPrefix}}Field], 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_v3/internal/generator/polynomial/generate.go b/wrappers/golang_v3/internal/generator/polynomial/generate.go new file mode 100644 index 000000000..f91eedd56 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/polynomial/generate.go @@ -0,0 +1,31 @@ +package polynomial + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/internal/generator/polynomial/templates/polynomial.go.tmpl b/wrappers/golang_v3/internal/generator/polynomial/templates/polynomial.go.tmpl new file mode 100644 index 000000000..56728249b --- /dev/null +++ b/wrappers/golang_v3/internal/generator/polynomial/templates/polynomial.go.tmpl @@ -0,0 +1,172 @@ +package polynomial + +// #cgo CFLAGS: -I./include/ +// #include "polynomial.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + {{.Field}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}" +) + +type PolynomialHandle = C.struct_PolynomialInst + +type DensePolynomial struct { + handle *PolynomialHandle +} + +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_v3/internal/generator/polynomial/templates/polynomial.h.tmpl b/wrappers/golang_v3/internal/generator/polynomial/templates/polynomial.h.tmpl new file mode 100644 index 000000000..7182a907c --- /dev/null +++ b/wrappers/golang_v3/internal/generator/polynomial/templates/polynomial.h.tmpl @@ -0,0 +1,50 @@ +#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; + +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_v3/internal/generator/polynomial/templates/polynomial_test.go.tmpl b/wrappers/golang_v3/internal/generator/polynomial/templates/polynomial_test.go.tmpl new file mode 100644 index 000000000..1b69c9d17 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/polynomial/templates/polynomial_test.go.tmpl @@ -0,0 +1,229 @@ +package tests + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + {{.Field}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}" + // "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}/ntt" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}/polynomial" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.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_v3/internal/generator/poseidon/generate.go b/wrappers/golang_v3/internal/generator/poseidon/generate.go new file mode 100644 index 000000000..e867a6359 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/poseidon/generate.go @@ -0,0 +1,26 @@ +package poseidon + +// var poseidonTemplates = map[string]string{ +// "src": "poseidon/templates/poseidon.go.tmpl", +// "test": "poseidon/templates/poseidon_test.go.tmpl", +// "header": "poseidon/templates/poseidon.h.tmpl", +// } + +func Generate(baseDir, additionalDirPath, field, fieldPrefix string) { + + // data := struct { + // PackageName string + // Field string + // FieldPrefix string + // BaseImportPath string + // }{ + // "poseidon", + // field, + // fieldPrefix, + // baseDir, + // } + + // generator.GenerateFile(poseidonTemplates["src"], path.Join(baseDir, additionalDirPath, "poseidon"), "", "", data) + // generator.GenerateFile(poseidonTemplates["header"], path.Join(baseDir, additionalDirPath, "poseidon", "include"), "", "", data) + // generator.GenerateFile(poseidonTemplates["test"], path.Join(baseDir, "tests"), "", "", data) +} diff --git a/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon.go.tmpl b/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon.go.tmpl new file mode 100644 index 000000000..b2ce7a679 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon.go.tmpl @@ -0,0 +1,57 @@ +package {{.PackageName}} + +// #cgo CFLAGS: -I./include/ +// #include "poseidon.h" +import "C" + +import ( + "unsafe" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + cr "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/cuda_runtime" +) + +func GetDefaultPoseidonConfig() core.PoseidonConfig { + return core.GetDefaultPoseidonConfig() +} + +func PoseidonHash[T any](scalars, results core.HostOrDeviceSlice, numberOfStates int, cfg *core.PoseidonConfig, constants *core.PoseidonConstants[T]) core.IcicleError { + scalarsPointer, resultsPointer, cfgPointer := core.PoseidonCheck(scalars, results, cfg, constants, numberOfStates) + + cScalars := (*C.scalar_t)(scalarsPointer) + cResults := (*C.scalar_t)(resultsPointer) + cNumberOfStates := (C.int)(numberOfStates) + cArity := (C.int)(constants.Arity) + cConstants := (*C.PoseidonConstants)(unsafe.Pointer(constants)) + cCfg := (*C.PoseidonConfig)(cfgPointer) + + __ret := C.{{.Field}}_poseidon_hash_cuda(cScalars, cResults, cNumberOfStates, cArity, cConstants, cCfg) + + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func CreateOptimizedPoseidonConstants[T any](arity, fullRoundsHalfs, partialRounds int, constants core.HostOrDeviceSlice, ctx cr.DeviceContext, poseidonConstants *core.PoseidonConstants[T]) core.IcicleError { + + cArity := (C.int)(arity) + cFullRoundsHalfs := (C.int)(fullRoundsHalfs) + cPartialRounds := (C.int)(partialRounds) + cConstants := (*C.scalar_t)(constants.AsUnsafePointer()) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cPoseidonConstants := (*C.PoseidonConstants)(unsafe.Pointer(poseidonConstants)) + + __ret := C.{{.Field}}_create_optimized_poseidon_constants_cuda(cArity, cFullRoundsHalfs, cPartialRounds, cConstants, cCtx, cPoseidonConstants) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} + +func InitOptimizedPoseidonConstantsCuda[T any](arity int, ctx cr.DeviceContext, constants *core.PoseidonConstants[T]) core.IcicleError { + + cArity := (C.int)(arity) + cCtx := (*C.DeviceContext)(unsafe.Pointer(&ctx)) + cConstants := (*C.PoseidonConstants)(unsafe.Pointer(constants)) + + __ret := C.{{.Field}}_init_optimized_poseidon_constants_cuda(cArity, cCtx, cConstants) + err := (cr.CudaError)(__ret) + return core.FromCudaError(err) +} diff --git a/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon.h.tmpl b/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon.h.tmpl new file mode 100644 index 000000000..e5ac4d1dd --- /dev/null +++ b/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon.h.tmpl @@ -0,0 +1,25 @@ +#include +#include + +#ifndef _{{toUpper .Field}}_POSEIDON_H +#define _{{toUpper .Field}}_POSEIDON_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct scalar_t scalar_t; +typedef struct PoseidonConfig PoseidonConfig; +typedef struct DeviceContext DeviceContext; +typedef struct PoseidonConstants PoseidonConstants; + + +cudaError_t {{.Field}}_poseidon_hash_cuda(const scalar_t* input, scalar_t* output, int number_of_states, int arity, PoseidonConstants* constants, PoseidonConfig* config); +cudaError_t {{.Field}}_create_optimized_poseidon_constants_cuda(int arity, int full_rounds_halfs, int partial_rounds, const scalar_t* constants, DeviceContext* ctx, PoseidonConstants* poseidon_constants); +cudaError_t {{.Field}}_init_optimized_poseidon_constants_cuda(int arity, DeviceContext* ctx, PoseidonConstants* constants); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon_test.go.tmpl b/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon_test.go.tmpl new file mode 100644 index 000000000..e4c9c84eb --- /dev/null +++ b/wrappers/golang_v3/internal/generator/poseidon/templates/poseidon_test.go.tmpl @@ -0,0 +1,59 @@ +package tests + +import ( + "testing" + + core "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + cr "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/cuda_runtime" + {{.Field}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}" + poseidon "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}/poseidon" + + {{if eq .Field "bls12_381"}} + "fmt" + "github.com/stretchr/testify/assert" + {{end}} +) +{{if eq .Field "bls12_381"}} +func formatOutput(x {{.Field}}.{{.FieldPrefix}}Field) string { + r := x.GetLimbs() + return fmt.Sprintf("%08x%08x%08x%08x%08x%08x%08x%08x", r[7], r[6], r[5], r[4], r[3], r[2], r[1], r[0]) +} +{{end}} + +func TestPoseidon(t *testing.T) { + + arity := 2 + numberOfStates := 1 + + cfg := poseidon.GetDefaultPoseidonConfig() + cfg.IsAsync = true + stream, _ := cr.CreateStream() + cfg.Ctx.Stream = &stream + + var constants core.PoseidonConstants[{{.Field}}.{{.FieldPrefix}}Field] + + poseidon.InitOptimizedPoseidonConstantsCuda(arity, cfg.Ctx, &constants) //generate constants + + scalars := {{.Field}}.GenerateScalars(numberOfStates * arity) + scalars[0] = scalars[0].Zero() + scalars[1] = scalars[0].Zero() + + scalarsCopy := core.HostSliceFromElements(scalars[:numberOfStates*arity]) + + var deviceInput core.DeviceSlice + scalarsCopy.CopyToDeviceAsync(&deviceInput, stream, true) + var deviceOutput core.DeviceSlice + deviceOutput.MallocAsync(numberOfStates*scalarsCopy.SizeOfElement(), scalarsCopy.SizeOfElement(), stream) + + poseidon.PoseidonHash(deviceInput, deviceOutput, numberOfStates, &cfg, &constants) //run Hash function + + output := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], numberOfStates) + output.CopyFromDeviceAsync(&deviceOutput, stream) + + {{if eq .Field "bls12_381"}} + expectedString := "48fe0b1331196f6cdb33a7c6e5af61b76fd388e1ef1d3d418be5147f0e4613d4" //This result is from https://github.com/triplewz/poseidon + outputString := formatOutput(output[0]) + + assert.Equal(t, outputString, expectedString, "Poseidon hash does not match expected result") + {{end}} +} diff --git a/wrappers/golang_v3/internal/generator/tests/generate.go b/wrappers/golang_v3/internal/generator/tests/generate.go new file mode 100644 index 000000000..3e4d733bc --- /dev/null +++ b/wrappers/golang_v3/internal/generator/tests/generate.go @@ -0,0 +1,29 @@ +package tests + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/internal/generator/tests/templates/main_test.go.tmpl b/wrappers/golang_v3/internal/generator/tests/templates/main_test.go.tmpl new file mode 100644 index 000000000..fefb8a4fd --- /dev/null +++ b/wrappers/golang_v3/internal/generator/tests/templates/main_test.go.tmpl @@ -0,0 +1,75 @@ +package tests + +import ( + "testing" + {{if or .SupportsNTT .SupportsPoly -}} + "fmt" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + {{.Field}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}"{{end}} + {{if .SupportsNTT -}} + ntt "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}/ntt"{{end}} + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + + {{if ne .GnarkImport "" -}} + "github.com/consensys/gnark-crypto/ecc/{{.GnarkImport}}/fr/fft" + {{end -}} +) + +const ( + largestTestSize = 20 +) +var DEVICE runtime.Device + +{{if or .SupportsNTT .SupportsPoly -}} +func initDomain(largestTestSize int, cfg core.NTTInitDomainConfig) runtime.EIcicleError { + {{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) + return e +}{{end}} + +func TestMain(m *testing.M) { + runtime.LoadBackendFromEnv() + devices, e := runtime.GetRegisteredDevices() + if e != runtime.Success { + panic("Failed to load registered devices") + } + for _, deviceType := range devices { + DEVICE = runtime.CreateDevice(deviceType, 0) + runtime.SetDevice(&DEVICE) + + {{if or .SupportsNTT .SupportsPoly -}}// setup domain + cfg := core.GetDefaultNTTInitDomainConfig() + e = initDomain(largestTestSize, cfg) + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("initDomain is not implemented for ", deviceType, " device type") + } else { + panic("initDomain failed") + } + }{{end}} + + // execute tests + m.Run() + + {{if or .SupportsNTT .SupportsPoly -}}// release domain + e = ntt.ReleaseDomain() + if e != runtime.Success { + if e != runtime.ApiNotImplemented { + fmt.Println("ReleaseDomain is not implemented for ", deviceType, " device type") + } else { + panic("ReleaseDomain failed") + } + }{{end}} + } +} diff --git a/wrappers/golang_v3/internal/generator/vecOps/generate.go b/wrappers/golang_v3/internal/generator/vecOps/generate.go new file mode 100644 index 000000000..a5e72497e --- /dev/null +++ b/wrappers/golang_v3/internal/generator/vecOps/generate.go @@ -0,0 +1,39 @@ +package vecops + +import ( + "path" + + generator "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/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_v3/internal/generator/vecOps/templates/vec_ops.go.tmpl b/wrappers/golang_v3/internal/generator/vecOps/templates/vec_ops.go.tmpl new file mode 100644 index 000000000..9b5952142 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/vecOps/templates/vec_ops.go.tmpl @@ -0,0 +1,45 @@ +package {{.PackageName}} + +// #cgo CFLAGS: -I./include/ +// #include "vec_ops.h" +import "C" + +import ( + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" +) + +func VecOp(a, b, out core.HostOrDeviceSlice, config core.VecOpsConfig, op core.VecOps) (ret runtime.EIcicleError) { + 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 = (runtime.EIcicleError)(C.{{.Field}}_vector_sub(cA, cB, cSize, cConfig, cOut)) + case core.Add: + ret = (runtime.EIcicleError)(C.{{.Field}}_vector_add(cA, cB, cSize, cConfig, cOut)) + case core.Mul: + ret = (runtime.EIcicleError)(C.{{.Field}}_vector_mul(cA, cB, cSize, cConfig, cOut)) + } + + return ret +} + +func TransposeMatrix(in, out core.HostOrDeviceSlice, columnSize, rowSize int, config core.VecOpsConfig) runtime.EIcicleError { + inPointer, _, outPointer, cfgPointer, _ := core.VecOpCheck(in, in, out, &config) + + cIn := (*C.scalar_t)(inPointer) + cRowSize := (C.int)(rowSize) + cColumnSize := (C.int)(columnSize) + cConfig := (*C.VecOpsConfig)(cfgPointer) + cOut := (*C.scalar_t)(outPointer) + + err := (C.{{.Field}}_matrix_transpose(cIn, cRowSize, cColumnSize, cConfig, cOut)) + return runtime.EIcicleError(err) +} + diff --git a/wrappers/golang_v3/internal/generator/vecOps/templates/vec_ops.h.tmpl b/wrappers/golang_v3/internal/generator/vecOps/templates/vec_ops.h.tmpl new file mode 100644 index 000000000..c4dad5266 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/vecOps/templates/vec_ops.h.tmpl @@ -0,0 +1,50 @@ +#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; + +int {{.Field}}_vector_mul( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int {{.Field}}_vector_add( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int {{.Field}}_vector_sub( + scalar_t* vec_a, + scalar_t* vec_b, + int n, + VecOpsConfig* config, + scalar_t* result +); + +int {{.Field}}_matrix_transpose( + scalar_t* mat_in, + int row_size, + int column_size, + VecOpsConfig* config, + scalar_t* mat_out +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/internal/generator/vecOps/templates/vec_ops_test.go.tmpl b/wrappers/golang_v3/internal/generator/vecOps/templates/vec_ops_test.go.tmpl new file mode 100644 index 000000000..b476cb476 --- /dev/null +++ b/wrappers/golang_v3/internal/generator/vecOps/templates/vec_ops_test.go.tmpl @@ -0,0 +1,66 @@ +package tests + +import ( + "testing" + + {{.Field}} "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.BaseImportPath}}" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/core" + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/{{.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 + + matrix := {{.Field}}.GenerateScalars(rowSize * columnSize) + + out := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], rowSize*columnSize) + out2 := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], rowSize*columnSize) + + cfg := core.DefaultVecOpsConfig() + + vecOps.TransposeMatrix(matrix, out, columnSize, rowSize, cfg) + vecOps.TransposeMatrix(out, out2, rowSize, columnSize, cfg) + + + assert.Equal(t, matrix, out2) + + var dMatrix, dOut, dOut2 core.DeviceSlice + + 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, cfg) + vecOps.TransposeMatrix(dOut, dOut2, rowSize, columnSize, cfg) + output := make(core.HostSlice[{{.Field}}.{{.FieldPrefix}}Field], rowSize*columnSize) + output.CopyFromDevice(&dOut2) + + assert.Equal(t, matrix, output) +} diff --git a/wrappers/golang_v3/runtime/config.go b/wrappers/golang_v3/runtime/config.go new file mode 100644 index 000000000..b4ee38226 --- /dev/null +++ b/wrappers/golang_v3/runtime/config.go @@ -0,0 +1,50 @@ +package runtime + +// #cgo CFLAGS: -I./include/ +// #include "config_extension.h" +import "C" +import ( + "unsafe" +) + +type ConfigExtensionHandler = unsafe.Pointer + +type ConfigExtension struct { + handler ConfigExtensionHandler +} + +func CreateConfigExtension() *ConfigExtension { + ext := &ConfigExtension{handler: C.create_config_extension()} + // runtime.SetFinalizer(ext, Delete) + return ext +} + +func Delete(ext *ConfigExtension) { + C.destroy_config_extension(ext.handler) +} + +func (ext *ConfigExtension) SetInt(key string, value int) { + cKey := C.CString(key) + cValue := C.int(value) + C.config_extension_set_int(ext.handler, cKey, cValue) +} + +func (ext *ConfigExtension) SetBool(key string, value bool) { + cKey := C.CString(key) + cValue := C._Bool(value) + C.config_extension_set_bool(ext.handler, cKey, cValue) +} + +func (ext *ConfigExtension) GetInt(key string) int { + cKey := C.CString(key) + return int(C.config_extension_get_int(ext.handler, cKey)) +} + +func (ext *ConfigExtension) GetBool(key string) bool { + cKey := C.CString(key) + return C.config_extension_get_bool(ext.handler, cKey) == C._Bool(true) +} + +func (ext ConfigExtension) AsUnsafePointer() unsafe.Pointer { + return ext.handler +} diff --git a/wrappers/golang_v3/runtime/device.go b/wrappers/golang_v3/runtime/device.go new file mode 100644 index 000000000..d3883271d --- /dev/null +++ b/wrappers/golang_v3/runtime/device.go @@ -0,0 +1,40 @@ +package runtime + +import "C" +import "unsafe" + +const MAX_TYPE_SIZE = 64 + +type Device struct { + DeviceType [MAX_TYPE_SIZE]C.char + Id int32 +} + +type DeviceProperties struct { + UsingHostMemory bool + NumMemoryRegions int32 + SupportsPinnedMemory bool +} + +func CreateDevice(deviceType string, id int) Device { + var cDeviceType [MAX_TYPE_SIZE]C.char + for i, v := range deviceType { + if i >= MAX_TYPE_SIZE { + break + } + cDeviceType[i] = C.char(v) + } + // Ensure the last character is null if the source string is too long + if len(deviceType) >= MAX_TYPE_SIZE { + cDeviceType[MAX_TYPE_SIZE-1] = C.char(0) + } + return Device{DeviceType: cDeviceType, Id: int32(id)} +} + +func (self *Device) GetDeviceType() string { + n := 0 + for n < MAX_TYPE_SIZE && self.DeviceType[n] != 0 { + n++ + } + return C.GoStringN((*C.char)(unsafe.Pointer(&self.DeviceType[0])), C.int(n)) +} diff --git a/wrappers/golang_v3/runtime/errors.go b/wrappers/golang_v3/runtime/errors.go new file mode 100644 index 000000000..a98cdca17 --- /dev/null +++ b/wrappers/golang_v3/runtime/errors.go @@ -0,0 +1,20 @@ +package runtime + +// eIcicleError represents the error codes +type EIcicleError int + +const ( + Success EIcicleError = iota // Operation completed successfully + InvalidDevice // The specified device is invalid + OutOfMemory // Memory allocation failed due to insufficient memory + InvalidPointer // The specified pointer is invalid + AllocationFailed // Memory allocation failed + DeallocationFailed // Memory deallocation failed + CopyFailed // Data copy operation failed + SynchronizationFailed // Device synchronization failed + StreamCreationFailed // Stream creation failed + StreamDestructionFailed // Stream destruction failed + ApiNotImplemented // The API is not implemented for a device + InvalidArgument // Invalid argument passed + UnknownError // An unknown error occurred +) diff --git a/wrappers/golang_v3/runtime/include/config_extension.h b/wrappers/golang_v3/runtime/include/config_extension.h new file mode 100644 index 000000000..4341b79a0 --- /dev/null +++ b/wrappers/golang_v3/runtime/include/config_extension.h @@ -0,0 +1,24 @@ +#include + +#ifndef _CONFIG_EXTENSION_H +#define _CONFIG_EXTENSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +// typedef ConfigExtension ConfigExtension; + +void* create_config_extension(); +void destroy_config_extension(void* ext); +void config_extension_set_int(void* ext, const char* key, int value); +void config_extension_set_bool(void* ext, const char* key, bool value); +int config_extension_get_int(const void* ext, const char* key); +bool config_extension_get_bool(const void* ext, const char* key); +void* clone_config_extension(const void* ext); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wrappers/golang_v3/runtime/include/runtime.h b/wrappers/golang_v3/runtime/include/runtime.h new file mode 100644 index 000000000..6be58b4ba --- /dev/null +++ b/wrappers/golang_v3/runtime/include/runtime.h @@ -0,0 +1,41 @@ +#include + +#ifndef _RUNTIME_H +#define _RUNTIME_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Device Device; +typedef struct DeviceProperties DeviceProperties; +typedef struct icicleStreamHandle icicleStreamHandle; + +int icicle_load_backend(const char* path, bool is_recursive); +int icicle_set_device(const Device* device); +int icicle_get_active_device(Device* device); +int icicle_is_host_memory(const void* ptr); +int icicle_is_active_device_memory(const void* ptr); +int icicle_get_device_count(int* device_count); +int icicle_malloc(void** ptr, size_t size); +int icicle_malloc_async(void** ptr, size_t size, void* stream); +int icicle_free(void* ptr); +int icicle_free_async(void* ptr, void* stream); +int icicle_get_available_memory(size_t* total, size_t* free); +int icicle_copy_to_host(void* dst, const void* src, size_t size); +int icicle_copy_to_host_async(void* dst, const void* src, size_t size, void* stream); +int icicle_copy_to_device(void* dst, const void* src, size_t size); +int icicle_copy_to_device_async(void* dst, const void* src, size_t size, void* stream); +int icicle_create_stream(void** stream); +int icicle_destroy_stream(void* stream); +int icicle_stream_synchronize(void* stream); +int icicle_device_synchronize(); +int icicle_get_device_properties(DeviceProperties* properties); +int icicle_is_device_avialable(const Device* dev); +int icicle_get_registered_devices(char* output, size_t output_size); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/wrappers/golang_v3/runtime/main.go b/wrappers/golang_v3/runtime/main.go new file mode 100644 index 000000000..abcbb2e6d --- /dev/null +++ b/wrappers/golang_v3/runtime/main.go @@ -0,0 +1,4 @@ +package runtime + +// #cgo LDFLAGS: -L${SRCDIR}/../../../build/lib -licicle_device -lstdc++ -lm -Wl,-rpath=${SRCDIR}/../../../build/lib +import "C" diff --git a/wrappers/golang_v3/runtime/memory.go b/wrappers/golang_v3/runtime/memory.go new file mode 100644 index 000000000..df3bee3c0 --- /dev/null +++ b/wrappers/golang_v3/runtime/memory.go @@ -0,0 +1,76 @@ +package runtime + +// #cgo CFLAGS: -I./include/ +// #include "runtime.h" +import "C" +import "unsafe" + +func Malloc(size uint) (unsafe.Pointer, EIcicleError) { + if size == 0 { + return nil, AllocationFailed + } + + var p C.void + devicePtr := unsafe.Pointer(&p) + cSize := (C.size_t)(size) + + ret := C.icicle_malloc(&devicePtr, cSize) + err := EIcicleError(ret) + + return devicePtr, err +} + +func MallocAsync(size uint, stream Stream) (unsafe.Pointer, EIcicleError) { + if size == 0 { + return nil, AllocationFailed + } + + var p C.void + devicePtr := unsafe.Pointer(&p) + cSize := (C.size_t)(size) + + ret := C.icicle_malloc_async(&devicePtr, cSize, stream) + err := EIcicleError(ret) + + return devicePtr, err +} + +func Free(devicePtr unsafe.Pointer) EIcicleError { + ret := C.icicle_free(devicePtr) + err := EIcicleError(ret) + return err +} + +func FreeAsync(devicePtr unsafe.Pointer, stream Stream) EIcicleError { + ret := C.icicle_free_async(devicePtr, stream) + err := EIcicleError(ret) + return err +} + +func CopyFromDevice(hostDst, deviceSrc unsafe.Pointer, size uint) (unsafe.Pointer, EIcicleError) { + cSize := (C.size_t)(size) + ret := C.icicle_copy_to_host(hostDst, deviceSrc, cSize) + err := (EIcicleError)(ret) + return hostDst, err +} + +func CopyFromDeviceAsync(hostDst, deviceSrc unsafe.Pointer, size uint, stream Stream) EIcicleError { + cSize := (C.size_t)(size) + ret := C.icicle_copy_to_host_async(hostDst, deviceSrc, cSize, stream) + err := (EIcicleError)(ret) + return err +} + +func CopyToDevice(deviceDst, hostSrc unsafe.Pointer, size uint) (unsafe.Pointer, EIcicleError) { + cSize := (C.size_t)(size) + ret := C.icicle_copy_to_device(deviceDst, hostSrc, cSize) + err := (EIcicleError)(ret) + return deviceDst, err +} + +func CopyToDeviceAsync(deviceDst, hostSrc unsafe.Pointer, size uint, stream Stream) EIcicleError { + cSize := (C.size_t)(size) + ret := C.icicle_copy_to_device_async(deviceDst, hostSrc, cSize, stream) + err := (EIcicleError)(ret) + return err +} diff --git a/wrappers/golang_v3/runtime/runtime.go b/wrappers/golang_v3/runtime/runtime.go new file mode 100644 index 000000000..fad3b592f --- /dev/null +++ b/wrappers/golang_v3/runtime/runtime.go @@ -0,0 +1,180 @@ +package runtime + +// #cgo CFLAGS: -I./include/ +// #include "runtime.h" +import "C" +import ( + "os" + "runtime" + "strings" + "unsafe" +) + +func LoadBackend(path string, isRecursive bool) EIcicleError { + cPath := C.CString(path) + cIsRecursive := C._Bool(isRecursive) + cErr := C.icicle_load_backend(cPath, cIsRecursive) + return EIcicleError(cErr) +} + +func LoadBackendFromEnv() EIcicleError { + path, exists := os.LookupEnv("DEFAULT_BACKEND_INSTALL_DIR") + if !exists { + path = "usr/local/" + } + return LoadBackend(path, true) +} + +func SetDevice(device *Device) EIcicleError { + cDevice := (*C.Device)(unsafe.Pointer(device)) + cErr := C.icicle_set_device(cDevice) + return EIcicleError(cErr) +} + +func GetActiveDevice() (*Device, EIcicleError) { + device := CreateDevice("invalid", -1) + cDevice := (*C.Device)(unsafe.Pointer(&device)) + cErr := C.icicle_get_active_device(cDevice) + err := EIcicleError(cErr) + if err != Success { + return nil, err + } + return &device, err +} + +func IsDeviceAvailable(device *Device) bool { + cDevice := (*C.Device)(unsafe.Pointer(device)) + cErr := C.icicle_is_device_avialable(cDevice) + return EIcicleError(cErr) == Success +} + +func GetDeviceCount() (int, EIcicleError) { + res := 0 + cRes := (*C.int)(unsafe.Pointer(&res)) + cErr := C.icicle_get_device_count(cRes) + return res, EIcicleError(cErr) +} + +func GetRegisteredDevices() ([]string, EIcicleError) { + const BUFFER_SIZE = 256 + var buffer [BUFFER_SIZE]C.char + cErr := C.icicle_get_registered_devices((*C.char)(unsafe.Pointer(&buffer[0])), BUFFER_SIZE) + err := EIcicleError(cErr) + if err != Success { + return nil, err + } + n := 0 + for n < BUFFER_SIZE && buffer[n] != 0 { + n++ + } + res := C.GoStringN((*C.char)(unsafe.Pointer(&buffer[0])), C.int(n)) + return strings.Split(res, ","), err +} + +func DeviceSynchronize() EIcicleError { + cErr := C.icicle_device_synchronize() + return EIcicleError(cErr) +} + +func GetDeviceProperties() (*DeviceProperties, EIcicleError) { + properties := DeviceProperties{ + UsingHostMemory: false, + NumMemoryRegions: 0, + SupportsPinnedMemory: false, + } + cProperties := (*C.DeviceProperties)(unsafe.Pointer(&properties)) + cErr := C.icicle_get_device_properties(cProperties) + err := EIcicleError(cErr) + if err != Success { + return nil, err + } + return &properties, err +} + +type AvailableMemory struct { + Total uint + Free uint +} + +func GetAvailableMemory() (*AvailableMemory, EIcicleError) { + memory := AvailableMemory{Total: 0, Free: 0} + cTotal := (*C.size_t)(unsafe.Pointer(&memory.Total)) + cFree := (*C.size_t)(unsafe.Pointer(&memory.Free)) + cErr := C.icicle_get_available_memory(cTotal, cFree) + err := EIcicleError(cErr) + if err != Success { + return nil, err + } + return &memory, err +} + +func IsHostMemory(ptr unsafe.Pointer) bool { + cErr := C.icicle_is_host_memory(ptr) + return EIcicleError(cErr) == Success +} + +func IsActiveDeviceMemory(ptr unsafe.Pointer) bool { + cErr := C.icicle_is_active_device_memory(ptr) + return EIcicleError(cErr) == Success +} + +// RunOnDevice forces the provided function to run all GPU related calls within it +// on the same host thread and therefore the same GPU device. +// +// NOTE: Goroutines launched within funcToRun are not bound to the +// same host thread as funcToRun and therefore not to the same GPU device. +// If that is a requirement, RunOnDevice should be called for each with the +// same deviceId as the original call. +// +// As an example: +// +// cr.RunOnDevice(i, func(args ...any) { +// defer wg.Done() +// cfg := GetDefaultMSMConfig() +// stream, _ := cr.CreateStream() +// for _, power := range []int{2, 3, 4, 5, 6, 7, 8, 10, 18} { +// size := 1 << power + +// // This will always print "Inner goroutine device: 0" +// // go func () { +// // device, _ := cr.GetDevice() +// // fmt.Println("Inner goroutine device: ", device) +// // }() +// // To force the above goroutine to same device as the wrapping function: +// // RunOnDevice(i, func(arg ...any) { +// // device, _ := cr.GetDevice() +// // fmt.Println("Inner goroutine device: ", device) +// // }) + +// scalars := GenerateScalars(size) +// points := GenerateAffinePoints(size) + +// var p 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 +// cfg.IsAsync = true + +// e = Msm(scalars, points, &cfg, out) +// assert.Equal(t, e, cr.CudaSuccess, "Msm failed") + +// outHost := make(core.HostSlice[Projective], 1) + +// cr.SynchronizeStream(&stream) +// outHost.CopyFromDevice(&out) +// out.Free() +// // Check with gnark-crypto +// assert.True(t, testAgainstGnarkCryptoMsm(scalars, points, outHost[0])) +// } +// }, i) +func RunOnDevice(device *Device, funcToRun func(args ...any), args ...any) { + go func(id *Device) { + defer runtime.UnlockOSThread() + runtime.LockOSThread() + originalDevice, _ := GetActiveDevice() + SetDevice(id) + funcToRun(args...) + SetDevice(originalDevice) + }(device) +} diff --git a/wrappers/golang_v3/runtime/stream.go b/wrappers/golang_v3/runtime/stream.go new file mode 100644 index 000000000..58b7cc6e7 --- /dev/null +++ b/wrappers/golang_v3/runtime/stream.go @@ -0,0 +1,30 @@ +package runtime + +// #cgo CFLAGS: -I./include/ +// #include "runtime.h" +import "C" +import "unsafe" + +type Stream = unsafe.Pointer + +func CreateStream() (Stream, EIcicleError) { + var stream Stream + ret := C.icicle_create_stream(&stream) + err := (EIcicleError)(ret) + return stream, err +} + +func DestroyStream(stream Stream) EIcicleError { + ret := C.icicle_destroy_stream(stream) + err := (EIcicleError)(ret) + if err == Success { + stream = nil + } + return err +} + +func SynchronizeStream(stream Stream) EIcicleError { + ret := C.icicle_stream_synchronize(stream) + err := (EIcicleError)(ret) + return err +} diff --git a/wrappers/golang_v3/runtime/tests/config_test.go b/wrappers/golang_v3/runtime/tests/config_test.go new file mode 100644 index 000000000..a6752dbde --- /dev/null +++ b/wrappers/golang_v3/runtime/tests/config_test.go @@ -0,0 +1,71 @@ +package test + +import ( + "fmt" + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + + "github.com/stretchr/testify/assert" +) + +func TestConfigExtensionIntValues(t *testing.T) { + config := runtime.CreateConfigExtension() + for i := 0; i < 100; i++ { + config.SetInt("test", i) + assert.Equal(t, config.GetInt("test"), i, "result does not match") + } + for i := 0; i < 100; i++ { + key := fmt.Sprintf("test%d", i) + config.SetInt(key, i) + assert.Equal(t, config.GetInt(key), i, "result does not match") + } + for i := 0; i < 100; i++ { + key := fmt.Sprintf("test%d", i) + config.SetInt(key, i*3) + assert.Equal(t, config.GetInt(key), i*3, "result does not match") + } +} + +func TestConfigExtensionBoolValues(t *testing.T) { + config := runtime.CreateConfigExtension() + for i := 0; i < 100; i++ { + config.SetBool("test", i%2 == 1) + assert.Equal(t, config.GetBool("test"), i%2 == 1, "result does not match") + } + for i := 0; i < 100; i++ { + key := fmt.Sprintf("test%d", i) + config.SetBool(key, i%2 == 1) + assert.Equal(t, config.GetBool(key), i%2 == 1, "result does not match") + } +} + +func TestConfigExtensionSetDifferentValueTypes(t *testing.T) { + config := runtime.CreateConfigExtension() + for i := 0; i < 100; i++ { + key := fmt.Sprintf("test%d", i) + config.SetInt(key, i) + assert.Equal(t, config.GetInt(key), i, "result does not match") + config.SetBool(key, i%2 == 1) + assert.Equal(t, config.GetBool(key), i%2 == 1, "result does not match") + } +} + +func TestConfigExtensionCreateManyConfigs(t *testing.T) { + var configs [5]runtime.ConfigExtension + for i := range configs { + configs[i] = *runtime.CreateConfigExtension() + } + for i := 1; i <= 100; i++ { + key := fmt.Sprintf("test%d", i) + for j, config := range configs { + config.SetInt(key, i*(j+1)) + } + } + for i := 1; i <= 100; i++ { + key := fmt.Sprintf("test%d", i) + for j, config := range configs { + assert.Equal(t, config.GetInt(key), i*(j+1), "result does not match") + } + } +} diff --git a/wrappers/golang_v3/runtime/tests/device_test.go b/wrappers/golang_v3/runtime/tests/device_test.go new file mode 100644 index 000000000..854ac9df4 --- /dev/null +++ b/wrappers/golang_v3/runtime/tests/device_test.go @@ -0,0 +1,16 @@ +package test + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + + "github.com/stretchr/testify/assert" +) + +func TestGetDeviceType(t *testing.T) { + config := runtime.CreateDevice("test", 0) + assert.Equal(t, config.GetDeviceType(), "test") + configLargeName := runtime.CreateDevice("testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest", 1) + assert.Equal(t, configLargeName.GetDeviceType(), "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttes") +} diff --git a/wrappers/golang_v3/runtime/tests/runtime_test.go b/wrappers/golang_v3/runtime/tests/runtime_test.go new file mode 100644 index 000000000..be272f180 --- /dev/null +++ b/wrappers/golang_v3/runtime/tests/runtime_test.go @@ -0,0 +1,58 @@ +package test + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/stretchr/testify/assert" +) + +func TestIsDeviceAvailable(t *testing.T) { + runtime.LoadBackendFromEnv() + dev := runtime.CreateDevice("CUDA", 0) + err := runtime.SetDevice(&dev) + res, err := runtime.GetDeviceCount() + assert.Equal(t, runtime.Success, err) + assert.Equal(t, res, 2) + err = runtime.LoadBackendFromEnv() + assert.Equal(t, runtime.Success, err) + devCuda := runtime.CreateDevice("CUDA", 0) + assert.True(t, runtime.IsDeviceAvailable(&devCuda)) + devCpu := runtime.CreateDevice("CPU", 0) + assert.True(t, runtime.IsDeviceAvailable(&devCpu)) + devInvalid := runtime.CreateDevice("invalid", 0) + assert.False(t, runtime.IsDeviceAvailable(&devInvalid)) +} + +func TestRegisteredDevices(t *testing.T) { + err := runtime.LoadBackendFromEnv() + assert.Equal(t, runtime.Success, err) + devices, err := runtime.GetRegisteredDevices() + assert.Equal(t, []string{"CUDA", "CPU"}, devices) +} + +func TestDeviceProperties(t *testing.T) { + err := runtime.LoadBackendFromEnv() + assert.Equal(t, runtime.Success, err) + dev := runtime.CreateDevice("CUDA", 0) + err = runtime.SetDevice(&dev) + assert.Equal(t, runtime.Success, err) + _, err = runtime.GetDeviceProperties() + assert.Equal(t, runtime.Success, err) + +} + +func TestActiveDevice(t *testing.T) { + err := runtime.LoadBackendFromEnv() + assert.Equal(t, runtime.Success, err) + dev1 := runtime.CreateDevice("CUDA", 0) + err = runtime.SetDevice(&dev1) + assert.Equal(t, runtime.Success, err) + activeDevice, err := runtime.GetActiveDevice() + assert.Equal(t, runtime.Success, err) + assert.Equal(t, dev1, *activeDevice) + memory1, err := runtime.GetAvailableMemory() + assert.Equal(t, runtime.Success, err) + assert.Greater(t, memory1.Total, uint(0)) + assert.Greater(t, memory1.Free, uint(0)) +} diff --git a/wrappers/golang_v3/runtime/tests/stream_test.go b/wrappers/golang_v3/runtime/tests/stream_test.go new file mode 100644 index 000000000..8daa558fb --- /dev/null +++ b/wrappers/golang_v3/runtime/tests/stream_test.go @@ -0,0 +1,55 @@ +package test + +import ( + "testing" + + "github.com/ingonyama-zk/icicle/v2/wrappers/golang_v3/runtime" + "github.com/stretchr/testify/assert" +) + +func TestCreateStream(t *testing.T) { + err := runtime.LoadBackendFromEnv() + assert.Equal(t, runtime.Success, err) + dev := runtime.CreateDevice("CUDA", 0) + assert.True(t, runtime.IsDeviceAvailable(&dev)) + err = runtime.SetDevice(&dev) + assert.Equal(t, runtime.Success, err) + _, err = runtime.CreateStream() + assert.Equal(t, runtime.Success, err, "Unable to create stream due to %d", err) +} + +func TestDestroyStream(t *testing.T) { + err := runtime.LoadBackend("/home/administrator/users/Timur/Projects/icicle/icicle_v3/build/backend", true) + assert.Equal(t, runtime.Success, err) + dev := runtime.CreateDevice("CUDA", 0) + assert.True(t, runtime.IsDeviceAvailable(&dev)) + stream, err := runtime.CreateStream() + assert.Equal(t, runtime.Success, err, "Unable to create stream due to %d", err) + + err = runtime.DestroyStream(stream) + assert.Equal(t, runtime.Success, err, "Unable to destroy stream due to %d", err) +} + +func TestSyncStream(t *testing.T) { + err := runtime.LoadBackendFromEnv() + assert.Equal(t, runtime.Success, err) + dev := runtime.CreateDevice("CUDA", 0) + assert.True(t, runtime.IsDeviceAvailable(&dev)) + runtime.SetDevice(&dev) + + stream, err := runtime.CreateStream() + assert.Equal(t, runtime.Success, err, "Unable to create stream due to %d", err) + + _, err = runtime.MallocAsync(200000, stream) + assert.Equal(t, runtime.Success, err, "Unable to allocate device memory due to %d", err) + + dp, err := runtime.Malloc(20) + assert.NotNil(t, dp) + assert.Equal(t, runtime.Success, err, "Unable to allocate device memory due to %d", err) + + err = runtime.SynchronizeStream(stream) + assert.Equal(t, runtime.Success, err, "Unable to sync stream due to %d", err) + + err = runtime.DestroyStream(stream) + assert.Equal(t, runtime.Success, err, "Unable to destroy stream due to %d", err) +} diff --git a/wrappers/golang_v3/test_helpers/helpers.go b/wrappers/golang_v3/test_helpers/helpers.go new file mode 100644 index 000000000..09a2a7dff --- /dev/null +++ b/wrappers/golang_v3/test_helpers/helpers.go @@ -0,0 +1,31 @@ +package test_helpers + +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/rust/icicle-core/src/curve.rs b/wrappers/rust/icicle-core/src/curve.rs index c0b82d113..a0bf70bc6 100644 --- a/wrappers/rust/icicle-core/src/curve.rs +++ b/wrappers/rust/icicle-core/src/curve.rs @@ -22,6 +22,8 @@ pub trait Curve: Debug + PartialEq + Copy + Clone { #[doc(hidden)] fn to_affine(point: *const Projective, point_aff: *mut Affine); #[doc(hidden)] + fn from_affine(point: *const Affine, point_proj: *mut Projective); + #[doc(hidden)] fn generate_random_projective_points(size: usize) -> Vec>; #[doc(hidden)] fn generate_random_affine_points(size: usize) -> Vec>; @@ -79,27 +81,17 @@ impl Affine { } pub fn to_projective(&self) -> Projective { - if *self == Self::zero() { - return Projective::::zero(); - } - Projective { - x: self.x, - y: self.y, - z: C::BaseField::one(), - } + let mut proj = Projective::::zero(); + C::from_affine(self as *const Self, &mut proj as *mut Projective); + proj } } impl From> for Projective { fn from(item: Affine) -> Self { - if item == (Affine::::zero()) { - return Self::zero(); - } - Self { - x: item.x, - y: item.y, - z: C::BaseField::one(), - } + let mut proj = Self::zero(); + C::from_affine(&item as *const Affine, &mut proj as *mut Self); + proj } } @@ -282,6 +274,8 @@ macro_rules! impl_curve { pub(crate) fn eq(point1: *const $projective_type, point2: *const $projective_type) -> bool; #[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, "_from_affine")] + pub(crate) fn proj_from_affine(point: *const $affine_type, point_out: *mut $projective_type); #[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, "_generate_affine_points")] @@ -315,6 +309,10 @@ macro_rules! impl_curve { unsafe { $curve_prefix_ident::proj_to_affine(point, point_out) }; } + fn from_affine(point: *const $affine_type, point_out: *mut $projective_type) { + unsafe { $curve_prefix_ident::proj_from_affine(point, point_out) }; + } + fn generate_random_projective_points(size: usize) -> Vec<$projective_type> { let mut res = vec![$projective_type::zero(); size]; unsafe { diff --git a/wrappers/rust/icicle-hash/src/keccak/mod.rs b/wrappers/rust/icicle-hash/src/keccak/mod.rs index 88fc5a2c7..df2cbfb73 100644 --- a/wrappers/rust/icicle-hash/src/keccak/mod.rs +++ b/wrappers/rust/icicle-hash/src/keccak/mod.rs @@ -25,6 +25,22 @@ extern "C" { config: &HashConfig, ) -> CudaError; + pub(crate) fn sha3_256_cuda( + input: *const u8, + input_block_size: u32, + number_of_blocks: u32, + output: *mut u8, + config: &HashConfig, + ) -> CudaError; + + pub(crate) fn sha3_512_cuda( + input: *const u8, + input_block_size: u32, + number_of_blocks: u32, + output: *mut u8, + config: &HashConfig, + ) -> CudaError; + pub(crate) fn build_keccak256_merkle_tree_cuda( leaves: *const u8, digests: *mut u64, @@ -40,6 +56,22 @@ extern "C" { input_block_len: u32, config: &TreeBuilderConfig, ) -> CudaError; + + pub(crate) fn build_sha3_256_merkle_tree_cuda( + leaves: *const u8, + digests: *mut u64, + height: u32, + input_block_len: u32, + config: &TreeBuilderConfig, + ) -> CudaError; + + pub(crate) fn build_sha3_512_merkle_tree_cuda( + leaves: *const u8, + digests: *mut u64, + height: u32, + input_block_len: u32, + config: &TreeBuilderConfig, + ) -> CudaError; } pub fn keccak256( @@ -86,6 +118,50 @@ pub fn keccak512( } } +pub fn sha3_256( + input: &(impl HostOrDeviceSlice + ?Sized), + input_block_size: u32, + number_of_blocks: u32, + output: &mut (impl HostOrDeviceSlice + ?Sized), + config: &HashConfig, +) -> IcicleResult<()> { + 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(); + unsafe { + sha3_256_cuda( + input.as_ptr(), + input_block_size, + number_of_blocks, + output.as_mut_ptr(), + &local_cfg, + ) + .wrap() + } +} + +pub fn sha3_512( + input: &(impl HostOrDeviceSlice + ?Sized), + input_block_size: u32, + number_of_blocks: u32, + output: &mut (impl HostOrDeviceSlice + ?Sized), + config: &HashConfig, +) -> IcicleResult<()> { + 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(); + unsafe { + sha3_512_cuda( + input.as_ptr(), + input_block_size, + number_of_blocks, + output.as_mut_ptr(), + &local_cfg, + ) + .wrap() + } +} + pub fn build_keccak256_merkle_tree( leaves: &(impl HostOrDeviceSlice + ?Sized), digests: &mut (impl HostOrDeviceSlice + ?Sized), @@ -123,3 +199,41 @@ pub fn build_keccak512_merkle_tree( .wrap() } } + +pub fn build_sha3_256_merkle_tree( + leaves: &(impl HostOrDeviceSlice + ?Sized), + digests: &mut (impl HostOrDeviceSlice + ?Sized), + height: usize, + input_block_len: usize, + config: &TreeBuilderConfig, +) -> IcicleResult<()> { + unsafe { + build_sha3_256_merkle_tree_cuda( + leaves.as_ptr(), + digests.as_mut_ptr(), + height as u32, + input_block_len as u32, + config, + ) + .wrap() + } +} + +pub fn build_sha3_512_merkle_tree( + leaves: &(impl HostOrDeviceSlice + ?Sized), + digests: &mut (impl HostOrDeviceSlice + ?Sized), + height: usize, + input_block_len: usize, + config: &TreeBuilderConfig, +) -> IcicleResult<()> { + unsafe { + build_sha3_512_merkle_tree_cuda( + leaves.as_ptr(), + digests.as_mut_ptr(), + height as u32, + input_block_len as u32, + config, + ) + .wrap() + } +} diff --git a/wrappers/rust/icicle-hash/src/keccak/tests.rs b/wrappers/rust/icicle-hash/src/keccak/tests.rs index 5675e9158..0aa8cbc0e 100644 --- a/wrappers/rust/icicle-hash/src/keccak/tests.rs +++ b/wrappers/rust/icicle-hash/src/keccak/tests.rs @@ -15,7 +15,7 @@ pub(crate) mod tests { let number_of_hashes = 1024; let preimages = vec![1u8; number_of_hashes * input_block_len]; - let mut digests = vec![0u8; number_of_hashes * 64]; + let mut digests = vec![0u8; number_of_hashes * 32]; let preimages_slice = HostSlice::from_slice(&preimages); let digests_slice = HostSlice::from_mut_slice(&mut digests); diff --git a/wrappers/rust_v3/icicle-core/src/msm/mod.rs b/wrappers/rust_v3/icicle-core/src/msm/mod.rs index 9850efb8a..7395ef11c 100644 --- a/wrappers/rust_v3/icicle-core/src/msm/mod.rs +++ b/wrappers/rust_v3/icicle-core/src/msm/mod.rs @@ -32,8 +32,8 @@ pub struct MSMConfig { /// (better) upper bound is known, it should be reflected in this variable. Default value: 0 (set to the bitsize of scalar field). pub bitsize: i32, - batch_size: i32, - are_bases_shared: bool, + pub batch_size: i32, + pub are_bases_shared: bool, /// MSMs in batch share the bases. If false, expecting #bases==#scalars are_scalars_on_device: bool, pub are_scalars_montgomery_form: bool, @@ -247,7 +247,7 @@ macro_rules! impl_msm { unsafe { $curve_prefix_ident::precompute_bases_ffi( points.as_ptr(), - points.len() as i32, + points.len() as i32 / config.batch_size, config, output_bases.as_mut_ptr(), ) @@ -281,6 +281,18 @@ macro_rules! impl_msm_tests { check_msm_batch::<$curve>() } + #[test] + fn test_msm_batch_shared() { + initialize(); + check_msm_batch_shared::<$curve>() + } + + #[test] + fn test_msm_batch_not_shared() { + initialize(); + check_msm_batch_not_shared::<$curve>() + } + #[test] fn test_msm_skewed_distributions() { initialize(); diff --git a/wrappers/rust_v3/icicle-core/src/msm/tests.rs b/wrappers/rust_v3/icicle-core/src/msm/tests.rs index 9714bb2d3..b6487700a 100644 --- a/wrappers/rust_v3/icicle-core/src/msm/tests.rs +++ b/wrappers/rust_v3/icicle-core/src/msm/tests.rs @@ -2,6 +2,7 @@ use crate::curve::{Affine, Curve, Projective}; use crate::msm::{msm, precompute_bases, MSMConfig, CUDA_MSM_LARGE_BUCKET_FACTOR, MSM}; use crate::test_utilities; use crate::traits::{FieldImpl, GenerateRandom, MontgomeryConvertible}; +use icicle_runtime::memory::HostOrDeviceSlice; use icicle_runtime::{ memory::{DeviceVec, HostSlice}, runtime, @@ -178,6 +179,172 @@ where .unwrap(); } +pub fn check_msm_batch_shared>() +where + ::Config: GenerateRandom, +{ + // let test_sizes = [1000, 1 << 16]; //TODO - uncomment this line after implementing fast msm + let test_sizes = [100]; + // let batch_sizes = [1, 3, 1 << 4]; + let batch_sizes = [1, 3]; //TODO - uncomment this line after implementing fast msm + let mut stream = IcicleStream::create().unwrap(); + let precompute_factor = 8; + let mut cfg = MSMConfig::default(); + cfg.stream_handle = *stream; + cfg.is_async = true; + cfg.ext + .set_int(CUDA_MSM_LARGE_BUCKET_FACTOR, 5); + cfg.c = 4; + runtime::warmup(&stream).unwrap(); + stream + .synchronize() + .unwrap(); + for test_size in test_sizes { + // (1) compute MSM with and w/o precompute on main device + test_utilities::test_set_main_device(); + cfg.precompute_factor = precompute_factor; + let points = generate_random_affine_points_with_zeroes::(test_size, 10); + let mut precomputed_points_d = + DeviceVec::>::device_malloc(cfg.precompute_factor as usize * test_size).unwrap(); + precompute_bases(HostSlice::from_slice(&points), &cfg, &mut precomputed_points_d).unwrap(); + for batch_size in batch_sizes { + let scalars = ::Config::generate_random(test_size * batch_size); + // a version of batched msm without using `cfg.points_size`, requires copying bases + // let points_cloned: Vec> = std::iter::repeat(points.clone()) + // .take(batch_size) + // .flatten() + // .collect(); + let scalars_h = HostSlice::from_slice(&scalars); + + let mut msm_results_1 = DeviceVec::>::device_malloc(batch_size).unwrap(); + let mut msm_results_2 = DeviceVec::>::device_malloc(batch_size).unwrap(); + let mut points_d = DeviceVec::>::device_malloc(test_size).unwrap(); + points_d + .copy_from_host_async(HostSlice::from_slice(&points), &stream) + .unwrap(); + + cfg.precompute_factor = precompute_factor; + 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(); + + 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(HostSlice::from_mut_slice(&mut msm_host_result_1), &stream) + .unwrap(); + msm_results_2 + .copy_to_host_async(HostSlice::from_mut_slice(&mut msm_host_result_2), &stream) + .unwrap(); + stream + .synchronize() + .unwrap(); + + // (2) compute on ref device and compare to both cases (with or w/o precompute) + test_utilities::test_set_ref_device(); + let mut msm_ref_result = vec![Projective::::zero(); batch_size]; + let mut ref_msm_config = MSMConfig::default(); + ref_msm_config.c = 4; + msm( + scalars_h, + HostSlice::from_slice(&points), + &MSMConfig::default(), + HostSlice::from_mut_slice(&mut msm_ref_result), + ) + .unwrap(); + + assert_eq!(msm_host_result_1, msm_ref_result); + assert_eq!(msm_host_result_2, msm_ref_result); + } + } + stream + .destroy() + .unwrap(); +} + +pub fn check_msm_batch_not_shared>() +where + ::Config: GenerateRandom, +{ + // let test_sizes = [1000, 1 << 16]; //TODO - uncomment this line after implementing fast msm + let test_sizes = [100]; + // let batch_sizes = [1, 3, 1 << 4]; + let batch_sizes = [3, 5]; //TODO - uncomment this line after implementing fast msm + let mut stream = IcicleStream::create().unwrap(); + let precompute_factor = 8; + let mut cfg = MSMConfig::default(); + cfg.stream_handle = *stream; + cfg.is_async = true; + cfg.ext + .set_int(CUDA_MSM_LARGE_BUCKET_FACTOR, 5); + cfg.c = 4; + runtime::warmup(&stream).unwrap(); + stream + .synchronize() + .unwrap(); + for test_size in test_sizes { + // (1) compute MSM with and w/o precompute on main device + test_utilities::test_set_main_device(); + for batch_size in batch_sizes { + cfg.precompute_factor = precompute_factor; + let scalars = ::Config::generate_random(test_size * batch_size); + let scalars_h = HostSlice::from_slice(&scalars); + + let points = generate_random_affine_points_with_zeroes::(test_size * batch_size, 10); + println!("points len: {}", points.len()); + let mut precomputed_points_d = + DeviceVec::>::device_malloc(cfg.precompute_factor as usize * test_size * batch_size).unwrap(); + cfg.batch_size = batch_size as i32; + cfg.are_bases_shared = false; + precompute_bases(HostSlice::from_slice(&points), &cfg, &mut precomputed_points_d).unwrap(); + println!("precomputed points len: {}", (precomputed_points_d).len()); + + let mut msm_results_1 = DeviceVec::>::device_malloc(batch_size).unwrap(); + let mut msm_results_2 = DeviceVec::>::device_malloc(batch_size).unwrap(); + let mut points_d = DeviceVec::>::device_malloc(test_size * batch_size).unwrap(); + points_d + .copy_from_host_async(HostSlice::from_slice(&points), &stream) + .unwrap(); + + cfg.precompute_factor = precompute_factor; + 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(); + + 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(HostSlice::from_mut_slice(&mut msm_host_result_1), &stream) + .unwrap(); + msm_results_2 + .copy_to_host_async(HostSlice::from_mut_slice(&mut msm_host_result_2), &stream) + .unwrap(); + stream + .synchronize() + .unwrap(); + + // (2) compute on ref device and compare to both cases (with or w/o precompute) + test_utilities::test_set_ref_device(); + let mut msm_ref_result = vec![Projective::::zero(); batch_size]; + let mut ref_msm_config = MSMConfig::default(); + ref_msm_config.c = 4; + msm( + scalars_h, + HostSlice::from_slice(&points), + &MSMConfig::default(), + HostSlice::from_mut_slice(&mut msm_ref_result), + ) + .unwrap(); + + assert_eq!(msm_host_result_1, msm_ref_result); + assert_eq!(msm_host_result_2, msm_ref_result); + } + } + stream + .destroy() + .unwrap(); +} + pub fn check_msm_skewed_distributions>() where ::Config: GenerateRandom, diff --git a/wrappers/rust_v3/icicle-core/src/vec_ops/tests.rs b/wrappers/rust_v3/icicle-core/src/vec_ops/tests.rs index 9b1e8476e..6762f06c9 100644 --- a/wrappers/rust_v3/icicle-core/src/vec_ops/tests.rs +++ b/wrappers/rust_v3/icicle-core/src/vec_ops/tests.rs @@ -2,8 +2,8 @@ use crate::test_utilities; use crate::traits::GenerateRandom; use crate::vec_ops::{ - add_scalars, accumulate_scalars, bit_reverse, bit_reverse_inplace, mul_scalars, sub_scalars, transpose_matrix, FieldImpl, VecOps, - VecOpsConfig, + accumulate_scalars, add_scalars, bit_reverse, bit_reverse_inplace, mul_scalars, sub_scalars, transpose_matrix, + FieldImpl, VecOps, VecOpsConfig, }; use icicle_runtime::device::Device; use icicle_runtime::memory::{DeviceVec, HostSlice}; @@ -73,7 +73,9 @@ where assert_eq!(result_main.as_slice(), result_ref.as_slice()); - stream.destroy().unwrap(); + stream + .destroy() + .unwrap(); } pub fn check_vec_ops_scalars_sub(test_size: usize) @@ -102,7 +104,9 @@ where assert_eq!(result_main.as_slice(), result_ref.as_slice()); - stream.destroy().unwrap(); + stream + .destroy() + .unwrap(); } pub fn check_vec_ops_scalars_mul(test_size: usize) @@ -131,7 +135,9 @@ where assert_eq!(result_main.as_slice(), result_ref.as_slice()); - stream.destroy().unwrap(); + stream + .destroy() + .unwrap(); } pub fn check_vec_ops_scalars_accumulate(test_size: usize) @@ -159,7 +165,9 @@ where assert_eq!(a_clone_slice.as_slice(), a_main_slice.as_slice()); - stream.destroy().unwrap(); + stream + .destroy() + .unwrap(); } pub fn check_matrix_transpose()