├── .clang-format ├── .flake8 ├── .github ├── dependabot.yml └── workflows │ ├── build-blst-nightly.yml │ ├── build-test-riscv64.yml │ ├── build-test.yaml │ ├── build-wheels.yml │ ├── check-commit-signing.yml │ ├── codeql.yml │ ├── dependency-review.yml │ ├── js-bindings.yml │ └── stale-issue.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── cmake_modules └── Findsodium.cmake ├── emsdk_build.sh ├── js-bindings ├── CMakeLists.txt ├── README.md ├── blsjs.d.ts ├── helpers.cpp ├── helpers.h ├── jsbindings.cpp ├── package-lock.json ├── package.json ├── tests │ ├── PrivateKey.spec.js │ ├── PublicKey.spec.js │ ├── Signature.spec.js │ ├── karma.conf.js │ ├── karma.test.js │ ├── test.js │ ├── typings.spec.ts │ ├── webpack.config.js │ └── yarn.lock └── wrappers │ ├── G1ElementWrapper.cpp │ ├── G1ElementWrapper.h │ ├── G2ElementWrapper.cpp │ ├── G2ElementWrapper.h │ ├── JSWrapper.h │ ├── PrivateKeyWrapper.cpp │ ├── PrivateKeyWrapper.h │ ├── SchemeMPLWrapper.cpp │ ├── SchemeMPLWrapper.h │ ├── UtilWrapper.cpp │ └── UtilWrapper.h ├── js_build.sh ├── js_test.sh ├── lgtm.yml ├── mypi.ini ├── pyproject.toml ├── python-bindings ├── CMakeLists.txt ├── README.md ├── benchmark.py ├── pythonbindings.cpp └── test.py ├── python-impl ├── README.md ├── bls12381.py ├── ec.py ├── fields.py ├── hash_to_field.py ├── hd_keys.py ├── hkdf.py ├── impl-test.py ├── op_swu_g2.py ├── pairing.py ├── private_key.py ├── schemes.py └── util.py ├── setup.py └── src ├── CMakeLists.txt ├── bls.cpp ├── bls.hpp ├── elements.cpp ├── elements.hpp ├── hdkeys.hpp ├── hkdf.hpp ├── privatekey.cpp ├── privatekey.hpp ├── schemes.cpp ├── schemes.hpp ├── test-bench.cpp ├── test-utils.hpp ├── test.cpp └── util.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | UseTab: Never 3 | ColumnLimit: 80 4 | IndentWidth: 4 5 | TabWidth: 4 6 | AllowShortIfStatementsOnASingleLine: false 7 | IndentCaseLabels: false 8 | AccessModifierOffset: -4 9 | BinPackArguments: false 10 | BinPackParameters: false 11 | AlignAfterOpenBracket: AlwaysBreak 12 | IndentCaseLabels: true 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | BreakBeforeBraces: Custom 15 | BraceWrapping: 16 | AfterFunction: true 17 | PenaltyReturnTypeOnItsOwnLine: 1000 18 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = ./typings/**/* python-impl/fields.py 4 | ignore = E203,W503,E501 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # This file is managed by the repo-content-updater project. Manual changes here will result in a PR to bring back 2 | # inline with the upstream template, unless you remove the dependabot managed file property from the repo 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "gomod" 7 | directory: / 8 | schedule: 9 | interval: "weekly" 10 | day: "tuesday" 11 | open-pull-requests-limit: 10 12 | rebase-strategy: auto 13 | labels: 14 | - dependencies 15 | - go 16 | - "Changed" 17 | reviewers: ["cmmarslender", "Starttoaster"] 18 | groups: 19 | global: 20 | patterns: 21 | - "*" 22 | 23 | - package-ecosystem: "pip" 24 | directory: / 25 | schedule: 26 | interval: "weekly" 27 | day: "tuesday" 28 | open-pull-requests-limit: 10 29 | rebase-strategy: auto 30 | labels: 31 | - dependencies 32 | - python 33 | - "Changed" 34 | reviewers: ["emlowe", "altendky"] 35 | 36 | - package-ecosystem: "github-actions" 37 | directories: ["/", ".github/actions/*"] 38 | schedule: 39 | interval: "weekly" 40 | day: "tuesday" 41 | open-pull-requests-limit: 10 42 | rebase-strategy: auto 43 | labels: 44 | - dependencies 45 | - github_actions 46 | - "Changed" 47 | reviewers: ["cmmarslender", "Starttoaster", "pmaslana"] 48 | 49 | - package-ecosystem: "npm" 50 | directory: / 51 | schedule: 52 | interval: "weekly" 53 | day: "tuesday" 54 | open-pull-requests-limit: 10 55 | rebase-strategy: auto 56 | labels: 57 | - dependencies 58 | - javascript 59 | - "Changed" 60 | reviewers: ["cmmarslender", "ChiaMineJP"] 61 | 62 | - package-ecosystem: cargo 63 | directory: / 64 | schedule: 65 | interval: "weekly" 66 | day: "tuesday" 67 | open-pull-requests-limit: 10 68 | rebase-strategy: auto 69 | labels: 70 | - dependencies 71 | - rust 72 | - "Changed" 73 | 74 | - package-ecosystem: swift 75 | directory: / 76 | schedule: 77 | interval: "weekly" 78 | day: "tuesday" 79 | open-pull-requests-limit: 10 80 | rebase-strategy: auto 81 | -------------------------------------------------------------------------------- /.github/workflows/build-blst-nightly.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test with blst Nightly 2 | 3 | on: 4 | schedule: 5 | - cron: "0 11 * * *" 6 | workflow_dispatch: 7 | 8 | concurrency: 9 | # SHA is added to the end if on `main` to let all main workflows run 10 | group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main') && github.sha || '' }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build_with_blst_main: 15 | name: Build and Test with blst Nightly 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [macos-latest, ubuntu-latest, windows-latest] 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Python 27 | uses: chia-network/actions/setup-python@main 28 | with: 29 | python-version: '3.10' 30 | 31 | - name: Ubuntu build C++ and test blst at origin/main 32 | if: startsWith(matrix.os, 'ubuntu') 33 | # blst is still using master instead of main 34 | env: 35 | BLST_MAIN: 1 36 | run: | 37 | echo "blst origin/main commit:" 38 | curl -H "application/vnd.github.v3.sha" \ 39 | https://api.github.com/repos/supranational/blst/commits/master | \ 40 | head -10 41 | cmake -B build . 42 | cmake --build build -- -j 6 43 | echo "Running ./src/runtest" 44 | ./build/src/runtest 45 | 46 | - name: Mac OS build C++ and test 47 | if: startsWith(matrix.os, 'macos') 48 | env: 49 | BLST_MAIN: 1 50 | MACOSX_DEPLOYMENT_TARGET: 10.14 51 | run: | 52 | cmake -B build . 53 | cmake --build build -- -j 6 54 | echo "Running ./src/runtest" 55 | ./build/src/runtest 56 | 57 | - name: Windows build C++ and test 58 | if: startsWith(matrix.os, 'windows') 59 | env: 60 | BLST_MAIN: 1 61 | run: | 62 | cmake -B build . 63 | cmake --build build 64 | echo "Running runtest" 65 | build\src\Debug\runtest.exe 66 | 67 | - name: Test pure python implementation 68 | run: | 69 | python python-impl/impl-test.py 70 | 71 | - name: Install emsdk 72 | uses: mymindstorm/setup-emsdk@v11 73 | 74 | - name: Test javascript bindings 75 | env: 76 | BLST_MAIN: 1 77 | run: | 78 | emcc -v 79 | sh emsdk_build.sh 80 | sh js_test.sh 81 | -------------------------------------------------------------------------------- /.github/workflows/build-test-riscv64.yml: -------------------------------------------------------------------------------- 1 | name: Build and test riscv64 on ubuntu-latest 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | tags: 9 | - '**' 10 | pull_request: 11 | branches: 12 | - '**' 13 | 14 | jobs: 15 | build_wheels: 16 | name: QEMU riscv64 via Debian on ubuntu-latest 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ ubuntu-latest ] 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 1 27 | 28 | - name: Set up QEMU on x86_64 29 | if: startsWith(matrix.os, 'ubuntu-latest') 30 | id: qemu 31 | uses: docker/setup-qemu-action@v3 32 | with: 33 | platforms: riscv64 34 | 35 | - name: Build and Test 36 | run: | 37 | docker run --rm --platform linux/riscv64 \ 38 | -v ${{ github.workspace }}:/ws --workdir=/ws \ 39 | chianetwork/ubuntu-22.04-risc-builder:latest \ 40 | bash -exc '\ 41 | cmake --version && \ 42 | uname -a && \ 43 | pip wheel -w dist . && \ 44 | python3 -m venv venv && \ 45 | ./venv/bin/python -m pip install dist/*.whl && \ 46 | ./venv/bin/python -m pip install pytest && \ 47 | ./venv/bin/python -m pytest -v python-bindings/test.py 48 | ' 49 | 50 | - name: Upload artifacts 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: packages-${{ matrix.os }} 54 | path: ./dist 55 | overwrite: true 56 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Test C++, Javascript, and Python 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '**' 9 | pull_request: 10 | branches: 11 | - '**' 12 | 13 | concurrency: 14 | # SHA is added to the end if on `main` to let all main workflows run 15 | group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main') && github.sha || '' }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | coverage: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | - name: Collect coverage data 25 | run: | 26 | sudo apt-get update 27 | sudo apt-get install lcov -y 28 | sudo apt-get install snap -y 29 | sudo snap install cmake --classic 30 | hash -r 31 | cmake -B ../build -DCMAKE_BUILD_TYPE=Debug -DWITH_COVERAGE=1 -DBUILD_BLS_PYTHON_BINDINGS=0 -DBUILD_BLS_BENCHMARKS=0 32 | cmake --build ../build -- -j 6 33 | ../build/src/runtest 34 | lcov --directory ../build --capture --output-file lcov.info 35 | lcov --remove lcov.info '*_deps/*' '/usr/*' --output-file lcov.info 36 | - name: Upload to Coveralls 37 | uses: coverallsapp/github-action@v2 38 | if: always() 39 | env: 40 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 41 | with: 42 | path-to-lcov: './lcov.info' 43 | 44 | build_wheels: 45 | name: Build and Test on ${{ matrix.os }} CPython ${{ matrix.python }} 46 | runs-on: ${{ matrix.os }} 47 | strategy: 48 | fail-fast: false 49 | matrix: 50 | os: [macos-latest, ubuntu-latest, windows-latest] 51 | python: ['3.8', '3.9', '3.10', '3.11', '3.12'] 52 | 53 | steps: 54 | - name: Checkout code 55 | uses: actions/checkout@v4 56 | 57 | - uses: chia-network/actions/setup-python@main 58 | with: 59 | python-version: ${{ matrix.python }} 60 | 61 | - name: Ubuntu build C++ and test with valgrind 62 | if: startsWith(matrix.os, 'ubuntu') 63 | run: | 64 | sudo apt-get update 65 | sudo apt-get install valgrind -y 66 | sudo apt-get install snap -y 67 | sudo apt-get remove --purge cmake -y 68 | sudo snap install cmake --classic 69 | hash -r 70 | cmake --version 71 | mkdir -p build 72 | cd build 73 | cmake ../ 74 | cmake --build . -- -j 6 75 | echo "Running ./src/runtest" 76 | ./src/runtest 77 | valgrind --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all ./src/runtest 78 | 79 | - name: Mac OS build C++ and test 80 | if: startsWith(matrix.os, 'macos') 81 | run: | 82 | export MACOSX_DEPLOYMENT_TARGET=11 83 | mkdir -p build 84 | cd build 85 | cmake ../ 86 | cmake --build . -- -j 6 87 | echo "Running ./src/runtest" 88 | ./src/runtest 89 | 90 | - name: Test pure python implementation 91 | run: | 92 | python python-impl/impl-test.py 93 | 94 | - name: Install emsdk 95 | uses: mymindstorm/setup-emsdk@v12 96 | 97 | - name: Test javascript bindings 98 | run: | 99 | emcc -v 100 | sh emsdk_build.sh 101 | sh js_test.sh 102 | -------------------------------------------------------------------------------- /.github/workflows/build-wheels.yml: -------------------------------------------------------------------------------- 1 | name: build - check - upload 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | release: 8 | types: [published] 9 | pull_request: 10 | branches: 11 | - '**' 12 | 13 | concurrency: 14 | # SHA is added to the end if on `main` to let all main workflows run 15 | group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }} 16 | cancel-in-progress: true 17 | 18 | permissions: 19 | contents: read 20 | id-token: write 21 | 22 | jobs: 23 | build-wheels: 24 | name: Wheel - ${{ matrix.os.name }} ${{ matrix.python.major-dot-minor }} ${{ matrix.arch.name }} 25 | runs-on: ${{ matrix.os.runs-on[matrix.arch.matrix] }} 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | os: 30 | - name: macOS 31 | matrix: macos 32 | runs-on: 33 | arm: [macOS, ARM64] 34 | intel: [macos-13] 35 | cibw-archs-macos: 36 | arm: arm64 37 | intel: x86_64 38 | - name: Ubuntu 39 | matrix: ubuntu 40 | runs-on: 41 | arm: [Linux, ARM64] 42 | intel: [ubuntu-latest] 43 | - name: Windows 44 | matrix: windows 45 | runs-on: 46 | intel: [windows-latest] 47 | python: 48 | - major-dot-minor: '3.8' 49 | cibw-build: 'cp38-*' 50 | manylinux: manylinux_2_28 51 | matrix: '3.8' 52 | - major-dot-minor: '3.9' 53 | cibw-build: 'cp39-*' 54 | manylinux: manylinux_2_28 55 | matrix: '3.9' 56 | - major-dot-minor: '3.10' 57 | cibw-build: 'cp310-*' 58 | manylinux: manylinux_2_28 59 | matrix: '3.10' 60 | - major-dot-minor: '3.11' 61 | cibw-build: 'cp311-*' 62 | manylinux: manylinux_2_28 63 | matrix: '3.11' 64 | - major-dot-minor: '3.12' 65 | cibw-build: 'cp312-*' 66 | manylinux: manylinux_2_28 67 | matrix: '3.12' 68 | 69 | arch: 70 | - name: ARM 71 | matrix: arm 72 | - name: Intel 73 | matrix: intel 74 | exclude: 75 | # Only partial entries are required here by GitHub Actions so generally I 76 | # only specify the `matrix:` entry. The super linter complains so for now 77 | # all entries are included to avoid that. Reported at 78 | # https://github.com/github/super-linter/issues/3016 79 | - os: 80 | name: Windows 81 | matrix: windows 82 | runs-on: 83 | intel: [windows-latest] 84 | arch: 85 | name: ARM 86 | matrix: arm 87 | 88 | steps: 89 | - name: Clean workspace 90 | uses: Chia-Network/actions/clean-workspace@main 91 | 92 | - name: Checkout code 93 | uses: actions/checkout@v4 94 | with: 95 | fetch-depth: 0 96 | 97 | - name: Set Env 98 | uses: Chia-Network/actions/setjobenv@main 99 | env: 100 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 101 | 102 | - uses: chia-network/actions/setup-python@main 103 | with: 104 | python-version: ${{ matrix.python.major-dot-minor }} 105 | 106 | - name: Install pipx 107 | run: | 108 | pip install pipx 109 | 110 | - name: Build and test 111 | env: 112 | CIBW_PRERELEASE_PYTHONS: True 113 | CIBW_BUILD: ${{ matrix.python.cibw-build }} 114 | CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.python.manylinux }} 115 | CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.python.manylinux }} 116 | CIBW_ARCHS_MACOS: ${{ matrix.os.cibw-archs-macos[matrix.arch.matrix] }} 117 | run: 118 | pipx run --spec='cibuildwheel==2.19.2' cibuildwheel --output-dir dist 2>&1 119 | 120 | - name: Upload artifacts 121 | uses: actions/upload-artifact@v4 122 | with: 123 | name: packages-${{ matrix.os.name }}-${{ matrix.python.major-dot-minor }}-${{ matrix.arch.name }} 124 | path: ./dist 125 | overwrite: true 126 | 127 | build-sdist: 128 | name: sdist - ${{ matrix.os.name }} ${{ matrix.python.major-dot-minor }} ${{ matrix.arch.name }} 129 | runs-on: ${{ matrix.os.runs-on[matrix.arch.matrix] }} 130 | strategy: 131 | fail-fast: false 132 | matrix: 133 | os: 134 | - name: Ubuntu 135 | matrix: ubuntu 136 | runs-on: 137 | arm: [Linux, ARM64] 138 | intel: [ubuntu-latest] 139 | python: 140 | - major-dot-minor: '3.9' 141 | matrix: '3.9' 142 | arch: 143 | - name: Intel 144 | matrix: intel 145 | 146 | steps: 147 | - name: Clean workspace 148 | uses: Chia-Network/actions/clean-workspace@main 149 | 150 | - name: Checkout code 151 | uses: actions/checkout@v4 152 | with: 153 | fetch-depth: 0 154 | 155 | - uses: Chia-Network/actions/setup-python@main 156 | with: 157 | python-version: ${{ matrix.python.major-dot-minor }} 158 | 159 | - name: Build source distribution 160 | run: | 161 | pip install build 162 | python -m build --sdist --outdir dist . 163 | 164 | - name: Upload artifacts 165 | uses: actions/upload-artifact@v4 166 | with: 167 | name: packages-sdist-${{ matrix.os.name }}-${{ matrix.python.major-dot-minor }}-${{ matrix.arch.name }} 168 | path: ./dist 169 | overwrite: true 170 | 171 | check: 172 | name: Check - ${{ matrix.os.name }} ${{ matrix.python.major-dot-minor }} ${{ matrix.arch.name }} 173 | runs-on: ${{ matrix.os.runs-on[matrix.arch.matrix] }} 174 | strategy: 175 | fail-fast: false 176 | matrix: 177 | os: 178 | - name: Ubuntu 179 | matrix: ubuntu 180 | runs-on: 181 | arm: [Linux, ARM64] 182 | intel: [ubuntu-latest] 183 | python: 184 | - major-dot-minor: '3.9' 185 | matrix: '3.9' 186 | arch: 187 | - name: Intel 188 | matrix: intel 189 | 190 | steps: 191 | - name: Clean workspace 192 | uses: Chia-Network/actions/clean-workspace@main 193 | 194 | - name: Checkout code 195 | uses: actions/checkout@v4 196 | with: 197 | fetch-depth: 0 198 | 199 | - uses: Chia-Network/actions/setup-python@main 200 | with: 201 | python-version: ${{ matrix.python.major-dot-minor }} 202 | 203 | - name: flake8 204 | run: | 205 | pip install flake8 206 | flake8 src setup.py python-bindings python-impl 207 | 208 | - name: mypy 209 | run: | 210 | pip install mypy 211 | mypy --config-file mypi.ini python-bindings python-impl 212 | 213 | upload: 214 | name: Upload to PyPI - ${{ matrix.os.name }} ${{ matrix.python.major-dot-minor }} ${{ matrix.arch.name }} 215 | runs-on: ${{ matrix.os.runs-on[matrix.arch.matrix] }} 216 | needs: 217 | - build-wheels 218 | - build-sdist 219 | - check 220 | strategy: 221 | fail-fast: false 222 | matrix: 223 | os: 224 | - name: Ubuntu 225 | matrix: ubuntu 226 | runs-on: 227 | arm: [Linux, ARM64] 228 | intel: [ubuntu-latest] 229 | python: 230 | - major-dot-minor: '3.9' 231 | matrix: '3.9' 232 | arch: 233 | - name: Intel 234 | matrix: intel 235 | 236 | steps: 237 | - name: Clean workspace 238 | uses: Chia-Network/actions/clean-workspace@main 239 | 240 | - name: Checkout code 241 | uses: actions/checkout@v4 242 | with: 243 | fetch-depth: 0 244 | 245 | - name: Set Env 246 | uses: Chia-Network/actions/setjobenv@main 247 | env: 248 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 249 | 250 | - uses: Chia-Network/actions/setup-python@main 251 | with: 252 | python-version: ${{ matrix.python.major-dot-minor }} 253 | 254 | - name: Download artifacts 255 | uses: actions/download-artifact@v4 256 | with: 257 | pattern: packages* 258 | path: ./dist 259 | merge-multiple: true 260 | 261 | - name: Publish distribution to PyPI 262 | if: env.RELEASE == 'true' 263 | uses: pypa/gh-action-pypi-publish@release/v1 264 | with: 265 | packages-dir: dist/ 266 | skip-existing: true 267 | 268 | - name: Publish distribution to Test PyPI 269 | if: env.PRE_RELEASE == 'true' 270 | uses: pypa/gh-action-pypi-publish@release/v1 271 | with: 272 | repository-url: https://test.pypi.org/legacy/ 273 | packages-dir: dist/ 274 | skip-existing: true 275 | -------------------------------------------------------------------------------- /.github/workflows/check-commit-signing.yml: -------------------------------------------------------------------------------- 1 | name: 🚨 Check commit signing 2 | 3 | on: 4 | push: 5 | branches: 6 | - long_lived/** 7 | - main 8 | - release/** 9 | pull_request: 10 | branches: 11 | - "**" 12 | 13 | concurrency: 14 | group: ${{ github.event_name == 'pull_request' && format('{0}-{1}', github.workflow_ref, github.event.pull_request.number) || github.run_id }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | check-commit-signing: 19 | name: Check commit signing 20 | runs-on: [ubuntu-latest] 21 | timeout-minutes: 5 22 | 23 | steps: 24 | - name: Checkout Code 25 | uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | 29 | - uses: chia-network/actions/check-commit-signing@main 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | schedule: 9 | - cron: "35 9 * * 1" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ javascript, python, cpp ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: After Prepare (cpp) 30 | if: ${{ matrix.language == 'cpp' }} 31 | run: | 32 | mkdir custom_cmake 33 | wget --quiet -O - "https://cmake.org/files/v3.16/cmake-3.16.3-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C custom_cmake 34 | export PATH=$(pwd)/custom_cmake/bin:${PATH} && echo "PATH=$PATH" >> $GITHUB_ENV 35 | cd $GITHUB_WORKSPACE/ 36 | export CMAKE_INCLUDE_PATH=$GITHUB_WORKSPACE/include:${CMAKE_INCLUDE_PATH} && echo "CMAKE_INCLUDE_PATH=$CMAKE_INCLUDE_PATH" >> $GITHUB_ENV 37 | export CMAKE_LIBRARY_PATH=$GITHUB_WORKSPACE/lib:${CMAKE_LIBRARY_PATH} && echo "CMAKE_LIBRARY_PATH=$CMAKE_LIBRARY_PATH" >> $GITHUB_ENV 38 | mkdir $GITHUB_WORKSPACE/_lgtm_build_dir 39 | cd $GITHUB_WORKSPACE/_lgtm_build_dir 40 | 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v3 43 | with: 44 | languages: ${{ matrix.language }} 45 | queries: +security-and-quality 46 | 47 | - name: Autobuild 48 | uses: github/codeql-action/autobuild@v3 49 | 50 | - name: Perform CodeQL Analysis 51 | uses: github/codeql-action/analyze@v3 52 | with: 53 | category: "/language:${{ matrix.language }}" 54 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Managed by repo-content-updater 2 | # Dependency Review Action 3 | # 4 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 5 | # 6 | # Source repository: https://github.com/actions/dependency-review-action 7 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 8 | name: "🚨 Dependency Review" 9 | on: [pull_request] 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | dependency-review: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: "Checkout Repository" 19 | uses: actions/checkout@v4 20 | 21 | - name: "Dependency Review" 22 | uses: actions/dependency-review-action@v4 23 | with: 24 | allow-dependencies-licenses: pkg:pypi/pyinstaller 25 | deny-licenses: AGPL-1.0-only, AGPL-1.0-or-later, AGPL-1.0-or-later, AGPL-3.0-or-later, GPL-1.0-only, GPL-1.0-or-later, GPL-2.0-only, GPL-2.0-or-later, GPL-3.0-only, GPL-3.0-or-later 26 | -------------------------------------------------------------------------------- /.github/workflows/js-bindings.yml: -------------------------------------------------------------------------------- 1 | name: Build & Publish JS Bindings 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | release: 8 | types: [published] 9 | pull_request: 10 | branches: 11 | - '**' 12 | 13 | concurrency: 14 | # SHA is added to the end if on `main` to let all main workflows run 15 | group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}-${{ (github.ref == 'refs/heads/main') && github.sha || '' }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | js_bindings: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Set Env 27 | uses: Chia-Network/actions/setjobenv@main 28 | env: 29 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - uses: actions/setup-node@v4 32 | with: 33 | node-version: 16 34 | 35 | - name: Install emsdk 36 | uses: mymindstorm/setup-emsdk@v14 37 | 38 | - name: Get the version 39 | id: version_info 40 | run: echo "SOURCE_TAG=${GITHUB_REF#refs/tags/}" >>$GITHUB_OUTPUT 41 | 42 | - name: Update version in package.json 43 | if: startsWith(github.ref, 'refs/tags/') 44 | working-directory: ${{ github.workspace }}/js-bindings 45 | env: 46 | SOURCE_TAG: ${{ steps.version_info.outputs.SOURCE_TAG }} 47 | run: | 48 | jq --arg VER "$SOURCE_TAG" '.version=$VER' package.json > temp.json && mv temp.json package.json 49 | 50 | - name: Build JS 51 | run: ./js_build.sh 52 | 53 | - name: Publish 54 | if: env.FULL_RELEASE == 'true' 55 | working-directory: ${{ github.workspace }}/js_build/js-bindings 56 | env: 57 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 58 | run: | 59 | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc 60 | npm publish --access public 61 | 62 | - name: Cleanup 63 | if: always() 64 | run: 65 | rm ${{ github.workspace }}/js_build/js-bindings/.npmrc || true 66 | -------------------------------------------------------------------------------- /.github/workflows/stale-issue.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 'Close stale issues' 3 | on: 4 | schedule: 5 | - cron: '0 11 * * *' 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v9 12 | with: 13 | operations-per-run: 10000 14 | ascending: true 15 | days-before-issue-stale: 14 16 | days-before-issue-close: 7 17 | days-before-pr-stale: 60 18 | days-before-pr-close: -1 19 | exempt-all-pr-milestones: true 20 | exempt-all-issue-milestones: true 21 | exempt-all-assignees: true 22 | stale-issue-label: stale-issue 23 | stale-pr-label: stale-pr 24 | remove-stale-when-updated: true 25 | stale-issue-message: > 26 | 'This issue has been flagged as stale as there has been no 27 | activity on it in 14 days. If this issue is still affecting you 28 | and in need of review, please update it to keep it open.' 29 | close-issue-message: > 30 | 'This issue was automatically closed because it has been flagged 31 | as stale and subsequently passed 7 days with no further activity.' 32 | stale-pr-message: > 33 | 'This PR has been flagged as stale due to no activity for over 60 34 | days. It will not be automatically closed, but it has been given 35 | a stale-pr label and should be manually reviewed.' 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | cmake_install.cmake 4 | Makefile 5 | src/bench 6 | src/test 7 | src/MakefilE 8 | src/cmake_install.cmake 9 | src/CMakeFile 10 | build/* 11 | blspy.egg-info 12 | dist 13 | python-impl/__pycache__/ 14 | blspy.*.so 15 | .mypy_cache/ 16 | .pytest_chache/ 17 | .eggs/ 18 | 19 | js_build 20 | node_modules 21 | 22 | main 23 | .o 24 | obj/ 25 | src/*.o 26 | 27 | .idea 28 | .vscode 29 | blstest 30 | blstest.* 31 | blsbench 32 | blsbench.* 33 | 34 | .dirstamp 35 | .libs 36 | .*.swp 37 | *.*~* 38 | *.bak 39 | *.rej 40 | *.orig 41 | *.pyc 42 | *.o 43 | *.o-* 44 | *.patch 45 | *.a 46 | *.pb.cc 47 | *.pb.h 48 | 49 | **/.DS_Store 50 | 51 | *.whl 52 | venv 53 | env/ 54 | yarn-error.log 55 | 56 | .vs/ 57 | out/ 58 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.14.0 FATAL_ERROR) 2 | set(CMAKE_CXX_STANDARD 17) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | set(CMAKE_C_STANDARD 99) 5 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15") 6 | 7 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 8 | 9 | if(NOT CMAKE_BUILD_TYPE) 10 | set(CMAKE_BUILD_TYPE "Release" 11 | CACHE STRING "Possible values are empty, Debug, Release, RelWithDebInfo, MinSizeRel, ..." 12 | FORCE 13 | ) 14 | endif() 15 | 16 | project(BLS) 17 | 18 | if(MSVC) 19 | enable_language(ASM_MASM) 20 | else() 21 | enable_language(ASM) 22 | endif() 23 | 24 | set(BUILD_BLS_PYTHON_BINDINGS "1" CACHE STRING "") 25 | set(BUILD_BLS_TESTS "1" CACHE STRING "") 26 | set(BUILD_BLS_BENCHMARKS "1" CACHE STRING "") 27 | 28 | message(STATUS "Build python bindings: ${BUILD_BLS_PYTHON_BINDINGS}") 29 | message(STATUS "Build tests: ${BUILD_BLS_TESTS}") 30 | message(STATUS "Build benchmarks: ${BUILD_BLS_BENCHMARKS}") 31 | 32 | # Add path for custom modules 33 | set(CMAKE_MODULE_PATH 34 | ${CMAKE_MODULE_PATH} 35 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules 36 | ) 37 | 38 | include(FetchContent) 39 | 40 | FetchContent_Declare(Sodium 41 | GIT_REPOSITORY https://github.com/AmineKhaldi/libsodium-cmake.git 42 | 43 | # Latest commit at the moment this was added here 44 | # Anchored to libsodium v1.0.18 45 | GIT_TAG f73a3fe1afdc4e37ac5fe0ddd401bf521f6bba65 46 | ) 47 | set(SODIUM_PCH "on" CACHE STRING "") 48 | set(SODIUM_DISABLE_TESTS "on" CACHE STRING "") 49 | set(SODIUM_CHIA_MINIMAL "on" CACHE STRING "") 50 | FetchContent_MakeAvailable(Sodium) 51 | 52 | if (DEFINED ENV{BLST_MAIN}) 53 | set(BLST_GIT_TAG "origin/master") 54 | else () 55 | # This is currently anchored to upstream 3dd0f804b1819e5d03fb22ca2e6fac105932043a dated 2023-08-09 v0.3.11 56 | set(BLST_GIT_TAG "3dd0f804b1819e5d03fb22ca2e6fac105932043a") 57 | endif () 58 | set(BLST_REPOSITORY "https://github.com/supranational/blst.git") 59 | 60 | message(STATUS "blst will be built from: ${BLST_GIT_TAG} and repository ${BLST_REPOSITORY}") 61 | 62 | FetchContent_Declare( 63 | blst 64 | GIT_REPOSITORY ${BLST_REPOSITORY} 65 | GIT_TAG ${BLST_GIT_TAG} 66 | ) 67 | FetchContent_MakeAvailable(blst) 68 | 69 | add_subdirectory(src) 70 | 71 | if(EMSCRIPTEN) 72 | add_subdirectory(js-bindings) 73 | else() 74 | # emscripten can't build python bindings, it produces only javascript 75 | # add_subdirectory(contrib/pybind11) 76 | if(BUILD_BLS_PYTHON_BINDINGS) 77 | add_subdirectory(python-bindings) 78 | endif() 79 | endif() 80 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md LICENSE 2 | global-include CMakeLists.txt *.cmake 3 | recursive-include cmake_modules * 4 | recursive-include src * 5 | recursive-include python-bindings * 6 | recursive-include contrib * -------------------------------------------------------------------------------- /cmake_modules/Findsodium.cmake: -------------------------------------------------------------------------------- 1 | # Written in 2016 by Henrik Steffen Gaßmann 2 | # 3 | # To the extent possible under law, the author(s) have dedicated all 4 | # copyright and related and neighboring rights to this software to the 5 | # public domain worldwide. This software is distributed without any warranty. 6 | # 7 | # You should have received a copy of the CC0 Public Domain Dedication 8 | # along with this software. If not, see 9 | # 10 | # http://creativecommons.org/publicdomain/zero/1.0/ 11 | # 12 | ######################################################################## 13 | # Tries to find the local libsodium installation. 14 | # 15 | # On Windows the sodium_DIR environment variable is used as a default 16 | # hint which can be overridden by setting the corresponding cmake variable. 17 | # 18 | # Once done the following variables will be defined: 19 | # 20 | # sodium_FOUND 21 | # sodium_INCLUDE_DIR 22 | # sodium_LIBRARY_DEBUG 23 | # sodium_LIBRARY_RELEASE 24 | # 25 | # 26 | # Furthermore an imported "sodium" target is created. 27 | # 28 | 29 | if (CMAKE_C_COMPILER_ID STREQUAL "GNU" 30 | OR CMAKE_C_COMPILER_ID STREQUAL "Clang") 31 | set(_GCC_COMPATIBLE 1) 32 | endif() 33 | 34 | # static library option 35 | if (NOT DEFINED sodium_USE_STATIC_LIBS) 36 | option(sodium_USE_STATIC_LIBS "enable to statically link against sodium" OFF) 37 | endif() 38 | if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST)) 39 | unset(sodium_LIBRARY CACHE) 40 | unset(sodium_LIBRARY_DEBUG CACHE) 41 | unset(sodium_LIBRARY_RELEASE CACHE) 42 | unset(sodium_DLL_DEBUG CACHE) 43 | unset(sodium_DLL_RELEASE CACHE) 44 | set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable") 45 | endif() 46 | 47 | 48 | ######################################################################## 49 | # UNIX 50 | if (UNIX) 51 | # import pkg-config 52 | find_package(PkgConfig QUIET) 53 | if (PKG_CONFIG_FOUND) 54 | pkg_check_modules(sodium_PKG QUIET libsodium) 55 | endif() 56 | 57 | if(sodium_USE_STATIC_LIBS) 58 | foreach(_libname ${sodium_PKG_STATIC_LIBRARIES}) 59 | if (NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending with .a 60 | list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a") 61 | endif() 62 | endforeach() 63 | list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES) 64 | 65 | # if pkgconfig for libsodium doesn't provide 66 | # static lib info, then override PKG_STATIC here.. 67 | if (NOT sodium_PKG_STATIC_FOUND) 68 | set(sodium_PKG_STATIC_LIBRARIES libsodium.a) 69 | endif() 70 | 71 | set(XPREFIX sodium_PKG_STATIC) 72 | else() 73 | if (NOT sodium_PKG_FOUND) 74 | set(sodium_PKG_LIBRARIES sodium) 75 | endif() 76 | 77 | set(XPREFIX sodium_PKG) 78 | endif() 79 | 80 | find_path(sodium_INCLUDE_DIR sodium.h 81 | HINTS ${${XPREFIX}_INCLUDE_DIRS} 82 | ) 83 | find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} 84 | HINTS ${${XPREFIX}_LIBRARY_DIRS} 85 | ) 86 | find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} 87 | HINTS ${${XPREFIX}_LIBRARY_DIRS} 88 | ) 89 | 90 | 91 | ######################################################################## 92 | # Windows 93 | elseif (WIN32) 94 | set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory") 95 | mark_as_advanced(sodium_DIR) 96 | 97 | find_path(sodium_INCLUDE_DIR sodium.h 98 | HINTS ${sodium_DIR} 99 | PATH_SUFFIXES include 100 | ) 101 | 102 | if (MSVC) 103 | # detect target architecture 104 | file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" [=[ 105 | #if defined _M_IX86 106 | #error ARCH_VALUE x86_32 107 | #elif defined _M_X64 108 | #error ARCH_VALUE x86_64 109 | #endif 110 | #error ARCH_VALUE unknown 111 | ]=]) 112 | try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" 113 | OUTPUT_VARIABLE _COMPILATION_LOG 114 | ) 115 | string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}") 116 | 117 | # construct library path 118 | if (_TARGET_ARCH STREQUAL "x86_32") 119 | string(APPEND _PLATFORM_PATH "Win32") 120 | elseif(_TARGET_ARCH STREQUAL "x86_64") 121 | string(APPEND _PLATFORM_PATH "x64") 122 | else() 123 | message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.") 124 | endif() 125 | string(APPEND _PLATFORM_PATH "/$$CONFIG$$") 126 | 127 | if (MSVC_VERSION LESS 1900) 128 | math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60") 129 | else() 130 | math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50") 131 | endif() 132 | string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}") 133 | 134 | if (sodium_USE_STATIC_LIBS) 135 | string(APPEND _PLATFORM_PATH "/static") 136 | else() 137 | string(APPEND _PLATFORM_PATH "/dynamic") 138 | endif() 139 | 140 | string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}") 141 | string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}") 142 | 143 | find_library(sodium_LIBRARY_DEBUG libsodium.lib 144 | HINTS ${sodium_DIR} 145 | PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} 146 | ) 147 | find_library(sodium_LIBRARY_RELEASE libsodium.lib 148 | HINTS ${sodium_DIR} 149 | PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} 150 | ) 151 | if (NOT sodium_USE_STATIC_LIBS) 152 | set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES}) 153 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") 154 | find_library(sodium_DLL_DEBUG libsodium 155 | HINTS ${sodium_DIR} 156 | PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} 157 | ) 158 | find_library(sodium_DLL_RELEASE libsodium 159 | HINTS ${sodium_DIR} 160 | PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} 161 | ) 162 | set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK}) 163 | endif() 164 | 165 | elseif(_GCC_COMPATIBLE) 166 | if (sodium_USE_STATIC_LIBS) 167 | find_library(sodium_LIBRARY_DEBUG libsodium.a 168 | HINTS ${sodium_DIR} 169 | PATH_SUFFIXES lib 170 | ) 171 | find_library(sodium_LIBRARY_RELEASE libsodium.a 172 | HINTS ${sodium_DIR} 173 | PATH_SUFFIXES lib 174 | ) 175 | else() 176 | find_library(sodium_LIBRARY_DEBUG libsodium.dll.a 177 | HINTS ${sodium_DIR} 178 | PATH_SUFFIXES lib 179 | ) 180 | find_library(sodium_LIBRARY_RELEASE libsodium.dll.a 181 | HINTS ${sodium_DIR} 182 | PATH_SUFFIXES lib 183 | ) 184 | 185 | file(GLOB _DLL 186 | LIST_DIRECTORIES false 187 | RELATIVE "${sodium_DIR}/bin" 188 | "${sodium_DIR}/bin/libsodium*.dll" 189 | ) 190 | find_library(sodium_DLL_DEBUG ${_DLL} libsodium 191 | HINTS ${sodium_DIR} 192 | PATH_SUFFIXES bin 193 | ) 194 | find_library(sodium_DLL_RELEASE ${_DLL} libsodium 195 | HINTS ${sodium_DIR} 196 | PATH_SUFFIXES bin 197 | ) 198 | endif() 199 | else() 200 | message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") 201 | endif() 202 | 203 | 204 | ######################################################################## 205 | # unsupported 206 | else() 207 | message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") 208 | endif() 209 | 210 | 211 | ######################################################################## 212 | # common stuff 213 | 214 | # extract sodium version 215 | if (sodium_INCLUDE_DIR) 216 | set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h") 217 | if (EXISTS _VERSION_HEADER) 218 | file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT) 219 | string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1" 220 | sodium_VERSION "${_VERSION_HEADER_CONTENT}") 221 | set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE) 222 | endif() 223 | endif() 224 | 225 | # communicate results 226 | include(FindPackageHandleStandardArgs) 227 | find_package_handle_standard_args( 228 | sodium # The name must be either uppercase or match the filename case. 229 | REQUIRED_VARS 230 | sodium_LIBRARY_RELEASE 231 | sodium_LIBRARY_DEBUG 232 | sodium_INCLUDE_DIR 233 | VERSION_VAR 234 | sodium_VERSION 235 | ) 236 | 237 | if(Sodium_FOUND) 238 | set(sodium_LIBRARIES 239 | optimized ${sodium_LIBRARY_RELEASE} debug ${sodium_LIBRARY_DEBUG}) 240 | endif() 241 | 242 | # mark file paths as advanced 243 | mark_as_advanced(sodium_INCLUDE_DIR) 244 | mark_as_advanced(sodium_LIBRARY_DEBUG) 245 | mark_as_advanced(sodium_LIBRARY_RELEASE) 246 | if (WIN32) 247 | mark_as_advanced(sodium_DLL_DEBUG) 248 | mark_as_advanced(sodium_DLL_RELEASE) 249 | endif() 250 | 251 | # create imported target 252 | if(sodium_USE_STATIC_LIBS) 253 | set(_LIB_TYPE STATIC) 254 | else() 255 | set(_LIB_TYPE SHARED) 256 | endif() 257 | add_library(sodium ${_LIB_TYPE} IMPORTED) 258 | 259 | set_target_properties(sodium PROPERTIES 260 | INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}" 261 | IMPORTED_LINK_INTERFACE_LANGUAGES "C" 262 | ) 263 | 264 | if (sodium_USE_STATIC_LIBS) 265 | set_target_properties(sodium PROPERTIES 266 | INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC" 267 | IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" 268 | IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" 269 | ) 270 | else() 271 | if (UNIX) 272 | set_target_properties(sodium PROPERTIES 273 | IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" 274 | IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" 275 | ) 276 | elseif (WIN32) 277 | set_target_properties(sodium PROPERTIES 278 | IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}" 279 | IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}" 280 | ) 281 | if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND")) 282 | set_target_properties(sodium PROPERTIES 283 | IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}" 284 | ) 285 | endif() 286 | if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND")) 287 | set_target_properties(sodium PROPERTIES 288 | IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}" 289 | IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}" 290 | IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}" 291 | ) 292 | endif() 293 | endif() 294 | endif() 295 | -------------------------------------------------------------------------------- /emsdk_build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git submodule update --init --recursive 4 | 5 | rm -rf js_build 6 | mkdir -p js_build 7 | cd js_build 8 | 9 | emcmake cmake -G "Unix Makefiles" -DBUILD_BLS_TESTS=0 -DBUILD_BLS_BENCHMARKS=0 .. 10 | emmake make 11 | -------------------------------------------------------------------------------- /js-bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.14.0 FATAL_ERROR) 2 | set(CMAKE_CXX_STANDARD 17) 3 | 4 | include_directories( 5 | ${INCLUDE_DIRECTORIES} 6 | ${CMAKE_CURRENT_SOURCE_DIR}/../contrib/catch 7 | ) 8 | 9 | file(GLOB_RECURSE WRAP_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/wrappers/*.h) 10 | file(GLOB_RECURSE WRAP_SRC ${CMAKE_CURRENT_SOURCE_DIR}/wrappers/*.cpp) 11 | 12 | add_executable(blsjs ${CMAKE_CURRENT_SOURCE_DIR}/jsbindings.cpp 13 | ${WRAP_HEADERS} ${WRAP_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/helpers.h ${CMAKE_CURRENT_SOURCE_DIR}/helpers.cpp 14 | ) 15 | add_custom_target(install_npm_dependencies npm ci) 16 | add_dependencies(blsjs install_npm_dependencies) 17 | target_link_libraries(blsjs PRIVATE bls) 18 | 19 | # Copy necessary files for the npm package 20 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/package.json package.json COPYONLY) 21 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/package-lock.json package-lock.json COPYONLY) 22 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/blsjs.d.ts blsjs.d.ts COPYONLY) 23 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/README.md README.md COPYONLY) 24 | 25 | # Copy test files 26 | file(GLOB JS_BINDINGS_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/tests/ ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.js ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.ts) 27 | foreach(file ${JS_BINDINGS_TESTS}) 28 | message(FILE ${file}) 29 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/${file} tests/${file} COPYONLY) 30 | endforeach() 31 | 32 | set_target_properties(blsjs PROPERTIES LINK_FLAGS "--bind -Oz --closure 1 -s MODULARIZE=1") 33 | -------------------------------------------------------------------------------- /js-bindings/README.md: -------------------------------------------------------------------------------- 1 | ## bls-signatures 2 | 3 | JavaScript library that implements BLS signatures with aggregation as in [Boneh, Drijvers, Neven 2018](https://crypto.stanford.edu/~dabo/pubs/papers/BLSmultisig.html), using the relic toolkit for cryptographic primitives (pairings, EC, hashing). 4 | 5 | This library is a JavaScript port of the [Chia Network's BLS lib](https://github.com/Chia-Network/bls-signatures). We also have typings, so you can use it with TypeScript too! 6 | 7 | ### Usage 8 | 9 | ```bash 10 | npm i bls-signatures --save # or yarn add bls-signatures 11 | ``` 12 | 13 | ### Creating keys and signatures 14 | ```javascript 15 | var loadBls = require("bls-signatures"); 16 | var BLS = await loadBls(); 17 | 18 | var seed = Uint8Array.from([ 19 | 0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192, 20 | 19, 18, 12, 89, 6, 220, 18, 102, 58, 209, 82, 21 | 12, 62, 89, 110, 182, 9, 44, 20, 254, 22 22 | ]); 23 | 24 | var sk = BLS.AugSchemeMPL.key_gen(seed); 25 | var pk = sk.get_g1(); 26 | 27 | var message = Uint8Array.from([1,2,3,4,5]); 28 | var signature = BLS.AugSchemeMPL.sign(sk, message); 29 | 30 | let ok = BLS.AugSchemeMPL.verify(pk, message, signature); 31 | console.log(ok); // true 32 | ``` 33 | 34 | ### Serializing keys and signatures to bytes 35 | ```javascript 36 | var skBytes = sk.serialize(); 37 | var pkBytes = pk.serialize(); 38 | var signatureBytes = signature.serialize(); 39 | 40 | console.log(BLS.Util.hex_str(skBytes)); 41 | console.log(BLS.Util.hex_str(pkBytes)); 42 | console.log(BLS.Util.hex_str(signatureBytes)); 43 | 44 | ``` 45 | 46 | ### Loading keys and signatures from bytes 47 | ```javascript 48 | var skc = BLS.PrivateKey.from_bytes(skBytes, false); 49 | var pk = BLS.G1Element.from_bytes(pkBytes); 50 | 51 | var signature = BLS.G2Element.from_bytes(signatureBytes); 52 | ``` 53 | 54 | ### Create aggregate signatures 55 | ```javascript 56 | // Generate some more private keys 57 | seed[0] = 1; 58 | var sk1 = BLS.AugSchemeMPL.key_gen(seed); 59 | seed[0] = 2; 60 | var sk2 = BLS.AugSchemeMPL.key_gen(seed); 61 | var message2 = Uint8Array.from([1,2,3,4,5,6,7]); 62 | 63 | // Generate first sig 64 | var pk1 = sk1.get_g1(); 65 | var sig1 = BLS.AugSchemeMPL.sign(sk1, message); 66 | 67 | // Generate second sig 68 | var pk2 = sk2.get_g1(); 69 | var sig2 = BLS.AugSchemeMPL.sign(sk2, message2); 70 | 71 | // Signatures can be non-interactively combined by anyone 72 | var aggSig = BLS.AugSchemeMPL.aggregate([sig1, sig2]); 73 | 74 | ok = BLS.AugSchemeMPL.aggregate_verify([pk1, pk2], [message, message2], aggSig); 75 | console.log(ok); // true 76 | 77 | ``` 78 | 79 | ### Arbitrary trees of aggregates 80 | ```javascript 81 | seed[0] = 3; 82 | var sk3 = BLS.AugSchemeMPL.key_gen(seed); 83 | var pk3 = sk3.get_g1(); 84 | var message3 = Uint8Array.from([100, 2, 254, 88, 90, 45, 23]); 85 | var sig3 = BLS.AugSchemeMPL.sign(sk3, message3); 86 | 87 | var aggSigFinal = BLS.AugSchemeMPL.aggregate([aggSig, sig3]); 88 | ok = BLS.AugSchemeMPL.aggregate_verify([pk1, pk2, pk3], [message, message2, message3], aggSigFinal); 89 | console.log(ok); // true 90 | ``` 91 | 92 | ### Very fast verification with Proof of Possession scheme 93 | ```javascript 94 | 95 | // If the same message is signed, you can use Proof of Posession (PopScheme) for efficiency 96 | // A proof of possession MUST be passed around with the PK to ensure security. 97 | var popSig1 = BLS.PopSchemeMPL.sign(sk1, message); 98 | var popSig2 = BLS.PopSchemeMPL.sign(sk2, message); 99 | var popSig3 = BLS.PopSchemeMPL.sign(sk3, message); 100 | var pop1 = BLS.PopSchemeMPL.pop_prove(sk1); 101 | var pop2 = BLS.PopSchemeMPL.pop_prove(sk2); 102 | var pop3 = BLS.PopSchemeMPL.pop_prove(sk3); 103 | 104 | ok = BLS.PopSchemeMPL.pop_verify(pk1, pop1); 105 | console.log(ok); // true 106 | ok = BLS.PopSchemeMPL.pop_verify(pk2, pop2); 107 | console.log(ok); // true 108 | ok = BLS.PopSchemeMPL.pop_verify(pk3, pop3); 109 | console.log(ok); // true 110 | 111 | var popSigAgg = BLS.PopSchemeMPL.aggregate([popSig1, popSig2, popSig3]); 112 | ok = BLS.PopSchemeMPL.fast_aggregate_verify([pk1, pk2, pk3], message, popSigAgg); 113 | console.log(ok); // true 114 | 115 | // Aggregate public key, indistinguishable from a single public key 116 | var popAggPk = pk1.add(pk2).add(pk3); 117 | ok = BLS.PopSchemeMPL.verify(popAggPk, message, popSigAgg); 118 | console.log(ok); // true 119 | 120 | // Aggregate private keys 121 | var aggSk = BLS.PrivateKey.aggregate([sk1, sk2, sk3]); 122 | ok = (BLS.PopSchemeMPL.sign(aggSk, message).equal_to(popSigAgg)); 123 | console.log(ok); // true 124 | ``` 125 | 126 | ### HD keys using [EIP-2333](https://github.com/ethereum/EIPs/pull/2333) 127 | ```javascript 128 | // You can derive 'child' keys from any key, to create arbitrary trees. 4 byte indeces are used. 129 | // Hardened (more secure, but no parent pk -> child pk) 130 | var masterSk = BLS.AugSchemeMPL.key_gen(seed); 131 | var child = BLS.AugSchemeMPL.derive_child_sk(masterSk, 152); 132 | var grandChild = BLS.AugSchemeMPL.derive_child_sk(child, 952); 133 | 134 | // Unhardened (less secure, but can go from parent pk -> child pk), BIP32 style 135 | var masterPk = masterSk.get_g1(); 136 | var childU = BLS.AugSchemeMPL.derive_child_sk_unhardened(masterSk, 22); 137 | var grandchildU = BLS.AugSchemeMPL.derive_child_sk_unhardened(childU, 0); 138 | 139 | var childUPk = BLS.AugSchemeMPL.derive_child_pk_unhardened(masterPk, 22); 140 | var grandchildUPk = BLS.AugSchemeMPL.derive_child_pk_unhardened(childUPk, 0); 141 | 142 | ok = (grandchildUPk.equal_to(grandchildU.get_g1())); 143 | console.log(ok); // true 144 | ``` 145 | 146 | Please refer to the library's [typings](./blsjs.d.ts) for detailed API information. Use cases can be found in the [original lib's readme](../README.md). 147 | 148 | __Important note on usage:__ Since this library is a WebAssembly port of the c++ library, JavaScript's automatic memory management isn't available. Please, delete all objects manually if they are not needed anymore by calling the delete method on them, as shown in the example below. 149 | 150 | ```javascript 151 | sk.delete(); 152 | // ... 153 | pk.delete(); 154 | // ... 155 | sig1.delete(); 156 | // ... 157 | ``` 158 | 159 | ### Build 160 | 161 | Building requires Node.js (with npm) and [Emscripten](https://emscripten.org/docs/getting_started/downloads.html) to be installed. 162 | The build process is the same as for the c++ lib, with one additional step: pass the Emscripten toolchain file as an option to CMake. 163 | From the project root directory, run: 164 | ``` 165 | #git submodule update --init --recursive 166 | mkdir js_build 167 | cd js_build 168 | cmake ../ -DCMAKE_TOOLCHAIN_FILE={path_to_your_emscripten_installation}/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake 169 | cmake --build . -- 170 | ``` 171 | 172 | Run the build after any changes to the library, including readme and tests, as the library will be deployed from the build directory, and the build system copies all the files from the source dir. 173 | ### Run tests 174 | Tests are run in node.js and Firefox, therefore you need to install node.js and Firefox. 175 | To run tests, build the library, then go to the `js_bindings` folder in the build directory and run 176 | ```bash 177 | npm test 178 | ``` 179 | -------------------------------------------------------------------------------- /js-bindings/blsjs.d.ts: -------------------------------------------------------------------------------- 1 | export declare class AugSchemeMPL { 2 | static sk_to_g1(sk: PrivateKey): G1Element; 3 | static key_gen(msg: Uint8Array): PrivateKey; 4 | static sign(sk: PrivateKey, msg: Uint8Array): G2Element; 5 | static sign_prepend(sk: PrivateKey, msg: Uint8Array, prependPk: G1Element): G2Element; 6 | static verify(pk: G1Element, msg: Uint8Array, sig: G2Element): boolean; 7 | static aggregate(g2Elements: G2Element[]): G2Element; 8 | static aggregate_verify(pks: G1Element[], msgs: Uint8Array[], sig: G2Element): boolean; 9 | static derive_child_sk(sk: PrivateKey, index: number): PrivateKey; 10 | static derive_child_sk_unhardened(sk: PrivateKey, index: number): PrivateKey; 11 | static derive_child_pk_unhardened(pk: G1Element, index: number): G1Element; 12 | } 13 | 14 | export declare class BasicSchemeMPL { 15 | static sk_to_g1(sk: PrivateKey): G1Element; 16 | static key_gen(msg: Uint8Array): PrivateKey; 17 | static sign(sk: PrivateKey, msg: Uint8Array): G2Element; 18 | static verify(pk: G1Element, msg: Uint8Array, sig: G2Element): boolean; 19 | static aggregate(g2Elements: G2Element[]): G2Element; 20 | static aggregate_verify(pks: G1Element[], msgs: Uint8Array[], sig: G2Element): boolean; 21 | static derive_child_sk(sk: PrivateKey, index: number): PrivateKey; 22 | static derive_child_sk_unhardened(sk: PrivateKey, index: number): PrivateKey; 23 | static derive_child_pk_unhardened(pk: G1Element, index: number): G1Element; 24 | } 25 | 26 | export declare class PopSchemeMPL { 27 | static sk_to_g1(sk: PrivateKey): G1Element; 28 | static key_gen(msg: Uint8Array): PrivateKey; 29 | static sign(sk: PrivateKey, msg: Uint8Array): G2Element; 30 | static verify(pk: G1Element, msg: Uint8Array, sig: G2Element): boolean; 31 | static aggregate(g2Elements: G2Element[]): G2Element; 32 | static aggregate_verify(pks: G1Element[], msgs: Uint8Array[], sig: G2Element): boolean; 33 | static derive_child_sk(sk: PrivateKey, index: number): PrivateKey; 34 | static derive_child_sk_unhardened(sk: PrivateKey, index: number): PrivateKey; 35 | static derive_child_pk_unhardened(pk: G1Element, index: number): G1Element; 36 | static pop_prove(sk: PrivateKey): G2Element; 37 | static pop_verify(pk: G1Element, signatureProof: G2Element): boolean; 38 | static fast_aggregate_verify(pks: G1Element[], msg: Uint8Array, sig: G2Element): boolean; 39 | } 40 | 41 | export declare class G1Element { 42 | static SIZE: number; 43 | static from_bytes(bytes: Uint8Array): G1Element; 44 | static generator(): G2Element; 45 | serialize(): Uint8Array; 46 | negate(): G1Element; 47 | deepcopy(): G1Element; 48 | get_fingerprint(): number; 49 | add(el: G1Element): G1Element; 50 | equal_to(el: G1Element): boolean; 51 | delete(): void; 52 | } 53 | 54 | export declare class G2Element { 55 | static SIZE: number; 56 | static from_bytes(bytes: Uint8Array): G2Element; 57 | static from_g2(sk: G2Element): G2Element; 58 | static aggregate_sigs(sigs: G2Element[]): G2Element; 59 | static generator(): G2Element; 60 | serialize(): Uint8Array; 61 | negate(): G2Element; 62 | deepcopy(): G2Element; 63 | add(el: G2Element): G2Element; 64 | equal_to(el: G2Element): boolean; 65 | delete(): void; 66 | } 67 | 68 | export declare class PrivateKey { 69 | static PRIVATE_KEY_SIZE: number; 70 | static from_bytes(bytes: Uint8Array, modOrder: boolean): PrivateKey; 71 | static aggregate(pks: PrivateKey[]): PrivateKey; 72 | deepcopy(): PrivateKey; 73 | serialize(): Uint8Array; 74 | get_g1(): G1Element; 75 | get_g2(): G2Element; 76 | mul_g1(el: G1Element): G1Element; 77 | mul_g2(el: G2Element): G2Element; 78 | equal_to(key: PrivateKey): boolean; 79 | delete(): void; 80 | } 81 | 82 | export declare class Util { 83 | static hash256(msg: Uint8Array): Uint8Array; 84 | static hex_str(msg: Uint8Array): string; 85 | } 86 | 87 | export interface ModuleInstance { 88 | AugSchemeMPL: typeof AugSchemeMPL; 89 | BasicSchemeMPL: typeof BasicSchemeMPL; 90 | PopSchemeMPL: typeof PopSchemeMPL; 91 | G1Element: typeof G1Element; 92 | G2Element: typeof G2Element; 93 | PrivateKey: typeof PrivateKey; 94 | Util: typeof Util; 95 | } 96 | 97 | export default function createModule(options?: {}): Promise; 98 | -------------------------------------------------------------------------------- /js-bindings/helpers.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "./helpers.h" 16 | 17 | namespace helpers { 18 | val toUint8Array(std::vector vec) { 19 | val arr = helpers::toJSArray(vec); 20 | return val::global("Uint8Array").call("from", arr); 21 | } 22 | 23 | val toUint8Array(uint8_t *pointer, size_t data_size) { 24 | std::vector vec = toVector(pointer, data_size); 25 | val buffer = toUint8Array(vec); 26 | return buffer; 27 | } 28 | 29 | std::vector toVector(uint8_t *pointer, size_t data_size) { 30 | std::vector data; 31 | data.reserve(data_size); 32 | std::copy(pointer, pointer + data_size, std::back_inserter(data)); 33 | return data; 34 | } 35 | 36 | std::vector toVector(val jsUint8Array) { 37 | auto l = jsUint8Array["length"].as(); 38 | std::vector vec; 39 | for (unsigned i = 0; i < l; ++i) { 40 | vec.push_back(jsUint8Array[i].as()); 41 | } 42 | return vec; 43 | } 44 | 45 | std::vector> jsBuffersArrayToVector(val buffersArray) { 46 | auto l = buffersArray["length"].as(); 47 | std::vector> vec; 48 | for (unsigned i = 0; i < l; ++i) { 49 | vec.push_back(toVector(buffersArray[i].as())); 50 | } 51 | return vec; 52 | } 53 | 54 | val byteArraysVectorToJsBuffersArray(std::vector arraysVector, size_t element_size) { 55 | auto vecSize = arraysVector.size(); 56 | std::vector valVector; 57 | for (unsigned i = 0; i < vecSize; ++i) { 58 | valVector.push_back(toUint8Array(arraysVector[i], element_size)); 59 | } 60 | val arr = helpers::toJSArray(valVector); 61 | return arr; 62 | } 63 | } // namespace helpers 64 | -------------------------------------------------------------------------------- /js-bindings/helpers.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef JS_BINDINGS_HELPERS_H_ 16 | #define JS_BINDINGS_HELPERS_H_ 17 | 18 | #include 19 | #include 20 | #include "emscripten/val.h" 21 | #include "../src/bls.hpp" 22 | 23 | using namespace emscripten; 24 | using namespace bls; 25 | 26 | namespace helpers { 27 | val toUint8Array(uint8_t *pointer, size_t data_size); 28 | 29 | val toUint8Array(std::vector vec); 30 | 31 | std::vector toVector(uint8_t *pointer, size_t data_size); 32 | 33 | std::vector toVector(val jsBuffer); 34 | 35 | template 36 | inline std::vector toVectorFromJSArray(val jsArray) { 37 | auto l = jsArray["length"].as(); 38 | std::vector vec; 39 | for (unsigned i = 0; i < l; ++i) { 40 | vec.push_back(jsArray[i].as()); 41 | } 42 | return vec; 43 | } 44 | 45 | template 46 | inline val toJSArray(std::vector vec) { 47 | val Array = val::global("Array"); 48 | val arr = Array.new_(); 49 | auto l = vec.size(); 50 | for (unsigned i = 0; i < l; ++i) { 51 | arr.call("push", vec[i]); 52 | } 53 | return arr; 54 | } 55 | 56 | std::vector> jsBuffersArrayToVector(val buffersArray); 57 | 58 | val byteArraysVectorToJsBuffersArray(std::vector arraysVector, size_t element_size); 59 | } // namespace helpers 60 | 61 | #endif // JS_BINDINGS_HELPERS_H_ 62 | -------------------------------------------------------------------------------- /js-bindings/jsbindings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include "wrappers/PrivateKeyWrapper.h" 17 | #include "wrappers/SchemeMPLWrapper.h" 18 | #include "wrappers/UtilWrapper.h" 19 | 20 | using namespace emscripten; 21 | 22 | namespace js_wrappers { 23 | EMSCRIPTEN_BINDINGS(blsjs) { 24 | class_("AugSchemeMPL") 25 | .class_function("sk_to_g1", &AugSchemeMPLWrapper::SkToG1) 26 | .class_function("key_gen", &AugSchemeMPLWrapper::KeyGen) 27 | .class_function("sign", &AugSchemeMPLWrapper::Sign) 28 | .class_function("sign_prepend", &AugSchemeMPLWrapper::SignPrepend) 29 | .class_function("verify", &AugSchemeMPLWrapper::Verify) 30 | .class_function("aggregate", &AugSchemeMPLWrapper::Aggregate) 31 | .class_function("aggregate_verify", &AugSchemeMPLWrapper::AggregateVerify) 32 | .class_function("derive_child_sk", &AugSchemeMPLWrapper::DeriveChildSk) 33 | .class_function("derive_child_sk_unhardened", &AugSchemeMPLWrapper::DeriveChildSkUnhardened) 34 | .class_function("derive_child_pk_unhardened", &AugSchemeMPLWrapper::DeriveChildPkUnhardened); 35 | 36 | class_>("BasicSchemeMPL") 37 | .class_function("sk_to_g1", &SchemeMPLWrapper::SkToG1) 38 | .class_function("key_gen", &SchemeMPLWrapper::KeyGen) 39 | .class_function("sign", &SchemeMPLWrapper::Sign) 40 | .class_function("verify", &SchemeMPLWrapper::Verify) 41 | .class_function("aggregate", &SchemeMPLWrapper::Aggregate) 42 | .class_function("aggregate_verify", &SchemeMPLWrapper::AggregateVerify) 43 | .class_function("derive_child_sk", &SchemeMPLWrapper::DeriveChildSk) 44 | .class_function("derive_child_sk_unhardened", &SchemeMPLWrapper::DeriveChildSkUnhardened) 45 | .class_function("derive_child_pk_unhardened", &SchemeMPLWrapper::DeriveChildPkUnhardened); 46 | 47 | class_("PopSchemeMPL") 48 | .class_function("sk_to_g1", &PopSchemeMPLWrapper::SkToG1) 49 | .class_function("key_gen", &PopSchemeMPLWrapper::KeyGen) 50 | .class_function("sign", &PopSchemeMPLWrapper::Sign) 51 | .class_function("verify", &PopSchemeMPLWrapper::Verify) 52 | .class_function("aggregate", &PopSchemeMPLWrapper::Aggregate) 53 | .class_function("aggregate_verify", &PopSchemeMPLWrapper::AggregateVerify) 54 | .class_function("derive_child_sk", &PopSchemeMPLWrapper::DeriveChildSk) 55 | .class_function("derive_child_sk_unhardened", &PopSchemeMPLWrapper::DeriveChildSkUnhardened) 56 | .class_function("derive_child_pk_unhardened", &PopSchemeMPLWrapper::DeriveChildPkUnhardened) 57 | .class_function("pop_prove", &PopSchemeMPLWrapper::PopProve) 58 | .class_function("pop_verify", &PopSchemeMPLWrapper::PopVerify) 59 | .class_function("fast_aggregate_verify", &PopSchemeMPLWrapper::FastAggregateVerify); 60 | 61 | 62 | class_("G1Element") 63 | .class_property("SIZE", &G1ElementWrapper::SIZE) 64 | .constructor<>() 65 | .class_function("fromBytes", &G1ElementWrapper::FromBytes) // Not removing this for compatibility 66 | .class_function("from_bytes", &G1ElementWrapper::FromBytes) 67 | .class_function("generator", &G2ElementWrapper::Generator) 68 | .function("serialize", &G1ElementWrapper::Serialize) 69 | .function("negate", &G1ElementWrapper::Negate) 70 | .function("deepcopy", &G1ElementWrapper::Deepcopy) 71 | .function("get_fingerprint", &G1ElementWrapper::GetFingerprint) 72 | .function("add", &G1ElementWrapper::Add) 73 | .function("equal_to", &G1ElementWrapper::EqualTo); 74 | 75 | class_("G2Element") 76 | .class_property("SIZE", &G2ElementWrapper::SIZE) 77 | .constructor<>() 78 | .class_function("fromBytes", &G2ElementWrapper::FromBytes) // Not removing this for compatibility 79 | .class_function("from_bytes", &G2ElementWrapper::FromBytes) 80 | .class_function("from_g2", &G2ElementWrapper::FromG2Element) 81 | .class_function("aggregate_sigs", &G2ElementWrapper::AggregateSigs) 82 | .class_function("generator", &G2ElementWrapper::Generator) 83 | .function("serialize", &G2ElementWrapper::Serialize) 84 | .function("negate", &G2ElementWrapper::Negate) 85 | .function("deepcopy", &G2ElementWrapper::Deepcopy) 86 | .function("add", &G2ElementWrapper::Add) 87 | .function("equal_to", &G2ElementWrapper::EqualTo); 88 | 89 | class_("PrivateKey") 90 | .class_property("PRIVATE_KEY_SIZE", &PrivateKeyWrapper::PRIVATE_KEY_SIZE) 91 | .class_function("fromBytes", &PrivateKeyWrapper::FromBytes) // Not removing this for compatibility 92 | .class_function("from_bytes", &PrivateKeyWrapper::FromBytes) 93 | .class_function("aggregate", &PrivateKeyWrapper::Aggregate) 94 | .function("deepcopy", &PrivateKeyWrapper::Deepcopy) 95 | .function("serialize", &PrivateKeyWrapper::Serialize) 96 | .function("get_g1", &PrivateKeyWrapper::GetG1) 97 | .function("get_g2", &PrivateKeyWrapper::GetG2) 98 | .function("mul_g1", &PrivateKeyWrapper::MulG1) 99 | .function("mul_g2", &PrivateKeyWrapper::MulG2) 100 | .function("equal_to", &PrivateKeyWrapper::EqualTo); 101 | 102 | class_("Util") 103 | .class_function("hash256", &UtilWrapper::Hash256) 104 | .class_function("hex_str", &UtilWrapper::HexStr); 105 | }; 106 | } // namespace js_wrappers 107 | -------------------------------------------------------------------------------- /js-bindings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bls-signatures", 3 | "version": "0.2.1-beta.0", 4 | "description": "The most advanced BLS library for JavaScript", 5 | "main": "blsjs.js", 6 | "types": "blsjs.d.ts", 7 | "files": [ 8 | "blsjs.js", 9 | "blsjs.wasm", 10 | "blsjs.d.ts" 11 | ], 12 | "directories": { 13 | "test": "tests" 14 | }, 15 | "scripts": { 16 | "test": "npm run test:node && npm run test:browser", 17 | "test:typings": "tsc --esModuleInterop ./tests/typings.spec.ts", 18 | "test:node": "npm run test:typings && mocha ./tests/*.spec.js", 19 | "test:browser": "npm run test:typings && ./node_modules/.bin/karma start ./tests/karma.conf.js --single-run" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:Chia-Network/bls-signatures.git" 24 | }, 25 | "homepage": "https://github.com/Chia-Network/bls-signatures/tree/master/js-bindings", 26 | "author": { 27 | "name": "Anton Suprunchuk", 28 | "email": "antouhou@gmail.com", 29 | "url": "https://github.com/antouhou" 30 | }, 31 | "keywords": [ 32 | "bls", 33 | "BLS", 34 | "threshold", 35 | "signature", 36 | "crypto", 37 | "cryptography" 38 | ], 39 | "devDependencies": { 40 | "@types/mocha": "^10.0.1", 41 | "@types/node": "^20.9.0", 42 | "assert": "^2.0.0", 43 | "babel-polyfill": "^6.26.0", 44 | "buffer": "^6.0.3", 45 | "crypto-browserify": "^3.12.0", 46 | "karma": "^6.4.2", 47 | "karma-firefox-launcher": "^2.1.2", 48 | "karma-mocha": "^2.0.1", 49 | "karma-mocha-reporter": "^2.2.5", 50 | "karma-webpack": "^5.0.0", 51 | "mime": "3.0.0", 52 | "mocha": "^10.2.0", 53 | "path-browserify": "^1.0.1", 54 | "process": "^0.11.10", 55 | "stream-browserify": "^3.0.0", 56 | "typescript": "^5.2.2", 57 | "webpack": "^5.89.0" 58 | }, 59 | "dependencies": { 60 | "binascii": "0.0.2", 61 | "core-js": "^3.33.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /js-bindings/tests/PrivateKey.spec.js: -------------------------------------------------------------------------------- 1 | const blsSignaturesModule = require('..')(); 2 | const assert = require('assert'); 3 | const crypto = require('crypto'); 4 | const {Buffer} = require('buffer'); 5 | 6 | // Values lifted from test.js, which is ported from test.py and is a more 7 | // primary source. 8 | function getPkSeed() { 9 | return Uint8Array.from([ 10 | 0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192, 19, 18, 12, 89, 6, 220, 11 | 18, 102, 58, 209, 82, 12, 62, 89, 110, 182, 9, 44, 20, 254, 22 12 | ]); 13 | } 14 | 15 | function getSeedAndFinferprint() { 16 | var seedArray = getPkSeed(); 17 | var seed = Buffer.from(seedArray); 18 | return { 19 | seed: seed, 20 | fingerprint: 3146750013 21 | }; 22 | } 23 | 24 | function getPkBuffer() { 25 | return Uint8Array.from([ 26 | 55, 112, 145, 240, 231, 40, 70, 59, 27 | 194, 218, 125, 84, 108, 83, 185, 246, 28 | 184, 29, 244, 161, 204, 26, 181, 191, 29 | 41, 197, 144, 139, 113, 81, 163, 45 30 | ]); 31 | } 32 | 33 | function getPkUint8Array() { 34 | return new Uint8Array(getPkBuffer()); 35 | } 36 | 37 | let blsSignatures = null; 38 | before((done) => { 39 | blsSignaturesModule.then((mod) => { 40 | blsSignatures = mod; 41 | done(); 42 | }); 43 | }); 44 | 45 | describe('PrivateKey', () => { 46 | it('Should sign and verify', () => { 47 | const {AugSchemeMPL} = blsSignatures; 48 | 49 | const message1 = Uint8Array.from([1, 65, 254, 88, 90, 45, 22]); 50 | 51 | const sk1 = AugSchemeMPL.key_gen(getPkSeed()); 52 | const pk1 = AugSchemeMPL.sk_to_g1(sk1); 53 | const sig1 = AugSchemeMPL.sign(sk1, message1); 54 | 55 | assert(AugSchemeMPL.verify(pk1, message1, sig1)); 56 | }); 57 | 58 | describe('.fromSeed', () => { 59 | it('Should create a private key from a seed', () => { 60 | const {AugSchemeMPL, PrivateKey} = blsSignatures; 61 | 62 | const pk = AugSchemeMPL.key_gen(getPkSeed()); 63 | assert(pk instanceof PrivateKey); 64 | assert.deepStrictEqual(pk.serialize(), getPkBuffer()); 65 | }); 66 | }); 67 | 68 | describe('.fromBytes', () => { 69 | it('Should create a private key from a Buffer', () => { 70 | const {PrivateKey} = blsSignatures; 71 | 72 | const pk = PrivateKey.from_bytes(getPkBuffer(), false); 73 | assert(pk instanceof PrivateKey); 74 | assert.deepStrictEqual(pk.serialize(), getPkBuffer()); 75 | }); 76 | it('Should create a private key from a Uint8Array', () => { 77 | const {PrivateKey} = blsSignatures; 78 | 79 | const pk = PrivateKey.from_bytes(getPkUint8Array(), false); 80 | assert(pk instanceof PrivateKey); 81 | assert.deepStrictEqual(pk.serialize(), getPkBuffer()); 82 | }); 83 | }); 84 | 85 | describe('#serialize', () => { 86 | it('Should serialize key to a Buffer', () => { 87 | const {AugSchemeMPL} = blsSignatures; 88 | 89 | const pk = AugSchemeMPL.key_gen(getPkSeed()); 90 | const serialized = pk.serialize(); 91 | assert(serialized instanceof Uint8Array); 92 | assert.deepStrictEqual(serialized, getPkBuffer()); 93 | }); 94 | }); 95 | 96 | describe('#sign', () => { 97 | it('Should return a verifiable signature', () => { 98 | const {AugSchemeMPL, PrivateKey, G2Element} = blsSignatures; 99 | 100 | const pk = PrivateKey.fromBytes(getPkBuffer(), false); 101 | const pubkey = AugSchemeMPL.sk_to_g1(pk); 102 | const message = 'Hello world'; 103 | const messageBuffer = Uint8Array.from(Buffer.from(message, 'utf8')); 104 | const signature = AugSchemeMPL.sign(pk, messageBuffer); 105 | assert(signature instanceof G2Element); 106 | assert(AugSchemeMPL.verify(pubkey, messageBuffer, signature)); 107 | }); 108 | }); 109 | 110 | describe('#getPublicKey', () => { 111 | it('Should return a public key with a verifiable fingerprint', () => { 112 | const {AugSchemeMPL, G1Element} = blsSignatures; 113 | 114 | const pk = AugSchemeMPL.key_gen(getPkSeed()); 115 | const publicKey = AugSchemeMPL.sk_to_g1(pk); 116 | assert(publicKey instanceof G1Element); 117 | assert.strictEqual(publicKey.get_fingerprint(), getSeedAndFinferprint().fingerprint); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /js-bindings/tests/PublicKey.spec.js: -------------------------------------------------------------------------------- 1 | const blsSignaturesModule = require('..')(); 2 | const assert = require('assert'); 3 | const {Buffer} = require('buffer'); 4 | 5 | // Value from test.js, ported from test.py 6 | // Form of serialized public key requires first two bits 10 unless infinite, 7 | // in which case 11 is required. 8 | // (elements.cpp:49) 9 | function getPublicKeyFixtureHex() { 10 | return '9790635de8740e9a6a6b15fb6b72f3a16afa0973d971979b6ba54761d6e2502c50db76f4d26143f05459a42cfd520d44'; 11 | } 12 | 13 | function getPublicKeyFixture() { 14 | return { 15 | buffer: Uint8Array.from(Buffer.from(getPublicKeyFixtureHex(), 'hex')), 16 | fingerprint: 0xa14c4f99 17 | }; 18 | } 19 | 20 | // These values were adapted to fit the constraints in elements.cpp, namely to set 21 | // the high bit of the first byte. 22 | function getPublicKeysHexes() { 23 | return [ 24 | '82a8d2aaa6a5e2e08d4b8d406aaf0121a2fc2088ed12431e6b0663028da9ac5922c9ea91cde7dd74b7d795580acc7a61', 25 | '856e742478d4e95e708b8ae0d487f94099b769cb7df4c674dc0c10fbbe7d175603d090ac6064aeeb249a00ba6b3d85eb' 26 | ]; 27 | } 28 | 29 | function getPublicKeysArray() { 30 | return getPublicKeysHexes().map(hex => { 31 | return Uint8Array.from(Buffer.from(hex, 'hex')); 32 | }); 33 | } 34 | 35 | let blsSignatures = null; 36 | before((done) => { 37 | blsSignaturesModule.then((mod) => { 38 | blsSignatures = mod; 39 | done(); 40 | }); 41 | }); 42 | 43 | describe('G1Element', () => { 44 | describe('.from_bytes', () => { 45 | it('Should create a public key from bytes', () => { 46 | const {G1Element} = blsSignatures; 47 | 48 | const pk = G1Element.from_bytes(getPublicKeyFixture().buffer); 49 | assert(pk instanceof G1Element); 50 | }); 51 | }); 52 | 53 | describe('.aggregate', () => { 54 | it('Should aggregate keys if keys array contains more than one key', () => { 55 | const {G1Element} = blsSignatures; 56 | 57 | const pks = getPublicKeysArray().map(buf => G1Element.from_bytes(buf)); 58 | let first_pk = pks[0]; 59 | for (var i = 1; i < pks.length; i++) { 60 | first_pk = first_pk.add(pks[i]); 61 | } 62 | assert(first_pk instanceof G1Element); 63 | }); 64 | }); 65 | 66 | describe('#serialize', () => { 67 | it('Should serialize key to the same buffer', () => { 68 | const {G1Element} = blsSignatures; 69 | 70 | const pk = G1Element.from_bytes(getPublicKeyFixture().buffer); 71 | const serialized = pk.serialize(); 72 | assert.deepStrictEqual(Buffer.from(serialized).toString('hex'), getPublicKeyFixtureHex()); 73 | }); 74 | }); 75 | 76 | describe('getFingerprint', () => { 77 | it('Should get correct fingerprint', () => { 78 | const {G1Element} = blsSignatures; 79 | 80 | const pk = G1Element.from_bytes(getPublicKeyFixture().buffer); 81 | const fingerprint = pk.get_fingerprint(); 82 | assert.strictEqual(fingerprint, getPublicKeyFixture().fingerprint); 83 | }); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /js-bindings/tests/Signature.spec.js: -------------------------------------------------------------------------------- 1 | const blsSignaturesModule = require('..')(); 2 | const assert = require('assert'); 3 | const crypto = require('crypto'); 4 | const {Buffer} = require('buffer'); 5 | 6 | // Current format of signature taken from test.js, ported from test.py 7 | function getSignatureHex() { 8 | return '900d5223412ee471b42dbb8a6706de5f5eba4ca7e1a0fb6b3fa431f510b3e6d73c440748693d787e1a25c8bc4596f66b1130634f4b32e8e09f52f2c6a843b700cbb5aeadb8ab3456d002c143be68998573166e5979e6a48fcbb67ac8fd981f73'; 9 | } 10 | 11 | function getSignatureBytes() { 12 | return Uint8Array.from(Buffer.from(getSignatureHex(), 'hex')); 13 | } 14 | 15 | function makehash(msg) { 16 | return crypto 17 | .createHash('sha256') 18 | .update(msg) 19 | .digest(); 20 | } 21 | 22 | var blsSignatures; 23 | before((done) => { 24 | blsSignaturesModule.then((mod) => { 25 | blsSignatures = mod; 26 | done(); 27 | }); 28 | }); 29 | 30 | describe('Signature', () => { 31 | describe('Integration', () => { 32 | it('Should verify signatures', function () { 33 | const {BasicSchemeMPL} = blsSignatures; 34 | 35 | this.timeout(10000); 36 | const message = Uint8Array.from([100, 2, 254, 88, 90, 45, 23]); 37 | const seed1 = makehash(Uint8Array.from([1, 2, 3, 4, 5])); 38 | const seed2 = makehash(Uint8Array.from([3, 4, 5, 6, 7])); 39 | const seed3 = makehash(Uint8Array.from([4, 5, 6, 7, 8])); 40 | 41 | const privateKey1 = BasicSchemeMPL.key_gen(seed1); 42 | const privateKey2 = BasicSchemeMPL.key_gen(seed2); 43 | const privateKey3 = BasicSchemeMPL.key_gen(seed3); 44 | 45 | const publicKey1 = BasicSchemeMPL.sk_to_g1(privateKey1); 46 | const publicKey2 = BasicSchemeMPL.sk_to_g1(privateKey2); 47 | const publicKey3 = BasicSchemeMPL.sk_to_g1(privateKey3); 48 | 49 | const sig1 = BasicSchemeMPL.sign(privateKey1, message); 50 | const sig2 = BasicSchemeMPL.sign(privateKey2, message); 51 | const sig3 = BasicSchemeMPL.sign(privateKey3, message); 52 | 53 | assert(BasicSchemeMPL.verify(publicKey1, message, sig1), 'Signature 1 is not verifiable'); 54 | assert(BasicSchemeMPL.verify(publicKey2, message, sig2), 'Signature 2 is not verifiable'); 55 | assert(BasicSchemeMPL.verify(publicKey3, message, sig3), 'Signature 3 is not verifiable'); 56 | 57 | const aggregatedSignature = BasicSchemeMPL.aggregate([sig1, sig2, sig3]); 58 | // PublicKey aggregate was replaced with G1Element add. 59 | const aggregatedPubKey = publicKey1.add(publicKey2).add(publicKey3); 60 | 61 | assert(BasicSchemeMPL.verify(aggregatedPubKey, message, aggregatedSignature)); 62 | 63 | privateKey1.delete(); 64 | privateKey2.delete(); 65 | privateKey3.delete(); 66 | sig1.delete(); 67 | sig2.delete(); 68 | sig3.delete(); 69 | aggregatedSignature.delete(); 70 | aggregatedPubKey.delete(); 71 | }); 72 | }); 73 | describe('.fromBytes', () => { 74 | it('Should create verifiable signature from bytes', () => { 75 | const {G2Element} = blsSignatures; 76 | 77 | const sig = G2Element.fromBytes(getSignatureBytes()); 78 | 79 | assert.strictEqual(Buffer.from(sig.serialize()).toString('hex'), getSignatureHex()); 80 | // Since there is no aggregation info, it's impossible to verify sig 81 | // This iteration of the library differs in that Signature objects 82 | // aren't stateful and therefore can't be self-verified without a 83 | // message and public key. 84 | 85 | sig.delete(); 86 | }); 87 | }); 88 | describe('.aggregateSigs', () => { 89 | it('Should aggregate signature', () => { 90 | const {AugSchemeMPL, G2Element} = blsSignatures; 91 | 92 | const sk = AugSchemeMPL.key_gen(makehash(Uint8Array.from([1, 2, 3]))); 93 | const pk = AugSchemeMPL.sk_to_g1(sk); 94 | const msg1 = Uint8Array.from([3, 4, 5]); 95 | const msg2 = Uint8Array.from([6, 7, 8]); 96 | const sig1 = AugSchemeMPL.sign(sk, msg1); 97 | const sig2 = AugSchemeMPL.sign(sk, msg2); 98 | const aggregatedSig = G2Element.aggregate_sigs([sig1, sig2]); 99 | assert.strictEqual(AugSchemeMPL.aggregate_verify([pk, pk], [msg1, msg2], aggregatedSig), true); 100 | 101 | sk.delete(); 102 | pk.delete(); 103 | sig1.delete(); 104 | sig2.delete(); 105 | aggregatedSig.delete(); 106 | }); 107 | }); 108 | describe('#serialize', () => { 109 | it('Should serialize signature to Buffer', () => { 110 | const {AugSchemeMPL, G2Element} = blsSignatures; 111 | 112 | const sk = AugSchemeMPL.key_gen(makehash(Uint8Array.from([1, 2, 3, 4, 5]))); 113 | const sig = AugSchemeMPL.sign(sk, Uint8Array.from([100, 2, 254, 88, 90, 45, 23])); 114 | assert(sig instanceof G2Element); 115 | assert.deepStrictEqual(Buffer.from(sig.serialize()).toString('hex'), getSignatureHex()); 116 | 117 | sk.delete(); 118 | sig.delete(); 119 | }); 120 | }); 121 | describe('#verify', () => { 122 | it('Should return true if signature can be verified', () => { 123 | const {AugSchemeMPL} = blsSignatures; 124 | 125 | const message = Uint8Array.from(Buffer.from('Message')); 126 | const seed1 = makehash(Buffer.from([1, 2, 3, 4, 5])); 127 | const seed2 = makehash(Buffer.from([1, 2, 3, 4, 6])); 128 | const sk1 = AugSchemeMPL.key_gen(seed1); 129 | const sk2 = AugSchemeMPL.key_gen(seed2); 130 | const pk1 = AugSchemeMPL.sk_to_g1(sk1); 131 | const pk2 = AugSchemeMPL.sk_to_g1(sk2); 132 | const sig1 = AugSchemeMPL.sign(sk1, message); 133 | const sig2 = AugSchemeMPL.sign(sk2, message); 134 | const sig = AugSchemeMPL.aggregate([sig1, sig2]); 135 | 136 | assert(AugSchemeMPL.aggregate_verify([pk1, pk2], [message, message], sig)); 137 | 138 | sk1.delete(); 139 | sk2.delete(); 140 | pk1.delete(); 141 | pk2.delete(); 142 | sig1.delete(); 143 | sig2.delete(); 144 | sig.delete(); 145 | }); 146 | it("Should return false if signature can't be verified", () => { 147 | const {AugSchemeMPL} = blsSignatures; 148 | 149 | const message1 = Uint8Array.from(Buffer.from('Message')); 150 | const message2 = Uint8Array.from(Buffer.from('Nessage')); 151 | const seed = makehash(Buffer.from([1, 2, 3, 4, 5])); 152 | const sk = AugSchemeMPL.key_gen(seed); 153 | const pk = AugSchemeMPL.sk_to_g1(sk); 154 | const sig = AugSchemeMPL.sign(sk, message1); 155 | assert.strictEqual(AugSchemeMPL.verify(pk, message2, sig), false); 156 | 157 | sk.delete(); 158 | pk.delete(); 159 | sig.delete(); 160 | }); 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /js-bindings/tests/karma.conf.js: -------------------------------------------------------------------------------- 1 | const testSource = './karma.test.js'; 2 | const fs = require('fs'); 3 | 4 | function webpackConfig() { 5 | const config = require('./webpack.config.js'); 6 | delete config.context; 7 | delete config.entry; 8 | delete config.output; 9 | // delete config.devServer; 10 | 11 | return config; 12 | } 13 | 14 | function MimeTypeMiddleware(config) { 15 | return function (request, response, next) { 16 | if (request.url.endsWith('.wasm')) { 17 | const content = fs.readFileSync('blsjs.wasm'); 18 | response.setHeader('Content-Type', 'application/wasm'); 19 | response.writeHead(200); 20 | response.write(content); 21 | response.end(); 22 | return next('route'); 23 | } else { 24 | return next(request,response); 25 | } 26 | }; 27 | } 28 | 29 | const karmaConfig = { 30 | frameworks: ['mocha', 'webpack'], 31 | files: [ 32 | 'node_modules/babel-polyfill/dist/polyfill.js', 33 | testSource 34 | ], 35 | preprocessors: { 36 | [testSource]: ['webpack'] 37 | }, 38 | webpack: webpackConfig(), 39 | reporters: ['mocha'], 40 | port: 9876, 41 | colors: true, 42 | autoWatch: false, 43 | browsers: ['FirefoxHeadless'], 44 | singleRun: false, 45 | concurrency: Infinity, 46 | plugins: [ 47 | 'karma-mocha', 48 | 'karma-mocha-reporter', 49 | 'karma-firefox-launcher', 50 | 'karma-webpack', 51 | {'middleware:mimetyper': ['factory', MimeTypeMiddleware]} 52 | ], 53 | middleware: [ 'mimetyper' ], 54 | customLaunchers: { 55 | FirefoxHeadless: { 56 | base: 'Firefox', 57 | flags: ['-headless'], 58 | }, 59 | }, 60 | }; 61 | 62 | module.exports = function (config) { 63 | config.set(karmaConfig); 64 | }; 65 | -------------------------------------------------------------------------------- /js-bindings/tests/karma.test.js: -------------------------------------------------------------------------------- 1 | // This file is needed to compile tests with webpack into one file for the browser tests 2 | 3 | const testsContext = require.context('./', true, /spec\.js$/); 4 | 5 | testsContext.keys().forEach(testsContext); -------------------------------------------------------------------------------- /js-bindings/tests/typings.spec.ts: -------------------------------------------------------------------------------- 1 | // This file is used to check if the typescript typings are working 2 | import createBlsSignaturesModule from '..'; 3 | import {ok, strictEqual} from 'assert'; 4 | 5 | createBlsSignaturesModule().then((blsSignatures) => { 6 | const { 7 | AugSchemeMPL, 8 | PrivateKey, 9 | G1Element, 10 | G2Element 11 | } = blsSignatures; 12 | 13 | function getSkSeed(): Uint8Array { 14 | return Uint8Array.from([ 15 | 0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192, 19, 18, 12, 89, 6, 220, 16 | 18, 102, 58, 209, 82, 12, 62, 89, 110, 182, 9, 44, 20, 254, 22 17 | ]); 18 | } 19 | 20 | /* 21 | function getSkBytes(): Uint8Array { 22 | return Uint8Array.from([ 23 | 55, 112, 145, 240, 231, 40, 70, 59, 24 | 194, 218, 125, 84, 108, 83, 185, 246, 25 | 184, 29, 244, 161, 204, 26, 181, 191, 26 | 41, 197, 144, 139, 113, 81, 163, 45 27 | ]); 28 | } 29 | */ 30 | 31 | function getPkBytes(): Uint8Array { 32 | return Uint8Array.from([ 33 | 134, 36, 50, 144, 187, 203, 253, 154, 231, 34 | 91, 222, 206, 121, 129, 150, 83, 80, 32, 35 | 142, 181, 233, 155, 4, 213, 205, 36, 233, 36 | 85, 173, 169, 97, 248, 192, 161, 98, 222, 37 | 231, 64, 190, 123, 220, 108, 60, 6, 19, 38 | 186, 46, 177 39 | ]); 40 | } 41 | 42 | function getMessageBytes(): Uint8Array { 43 | return Uint8Array.from([1, 2, 3]); 44 | } 45 | 46 | function getSignatureBytes(): Uint8Array { 47 | return Uint8Array.from([ 48 | 150, 18, 129, 243, 101, 246, 44, 55, 26, 188, 24, 50, 49 | 34, 223, 147, 98, 184, 115, 124, 54, 133, 86, 100, 135, 50 | 205, 155, 191, 212, 36, 49, 2, 244, 241, 202, 143, 223, 51 | 89, 34, 7, 115, 96, 209, 22, 24, 100, 22, 55, 175, 52 | 2, 199, 236, 131, 152, 217, 76, 146, 32, 13, 235, 31, 53 | 129, 47, 84, 187, 229, 161, 27, 89, 214, 21, 7, 240, 54 | 1, 130, 214, 13, 134, 189, 139, 112, 40, 94, 221, 76, 55 | 249, 168, 114, 254, 55, 45, 42, 30, 14, 22, 107, 10 56 | ]); 57 | } 58 | 59 | /* 60 | function getChainCodeBytes(): Uint8Array { 61 | return Uint8Array.from([137, 75, 79, 148, 193, 235, 158, 172, 163, 41, 102, 134, 72, 161, 187, 104, 97, 202, 38, 27, 206, 125, 64, 60, 149, 248, 29, 53, 180, 23, 253, 255]); 62 | } 63 | */ 64 | 65 | describe('typings', () => { 66 | it('PrivateKey', () => { 67 | strictEqual(PrivateKey.PRIVATE_KEY_SIZE, 32); 68 | const sk = AugSchemeMPL.key_gen(getSkSeed()); 69 | const aggSk = PrivateKey.aggregate([sk]); 70 | const pk = sk.get_g1(); 71 | sk.serialize(); 72 | const sig = AugSchemeMPL.sign(sk, getMessageBytes()); 73 | ok(AugSchemeMPL.verify(pk, getMessageBytes(), sig)); 74 | aggSk.delete(); 75 | pk.delete(); 76 | sig.delete(); 77 | }); 78 | 79 | it('G1Element', () => { 80 | strictEqual(G1Element.SIZE, 48); 81 | const pk = G1Element.from_bytes(getPkBytes()); 82 | const aggPk = pk.add(pk); 83 | pk.get_fingerprint(); 84 | pk.serialize(); 85 | pk.delete(); 86 | aggPk.delete(); 87 | }); 88 | 89 | it('G2Element', () => { 90 | strictEqual(G2Element.SIZE, 96); 91 | const pk = G1Element.from_bytes(getPkBytes()); 92 | const sig = G2Element.from_bytes(getSignatureBytes()); 93 | const aggSig = AugSchemeMPL.aggregate([sig]); 94 | const sig2 = G2Element.from_bytes(getSignatureBytes()); 95 | const isValid: boolean = 96 | AugSchemeMPL.verify(pk, getMessageBytes(), sig); 97 | sig.serialize(); 98 | ok(isValid); 99 | sig.delete(); 100 | aggSig.delete(); 101 | sig2.delete(); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /js-bindings/tests/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | module.exports = { 4 | mode: 'development', 5 | plugins: [ 6 | new webpack.ProvidePlugin({ 7 | process: 'process', 8 | 9 | }) 10 | ], 11 | resolve: { 12 | fallback: { 13 | "path": require.resolve("path-browserify"), 14 | "crypto": require.resolve("crypto-browserify"), 15 | "stream": require.resolve("stream-browserify"), 16 | "fs": false, 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /js-bindings/wrappers/G1ElementWrapper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "G1ElementWrapper.h" 16 | 17 | namespace js_wrappers { 18 | G1ElementWrapper::G1ElementWrapper(const G1Element &publicKey) : JSWrapper(publicKey) {} 19 | 20 | G1ElementWrapper::G1ElementWrapper() : JSWrapper(G1Element()) { } 21 | 22 | const size_t G1ElementWrapper::SIZE = G1Element::SIZE; 23 | 24 | std::vector G1ElementWrapper::Unwrap(std::vector wrappers) { 25 | std::vector unwrapped; 26 | for (auto &wrapper : wrappers) { 27 | unwrapped.push_back(wrapper.GetWrappedInstance()); 28 | } 29 | return unwrapped; 30 | } 31 | 32 | G1ElementWrapper G1ElementWrapper::FromBytes(val buffer) { 33 | std::vector bytes = helpers::toVector(buffer); 34 | const bls::Bytes bytesView(bytes); 35 | G1Element pk = G1Element::FromBytes(bytesView); 36 | return G1ElementWrapper(pk); 37 | } 38 | 39 | val G1ElementWrapper::Serialize() const { 40 | return helpers::toUint8Array(wrapped.Serialize()); 41 | } 42 | 43 | G1ElementWrapper G1ElementWrapper::Add(const G1ElementWrapper &other) { 44 | return G1ElementWrapper(GetWrappedInstance() + other.GetWrappedInstance()); 45 | } 46 | 47 | bool G1ElementWrapper::EqualTo(const G1ElementWrapper &others) { 48 | return GetWrappedInstance() == others.GetWrappedInstance(); 49 | } 50 | 51 | G1ElementWrapper G1ElementWrapper::Negate() { 52 | return G1ElementWrapper(GetWrappedInstance().Negate()); 53 | } 54 | 55 | G1ElementWrapper G1ElementWrapper::Generator() { 56 | return G1ElementWrapper(G1Element::Generator()); 57 | } 58 | 59 | G1ElementWrapper G1ElementWrapper::Deepcopy() { 60 | return G1ElementWrapper(GetWrappedInstance()); 61 | } 62 | 63 | uint32_t G1ElementWrapper::GetFingerprint() const { 64 | return wrapped.GetFingerprint(); 65 | } 66 | } // namespace js_wrappers 67 | -------------------------------------------------------------------------------- /js-bindings/wrappers/G1ElementWrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef JS_BINDINGS_WRAPPERS_G1ELEMENTWRAPPER_H_ 16 | #define JS_BINDINGS_WRAPPERS_G1ELEMENTWRAPPER_H_ 17 | 18 | #include "../helpers.h" 19 | #include "JSWrapper.h" 20 | 21 | namespace js_wrappers { 22 | class G1ElementWrapper : public JSWrapper { 23 | public: 24 | explicit G1ElementWrapper(const G1Element &publicKey); 25 | 26 | G1ElementWrapper(); 27 | 28 | static const size_t SIZE; 29 | 30 | static std::vector Unwrap(std::vector wrappers); 31 | 32 | static G1ElementWrapper FromBytes(val buffer); 33 | 34 | static G1ElementWrapper Generator(); 35 | 36 | val Serialize() const; 37 | 38 | G1ElementWrapper Add(const G1ElementWrapper &other); 39 | 40 | bool EqualTo(const G1ElementWrapper &others); 41 | 42 | G1ElementWrapper Negate(); 43 | 44 | G1ElementWrapper Deepcopy(); 45 | 46 | uint32_t GetFingerprint() const; 47 | }; 48 | } // namespace js_wrappers 49 | 50 | #endif // JS_BINDINGS_WRAPPERS_G1ELEMENTWRAPPER_H_ 51 | -------------------------------------------------------------------------------- /js-bindings/wrappers/G2ElementWrapper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "G2ElementWrapper.h" 16 | 17 | namespace js_wrappers { 18 | G2ElementWrapper::G2ElementWrapper(const G2Element &signature) : JSWrapper(signature) {} 19 | 20 | G2ElementWrapper::G2ElementWrapper() : JSWrapper(G2Element()) { } 21 | 22 | const size_t G2ElementWrapper::SIZE = G2Element::SIZE; 23 | 24 | 25 | std::vector G2ElementWrapper::Unwrap(std::vector sigWrappers) { 26 | std::vector signatures; 27 | for (auto &sigWrapper : sigWrappers) { 28 | signatures.push_back(sigWrapper.GetWrappedInstance()); 29 | } 30 | return signatures; 31 | } 32 | 33 | G2ElementWrapper G2ElementWrapper::FromG2Element(const G2Element &signature) { 34 | return G2ElementWrapper(signature); 35 | } 36 | 37 | G2ElementWrapper G2ElementWrapper::FromBytes(val buffer) { 38 | std::vector bytes = helpers::toVector(buffer); 39 | const bls::Bytes bytesView(bytes); 40 | G2Element sig = G2Element::FromBytes(bytesView); 41 | return G2ElementWrapper(sig); 42 | } 43 | 44 | G2ElementWrapper G2ElementWrapper::AggregateSigs(val signatureWrappers) { 45 | std::vector signatures = G2ElementWrapper::Unwrap( 46 | helpers::toVectorFromJSArray(signatureWrappers)); 47 | return G2ElementWrapper::FromG2Element(BasicSchemeMPL().Aggregate(signatures)); 48 | } 49 | 50 | G2ElementWrapper G2ElementWrapper::Generator() { 51 | return G2ElementWrapper(G2Element::Generator()); 52 | } 53 | 54 | G2ElementWrapper G2ElementWrapper::Deepcopy() { 55 | return G2ElementWrapper(GetWrappedInstance()); 56 | } 57 | 58 | G2ElementWrapper G2ElementWrapper::Negate() { 59 | return G2ElementWrapper(GetWrappedInstance().Negate()); 60 | } 61 | 62 | val G2ElementWrapper::Serialize() const { 63 | return helpers::toUint8Array(wrapped.Serialize()); 64 | } 65 | 66 | G2ElementWrapper G2ElementWrapper::Add(const G2ElementWrapper& other) { 67 | return G2ElementWrapper(GetWrappedInstance() + other.GetWrappedInstance()); 68 | } 69 | 70 | bool G2ElementWrapper::EqualTo(const G2ElementWrapper& others) { 71 | return GetWrappedInstance() == others.GetWrappedInstance(); 72 | } 73 | } // namespace js_wrappers 74 | -------------------------------------------------------------------------------- /js-bindings/wrappers/G2ElementWrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef JS_BINDINGS_WRAPPERS_G2ELEMENTWRAPPER_H_ 16 | #define JS_BINDINGS_WRAPPERS_G2ELEMENTWRAPPER_H_ 17 | 18 | #include "../helpers.h" 19 | #include "JSWrapper.h" 20 | 21 | namespace js_wrappers { 22 | class G2ElementWrapper : public JSWrapper { 23 | public: 24 | explicit G2ElementWrapper(const G2Element &signature); 25 | 26 | G2ElementWrapper(); 27 | 28 | static const size_t SIZE; 29 | 30 | static std::vector Unwrap(std::vector sigWrappers); 31 | 32 | static G2ElementWrapper FromG2Element(const G2Element &signature); 33 | 34 | static G2ElementWrapper FromBytes(val buffer); 35 | 36 | static G2ElementWrapper AggregateSigs(val signatureWrappers); 37 | 38 | static G2ElementWrapper Generator(); 39 | 40 | G2ElementWrapper Deepcopy(); 41 | 42 | G2ElementWrapper Negate(); 43 | 44 | val Serialize() const; 45 | 46 | G2ElementWrapper Add(const G2ElementWrapper &other); 47 | 48 | bool EqualTo(const G2ElementWrapper &others); 49 | }; 50 | } // namespace js_wrappers 51 | 52 | #endif // JS_BINDINGS_WRAPPERS_G2ELEMENTWRAPPER_H_ 53 | -------------------------------------------------------------------------------- /js-bindings/wrappers/JSWrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef JS_BINDINGS_WRAPPERS_JSWRAPPER_H_ 16 | #define JS_BINDINGS_WRAPPERS_JSWRAPPER_H_ 17 | 18 | namespace js_wrappers { 19 | template 20 | class JSWrapper { 21 | public: 22 | inline explicit JSWrapper(const T &wrappedInstance) : wrapped(wrappedInstance) {} 23 | 24 | inline T GetWrappedInstance() const { 25 | return wrapped; 26 | } 27 | protected: 28 | T wrapped; 29 | }; 30 | } // namespace js_wrappers 31 | 32 | #endif // JS_BINDINGS_WRAPPERS_JSWRAPPER_H_ 33 | -------------------------------------------------------------------------------- /js-bindings/wrappers/PrivateKeyWrapper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "PrivateKeyWrapper.h" 16 | 17 | namespace js_wrappers { 18 | PrivateKeyWrapper::PrivateKeyWrapper(const PrivateKey &privateKey) : JSWrapper(privateKey) {} 19 | 20 | const size_t PrivateKeyWrapper::PRIVATE_KEY_SIZE = PrivateKey::PRIVATE_KEY_SIZE; 21 | 22 | std::vector PrivateKeyWrapper::Unwrap(std::vector wrappers) { 23 | std::vector unwrapped; 24 | for (auto &wrapper : wrappers) { 25 | unwrapped.push_back(wrapper.GetWrappedInstance()); 26 | } 27 | return unwrapped; 28 | } 29 | 30 | PrivateKeyWrapper PrivateKeyWrapper::Aggregate(val privateKeysArray) { 31 | std::vector privateKeys = PrivateKeyWrapper::Unwrap( 32 | helpers::toVectorFromJSArray(privateKeysArray)); 33 | 34 | PrivateKey aggregatedSk = PrivateKey::Aggregate(privateKeys); 35 | return PrivateKeyWrapper(aggregatedSk); 36 | } 37 | 38 | PrivateKeyWrapper PrivateKeyWrapper::FromBytes(val buffer, bool modOrder) { 39 | std::vector bytes = helpers::toVector(buffer); 40 | const bls::Bytes bytesView(bytes); 41 | PrivateKey pk = PrivateKey::FromBytes(bytesView, modOrder); 42 | return PrivateKeyWrapper(pk); 43 | } 44 | 45 | val PrivateKeyWrapper::Serialize() const { 46 | return helpers::toUint8Array(wrapped.Serialize()); 47 | } 48 | 49 | PrivateKeyWrapper PrivateKeyWrapper::Deepcopy() { 50 | return PrivateKeyWrapper(GetWrappedInstance()); 51 | } 52 | 53 | G1ElementWrapper PrivateKeyWrapper::GetG1() const { 54 | G1Element pk = wrapped.GetG1Element(); 55 | return G1ElementWrapper(pk); 56 | } 57 | 58 | G2ElementWrapper PrivateKeyWrapper::GetG2() const { 59 | G2Element sk = wrapped.GetG2Element(); 60 | return G2ElementWrapper(sk); 61 | } 62 | 63 | G2ElementWrapper PrivateKeyWrapper::GetG2Power(const G2ElementWrapper& element) const { 64 | return G2ElementWrapper(GetWrappedInstance().GetG2Power(element.GetWrappedInstance())); 65 | } 66 | 67 | G1ElementWrapper PrivateKeyWrapper::MulG1(const G1ElementWrapper& other) { 68 | return G1ElementWrapper(GetWrappedInstance() * other.GetWrappedInstance()); 69 | } 70 | 71 | G2ElementWrapper PrivateKeyWrapper::MulG2(const G2ElementWrapper& other) { 72 | return G2ElementWrapper(GetWrappedInstance() * other.GetWrappedInstance()); 73 | } 74 | 75 | bool PrivateKeyWrapper::EqualTo(const PrivateKeyWrapper& others) { 76 | return GetWrappedInstance() == others.GetWrappedInstance(); 77 | } 78 | 79 | } // namespace js_wrappers 80 | -------------------------------------------------------------------------------- /js-bindings/wrappers/PrivateKeyWrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef JS_BINDINGS_WRAPPERS_PRIVATEKEYWRAPPER_H_ 16 | #define JS_BINDINGS_WRAPPERS_PRIVATEKEYWRAPPER_H_ 17 | 18 | #include "../helpers.h" 19 | #include "JSWrapper.h" 20 | #include "G1ElementWrapper.h" 21 | #include "G2ElementWrapper.h" 22 | 23 | namespace js_wrappers { 24 | class PrivateKeyWrapper : public JSWrapper { 25 | public: 26 | explicit PrivateKeyWrapper(const PrivateKey &privateKey); 27 | 28 | static const size_t PRIVATE_KEY_SIZE; 29 | 30 | static std::vector Unwrap(std::vector wrappers); 31 | 32 | static PrivateKeyWrapper FromBytes(val buffer, bool modOrder); 33 | 34 | static PrivateKeyWrapper Aggregate(val privateKeysArray); 35 | 36 | val Serialize() const; 37 | 38 | PrivateKeyWrapper Deepcopy(); 39 | 40 | G1ElementWrapper GetG1() const; 41 | 42 | G2ElementWrapper GetG2() const; 43 | 44 | G2ElementWrapper GetG2Power(const G2ElementWrapper &element) const; 45 | 46 | G1ElementWrapper MulG1(const G1ElementWrapper &other); 47 | 48 | G2ElementWrapper MulG2(const G2ElementWrapper &other); 49 | 50 | bool EqualTo(const PrivateKeyWrapper &others); 51 | }; 52 | } // namespace js_wrappers 53 | 54 | 55 | #endif // JS_BINDINGS_WRAPPERS_PRIVATEKEYWRAPPER_H_ 56 | -------------------------------------------------------------------------------- /js-bindings/wrappers/SchemeMPLWrapper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "SchemeMPLWrapper.h" 16 | 17 | namespace js_wrappers { 18 | 19 | // Non template code. 20 | 21 | } // namespace js_wrappers 22 | -------------------------------------------------------------------------------- /js-bindings/wrappers/SchemeMPLWrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef JS_BINDINGS_WRAPPERS_SCHEMEMPLWRAPPER_H_ 16 | #define JS_BINDINGS_WRAPPERS_SCHEMEMPLWRAPPER_H_ 17 | 18 | #include "../helpers.h" 19 | #include "JSWrapper.h" 20 | #include "G1ElementWrapper.h" 21 | #include "PrivateKeyWrapper.h" 22 | 23 | namespace js_wrappers { 24 | 25 | // Basic scheme wrapper providing most of the methods. 26 | template class SchemeMPLWrapper : public JSWrapper { 27 | public: 28 | static G1ElementWrapper SkToG1(const PrivateKeyWrapper &seckey) { 29 | return G1ElementWrapper(mpl.SkToG1(seckey.GetWrappedInstance())); 30 | } 31 | 32 | static PrivateKeyWrapper KeyGen(val seedVal) { 33 | std::vector seed = helpers::toVector(seedVal); 34 | return PrivateKeyWrapper(mpl.KeyGen(seed)); 35 | } 36 | 37 | static G2ElementWrapper Sign(const PrivateKeyWrapper &seckey, val messageVal) { 38 | std::vector message = helpers::toVector(messageVal); 39 | return G2ElementWrapper(mpl.Sign(seckey.GetWrappedInstance(), message)); 40 | } 41 | 42 | static bool Verify(const G1ElementWrapper &pubkey, val messageVal, const G2ElementWrapper &signature) { 43 | std::vector message = helpers::toVector(messageVal); 44 | return mpl.Verify(pubkey.GetWrappedInstance(), message, signature.GetWrappedInstance()); 45 | } 46 | 47 | static std::vector Unwrap(const std::vector &wrappers) { 48 | std::vector unwrapped; 49 | for (auto &wrapper : wrappers) { 50 | unwrapped.push_back(wrapper.GetWrappedInstance()); 51 | } 52 | return unwrapped; 53 | } 54 | 55 | static G2ElementWrapper Aggregate(val g2Elements) { 56 | std::vector signatures = G2ElementWrapper::Unwrap 57 | (helpers::toVectorFromJSArray(g2Elements)); 58 | return G2ElementWrapper(mpl.Aggregate(signatures)); 59 | } 60 | 61 | static bool AggregateVerify(val pubkeyArray, val messagesVal, const G2ElementWrapper &signature) { 62 | std::vector pubkeys = G1ElementWrapper::Unwrap 63 | (helpers::toVectorFromJSArray(pubkeyArray)); 64 | std::vector messagesVec = helpers::toVectorFromJSArray(messagesVal); 65 | std::vector> messages; 66 | for (auto msgVal : messagesVec) { 67 | messages.push_back(helpers::toVector(msgVal)); 68 | } 69 | return mpl.AggregateVerify(pubkeys, messages, signature.GetWrappedInstance()); 70 | } 71 | 72 | static PrivateKeyWrapper DeriveChildSk(const PrivateKeyWrapper &sk, uint32_t index) { 73 | return PrivateKeyWrapper(mpl.DeriveChildSk(sk.GetWrappedInstance(), index)); 74 | } 75 | 76 | static PrivateKeyWrapper DeriveChildSkUnhardened(const PrivateKeyWrapper &sk, uint32_t index) { 77 | return PrivateKeyWrapper(mpl.DeriveChildSkUnhardened(sk.GetWrappedInstance(), index)); 78 | } 79 | 80 | static G1ElementWrapper DeriveChildPkUnhardened(const G1ElementWrapper &pk, uint32_t index) { 81 | return G1ElementWrapper(mpl.DeriveChildPkUnhardened(pk.GetWrappedInstance(), index)); 82 | } 83 | 84 | protected: 85 | static inline SchemeMPL mpl; 86 | }; 87 | 88 | class AugSchemeMPLWrapper : public SchemeMPLWrapper { 89 | public: 90 | static G2ElementWrapper SignPrepend(const PrivateKeyWrapper &seckey, val messageVal, const G1ElementWrapper &prependPk) { 91 | std::vector message = helpers::toVector(messageVal); 92 | return G2ElementWrapper(mpl.Sign(seckey.GetWrappedInstance(), message, prependPk.GetWrappedInstance())); 93 | } 94 | }; 95 | 96 | class PopSchemeMPLWrapper : public SchemeMPLWrapper { 97 | public: 98 | static G2ElementWrapper PopProve(const PrivateKeyWrapper &seckey) { 99 | return G2ElementWrapper(mpl.PopProve(seckey.GetWrappedInstance())); 100 | } 101 | 102 | static bool PopVerify(const G1ElementWrapper &pubkey, const G2ElementWrapper &signatureProof) { 103 | return mpl.PopVerify(pubkey.GetWrappedInstance(), signatureProof.GetWrappedInstance()); 104 | } 105 | 106 | static bool FastAggregateVerify 107 | (val pubkeyArray, 108 | val messageVal, 109 | const G2ElementWrapper &signature) { 110 | std::vector pubkeys = G1ElementWrapper::Unwrap 111 | (helpers::toVectorFromJSArray(pubkeyArray)); 112 | std::vector message = helpers::toVector(messageVal); 113 | return mpl.FastAggregateVerify(pubkeys, message, signature.GetWrappedInstance()); 114 | } 115 | }; 116 | 117 | } // namespace js_wrappers 118 | 119 | #endif // JS_BINDINGS_WRAPPERS_SIGNATUREWRAPPER_H_ 120 | -------------------------------------------------------------------------------- /js-bindings/wrappers/UtilWrapper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "UtilWrapper.h" 16 | 17 | namespace js_wrappers { 18 | val UtilWrapper::Hash256(val msg) { 19 | std::vector bytes = helpers::toVector(msg); 20 | std::vector output(BLS::MESSAGE_HASH_LEN); 21 | Util::Hash256(&output[0], (const uint8_t *)bytes.data(), bytes.size()); 22 | return helpers::toUint8Array(&output[0], output.size()); 23 | } 24 | std::string UtilWrapper::HexStr(val msg) { 25 | std::vector bytes = helpers::toVector(msg); 26 | return Util::HexStr(bytes); 27 | } 28 | } // namespace js_wrappers 29 | -------------------------------------------------------------------------------- /js-bindings/wrappers/UtilWrapper.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef JS_BINDINGS_WRAPPERS_UTILWRAPPER_H_ 16 | #define JS_BINDINGS_WRAPPERS_UTILWRAPPER_H_ 17 | 18 | #include "../helpers.h" 19 | #include "JSWrapper.h" 20 | 21 | namespace js_wrappers { 22 | class UtilWrapper : public JSWrapper { 23 | public: 24 | static val Hash256(val message); 25 | static std::string HexStr(val message); 26 | }; 27 | } // namespace js_wrappers 28 | 29 | #endif // JS_BINDINGS_WRAPPERS_SIGNATUREWRAPPER_H_ 30 | -------------------------------------------------------------------------------- /js_build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git submodule update --init --recursive 4 | 5 | mkdir js_build 6 | cd js_build 7 | 8 | cmake ../ -DBUILD_BLS_TESTS=0 -DBUILD_BLS_BENCHMARKS=0 -DCMAKE_TOOLCHAIN_FILE=$(dirname $(realpath $(which emcc)))/cmake/Modules/Platform/Emscripten.cmake 9 | cmake --build . -- 10 | -------------------------------------------------------------------------------- /js_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | YARN=yarn 4 | if which yarn >/dev/null ; then 5 | YARN=yarn 6 | else 7 | if which npm >/dev/null ; then 8 | YARN=npm 9 | else 10 | echo "No yarn or npm installed." 11 | exit 1 12 | fi 13 | fi 14 | 15 | cd js_build/js-bindings/tests && ${YARN} install && exec node ./test.js 16 | -------------------------------------------------------------------------------- /lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | cpp: 3 | after_prepare: 4 | - "mkdir custom_cmake" 5 | - "wget --quiet -O - \"https://cmake.org/files/v3.16/cmake-3.16.3-Linux-x86_64.tar.gz\"\ 6 | \ | tar --strip-components=1 -xz -C custom_cmake" 7 | - "export PATH=$(pwd)/custom_cmake/bin:${PATH}" 8 | - "cd $LGTM_SRC/" 9 | - "export CMAKE_INCLUDE_PATH=$LGTM_SRC/include:${CMAKE_INCLUDE_PATH}" 10 | - "export CMAKE_LIBRARY_PATH=$LGTM_SRC/lib:${CMAKE_LIBRARY_PATH}" 11 | - "mkdir $LGTM_SRC/_lgtm_build_dir" 12 | - "cd $LGTM_SRC/_lgtm_build_dir" 13 | -------------------------------------------------------------------------------- /mypi.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=42", "wheel", "setuptools_scm[toml]>=3.5.0", "pybind11"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.setuptools_scm] 6 | local_scheme = "no-local-version" 7 | 8 | [tool.cibuildwheel] 9 | test-requires = "pytest" 10 | test-command = "py.test -v {project}/python-bindings/test.py" 11 | skip = "*-manylinux_i686 *-win32 *-musllinux_*" 12 | 13 | [tool.cibuildwheel.linux] 14 | build-verbosity = "" 15 | before-build = "python -m pip install --upgrade pip" 16 | 17 | [tool.cibuildwheel.macos] 18 | build-verbosity = "" 19 | before-all = "brew install cmake" 20 | before-build = "python -m pip install --upgrade pip" 21 | environment = {MACOSX_DEPLOYMENT_TARGET="11", SYSTEM_VERSION_COMPAT=0} 22 | 23 | [tool.cibuildwheel.windows] 24 | build-verbosity = "" 25 | -------------------------------------------------------------------------------- /python-bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | FetchContent_Declare( 3 | pybind11 4 | GIT_REPOSITORY https://github.com/pybind/pybind11.git 5 | GIT_TAG v2.11.1 6 | ) 7 | FetchContent_MakeAvailable(pybind11) 8 | 9 | pybind11_add_module(blspy ${CMAKE_CURRENT_SOURCE_DIR}/pythonbindings.cpp) 10 | target_link_libraries(blspy PRIVATE bls) 11 | 12 | if((NOT MSVC) AND (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin")) 13 | target_link_options(blspy PRIVATE -Wl,-Bsymbolic) 14 | endif() 15 | -------------------------------------------------------------------------------- /python-bindings/README.md: -------------------------------------------------------------------------------- 1 | # Python bindings 2 | 3 | Use the full power and efficiency of the C++ bls library, but in a few lines of python! 4 | 5 | ## Install 6 | 7 | ```bash 8 | pip3 install blspy 9 | 10 | ``` 11 | 12 | Alternatively, to install from source, run the following, in the project root directory: 13 | 14 | ```bash 15 | pip3 install . 16 | ``` 17 | 18 | Cmake, a c++ compiler, and a recent version of pip3 (v18) are required for source install. 19 | Public keys are G1Elements, and signatures are G2Elements. 20 | 21 | Then, to use: 22 | 23 | ## Import the library 24 | 25 | ```python 26 | from blspy import (PrivateKey, Util, AugSchemeMPL, PopSchemeMPL, 27 | G1Element, G2Element) 28 | ``` 29 | 30 | ## Creating keys and signatures 31 | 32 | ```python 33 | # Example seed, used to generate private key. Always use 34 | # a secure RNG with sufficient entropy to generate a seed (at least 32 bytes). 35 | seed: bytes = bytes([0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192, 36 | 19, 18, 12, 89, 6, 220, 18, 102, 58, 209, 82, 37 | 12, 62, 89, 110, 182, 9, 44, 20, 254, 22]) 38 | sk: PrivateKey = AugSchemeMPL.key_gen(seed) 39 | pk: G1Element = sk.get_g1() 40 | 41 | message: bytes = bytes([1, 2, 3, 4, 5]) 42 | signature: G2Element = AugSchemeMPL.sign(sk, message) 43 | 44 | # Verify the signature 45 | ok: bool = AugSchemeMPL.verify(pk, message, signature) 46 | assert ok 47 | ``` 48 | 49 | ## Serializing keys and signatures to bytes 50 | 51 | ```python 52 | sk_bytes: bytes = bytes(sk) # 32 bytes 53 | pk_bytes: bytes = bytes(pk) # 48 bytes 54 | signature_bytes: bytes = bytes(signature) # 96 bytes 55 | 56 | print(sk_bytes.hex(), pk_bytes.hex(), signature_bytes.hex()) 57 | ``` 58 | 59 | ## Loading keys and signatures from bytes 60 | 61 | ```python 62 | sk = PrivateKey.from_bytes(sk_bytes) 63 | pk = G1Element.from_bytes(pk_bytes) 64 | signature = G2Element.from_bytes(signature_bytes) 65 | ``` 66 | 67 | ## Create aggregate signatures 68 | 69 | ```python 70 | # Generate some more private keys 71 | seed = bytes([1]) + seed[1:] 72 | sk1: PrivateKey = AugSchemeMPL.key_gen(seed) 73 | seed = bytes([2]) + seed[1:] 74 | sk2: PrivateKey = AugSchemeMPL.key_gen(seed) 75 | message2: bytes = bytes([1, 2, 3, 4, 5, 6, 7]) 76 | 77 | # Generate first sig 78 | pk1: G1Element = sk1.get_g1() 79 | sig1: G2Element = AugSchemeMPL.sign(sk1, message) 80 | 81 | # Generate second sig 82 | pk2: G1Element = sk2.get_g1() 83 | sig2: G2Element = AugSchemeMPL.sign(sk2, message2) 84 | 85 | # Signatures can be non-interactively combined by anyone 86 | agg_sig: G2Element = AugSchemeMPL.aggregate([sig1, sig2]) 87 | 88 | ok = AugSchemeMPL.aggregate_verify([pk1, pk2], [message, message2], agg_sig) 89 | ``` 90 | 91 | ## Arbitrary trees of aggregates 92 | 93 | ```python 94 | seed = bytes([3]) + seed[1:] 95 | sk3: PrivateKey = AugSchemeMPL.key_gen(seed) 96 | pk3: G1Element = sk3.get_g1() 97 | message3: bytes = bytes([100, 2, 254, 88, 90, 45, 23]) 98 | sig3: G2Element = AugSchemeMPL.sign(sk3, message3) 99 | 100 | agg_sig_final: G2Element = AugSchemeMPL.aggregate([agg_sig, sig3]) 101 | ok = AugSchemeMPL.aggregate_verify([pk1, pk2, pk3], [message, message2, message3], agg_sig_final) 102 | ``` 103 | 104 | ## Very fast verification with Proof of Possession scheme 105 | 106 | ```python 107 | # If the same message is signed, you can use Proof of Posession (PopScheme) for efficiency 108 | # A proof of possession MUST be passed around with the PK to ensure security. 109 | pop_sig1: G2Element = PopSchemeMPL.sign(sk1, message) 110 | pop_sig2: G2Element = PopSchemeMPL.sign(sk2, message) 111 | pop_sig3: G2Element = PopSchemeMPL.sign(sk3, message) 112 | pop1: G2Element = PopSchemeMPL.pop_prove(sk1) 113 | pop2: G2Element = PopSchemeMPL.pop_prove(sk2) 114 | pop3: G2Element = PopSchemeMPL.pop_prove(sk3) 115 | 116 | ok = PopSchemeMPL.pop_verify(pk1, pop1) 117 | ok = PopSchemeMPL.pop_verify(pk2, pop2) 118 | ok = PopSchemeMPL.pop_verify(pk3, pop3) 119 | 120 | pop_sig_agg: G2Element = PopSchemeMPL.aggregate([pop_sig1, pop_sig2, pop_sig3]) 121 | 122 | ok = PopSchemeMPL.fast_aggregate_verify([pk1, pk2, pk3], message, pop_sig_agg) 123 | 124 | # Aggregate public key, indistinguishable from a single public key 125 | pop_agg_pk: G1Element = pk1 + pk2 + pk3 126 | ok = PopSchemeMPL.verify(pop_agg_pk, message, pop_sig_agg) 127 | 128 | # Aggregate private keys 129 | pop_agg_sk: PrivateKey = PrivateKey.aggregate([sk1, sk2, sk3]) 130 | ok = PopSchemeMPL.sign(pop_agg_sk, message) == pop_sig_agg 131 | ``` 132 | 133 | ## HD keys using [EIP-2333](https://github.com/ethereum/EIPs/pull/2333) 134 | 135 | ```python 136 | master_sk: PrivateKey = AugSchemeMPL.key_gen(seed) 137 | child: PrivateKey = AugSchemeMPL.derive_child_sk(master_sk, 152) 138 | grandchild: PrivateKey = AugSchemeMPL.derive_child_sk(child, 952) 139 | 140 | master_pk: G1Element = master_sk.get_g1() 141 | child_u: PrivateKey = AugSchemeMPL.derive_child_sk_unhardened(master_sk, 22) 142 | grandchild_u: PrivateKey = AugSchemeMPL.derive_child_sk_unhardened(child_u, 0) 143 | 144 | child_u_pk: G1Element = AugSchemeMPL.derive_child_pk_unhardened(master_pk, 22) 145 | grandchild_u_pk: G1Element = AugSchemeMPL.derive_child_pk_unhardened(child_u_pk, 0) 146 | 147 | ok = (grandchild_u_pk == grandchild_u.get_g1()) 148 | ``` 149 | -------------------------------------------------------------------------------- /python-bindings/benchmark.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa: E501 2 | import time 3 | import secrets 4 | 5 | from blspy import ( 6 | AugSchemeMPL, 7 | G1Element, 8 | G2Element, 9 | PrivateKey, 10 | ) 11 | 12 | def startStopwatch(): 13 | return time.perf_counter() 14 | 15 | def endStopwatch(test_name, start, numIters): 16 | end_time = time.perf_counter() 17 | 18 | duration = end_time - start 19 | 20 | print("\n%s\nTotal: %d runs in %0.1f ms\nAvg: %f" 21 | % (test_name, numIters, duration * 1000, duration * 1000 / numIters)) 22 | 23 | def batch_verification(): 24 | 25 | numIters = 100000 26 | sig_bytes = [] 27 | pk_bytes = [] 28 | ms = [] 29 | 30 | for i in range(numIters): 31 | message = b"%d" % i 32 | sk: PrivateKey = AugSchemeMPL.key_gen(secrets.token_bytes(32)) 33 | pk: G1Element = sk.get_g1() 34 | sig: G2Element = AugSchemeMPL.sign(sk, message) 35 | 36 | sig_bytes.append(bytes(sig)) 37 | pk_bytes.append(bytes(pk)) 38 | ms.append(message) 39 | 40 | pks = [] 41 | 42 | start = startStopwatch(); 43 | for pk in pk_bytes: 44 | pks.append(G1Element.from_bytes(pk)) 45 | 46 | endStopwatch("Public key validation", start, numIters); 47 | 48 | sigs = [] 49 | 50 | start = startStopwatch() 51 | for sig in sig_bytes: 52 | sigs.append(G2Element.from_bytes(sig)) 53 | endStopwatch("Signature validation", start, numIters); 54 | 55 | start = startStopwatch() 56 | aggSig = AugSchemeMPL.aggregate(sigs) 57 | endStopwatch("Aggregation", start, numIters); 58 | 59 | start = startStopwatch() 60 | ok = AugSchemeMPL.aggregate_verify(pks, ms, aggSig); 61 | endStopwatch("Batch verification", start, numIters); 62 | if not ok: 63 | print("aggregate_verification failed!") 64 | sys.exit(1) 65 | 66 | batch_verification() 67 | -------------------------------------------------------------------------------- /python-impl/README.md: -------------------------------------------------------------------------------- 1 | # BLS12-381 and Signatures in python 2 | 3 | Implements the BLS12 curve and optimal ate pairing, as well 4 | as BLS signatures and aggregation. Use for reference / educational purposes only. 5 | 6 | For an optimized implementation, use the [Python bindings](https://github.com/Chia-Network/bls-signatures/tree/main/python-bindings). 7 | 8 | For a good introduction to pairings, read [Pairings for Beginners](https://static1.squarespace.com/static/5fdbb09f31d71c1227082339/t/5ff394720493bd28278889c6/1609798774687/PairingsForBeginners.pdf) by Craig Costello. 9 | 10 | Map to curve implementation from [Algorand](https://github.com/algorand/bls_sigs_ref/). 11 | 12 | Run the tests with `python impl-test.py`. 13 | -------------------------------------------------------------------------------- /python-impl/bls12381.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from fields import Fq, Fq2 3 | 4 | # BLS parameter used to generate the other parameters 5 | # Spec is found here: https://github.com/zkcrypto/pairing/tree/master/src/bls12_381 6 | x = -0xD201000000010000 7 | 8 | # 381 bit prime 9 | # Also see fields:bls12381_q 10 | q = 0x1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFAAAB 11 | 12 | # a,b and a2, b2, define the elliptic curve and twisted curve. 13 | # y^2 = x^3 + 4 14 | # y^2 = x^3 + 4(u + 1) 15 | a = Fq(q, 0) 16 | b = Fq(q, 4) 17 | a_twist = Fq2(q, 0, 0) 18 | b_twist = Fq2(q, 4, 4) 19 | 20 | # The generators for g1 and g2 21 | gx = Fq( 22 | q, 23 | 0x17F1D3A73197D7942695638C4FA9AC0FC3688C4F9774B905A14E3A3F171BAC586C55E83FF97A1AEFFB3AF00ADB22C6BB, 24 | ) 25 | gy = Fq( 26 | q, 27 | 0x08B3F481E3AAA0F1A09E30ED741D8AE4FCF5E095D5D00AF600DB18CB2C04B3EDD03CC744A2888AE40CAA232946C5E7E1, 28 | ) 29 | 30 | g2x = Fq2( 31 | q, 32 | 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160, 33 | 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758, 34 | ) 35 | g2y = Fq2( 36 | q, 37 | 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905, 38 | 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582, 39 | ) 40 | 41 | # The order of all three groups (g1, g2, and gt). Note, the elliptic curve E_twist 42 | # actually has more valid points than this. This is relevant when hashing onto the 43 | # curve, where we use a point that is not in g2, and map it into g2. 44 | n = 0x73EDA753299D7D483339D80809A1D80553BDA402FFFE5BFEFFFFFFFF00000001 45 | 46 | # Cofactor used to generate r torsion points 47 | h = 0x396C8C005555E1568C00AAAB0000AAAB 48 | 49 | # https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-07#section-8.8.2 50 | h_eff = 0xBC69F08F2EE75B3584C6A0EA91B352888E2A8E9145AD7689986FF031508FFE1329C2F178731DB956D82BF015D1212B02EC0EC69D7477C1AE954CBC06689F6A359894C0ADEBBF6B4E8020005AAA95551 51 | 52 | # Embedding degree 53 | k = 12 54 | 55 | # sqrt(-3) mod q 56 | sqrt_n3 = 1586958781458431025242759403266842894121773480562120986020912974854563298150952611241517463240701 57 | 58 | # (sqrt(-3) - 1) / 2 mod q 59 | sqrt_n3m1o2 = 793479390729215512621379701633421447060886740281060493010456487427281649075476305620758731620350 60 | 61 | # This is the normal elliptic curve. G1 points are on here. 62 | def parameters(): 63 | return (q, a, b, gx, gy, g2x, g2y, n, h, x, k, sqrt_n3, sqrt_n3m1o2) 64 | 65 | 66 | # This is the sextic twist used to send elements of G2 from 67 | # coordinates in Fq12 to coordinates in Fq2. It's isomorphic 68 | # to the above elliptic curve. See Page 63 of Costello. 69 | def parameters_twist(): 70 | return ( 71 | q, 72 | a_twist, 73 | b_twist, 74 | gx, 75 | gy, 76 | g2x, 77 | g2y, 78 | n, 79 | h_eff, 80 | x, 81 | k, 82 | sqrt_n3, 83 | sqrt_n3m1o2, 84 | ) 85 | 86 | 87 | """ 88 | Copyright 2020 Chia Network Inc 89 | 90 | Licensed under the Apache License, Version 2.0 (the "License"); 91 | you may not use this file except in compliance with the License. 92 | You may obtain a copy of the License at 93 | 94 | http://www.apache.org/licenses/LICENSE-2.0 95 | 96 | Unless required by applicable law or agreed to in writing, software 97 | distributed under the License is distributed on an "AS IS" BASIS, 98 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 99 | See the License for the specific language governing permissions and 100 | limitations under the License. 101 | """ 102 | -------------------------------------------------------------------------------- /python-impl/hash_to_field.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # pure Python implementation of hash-to-field as specified in 4 | # https://github.com/pairingwg/bls_standard/blob/master/minutes/spec-v1.md 5 | 6 | import hashlib 7 | 8 | from bls12381 import q 9 | 10 | 11 | # defined in RFC 3447, section 4.1 12 | def I2OSP(val, length): 13 | if val < 0 or val >= (1 << (8 * length)): 14 | raise ValueError("bad I2OSP call: val=%d length=%d" % (val, length)) 15 | ret = [0] * length 16 | val_ = val 17 | for idx in reversed(range(0, length)): 18 | ret[idx] = val_ & 0xFF 19 | val_ = val_ >> 8 20 | ret = bytes(ret) 21 | assert ret == int(val).to_bytes(length, "big"), "oops: %s %s" % ( 22 | str(ret), 23 | str(int(val).to_bytes(length, "big")), 24 | ) 25 | return ret 26 | 27 | 28 | # defined in RFC 3447, section 4.2 29 | def OS2IP(octets): 30 | ret = 0 31 | for o in octets: 32 | ret = ret << 8 33 | ret += o 34 | assert ret == int.from_bytes(octets, "big") 35 | return ret 36 | 37 | 38 | # expand_message_xmd from draft-irtf-cfrg-hash-to-curve-06 39 | def _strxor(str1, str2): 40 | return bytes(s1 ^ s2 for (s1, s2) in zip(str1, str2)) 41 | 42 | 43 | def expand_message_xmd(msg, DST, len_in_bytes, hash_fn): 44 | # input and output lengths for hash_fn 45 | b_in_bytes = hash_fn().digest_size 46 | r_in_bytes = hash_fn().block_size 47 | 48 | # ell, DST_prime, etc 49 | ell = (len_in_bytes + b_in_bytes - 1) // b_in_bytes 50 | if ell > 255: 51 | raise ValueError("expand_message_xmd: ell=%d out of range" % ell) 52 | DST_prime = DST + I2OSP(len(DST), 1) 53 | Z_pad = I2OSP(0, r_in_bytes) 54 | l_i_b_str = I2OSP(len_in_bytes, 2) 55 | 56 | b_0 = hash_fn(Z_pad + msg + l_i_b_str + I2OSP(0, 1) + DST_prime).digest() 57 | b_vals = [None] * ell 58 | b_vals[0] = hash_fn(b_0 + I2OSP(1, 1) + DST_prime).digest() 59 | for idx in range(1, ell): 60 | b_vals[idx] = hash_fn( 61 | _strxor(b_0, b_vals[idx - 1]) + I2OSP(idx + 1, 1) + DST_prime 62 | ).digest() 63 | pseudo_random_bytes = b"".join(b_vals) 64 | return pseudo_random_bytes[0:len_in_bytes] 65 | 66 | 67 | def expand_message_xof(msg, DST, len_in_bytes, hash_fn): 68 | DST_prime = DST + I2OSP(len(DST), 1) 69 | msg_prime = msg + I2OSP(len_in_bytes, 2) + DST_prime 70 | return hash_fn(msg_prime).digest(len_in_bytes) 71 | 72 | 73 | # hash_to_field from draft-irtf-cfrg-hash-to-curve-06 74 | def hash_to_field(msg, count, DST, modulus, degree, blen, expand_fn, hash_fn): 75 | # get pseudorandom bytes 76 | len_in_bytes = count * degree * blen 77 | pseudo_random_bytes = expand_fn(msg, DST, len_in_bytes, hash_fn) 78 | 79 | u_vals = [None] * count 80 | for idx in range(0, count): 81 | e_vals = [None] * degree 82 | for jdx in range(0, degree): 83 | elm_offset = blen * (jdx + idx * degree) 84 | tv = pseudo_random_bytes[elm_offset : elm_offset + blen] 85 | e_vals[jdx] = OS2IP(tv) % modulus 86 | u_vals[idx] = e_vals 87 | return u_vals 88 | 89 | 90 | def Hp(msg, count, dst): 91 | if not isinstance(msg, bytes): 92 | raise ValueError("Hp can't hash anything but bytes") 93 | return hash_to_field(msg, count, dst, q, 1, 64, expand_message_xmd, hashlib.sha256) 94 | 95 | 96 | def Hp2(msg, count, dst): 97 | if not isinstance(msg, bytes): 98 | raise ValueError("Hp2 can't hash anything but bytes") 99 | return hash_to_field(msg, count, dst, q, 2, 64, expand_message_xmd, hashlib.sha256) 100 | -------------------------------------------------------------------------------- /python-impl/hd_keys.py: -------------------------------------------------------------------------------- 1 | from ec import G1Generator, G2Generator, JacobianPoint, default_ec 2 | from hkdf import extract_expand 3 | from private_key import PrivateKey 4 | from util import hash256 5 | 6 | 7 | def key_gen(seed: bytes) -> PrivateKey: 8 | # KeyGen 9 | # 1. PRK = HKDF-Extract("BLS-SIG-KEYGEN-SALT-", IKM || I2OSP(0, 1)) 10 | # 2. OKM = HKDF-Expand(PRK, keyInfo || I2OSP(L, 2), L) 11 | # 3. SK = OS2IP(OKM) mod r 12 | # 4. return SK 13 | 14 | L = 48 15 | # `ceil((3 * ceil(log2(r))) / 16)`, where `r` is the order of the BLS 12-381 curve 16 | okm = extract_expand(L, seed + bytes([0]), b"BLS-SIG-KEYGEN-SALT-", bytes([0, L])) 17 | return PrivateKey(int.from_bytes(okm, "big") % default_ec.n) 18 | 19 | 20 | def ikm_to_lamport_sk(ikm: bytes, salt: bytes) -> bytes: 21 | return extract_expand(32 * 255, ikm, salt, b"") 22 | 23 | 24 | def parent_sk_to_lamport_pk(parent_sk: PrivateKey, index: int) -> bytes: 25 | salt = index.to_bytes(4, "big") 26 | ikm = bytes(parent_sk) 27 | not_ikm = bytes([e ^ 0xFF for e in ikm]) # Flip bits 28 | lamport0 = ikm_to_lamport_sk(ikm, salt) 29 | lamport1 = ikm_to_lamport_sk(not_ikm, salt) 30 | 31 | lamport_pk = bytes() 32 | for i in range(255): 33 | lamport_pk += hash256(lamport0[i * 32 : (i + 1) * 32]) 34 | for i in range(255): 35 | lamport_pk += hash256(lamport1[i * 32 : (i + 1) * 32]) 36 | 37 | return hash256(lamport_pk) 38 | 39 | 40 | def derive_child_sk(parent_sk: PrivateKey, index: int) -> PrivateKey: 41 | """ 42 | Derives a hardened EIP-2333 child private key, from a parent private key, 43 | at the specified index. 44 | """ 45 | lamport_pk = parent_sk_to_lamport_pk(parent_sk, index) 46 | return key_gen(lamport_pk) 47 | 48 | 49 | def derive_child_sk_unhardened(parent_sk: PrivateKey, index: int) -> PrivateKey: 50 | """ 51 | Derives an unhardened BIP-32 child private key, from a parent private key, 52 | at the specified index. WARNING: this key is not as secure as a hardened key. 53 | """ 54 | h = hash256(bytes(parent_sk.get_g1()) + index.to_bytes(4, "big")) 55 | return PrivateKey.aggregate([PrivateKey.from_bytes(h), parent_sk]) 56 | 57 | 58 | def derive_child_g1_unhardened(parent_pk: JacobianPoint, index: int) -> JacobianPoint: 59 | """ 60 | Derives an unhardened BIP-32 child public key, from a parent public key, 61 | at the specified index. WARNING: this key is not as secure as a hardened key. 62 | """ 63 | h = hash256(bytes(parent_pk) + index.to_bytes(4, "big")) 64 | return parent_pk + PrivateKey.from_bytes(h).value * G1Generator() 65 | 66 | 67 | def derive_child_g2_unhardened(parent_pk: JacobianPoint, index: int) -> JacobianPoint: 68 | """ 69 | Derives an unhardened BIP-32 child public key, from a parent public key, 70 | at the specified index. WARNING: this key is not as secure as a hardened key. 71 | """ 72 | h = hash256(bytes(parent_pk) + index.to_bytes(4, "big")) 73 | return parent_pk + PrivateKey.from_bytes(h) * G2Generator() 74 | 75 | 76 | """ 77 | Copyright 2020 Chia Network Inc 78 | 79 | Licensed under the Apache License, Version 2.0 (the "License"); 80 | you may not use this file except in compliance with the License. 81 | You may obtain a copy of the License at 82 | 83 | http://www.apache.org/licenses/LICENSE-2.0 84 | 85 | Unless required by applicable law or agreed to in writing, software 86 | distributed under the License is distributed on an "AS IS" BASIS, 87 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 88 | See the License for the specific language governing permissions and 89 | limitations under the License. 90 | """ 91 | -------------------------------------------------------------------------------- /python-impl/hkdf.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | from math import ceil 4 | 5 | BLOCK_SIZE = 32 6 | 7 | 8 | def extract(salt: bytes, ikm: bytes) -> bytes: 9 | h = hmac.new(salt, ikm, hashlib.sha256) 10 | return h.digest() 11 | 12 | 13 | def expand(L: int, prk: bytes, info: bytes) -> bytes: 14 | N: int = ceil(L / BLOCK_SIZE) 15 | bytes_written: int = 0 16 | okm: bytes = b"" 17 | 18 | for i in range(1, N + 1): 19 | if i == 1: 20 | h = hmac.new(prk, info + bytes([1]), hashlib.sha256) 21 | T: bytes = h.digest() 22 | else: 23 | h = hmac.new(prk, T + info + bytes([i]), hashlib.sha256) 24 | T = h.digest() 25 | to_write = L - bytes_written 26 | if to_write > BLOCK_SIZE: 27 | to_write = BLOCK_SIZE 28 | okm += T[:to_write] 29 | bytes_written += to_write 30 | assert bytes_written == L 31 | return okm 32 | 33 | 34 | def extract_expand(L: int, key: bytes, salt: bytes, info: bytes) -> bytes: 35 | prk = extract(salt, key) 36 | return expand(L, prk, info) 37 | 38 | 39 | """ 40 | Copyright 2020 Chia Network Inc 41 | 42 | Licensed under the Apache License, Version 2.0 (the "License"); 43 | you may not use this file except in compliance with the License. 44 | You may obtain a copy of the License at 45 | 46 | http://www.apache.org/licenses/LICENSE-2.0 47 | 48 | Unless required by applicable law or agreed to in writing, software 49 | distributed under the License is distributed on an "AS IS" BASIS, 50 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 51 | See the License for the specific language governing permissions and 52 | limitations under the License. 53 | """ 54 | -------------------------------------------------------------------------------- /python-impl/op_swu_g2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # pure Python implementation of optimized simplified SWU map to BLS12-381 G2 4 | # https://github.com/algorand/bls_sigs_ref 5 | # 6 | # This software is (C) 2019 Algorand, Inc. 7 | # 8 | # Licensed under the MIT license (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | 12 | # http://opensource.org/licenses/MIT 13 | 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | 20 | from bls12381 import h_eff, q 21 | from ec import JacobianPoint, default_ec_twist, eval_iso 22 | from fields import Fq, Fq2, roots_of_unity 23 | from hash_to_field import Hp2 24 | from typing import Optional 25 | 26 | 27 | def sgn0(x: Fq2) -> int: 28 | # https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-07#section-4.1 29 | 30 | sign_0: int = x[0].value % 2 31 | zero_0: bool = x[0] == 0 32 | sign_1: int = x[1].value % 2 33 | return sign_0 or (zero_0 and sign_1) 34 | 35 | 36 | # distinguished non-square in Fp2 for SWU map 37 | xi_2 = Fq2(q, -2, -1) 38 | 39 | # 3-isogenous curve parameters 40 | Ell2p_a = Fq2(q, 0, 240) 41 | Ell2p_b = Fq2(q, 1012, 1012) 42 | 43 | 44 | # eta values, used for computing sqrt(g(X1(t))) 45 | # For details on how to compute, see ../sage-impl/opt_sswu_g2.sage 46 | ev1 = 0x699BE3B8C6870965E5BF892AD5D2CC7B0E85A117402DFD83B7F4A947E02D978498255A2AAEC0AC627B5AFBDF1BF1C90 47 | ev2 = 0x8157CD83046453F5DD0972B6E3949E4288020B5B8A9CC99CA07E27089A2CE2436D965026ADAD3EF7BABA37F2183E9B5 48 | ev3 = 0xAB1C2FFDD6C253CA155231EB3E71BA044FD562F6F72BC5BAD5EC46A0B7A3B0247CF08CE6C6317F40EDBC653A72DEE17 49 | ev4 = 0xAA404866706722864480885D68AD0CCAC1967C7544B447873CC37E0181271E006DF72162A3D3E0287BF597FBF7F8FC1 50 | etas = (Fq2(q, ev1, ev2), Fq2(q, q - ev2, ev1), Fq2(q, ev3, ev4), Fq2(q, q - ev4, ev3)) 51 | del ev1, ev2, ev3, ev4 52 | 53 | 54 | # 55 | # Simplified SWU map, optimized and adapted to Ell2' 56 | # 57 | # This function maps an element of Fp^2 to the curve Ell2', 3-isogenous to Ell2. 58 | def osswu2_help(t): 59 | assert isinstance(t, Fq2) 60 | 61 | # first, compute X0(t), detecting and handling exceptional case 62 | num_den_common = xi_2 ** 2 * t ** 4 + xi_2 * t ** 2 63 | x0_num = Ell2p_b * (num_den_common + Fq(q, 1)) 64 | x0_den = -Ell2p_a * num_den_common 65 | x0_den = Ell2p_a * xi_2 if x0_den == 0 else x0_den 66 | 67 | # compute num and den of g(X0(t)) 68 | gx0_den = pow(x0_den, 3) 69 | gx0_num = Ell2p_b * gx0_den 70 | gx0_num += Ell2p_a * x0_num * pow(x0_den, 2) 71 | gx0_num += pow(x0_num, 3) 72 | 73 | # try taking sqrt of g(X0(t)) 74 | # this uses the trick for combining division and sqrt from Section 5 of 75 | # Bernstein, Duif, Lange, Schwabe, and Yang, "High-speed high-security signatures." 76 | # J Crypt Eng 2(2):77--89, Sept. 2012. http://ed25519.cr.yp.to/ed25519-20110926.pdf 77 | tmp1 = pow(gx0_den, 7) # v^7 78 | tmp2 = gx0_num * tmp1 # u v^7 79 | tmp1 = tmp1 * tmp2 * gx0_den # u v^15 80 | sqrt_candidate = tmp2 * pow(tmp1, (q ** 2 - 9) // 16) 81 | 82 | # check if g(X0(t)) is square and return the sqrt if so 83 | for root in roots_of_unity: 84 | y0 = sqrt_candidate * root 85 | if y0 ** 2 * gx0_den == gx0_num: 86 | # found sqrt(g(X0(t))). force sign of y to equal sign of t 87 | if sgn0(y0) != sgn0(t): 88 | y0 = -y0 89 | assert sgn0(y0) == sgn0(t) 90 | return JacobianPoint( 91 | x0_num * x0_den, y0 * pow(x0_den, 3), x0_den, False, default_ec_twist 92 | ) 93 | 94 | # if we've gotten here, then g(X0(t)) is not square. convert srqt_candidate to sqrt(g(X1(t))) 95 | (x1_num, x1_den) = (xi_2 * t ** 2 * x0_num, x0_den) 96 | (gx1_num, gx1_den) = (xi_2 ** 3 * t ** 6 * gx0_num, gx0_den) 97 | sqrt_candidate *= t ** 3 98 | for eta in etas: 99 | y1 = eta * sqrt_candidate 100 | if y1 ** 2 * gx1_den == gx1_num: 101 | # found sqrt(g(X1(t))). force sign of y to equal sign of t 102 | if sgn0(y1) != sgn0(t): 103 | y1 = -y1 104 | assert sgn0(y1) == sgn0(t) 105 | return JacobianPoint( 106 | x1_num * x1_den, y1 * pow(x1_den, 3), x1_den, False, default_ec_twist 107 | ) 108 | 109 | # if we got here, something is wrong 110 | raise RuntimeError("osswu2_help failed for unknown reasons") 111 | 112 | 113 | # 114 | # 3-Isogeny from Ell2' to Ell2 115 | # 116 | # coefficients for the 3-isogeny map from Ell2' to Ell2 117 | xnum = ( 118 | Fq2( 119 | q, 120 | 0x5C759507E8E333EBB5B7A9A47D7ED8532C52D39FD3A042A88B58423C50AE15D5C2638E343D9C71C6238AAAAAAAA97D6, 121 | 0x5C759507E8E333EBB5B7A9A47D7ED8532C52D39FD3A042A88B58423C50AE15D5C2638E343D9C71C6238AAAAAAAA97D6, 122 | ), 123 | Fq2( 124 | q, 125 | 0x0, 126 | 0x11560BF17BAA99BC32126FCED787C88F984F87ADF7AE0C7F9A208C6B4F20A4181472AAA9CB8D555526A9FFFFFFFFC71A, 127 | ), 128 | Fq2( 129 | q, 130 | 0x11560BF17BAA99BC32126FCED787C88F984F87ADF7AE0C7F9A208C6B4F20A4181472AAA9CB8D555526A9FFFFFFFFC71E, 131 | 0x8AB05F8BDD54CDE190937E76BC3E447CC27C3D6FBD7063FCD104635A790520C0A395554E5C6AAAA9354FFFFFFFFE38D, 132 | ), 133 | Fq2( 134 | q, 135 | 0x171D6541FA38CCFAED6DEA691F5FB614CB14B4E7F4E810AA22D6108F142B85757098E38D0F671C7188E2AAAAAAAA5ED1, 136 | 0x0, 137 | ), 138 | ) 139 | xden = ( 140 | Fq2( 141 | q, 142 | 0x0, 143 | 0x1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFAA63, 144 | ), 145 | Fq2( 146 | q, 147 | 0xC, 148 | 0x1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFAA9F, 149 | ), 150 | Fq2(q, 0x1, 0x0), 151 | ) 152 | ynum = ( 153 | Fq2( 154 | q, 155 | 0x1530477C7AB4113B59A4C18B076D11930F7DA5D4A07F649BF54439D87D27E500FC8C25EBF8C92F6812CFC71C71C6D706, 156 | 0x1530477C7AB4113B59A4C18B076D11930F7DA5D4A07F649BF54439D87D27E500FC8C25EBF8C92F6812CFC71C71C6D706, 157 | ), 158 | Fq2( 159 | q, 160 | 0x0, 161 | 0x5C759507E8E333EBB5B7A9A47D7ED8532C52D39FD3A042A88B58423C50AE15D5C2638E343D9C71C6238AAAAAAAA97BE, 162 | ), 163 | Fq2( 164 | q, 165 | 0x11560BF17BAA99BC32126FCED787C88F984F87ADF7AE0C7F9A208C6B4F20A4181472AAA9CB8D555526A9FFFFFFFFC71C, 166 | 0x8AB05F8BDD54CDE190937E76BC3E447CC27C3D6FBD7063FCD104635A790520C0A395554E5C6AAAA9354FFFFFFFFE38F, 167 | ), 168 | Fq2( 169 | q, 170 | 0x124C9AD43B6CF79BFBF7043DE3811AD0761B0F37A1E26286B0E977C69AA274524E79097A56DC4BD9E1B371C71C718B10, 171 | 0x0, 172 | ), 173 | ) 174 | yden = ( 175 | Fq2( 176 | q, 177 | 0x1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFA8FB, 178 | 0x1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFA8FB, 179 | ), 180 | Fq2( 181 | q, 182 | 0x0, 183 | 0x1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFA9D3, 184 | ), 185 | Fq2( 186 | q, 187 | 0x12, 188 | 0x1A0111EA397FE69A4B1BA7B6434BACD764774B84F38512BF6730D2A0F6B0F6241EABFFFEB153FFFFB9FEFFFFFFFFAA99, 189 | ), 190 | Fq2(q, 0x1, 0x0), 191 | ) 192 | 193 | 194 | # compute 3-isogeny map from Ell2' to Ell2 195 | def iso3(P): 196 | return eval_iso(P, (xnum, xden, ynum, yden), default_ec_twist) 197 | 198 | 199 | # 200 | # map from Fq2 element(s) to point in G2 subgroup of Ell2 201 | # 202 | def opt_swu2_map(t: Fq2, t2: Optional[Fq2] = None) -> JacobianPoint: 203 | Pp = iso3(osswu2_help(t)) 204 | if t2 is not None: 205 | Pp2 = iso3(osswu2_help(t2)) 206 | Pp = Pp + Pp2 207 | return Pp * h_eff 208 | 209 | 210 | # 211 | # map from bytes() to point in G2 subgroup of Ell2 212 | # 213 | def g2_map(alpha: bytes, dst=None): 214 | return opt_swu2_map(*(Fq2(q, *hh) for hh in Hp2(alpha, 2, dst))) 215 | -------------------------------------------------------------------------------- /python-impl/pairing.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from typing import List 3 | 4 | import bls12381 5 | from ec import AffinePoint, JacobianPoint, untwist 6 | from fields import Fq, Fq12 7 | 8 | # Struct for elliptic curve parameters 9 | EC = namedtuple("EC", "q a b gx gy g2x g2y n h x k sqrt_n3 sqrt_n3m1o2") 10 | 11 | default_ec = EC(*bls12381.parameters()) 12 | default_ec_twist = EC(*bls12381.parameters()) 13 | 14 | 15 | def int_to_bits(i: int) -> List[int]: 16 | if i < 1: 17 | return [0] 18 | bits = [] 19 | while i != 0: 20 | bits.append(i % 2) 21 | i = i // 2 22 | return list(reversed(bits)) 23 | 24 | 25 | def double_line_eval(R: AffinePoint, P: AffinePoint, ec=default_ec): 26 | """ 27 | Creates an equation for a line tangent to R, 28 | and evaluates this at the point P. f(x) = y - sv - v. 29 | f(P). 30 | """ 31 | R12 = untwist(R) 32 | 33 | slope = (Fq(ec.q, 3) * (R12.x ** 2) + ec.a) / (Fq(ec.q, 2) * R12.y) 34 | v = R12.y - slope * R12.x 35 | 36 | return P.y - P.x * slope - v 37 | 38 | 39 | def add_line_eval(R: AffinePoint, Q: AffinePoint, P: AffinePoint, ec=default_ec) -> Fq: 40 | """ 41 | Creates an equation for a line between R and Q, 42 | and evaluates this at the point P. f(x) = y - sv - v. 43 | f(P). 44 | """ 45 | R12 = untwist(R) 46 | Q12 = untwist(Q) 47 | 48 | # This is the case of a vertical line, where the denominator 49 | # will be 0. 50 | if R12 == Q12.negate(): 51 | return P.x - R12.x 52 | 53 | slope = (Q12.y - R12.y) / (Q12.x - R12.x) 54 | v = (Q12.y * R12.x - R12.y * Q12.x) / (R12.x - Q12.x) 55 | 56 | return P.y - P.x * slope - v 57 | 58 | 59 | def miller_loop(T: int, P: AffinePoint, Q: AffinePoint, ec=default_ec) -> Fq12: 60 | """ 61 | Performs a double and add algorithm for the ate pairing. This algorithm 62 | is taken from Craig Costello's "Pairing for Beginners". 63 | """ 64 | T_bits = int_to_bits(T) 65 | R = Q 66 | f = Fq12.one(ec.q) # f is an element of Fq12 67 | for i in range(1, len(T_bits)): 68 | # Compute sloped line lrr 69 | lrr = double_line_eval(R, P, ec) 70 | f = f * f * lrr 71 | 72 | R = Fq(ec.q, 2) * R 73 | if T_bits[i] == 1: 74 | # Compute sloped line lrq 75 | lrq = add_line_eval(R, Q, P, ec) 76 | f = f * lrq 77 | 78 | R = R + Q 79 | return f 80 | 81 | 82 | def final_exponentiation(element: Fq12, ec=default_ec) -> Fq12: 83 | """ 84 | Performs a final exponentiation to map the result of the Miller 85 | loop to a unique element of Fq12. 86 | """ 87 | if ec.k == 12: 88 | ans = element ** ((pow(ec.q, 4) - pow(ec.q, 2) + 1) // ec.n) 89 | ans = ans.qi_power(2) * ans 90 | ans = ans.qi_power(6) / ans 91 | return ans 92 | else: 93 | return element ** ((pow(ec.q, ec.k) - 1) // ec.n) 94 | 95 | 96 | def ate_pairing(P: JacobianPoint, Q: JacobianPoint, ec=default_ec) -> Fq12: 97 | """ 98 | Performs one ate pairing. 99 | """ 100 | t = default_ec.x + 1 101 | T = abs(t - 1) 102 | element = miller_loop(T, P.to_affine(), Q.to_affine(), ec) 103 | return final_exponentiation(element, ec) 104 | 105 | 106 | def ate_pairing_multi( 107 | Ps: List[JacobianPoint], Qs: List[JacobianPoint], ec=default_ec 108 | ) -> Fq12: 109 | """ 110 | Computes multiple pairings at once. This is more efficient, 111 | since we can multiply all the results of the miller loops, 112 | and perform just one final exponentiation. 113 | """ 114 | t = default_ec.x + 1 115 | T = abs(t - 1) 116 | prod = Fq12.one(ec.q) 117 | for i in range(len(Qs)): 118 | prod *= miller_loop(T, Ps[i].to_affine(), Qs[i].to_affine(), ec) 119 | return final_exponentiation(prod, ec) 120 | 121 | 122 | """ 123 | Copyright 2020 Chia Network Inc 124 | 125 | Licensed under the Apache License, Version 2.0 (the "License"); 126 | you may not use this file except in compliance with the License. 127 | You may obtain a copy of the License at 128 | 129 | http://www.apache.org/licenses/LICENSE-2.0 130 | 131 | Unless required by applicable law or agreed to in writing, software 132 | distributed under the License is distributed on an "AS IS" BASIS, 133 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 134 | See the License for the specific language governing permissions and 135 | limitations under the License. 136 | """ 137 | -------------------------------------------------------------------------------- /python-impl/private_key.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from ec import G1Generator, default_ec 4 | from hkdf import extract_expand 5 | 6 | 7 | class PrivateKey: 8 | """ 9 | Private keys are just random integers between 1 and the group order. 10 | """ 11 | 12 | PRIVATE_KEY_SIZE = 32 13 | 14 | def __init__(self, value): 15 | assert value < default_ec.n 16 | self.value = value 17 | 18 | @staticmethod 19 | def from_bytes(buffer): 20 | return PrivateKey(int.from_bytes(buffer, "big") % default_ec.n) 21 | 22 | @staticmethod 23 | def from_seed(seed): 24 | L = 48 25 | # `ceil((3 * ceil(log2(r))) / 16)`, where `r` is the order of the BLS 12-381 curve 26 | okm = extract_expand( 27 | L, seed + bytes([0]), b"BLS-SIG-KEYGEN-SALT-", bytes([0, L]) 28 | ) 29 | return PrivateKey(int.from_bytes(okm, "big") % default_ec.n) 30 | 31 | @staticmethod 32 | def from_int(n: int): 33 | return PrivateKey(n % default_ec.n) 34 | 35 | def get_g1(self): 36 | return self.value * G1Generator() 37 | 38 | def sign(self, m): 39 | pass 40 | 41 | def __eq__(self, other): 42 | return self.value == other.value 43 | 44 | def __hash__(self): 45 | return self.value 46 | 47 | def __bytes__(self): 48 | return self.value.to_bytes(self.PRIVATE_KEY_SIZE, "big") 49 | 50 | def size(self): 51 | return self.PRIVATE_KEY_SIZE 52 | 53 | def __str__(self): 54 | return "PrivateKey(0x" + bytes(self).hex() + ")" 55 | 56 | def __repr__(self): 57 | return "PrivateKey(0x" + bytes(self).hex() + ")" 58 | 59 | @staticmethod 60 | def aggregate(private_keys): 61 | """ 62 | Aggregates private keys together 63 | """ 64 | return PrivateKey(sum(pk.value for pk in private_keys) % default_ec.n) 65 | 66 | 67 | """ 68 | Copyright 2020 Chia Network Inc 69 | 70 | Licensed under the Apache License, Version 2.0 (the "License"); 71 | you may not use this file except in compliance with the License. 72 | You may obtain a copy of the License at 73 | 74 | http://www.apache.org/licenses/LICENSE-2.0 75 | 76 | Unless required by applicable law or agreed to in writing, software 77 | distributed under the License is distributed on an "AS IS" BASIS, 78 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 79 | See the License for the specific language governing permissions and 80 | limitations under the License. 81 | """ 82 | -------------------------------------------------------------------------------- /python-impl/schemes.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from ec import G1Generator, JacobianPoint, default_ec 4 | from fields import Fq12 5 | from hd_keys import (derive_child_g1_unhardened, derive_child_sk, 6 | derive_child_sk_unhardened, key_gen) 7 | from op_swu_g2 import g2_map 8 | from pairing import ate_pairing_multi 9 | from private_key import PrivateKey 10 | 11 | basic_scheme_dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_" 12 | aug_scheme_dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_" 13 | pop_scheme_dst = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" 14 | pop_scheme_pop_dst = b"BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_" 15 | 16 | 17 | def core_sign_mpl(sk: PrivateKey, message: bytes, dst: bytes) -> JacobianPoint: 18 | return sk.value * g2_map(message, dst) 19 | 20 | 21 | def core_verify_mpl( 22 | pk: JacobianPoint, message: bytes, signature: JacobianPoint, dst: bytes 23 | ) -> bool: 24 | try: 25 | signature.check_valid() 26 | pk.check_valid() 27 | except AssertionError: 28 | return False 29 | q = g2_map(message, dst) 30 | one = Fq12.one(default_ec.q) 31 | pairing_result = ate_pairing_multi([pk, G1Generator().negate()], [q, signature]) 32 | return pairing_result == one 33 | 34 | 35 | def core_aggregate_mpl(signatures: List[JacobianPoint]) -> JacobianPoint: 36 | if len(signatures) < 1: 37 | raise ValueError("Must aggregate at least 1 signature") 38 | aggregate = signatures[0] 39 | aggregate.check_valid() 40 | for signature in signatures[1:]: 41 | signature.check_valid() 42 | aggregate += signature 43 | return aggregate 44 | 45 | 46 | def core_aggregate_verify( 47 | pks: List[JacobianPoint], ms: List[bytes], signature: JacobianPoint, dst: bytes 48 | ) -> bool: 49 | if len(pks) != len(ms) or len(pks) < 1: 50 | return False 51 | try: 52 | signature.check_valid() 53 | qs = [signature] 54 | ps = [G1Generator().negate()] 55 | for i in range(len(pks)): 56 | pks[i].check_valid() 57 | qs.append(g2_map(ms[i], dst)) 58 | ps.append(pks[i]) 59 | return Fq12.one(default_ec.q) == ate_pairing_multi(ps, qs) 60 | 61 | except AssertionError: 62 | return False 63 | 64 | 65 | class BasicSchemeMPL: 66 | @staticmethod 67 | def key_gen(seed: bytes) -> PrivateKey: 68 | return key_gen(seed) 69 | 70 | @staticmethod 71 | def sign(sk: PrivateKey, message: bytes) -> JacobianPoint: 72 | return core_sign_mpl(sk, message, basic_scheme_dst) 73 | 74 | @staticmethod 75 | def verify(pk: JacobianPoint, message: bytes, signature: JacobianPoint) -> bool: 76 | return core_verify_mpl(pk, message, signature, basic_scheme_dst) 77 | 78 | @staticmethod 79 | def aggregate(signatures: List[JacobianPoint]) -> JacobianPoint: 80 | return core_aggregate_mpl(signatures) 81 | 82 | @staticmethod 83 | def aggregate_verify( 84 | pks: List[JacobianPoint], ms: List[bytes], signature: JacobianPoint 85 | ) -> bool: 86 | if len(pks) != len(ms) or len(pks) < 1: 87 | return False 88 | if len(set(ms)) != len(ms): 89 | # Disallow repeated messages 90 | return False 91 | return core_aggregate_verify(pks, ms, signature, basic_scheme_dst) 92 | 93 | @staticmethod 94 | def derive_child_sk(sk: PrivateKey, index: int) -> PrivateKey: 95 | return derive_child_sk(sk, index) 96 | 97 | @staticmethod 98 | def derive_child_sk_unhardened(sk: PrivateKey, index: int) -> PrivateKey: 99 | return derive_child_sk_unhardened(sk, index) 100 | 101 | @staticmethod 102 | def derive_child_pk_unhardened(pk: JacobianPoint, index: int) -> JacobianPoint: 103 | return derive_child_g1_unhardened(pk, index) 104 | 105 | 106 | class AugSchemeMPL: 107 | @staticmethod 108 | def key_gen(seed: bytes) -> PrivateKey: 109 | return key_gen(seed) 110 | 111 | @staticmethod 112 | def sign(sk: PrivateKey, message: bytes) -> JacobianPoint: 113 | pk = sk.get_g1() 114 | return core_sign_mpl(sk, bytes(pk) + message, aug_scheme_dst) 115 | 116 | @staticmethod 117 | def verify(pk: JacobianPoint, message: bytes, signature: JacobianPoint) -> bool: 118 | return core_verify_mpl(pk, bytes(pk) + message, signature, aug_scheme_dst) 119 | 120 | @staticmethod 121 | def aggregate(signatures: List[JacobianPoint]) -> JacobianPoint: 122 | return core_aggregate_mpl(signatures) 123 | 124 | @staticmethod 125 | def aggregate_verify( 126 | pks: List[JacobianPoint], ms: List[bytes], signature: JacobianPoint 127 | ) -> bool: 128 | if len(pks) != len(ms) or len(pks) < 1: 129 | return False 130 | m_primes = [bytes(pks[i]) + ms[i] for i in range(len(pks))] 131 | return core_aggregate_verify(pks, m_primes, signature, aug_scheme_dst) 132 | 133 | @staticmethod 134 | def derive_child_sk(sk: PrivateKey, index: int) -> PrivateKey: 135 | return derive_child_sk(sk, index) 136 | 137 | @staticmethod 138 | def derive_child_sk_unhardened(sk: PrivateKey, index: int) -> PrivateKey: 139 | return derive_child_sk_unhardened(sk, index) 140 | 141 | @staticmethod 142 | def derive_child_pk_unhardened(pk: JacobianPoint, index: int) -> JacobianPoint: 143 | return derive_child_g1_unhardened(pk, index) 144 | 145 | 146 | class PopSchemeMPL: 147 | @staticmethod 148 | def key_gen(seed: bytes) -> PrivateKey: 149 | return key_gen(seed) 150 | 151 | @staticmethod 152 | def sign(sk: PrivateKey, message: bytes) -> JacobianPoint: 153 | return core_sign_mpl(sk, message, pop_scheme_dst) 154 | 155 | @staticmethod 156 | def verify(pk: JacobianPoint, message: bytes, signature: JacobianPoint) -> bool: 157 | return core_verify_mpl(pk, message, signature, pop_scheme_dst) 158 | 159 | @staticmethod 160 | def aggregate(signatures: List[JacobianPoint]) -> JacobianPoint: 161 | return core_aggregate_mpl(signatures) 162 | 163 | @staticmethod 164 | def aggregate_verify( 165 | pks: List[JacobianPoint], ms: List[bytes], signature: JacobianPoint 166 | ) -> bool: 167 | if len(pks) != len(ms) or len(pks) < 1: 168 | return False 169 | return core_aggregate_verify(pks, ms, signature, pop_scheme_dst) 170 | 171 | @staticmethod 172 | def pop_prove(sk: PrivateKey) -> JacobianPoint: 173 | pk: JacobianPoint = sk.get_g1() 174 | return sk.value * g2_map(bytes(pk), pop_scheme_pop_dst) 175 | 176 | @staticmethod 177 | def pop_verify(pk: JacobianPoint, proof: JacobianPoint) -> bool: 178 | try: 179 | proof.check_valid() 180 | pk.check_valid() 181 | q = g2_map(bytes(pk), pop_scheme_pop_dst) 182 | one = Fq12.one(default_ec.q) 183 | pairing_result = ate_pairing_multi([pk, G1Generator().negate()], [q, proof]) 184 | return pairing_result == one 185 | except AssertionError: 186 | return False 187 | 188 | @staticmethod 189 | def fast_aggregate_verify( 190 | pks: List[JacobianPoint], message: bytes, signature: JacobianPoint 191 | ) -> bool: 192 | if len(pks) < 1: 193 | return False 194 | aggregate: JacobianPoint = pks[0] 195 | for pk in pks[1:]: 196 | aggregate += pk 197 | return core_verify_mpl(aggregate, message, signature, pop_scheme_dst) 198 | 199 | @staticmethod 200 | def derive_child_sk(sk: PrivateKey, index: int) -> PrivateKey: 201 | return derive_child_sk(sk, index) 202 | 203 | @staticmethod 204 | def derive_child_sk_unhardened(sk: PrivateKey, index: int) -> PrivateKey: 205 | return derive_child_sk_unhardened(sk, index) 206 | 207 | @staticmethod 208 | def derive_child_pk_unhardened(pk: JacobianPoint, index: int) -> JacobianPoint: 209 | return derive_child_g1_unhardened(pk, index) 210 | -------------------------------------------------------------------------------- /python-impl/util.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | HMAC_BLOCK_SIZE = 64 4 | 5 | 6 | def hash256(m): 7 | if type(m) is not bytes: 8 | m = m.encode("utf-8") 9 | return hashlib.sha256(m).digest() 10 | 11 | 12 | def hash512(m): 13 | if type(m) is not bytes: 14 | m = m.encode("utf-8") 15 | return hash256(m + bytes([0])) + hash256(m + bytes([1])) 16 | 17 | 18 | def hmac256(m, k): 19 | if type(m) is not bytes and type(m) is not bytearray: 20 | m = m.encode("utf-8") 21 | if type(k) is not bytes and type(k) is not bytearray: 22 | k = k.encode("utf-8") 23 | k = bytes(k) 24 | if len(k) > HMAC_BLOCK_SIZE: 25 | k = hash256(k) 26 | while len(k) < HMAC_BLOCK_SIZE: 27 | k += bytes([0]) 28 | opad = bytes([0x5C] * HMAC_BLOCK_SIZE) 29 | ipad = bytes([0x36] * HMAC_BLOCK_SIZE) 30 | kopad = bytes([k[i] ^ opad[i] for i in range(HMAC_BLOCK_SIZE)]) 31 | kipad = bytes([k[i] ^ ipad[i] for i in range(HMAC_BLOCK_SIZE)]) 32 | return hash256(kopad + hash256(kipad + m)) 33 | 34 | 35 | """ 36 | Copyright 2020 Chia Network Inc 37 | 38 | Licensed under the Apache License, Version 2.0 (the "License"); 39 | you may not use this file except in compliance with the License. 40 | You may obtain a copy of the License at 41 | 42 | http://www.apache.org/licenses/LICENSE-2.0 43 | 44 | Unless required by applicable law or agreed to in writing, software 45 | distributed under the License is distributed on an "AS IS" BASIS, 46 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 47 | See the License for the specific language governing permissions and 48 | limitations under the License. 49 | """ 50 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import platform 4 | import subprocess 5 | import sys 6 | from pathlib import Path 7 | from setuptools import Extension, setup 8 | from setuptools.command.build_ext import build_ext 9 | 10 | 11 | class CMakeExtension(Extension): 12 | def __init__(self, name, sourcedir=""): 13 | Extension.__init__(self, name, sources=["./"]) 14 | self.sourcedir = os.path.abspath(sourcedir) 15 | 16 | 17 | class CMakeBuild(build_ext): 18 | def run(self): 19 | try: 20 | subprocess.check_output(["cmake", "--version"]) 21 | except OSError: 22 | raise RuntimeError( 23 | "CMake must be installed to build" 24 | + " the following extensions: " 25 | + ", ".join(e.name for e in self.extensions) 26 | ) 27 | 28 | for ext in self.extensions: 29 | self.build_extension(ext) 30 | 31 | def build_extension(self, ext): 32 | extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) 33 | cmake_args = [ 34 | "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + extdir, 35 | "-DPYTHON_EXECUTABLE=" + sys.executable, 36 | "-DBUILD_BLS_TESTS=0", 37 | "-DBUILD_BLS_BENCHMARKS=0", 38 | ] 39 | 40 | cfg = "Debug" if self.debug else "Release" 41 | build_args = ["--config", cfg] 42 | 43 | if platform.system() == "Windows": 44 | cmake_args += [ 45 | "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir) 46 | ] 47 | else: 48 | cmake_args += ["-DCMAKE_BUILD_TYPE=" + cfg] 49 | build_args += ["--", "-j", "6"] 50 | 51 | env = os.environ.copy() 52 | env["CXXFLAGS"] = '{} -DVERSION_INFO=\\"{}\\"'.format( 53 | env.get("CXXFLAGS", ""), self.distribution.get_version() 54 | ) 55 | if not os.path.exists(self.build_temp): 56 | os.makedirs(self.build_temp) 57 | subprocess.check_call( 58 | ["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env 59 | ) 60 | subprocess.check_call( 61 | ["cmake", "--build", "."] + build_args, cwd=self.build_temp 62 | ) 63 | 64 | 65 | setup( 66 | name="blspy", 67 | author="Mariano Sorgente", 68 | author_email="mariano@chia.net", 69 | description="BLS signatures in c++ (python bindings)", 70 | python_requires=">=3.7", 71 | install_requires=["wheel"], 72 | long_description=Path("README.md").read_text(), 73 | long_description_content_type="text/markdown", 74 | url="https://github.com/Chia-Network/bls-signatures", 75 | ext_modules=[CMakeExtension("blspy", ".")], 76 | cmdclass=dict(build_ext=CMakeBuild), 77 | zip_safe=False, 78 | ) 79 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp) 2 | source_group("SrcHeaders" FILES ${HEADERS}) 3 | 4 | list(APPEND bls_sources 5 | ${HEADERS} 6 | privatekey.cpp 7 | bls.cpp 8 | elements.cpp 9 | schemes.cpp 10 | ${blst_SOURCE_DIR}/src/server.c 11 | ) 12 | 13 | if(MSVC) 14 | list(APPEND bls_sources 15 | ${blst_SOURCE_DIR}/build/win64/add_mod_256-x86_64.asm 16 | ${blst_SOURCE_DIR}/build/win64/add_mod_384-x86_64.asm 17 | ${blst_SOURCE_DIR}/build/win64/add_mod_384x384-x86_64.asm 18 | ${blst_SOURCE_DIR}/build/win64/ct_inverse_mod_256-x86_64.asm 19 | ${blst_SOURCE_DIR}/build/win64/ct_is_square_mod_384-x86_64.asm 20 | ${blst_SOURCE_DIR}/build/win64/ctq_inverse_mod_384-x86_64.asm 21 | ${blst_SOURCE_DIR}/build/win64/ctx_inverse_mod_384-x86_64.asm 22 | ${blst_SOURCE_DIR}/build/win64/div3w-x86_64.asm 23 | ${blst_SOURCE_DIR}/build/win64/mulq_mont_256-x86_64.asm 24 | ${blst_SOURCE_DIR}/build/win64/mulq_mont_384-x86_64.asm 25 | ${blst_SOURCE_DIR}/build/win64/mulx_mont_256-x86_64.asm 26 | ${blst_SOURCE_DIR}/build/win64/mulx_mont_384-x86_64.asm 27 | ${blst_SOURCE_DIR}/build/win64/sha256-x86_64.asm 28 | ) 29 | else() 30 | list(APPEND bls_sources 31 | ${blst_SOURCE_DIR}/build/assembly.S 32 | ) 33 | add_compile_options(-fno-builtin) 34 | add_compile_options(-fPIC) 35 | add_compile_options(-Wall) 36 | add_compile_options(-Wextra) 37 | if((CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") AND (NOT EMSCRIPTEN)) 38 | add_compile_options(-mno-avx) 39 | endif() 40 | endif() 41 | 42 | add_library(bls ${bls_sources}) 43 | target_include_directories(bls PUBLIC 44 | ${CMAKE_CURRENT_SOURCE_DIR} 45 | ${blst_SOURCE_DIR} 46 | ) 47 | target_compile_definitions(bls PRIVATE __BLST_PORTABLE__ BLSALLOC_SODIUM=1) 48 | 49 | if(CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64") 50 | list(REMOVE_ITEM bls_sources ${blst_SOURCE_DIR}/build/assembly.S) 51 | target_compile_definitions(bls PRIVATE __BLST_NO_ASM__) 52 | endif() 53 | 54 | target_link_libraries(bls PUBLIC sodium) 55 | 56 | if(WITH_COVERAGE) 57 | target_compile_options(bls PRIVATE --coverage) 58 | target_link_options(bls PRIVATE --coverage) 59 | endif() 60 | 61 | install(DIRECTORY ${blst_SOURCE_DIR}/include/ DESTINATION include/chiabls) 62 | install(DIRECTORY ${blst_BINARY_DIR}/include/ DESTINATION include/chiabls) 63 | install(FILES ${HEADERS} DESTINATION include/chiabls) 64 | install(FILES $ DESTINATION lib) 65 | 66 | if(BUILD_BLS_TESTS) 67 | FetchContent_Declare( 68 | Catch2 69 | GIT_REPOSITORY https://github.com/catchorg/Catch2.git 70 | GIT_TAG v3.3.2 71 | ) 72 | FetchContent_MakeAvailable(Catch2) 73 | find_package(Threads REQUIRED) 74 | add_executable(runtest test.cpp) 75 | 76 | if(EMSCRIPTEN) 77 | target_link_options(runtest PRIVATE "-sEXPORTED_FUNCTIONS=_malloc") 78 | endif() 79 | 80 | target_link_libraries(runtest 81 | PRIVATE 82 | bls 83 | Catch2::Catch2 84 | Threads::Threads 85 | ) 86 | 87 | if(WITH_COVERAGE) 88 | target_compile_options(runtest PRIVATE --coverage) 89 | target_link_options(runtest PRIVATE --coverage) 90 | endif() 91 | endif() 92 | 93 | if(BUILD_BLS_BENCHMARKS) 94 | add_executable(runbench test-bench.cpp) 95 | target_link_libraries(runbench PRIVATE bls) 96 | endif() 97 | -------------------------------------------------------------------------------- /src/bls.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "bls.hpp" 16 | 17 | #if BLSALLOC_SODIUM 18 | #include "sodium.h" 19 | #endif 20 | 21 | namespace bls { 22 | 23 | const size_t BLS::MESSAGE_HASH_LEN; 24 | 25 | bool BLSInitResult = BLS::Init(); 26 | 27 | Util::SecureAllocCallback Util::secureAllocCallback; 28 | Util::SecureFreeCallback Util::secureFreeCallback; 29 | 30 | bool BLS::Init() 31 | { 32 | #if BLSALLOC_SODIUM 33 | if (sodium_init() < 0) { 34 | throw std::runtime_error("libsodium init failed"); 35 | } 36 | SetSecureAllocator(sodium_malloc, sodium_free); 37 | #else 38 | SetSecureAllocator(malloc, free); 39 | #endif 40 | 41 | return true; 42 | } 43 | 44 | void BLS::SetSecureAllocator( 45 | Util::SecureAllocCallback allocCb, 46 | Util::SecureFreeCallback freeCb) 47 | { 48 | Util::secureAllocCallback = allocCb; 49 | Util::secureFreeCallback = freeCb; 50 | } 51 | 52 | } // end namespace bls 53 | -------------------------------------------------------------------------------- /src/bls.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SRC_BLS_HPP_ 16 | #define SRC_BLS_HPP_ 17 | 18 | #include "privatekey.hpp" 19 | #include "util.hpp" 20 | #include "schemes.hpp" 21 | #include "elements.hpp" 22 | #include "hkdf.hpp" 23 | #include "hdkeys.hpp" 24 | 25 | namespace bls { 26 | 27 | /* 28 | * Principal class for verification and signature aggregation. 29 | * Include this file to use the library. 30 | */ 31 | class BLS { 32 | public: 33 | static const size_t MESSAGE_HASH_LEN = 32; 34 | 35 | // Initializes the BLS library (called automatically) 36 | static bool Init(); 37 | 38 | static void SetSecureAllocator(Util::SecureAllocCallback allocCb, Util::SecureFreeCallback freeCb); 39 | }; 40 | } // end namespace bls 41 | 42 | #endif // SRC_BLS_HPP_ 43 | -------------------------------------------------------------------------------- /src/elements.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SRC_BLSELEMENTS_HPP_ 16 | #define SRC_BLSELEMENTS_HPP_ 17 | 18 | extern "C" { 19 | #include "bindings/blst.h" 20 | } 21 | #include 22 | 23 | #include "util.hpp" 24 | 25 | namespace bls { 26 | class G1Element; 27 | class G2Element; 28 | class GTElement; 29 | 30 | class G1Element { 31 | public: 32 | static const size_t SIZE = 48; 33 | 34 | G1Element() { memset(&p, 0x00, sizeof(blst_p1)); } 35 | 36 | static G1Element FromBytes(Bytes bytes); 37 | static G1Element FromBytesUnchecked(Bytes bytes); 38 | static G1Element FromByteVector(const std::vector &bytevec); 39 | static G1Element FromNative(const blst_p1 &element); 40 | static G1Element FromAffine(const blst_p1_affine &element); 41 | static G1Element FromMessage( 42 | const std::vector &message, 43 | const uint8_t *dst, 44 | int dst_len); 45 | static G1Element FromMessage( 46 | Bytes message, 47 | const uint8_t *dst, 48 | int dst_len); 49 | static G1Element Generator(); 50 | 51 | bool IsValid() const; 52 | void CheckValid() const; 53 | void ToNative(blst_p1 *output) const; 54 | void ToAffine(blst_p1_affine *output) const; 55 | G1Element Negate() const; 56 | GTElement Pair(const G2Element &b) const; 57 | uint32_t GetFingerprint() const; 58 | std::vector Serialize() const; 59 | 60 | friend bool operator==(const G1Element &a, const G1Element &b); 61 | friend bool operator!=(const G1Element &a, const G1Element &b); 62 | friend std::ostream &operator<<(std::ostream &os, const G1Element &s); 63 | friend G1Element &operator+=(G1Element &a, const G1Element &b); 64 | friend G1Element operator+(const G1Element &a, const G1Element &b); 65 | friend G1Element operator*(const G1Element &a, const blst_scalar &k); 66 | friend G1Element operator*(const blst_scalar &k, const G1Element &a); 67 | friend GTElement operator&(const G1Element &a, const G2Element &b); 68 | 69 | private: 70 | blst_p1 p; 71 | }; 72 | 73 | class G2Element { 74 | public: 75 | static const size_t SIZE = 96; 76 | 77 | G2Element() { memset(&q, 0x00, sizeof(blst_p2)); } 78 | 79 | static G2Element FromBytes(Bytes bytes); 80 | static G2Element FromBytesUnchecked(Bytes bytes); 81 | static G2Element FromByteVector(const std::vector &bytevec); 82 | static G2Element FromNative(const blst_p2 &element); 83 | static G2Element FromAffine(const blst_p2_affine &element); 84 | static G2Element FromMessage( 85 | const std::vector &message, 86 | const uint8_t *dst, 87 | int dst_len); 88 | static G2Element FromMessage( 89 | Bytes message, 90 | const uint8_t *dst, 91 | int dst_len); 92 | static G2Element Generator(); 93 | 94 | bool IsValid() const; 95 | void CheckValid() const; 96 | void ToNative(blst_p2 *output) const; 97 | void ToAffine(blst_p2_affine *output) const; 98 | G2Element Negate() const; 99 | GTElement Pair(const G1Element &a) const; 100 | std::vector Serialize() const; 101 | 102 | friend bool operator==(G2Element const &a, G2Element const &b); 103 | friend bool operator!=(G2Element const &a, G2Element const &b); 104 | friend std::ostream &operator<<(std::ostream &os, const G2Element &s); 105 | friend G2Element &operator+=(G2Element &a, const G2Element &b); 106 | friend G2Element operator+(const G2Element &a, const G2Element &b); 107 | friend G2Element operator*(const G2Element &a, const blst_scalar &k); 108 | friend G2Element operator*(const blst_scalar &k, const G2Element &a); 109 | 110 | private: 111 | blst_p2 q; 112 | }; 113 | 114 | class GTElement { 115 | public: 116 | static const size_t SIZE = sizeof(blst_fp12); 117 | 118 | static GTElement FromBytes(Bytes bytes); 119 | static GTElement FromBytesUnchecked(Bytes bytes); 120 | static GTElement FromByteVector(const std::vector &bytevec); 121 | static GTElement FromNative(const blst_fp12 *element); 122 | static GTElement FromAffine(const blst_p1_affine &element); 123 | static GTElement FromAffine(const blst_p2_affine &element); 124 | static GTElement Unity(); // unity 125 | 126 | void Serialize(uint8_t *buffer) const; 127 | std::vector Serialize() const; 128 | 129 | friend bool operator==(GTElement const &a, GTElement const &b); 130 | friend bool operator!=(GTElement const &a, GTElement const &b); 131 | friend std::ostream &operator<<(std::ostream &os, const GTElement &s); 132 | friend GTElement operator*(GTElement &a, GTElement &b); 133 | 134 | private: 135 | blst_fp12 r; 136 | GTElement() {} 137 | }; 138 | 139 | } // end namespace bls 140 | 141 | #endif // SRC_BLSELEMENTS_HPP_ 142 | -------------------------------------------------------------------------------- /src/hdkeys.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SRC_BLSHDKEYS_HPP_ 16 | #define SRC_BLSHDKEYS_HPP_ 17 | 18 | #include 19 | 20 | #include "hkdf.hpp" 21 | #include "privatekey.hpp" 22 | #include "util.hpp" 23 | 24 | namespace bls { 25 | 26 | class HDKeys { 27 | /** 28 | * Implements HD keys as specified in EIP2333. 29 | **/ 30 | public: 31 | static const uint8_t HASH_LEN = 32; 32 | 33 | static PrivateKey KeyGen(const std::vector& seed) 34 | { 35 | return KeyGen(Bytes(seed)); 36 | } 37 | 38 | static PrivateKey KeyGen(const Bytes& seed) 39 | { 40 | // KeyGen 41 | // 1. PRK = HKDF-Extract("BLS-SIG-KEYGEN-SALT-", IKM || I2OSP(0, 1)) 42 | // 2. OKM = HKDF-Expand(PRK, keyInfo || I2OSP(L, 2), L) 43 | // 3. SK = OS2IP(OKM) mod r 44 | // 4. return SK 45 | 46 | const uint8_t info[1] = {0}; 47 | const size_t infoLen = 0; 48 | 49 | // Required by the ietf spec to be at least 32 bytes 50 | if (seed.size() < 32) { 51 | throw std::invalid_argument("Seed size must be at least 32 bytes"); 52 | } 53 | 54 | blst_scalar* skBn = Util::SecAlloc(1); 55 | blst_keygen_v3(skBn, seed.begin(), seed.size(), info, infoLen); 56 | uint8_t* skBytes = Util::SecAlloc(32); 57 | blst_bendian_from_scalar(skBytes, skBn); 58 | 59 | PrivateKey k = PrivateKey::FromBytes(Bytes(skBytes, 32)); 60 | 61 | Util::SecFree(skBn); 62 | Util::SecFree(skBytes); 63 | 64 | return k; 65 | } 66 | 67 | static void IKMToLamportSk( 68 | uint8_t* outputLamportSk, 69 | const uint8_t* ikm, 70 | size_t ikmLen, 71 | const uint8_t* salt, 72 | size_t saltLen) 73 | { 74 | // Expands the ikm to 255*HASH_LEN bytes for the lamport sk 75 | const uint8_t info[1] = {0}; 76 | HKDF256::ExtractExpand( 77 | outputLamportSk, 78 | HASH_LEN * 255, 79 | ikm, 80 | ikmLen, 81 | salt, 82 | saltLen, 83 | info, 84 | 0); 85 | } 86 | 87 | static void ParentSkToLamportPK( 88 | uint8_t* outputLamportPk, 89 | const PrivateKey& parentSk, 90 | uint32_t index) 91 | { 92 | uint8_t* salt = Util::SecAlloc(4); 93 | uint8_t* ikm = Util::SecAlloc(HASH_LEN); 94 | uint8_t* notIkm = Util::SecAlloc(HASH_LEN); 95 | uint8_t* lamport0 = Util::SecAlloc(HASH_LEN * 255); 96 | uint8_t* lamport1 = Util::SecAlloc(HASH_LEN * 255); 97 | 98 | Util::IntToFourBytes(salt, index); 99 | parentSk.Serialize(ikm); 100 | 101 | for (size_t i = 0; i < HASH_LEN; i++) { // Flips the bits 102 | notIkm[i] = ikm[i] ^ 0xff; 103 | } 104 | 105 | HDKeys::IKMToLamportSk(lamport0, ikm, HASH_LEN, salt, 4); 106 | HDKeys::IKMToLamportSk(lamport1, notIkm, HASH_LEN, salt, 4); 107 | 108 | uint8_t* lamportPk = Util::SecAlloc(HASH_LEN * 255 * 2); 109 | 110 | for (size_t i = 0; i < 255; i++) { 111 | Util::Hash256( 112 | lamportPk + i * HASH_LEN, lamport0 + i * HASH_LEN, HASH_LEN); 113 | } 114 | 115 | for (size_t i = 0; i < 255; i++) { 116 | Util::Hash256( 117 | lamportPk + 255 * HASH_LEN + i * HASH_LEN, 118 | lamport1 + i * HASH_LEN, 119 | HASH_LEN); 120 | } 121 | Util::Hash256(outputLamportPk, lamportPk, HASH_LEN * 255 * 2); 122 | 123 | Util::SecFree(salt); 124 | Util::SecFree(ikm); 125 | Util::SecFree(notIkm); 126 | Util::SecFree(lamport0); 127 | Util::SecFree(lamport1); 128 | Util::SecFree(lamportPk); 129 | } 130 | 131 | static PrivateKey DeriveChildSk(const PrivateKey& parentSk, uint32_t index) 132 | { 133 | uint8_t* lamportPk = Util::SecAlloc(HASH_LEN); 134 | HDKeys::ParentSkToLamportPK(lamportPk, parentSk, index); 135 | std::vector lamportPkVector(lamportPk, lamportPk + HASH_LEN); 136 | PrivateKey child = HDKeys::KeyGen(lamportPkVector); 137 | Util::SecFree(lamportPk); 138 | return child; 139 | } 140 | 141 | static PrivateKey DeriveChildSkUnhardened( 142 | const PrivateKey& parentSk, 143 | uint32_t index) 144 | { 145 | uint8_t* buf = Util::SecAlloc(G1Element::SIZE + 4); 146 | uint8_t* digest = Util::SecAlloc(HASH_LEN); 147 | memcpy( 148 | buf, parentSk.GetG1Element().Serialize().data(), G1Element::SIZE); 149 | Util::IntToFourBytes(buf + G1Element::SIZE, index); 150 | Util::Hash256(digest, buf, G1Element::SIZE + 4); 151 | 152 | PrivateKey ret = PrivateKey::Aggregate( 153 | {parentSk, PrivateKey::FromBytes(Bytes(digest, HASH_LEN), true)}); 154 | 155 | Util::SecFree(buf); 156 | Util::SecFree(digest); 157 | return ret; 158 | } 159 | 160 | static G1Element DeriveChildG1Unhardened( 161 | const G1Element& pk, 162 | uint32_t index) 163 | { 164 | uint8_t* buf = Util::SecAlloc(G1Element::SIZE + 4); 165 | uint8_t* digest = Util::SecAlloc(HASH_LEN); 166 | memcpy(buf, pk.Serialize().data(), G1Element::SIZE); 167 | 168 | Util::IntToFourBytes(buf + G1Element::SIZE, index); 169 | Util::Hash256(digest, buf, G1Element::SIZE + 4); 170 | 171 | blst_scalar nonce; 172 | blst_scalar_from_lendian(&nonce, digest); 173 | 174 | Util::SecFree(buf); 175 | Util::SecFree(digest); 176 | 177 | G1Element gen = G1Element::Generator(); 178 | 179 | return pk + (gen * nonce); 180 | } 181 | 182 | static G2Element DeriveChildG2Unhardened( 183 | const G2Element& pk, 184 | uint32_t index) 185 | { 186 | uint8_t* buf = Util::SecAlloc(G2Element::SIZE + 4); 187 | uint8_t* digest = Util::SecAlloc(HASH_LEN); 188 | memcpy(buf, pk.Serialize().data(), G2Element::SIZE); 189 | Util::IntToFourBytes(buf + G2Element::SIZE, index); 190 | Util::Hash256(digest, buf, G2Element::SIZE + 4); 191 | 192 | blst_scalar nonce; 193 | blst_scalar_from_lendian(&nonce, digest); 194 | 195 | Util::SecFree(buf); 196 | Util::SecFree(digest); 197 | 198 | G2Element gen = G2Element::Generator(); 199 | return pk + gen * nonce; 200 | } 201 | }; 202 | } // end namespace bls 203 | #endif // SRC_BLSHDKEYS_HPP_ 204 | -------------------------------------------------------------------------------- /src/hkdf.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SRC_BLSHKDF_HPP_ 16 | #define SRC_BLSHKDF_HPP_ 17 | 18 | #include 19 | #include 20 | #include "util.hpp" 21 | 22 | namespace bls { 23 | 24 | class HKDF256 { 25 | /** 26 | * Implements HKDF as specified in RFC5869: https://tools.ietf.org/html/rfc5869, 27 | * with sha256 as the hash function. 28 | **/ 29 | public: 30 | static const uint8_t HASH_LEN = 32; 31 | 32 | static void Extract(uint8_t* prk_output, const uint8_t* salt, const size_t saltLen, const uint8_t* ikm, const size_t ikm_len) { 33 | // assert(saltLen == 4); // Used for EIP2333 key derivation 34 | // assert(ikm_len == 32); // Used for EIP2333 key derivation 35 | // Hash256 used as the hash function (sha256) 36 | // PRK Output is 32 bytes (HashLen) 37 | Util::md_hmac(prk_output, ikm, ikm_len, salt, saltLen); 38 | } 39 | 40 | static void Expand(uint8_t* okm, size_t L, const uint8_t* prk, const uint8_t* info, const size_t infoLen) { 41 | assert(L <= 255 * HASH_LEN); // L <= 255 * HashLen 42 | assert(infoLen >= 0); 43 | size_t N = (L + HASH_LEN - 1) / HASH_LEN; // Round up 44 | size_t bytesWritten = 0; 45 | 46 | uint8_t* T = Util::SecAlloc(HASH_LEN); 47 | uint8_t* hmacInput1 = Util::SecAlloc(infoLen + 1); 48 | uint8_t* hmacInput = Util::SecAlloc(HASH_LEN + infoLen + 1); 49 | 50 | assert(N >= 1 && N <= 255); 51 | 52 | for (size_t i = 1; i <= N; i++) { 53 | if (i == 1) { 54 | memcpy(hmacInput1, info, infoLen); 55 | hmacInput1[infoLen] = i; 56 | Util::md_hmac(T, hmacInput1, infoLen + 1, prk, HASH_LEN); 57 | } else { 58 | memcpy(hmacInput, T, HASH_LEN); 59 | memcpy(hmacInput + HASH_LEN, info, infoLen); 60 | hmacInput[HASH_LEN + infoLen] = i; 61 | Util::md_hmac(T, hmacInput, HASH_LEN + infoLen + 1, prk, HASH_LEN); 62 | } 63 | size_t to_write = L - bytesWritten; 64 | if (to_write > HASH_LEN) { 65 | to_write = HASH_LEN; 66 | } 67 | assert (to_write > 0 && to_write <= HASH_LEN); 68 | memcpy(okm + bytesWritten, T, to_write); 69 | bytesWritten += to_write; 70 | } 71 | Util::SecFree(T); 72 | Util::SecFree(hmacInput1); 73 | Util::SecFree(hmacInput); 74 | assert(bytesWritten == L); 75 | } 76 | 77 | static void ExtractExpand(uint8_t* output, size_t outputLen, 78 | const uint8_t* key, size_t keyLen, 79 | const uint8_t* salt, size_t saltLen, 80 | const uint8_t* info, size_t infoLen) { 81 | uint8_t* prk = Util::SecAlloc(HASH_LEN); 82 | HKDF256::Extract(prk, salt, saltLen, key, keyLen); 83 | HKDF256::Expand(output, outputLen, prk, info, infoLen); 84 | Util::SecFree(prk); 85 | } 86 | }; 87 | } // end namespace bls 88 | #endif // SRC_BLSHKDF_HPP_ 89 | -------------------------------------------------------------------------------- /src/privatekey.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "bls.hpp" 18 | 19 | namespace bls { 20 | 21 | const size_t PrivateKey::PRIVATE_KEY_SIZE; 22 | 23 | // Construct a private key from a bytearray. 24 | PrivateKey PrivateKey::FromBytes(const Bytes &bytes, bool modOrder) 25 | { 26 | if (bytes.size() != PRIVATE_KEY_SIZE) { 27 | throw std::invalid_argument("PrivateKey::FromBytes: Invalid size"); 28 | } 29 | 30 | PrivateKey k; 31 | if (modOrder) 32 | // this allows any bytes to be input and does proper mod order 33 | blst_scalar_from_be_bytes(k.keydata, bytes.begin(), bytes.size()); 34 | else 35 | // this should only be the output of deserialization 36 | blst_scalar_from_bendian(k.keydata, bytes.begin()); 37 | 38 | if (Util::HasOnlyZeros(bytes)) { 39 | return k; // don't check anything else, we allow zero private key 40 | } 41 | 42 | if (!blst_sk_check(k.keydata)) 43 | throw std::invalid_argument( 44 | "PrivateKey byte data must be less than the group order"); 45 | 46 | return k; 47 | } 48 | 49 | // Construct a private key from a bytearray. 50 | PrivateKey PrivateKey::FromByteVector( 51 | const std::vector bytes, 52 | bool modOrder) 53 | { 54 | return PrivateKey::FromBytes(Bytes(bytes), modOrder); 55 | } 56 | 57 | PrivateKey::PrivateKey() { AllocateKeyData(); }; 58 | 59 | // Construct a private key from another private key. 60 | PrivateKey::PrivateKey(const PrivateKey &privateKey) 61 | { 62 | privateKey.CheckKeyData(); 63 | AllocateKeyData(); 64 | memcpy(keydata, privateKey.keydata, 32); 65 | } 66 | 67 | PrivateKey::PrivateKey(PrivateKey &&k) 68 | : keydata(std::exchange(k.keydata, nullptr)) 69 | { 70 | k.InvalidateCaches(); 71 | } 72 | 73 | PrivateKey::~PrivateKey() { DeallocateKeyData(); } 74 | 75 | void PrivateKey::DeallocateKeyData() 76 | { 77 | if (keydata != nullptr) { 78 | Util::SecFree(keydata); 79 | keydata = nullptr; 80 | } 81 | InvalidateCaches(); 82 | } 83 | 84 | void PrivateKey::InvalidateCaches() 85 | { 86 | fG1CacheValid = false; 87 | fG2CacheValid = false; 88 | } 89 | 90 | PrivateKey &PrivateKey::operator=(const PrivateKey &other) 91 | { 92 | CheckKeyData(); 93 | other.CheckKeyData(); 94 | InvalidateCaches(); 95 | memcpy(keydata, other.keydata, 32); 96 | return *this; 97 | } 98 | 99 | PrivateKey &PrivateKey::operator=(PrivateKey &&other) 100 | { 101 | DeallocateKeyData(); 102 | keydata = std::exchange(other.keydata, nullptr); 103 | other.InvalidateCaches(); 104 | return *this; 105 | } 106 | 107 | const G1Element &PrivateKey::GetG1Element() const 108 | { 109 | if (!fG1CacheValid) { 110 | CheckKeyData(); 111 | blst_p1 *p = Util::SecAlloc(1); 112 | blst_sk_to_pk_in_g1(p, keydata); 113 | 114 | g1Cache = G1Element::FromNative(*p); 115 | Util::SecFree(p); 116 | fG1CacheValid = true; 117 | } 118 | return g1Cache; 119 | } 120 | 121 | const G2Element &PrivateKey::GetG2Element() const 122 | { 123 | if (!fG2CacheValid) { 124 | CheckKeyData(); 125 | blst_p2 *q = Util::SecAlloc(1); 126 | blst_sk_to_pk_in_g2(q, keydata); 127 | 128 | g2Cache = G2Element::FromNative(*q); 129 | Util::SecFree(q); 130 | fG2CacheValid = true; 131 | } 132 | return g2Cache; 133 | } 134 | 135 | G1Element operator*(const G1Element &a, const PrivateKey &k) 136 | { 137 | k.CheckKeyData(); 138 | 139 | blst_p1 *ans = Util::SecAlloc(1); 140 | a.ToNative(ans); 141 | byte *bte = Util::SecAlloc(32); 142 | blst_bendian_from_scalar(bte, k.keydata); 143 | blst_p1_mult(ans, ans, bte, 256); 144 | G1Element ret = G1Element::FromNative(*ans); 145 | Util::SecFree(ans); 146 | Util::SecFree(bte); 147 | return ret; 148 | } 149 | 150 | G1Element operator*(const PrivateKey &k, const G1Element &a) { return a * k; } 151 | 152 | G2Element operator*(const G2Element &a, const PrivateKey &k) 153 | { 154 | k.CheckKeyData(); 155 | blst_p2 *ans = Util::SecAlloc(1); 156 | a.ToNative(ans); 157 | byte *bte = Util::SecAlloc(32); 158 | blst_bendian_from_scalar(bte, k.keydata); 159 | blst_p2_mult(ans, ans, bte, 256); 160 | G2Element ret = G2Element::FromNative(*ans); 161 | Util::SecFree(ans); 162 | Util::SecFree(bte); 163 | return ret; 164 | } 165 | 166 | G2Element operator*(const PrivateKey &k, const G2Element &a) { return a * k; } 167 | 168 | G2Element PrivateKey::GetG2Power(const G2Element &element) const 169 | { 170 | CheckKeyData(); 171 | blst_p2 *q = Util::SecAlloc(1); 172 | element.ToNative(q); 173 | byte *bte = Util::SecAlloc(32); 174 | blst_bendian_from_scalar(bte, keydata); 175 | blst_p2_mult(q, q, bte, 255); 176 | const G2Element ret = G2Element::FromNative(*q); 177 | Util::SecFree(q); 178 | Util::SecFree(bte); 179 | return ret; 180 | } 181 | 182 | PrivateKey PrivateKey::Aggregate(std::vector const &privateKeys) 183 | { 184 | if (privateKeys.empty()) { 185 | throw std::length_error("Number of private keys must be at least 1"); 186 | } 187 | 188 | PrivateKey ret; 189 | assert(ret.IsZero()); 190 | for (size_t i = 0; i < privateKeys.size(); i++) { 191 | privateKeys[i].CheckKeyData(); 192 | blst_sk_add_n_check(ret.keydata, ret.keydata, privateKeys[i].keydata); 193 | } 194 | return ret; 195 | } 196 | 197 | bool PrivateKey::IsZero() const 198 | { 199 | CheckKeyData(); 200 | blst_scalar zro; 201 | memset(&zro, 0x00, sizeof(blst_scalar)); 202 | 203 | return memcmp(keydata, &zro, 32) == 0; 204 | } 205 | 206 | bool operator==(const PrivateKey &a, const PrivateKey &b) 207 | { 208 | a.CheckKeyData(); 209 | b.CheckKeyData(); 210 | return memcmp(a.keydata, b.keydata, sizeof(blst_scalar)) == 0; 211 | } 212 | 213 | bool operator!=(const PrivateKey &a, const PrivateKey &b) { return !(a == b); } 214 | 215 | void PrivateKey::Serialize(uint8_t *buffer) const 216 | { 217 | if (buffer == nullptr) { 218 | throw std::runtime_error("PrivateKey::Serialize buffer invalid"); 219 | } 220 | CheckKeyData(); 221 | blst_bendian_from_scalar(buffer, keydata); 222 | } 223 | 224 | std::vector PrivateKey::Serialize() const 225 | { 226 | std::vector data(PRIVATE_KEY_SIZE); 227 | Serialize(data.data()); 228 | return data; 229 | } 230 | 231 | G2Element PrivateKey::SignG2( 232 | const uint8_t *msg, 233 | size_t len, 234 | const uint8_t *dst, 235 | size_t dst_len) const 236 | { 237 | CheckKeyData(); 238 | 239 | blst_p2 *pt = Util::SecAlloc(1); 240 | 241 | blst_hash_to_g2(pt, msg, len, dst, dst_len, nullptr, 0); 242 | blst_sign_pk_in_g1(pt, pt, keydata); 243 | 244 | G2Element ret = G2Element::FromNative(*pt); 245 | Util::SecFree(pt); 246 | return ret; 247 | } 248 | 249 | void PrivateKey::AllocateKeyData() 250 | { 251 | assert(!keydata); 252 | keydata = Util::SecAlloc(1); 253 | memset(keydata, 0x00, sizeof(blst_scalar)); 254 | } 255 | 256 | void PrivateKey::CheckKeyData() const 257 | { 258 | if (keydata == nullptr) { 259 | throw std::runtime_error( 260 | "PrivateKey::CheckKeyData keydata not initialized"); 261 | } 262 | } 263 | 264 | } // end namespace bls 265 | -------------------------------------------------------------------------------- /src/privatekey.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SRC_BLSPRIVATEKEY_HPP_ 16 | #define SRC_BLSPRIVATEKEY_HPP_ 17 | 18 | #include "elements.hpp" 19 | 20 | namespace bls { 21 | class PrivateKey { 22 | public: 23 | // Private keys are represented as 32 byte field elements. Note that 24 | // not all 32 byte integers are valid keys, the private key must be 25 | // less than the group order (which is in bls.hpp). 26 | static const size_t PRIVATE_KEY_SIZE = 32; 27 | 28 | // Construct a private key from a bytearray. 29 | static PrivateKey FromBytes(const Bytes& bytes, bool modOrder = false); 30 | 31 | // Construct a private key from a bytearray. 32 | static PrivateKey FromByteVector(const std::vector bytes, bool modOrder = false); 33 | 34 | // Aggregate many private keys into one (sum of keys mod order) 35 | static PrivateKey Aggregate(std::vector const &privateKeys); 36 | 37 | // Construct a private key from another private key. Allocates memory in 38 | // secure heap, and copies keydata. 39 | PrivateKey(const PrivateKey &k); 40 | PrivateKey(PrivateKey &&k); 41 | 42 | PrivateKey& operator=(const PrivateKey& other); 43 | PrivateKey& operator=(PrivateKey&& other); 44 | 45 | ~PrivateKey(); 46 | 47 | const G1Element& GetG1Element() const; 48 | const G2Element& GetG2Element() const; 49 | 50 | G2Element GetG2Power(const G2Element& element) const; 51 | 52 | bool IsZero() const; 53 | 54 | // Compare to different private key 55 | friend bool operator==(const PrivateKey &a, const PrivateKey &b); 56 | friend bool operator!=(const PrivateKey &a, const PrivateKey &b); 57 | 58 | // Multiply private key by G1 or G2 elements 59 | friend G1Element operator*(const G1Element &a, const PrivateKey &k); 60 | friend G1Element operator*(const PrivateKey &k, const G1Element &a); 61 | 62 | friend G2Element operator*(const G2Element &a, const PrivateKey &k); 63 | friend G2Element operator*(const PrivateKey &k, const G2Element &a); 64 | 65 | // Serialize the key into bytes 66 | void Serialize(uint8_t *buffer) const; 67 | std::vector Serialize() const; 68 | 69 | G2Element SignG2( 70 | const uint8_t *msg, 71 | size_t len, 72 | const uint8_t *dst, 73 | size_t dst_len) const; 74 | 75 | private: 76 | // Don't allow public construction, force static methods 77 | PrivateKey(); 78 | 79 | // Allocate memory for private key 80 | void AllocateKeyData(); 81 | /// Throw an error if keydata isn't initialized 82 | void CheckKeyData() const; 83 | /// Deallocate *keydata and keydata if requried 84 | void DeallocateKeyData(); 85 | 86 | void InvalidateCaches(); 87 | 88 | // The actual byte data 89 | blst_scalar* keydata{nullptr}; 90 | 91 | mutable bool fG1CacheValid{false}; 92 | mutable G1Element g1Cache; 93 | 94 | mutable bool fG2CacheValid{false}; 95 | mutable G2Element g2Cache; 96 | }; 97 | } // end namespace bls 98 | 99 | #endif // SRC_BLSPRIVATEKEY_HPP_ 100 | -------------------------------------------------------------------------------- /src/schemes.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in coiance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or iied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SRC_BLSSCHEMES_HPP_ 16 | #define SRC_BLSSCHEMES_HPP_ 17 | 18 | #include 19 | #include 20 | 21 | #include "elements.hpp" 22 | #include "privatekey.hpp" 23 | 24 | using std::vector; 25 | 26 | // These are all MPL schemes 27 | namespace bls { 28 | 29 | class Bytes; 30 | 31 | class CoreMPL { 32 | public: 33 | CoreMPL() = delete; 34 | CoreMPL(const std::string& strId) : strCiphersuiteId(strId) {} 35 | virtual ~CoreMPL() {} 36 | // Generates a private key from a seed, similar to HD key generation 37 | // (hashes the seed), and reduces it mod the group order 38 | virtual PrivateKey KeyGen(const vector& seed); 39 | virtual PrivateKey KeyGen(const Bytes& seed); 40 | 41 | // Generates a public key from a secret key 42 | virtual vector SkToPk(const PrivateKey& seckey); 43 | 44 | virtual G1Element SkToG1(const PrivateKey& seckey); 45 | 46 | virtual G2Element Sign( 47 | const PrivateKey& seckey, 48 | const vector& message); 49 | virtual G2Element Sign(const PrivateKey& seckey, const Bytes& message); 50 | 51 | virtual bool Verify( 52 | const vector& pubkey, 53 | const vector& message, 54 | const vector& signature); 55 | 56 | virtual bool Verify( 57 | const Bytes& pubkey, 58 | const Bytes& message, 59 | const Bytes& signature); 60 | 61 | virtual bool Verify( 62 | const G1Element& pubkey, 63 | const vector& message, 64 | const G2Element& signature); 65 | 66 | virtual bool Verify( 67 | const G1Element& pubkey, 68 | const Bytes& message, 69 | const G2Element& signature); 70 | 71 | virtual vector Aggregate( 72 | const vector>& signatures); 73 | virtual vector Aggregate(const vector& signatures); 74 | 75 | virtual G2Element Aggregate(const vector& signatures); 76 | 77 | virtual G1Element Aggregate(const vector& publicKeys); 78 | 79 | virtual bool AggregateVerify( 80 | const vector>& pubkeys, 81 | const vector>& messages, 82 | const vector& signature); 83 | 84 | virtual bool AggregateVerify( 85 | const vector& pubkeys, 86 | const vector& messages, 87 | const Bytes& signature); 88 | 89 | virtual bool AggregateVerify( 90 | const vector& pubkeys, 91 | const vector>& messages, 92 | const G2Element& signature); 93 | 94 | virtual bool AggregateVerify( 95 | const vector& pubkeys, 96 | const vector& messages, 97 | const G2Element& signature); 98 | 99 | PrivateKey DeriveChildSk(const PrivateKey& sk, uint32_t index); 100 | PrivateKey DeriveChildSkUnhardened(const PrivateKey& sk, uint32_t index); 101 | G1Element DeriveChildPkUnhardened(const G1Element& sk, uint32_t index); 102 | 103 | protected: 104 | const std::string& strCiphersuiteId; 105 | // bool NativeVerify(blst_p1 *pubKeys, blst_p2 *mappedHashes, size_t 106 | // length); 107 | }; 108 | 109 | class BasicSchemeMPL final : public CoreMPL { 110 | public: 111 | static const std::string CIPHERSUITE_ID; 112 | BasicSchemeMPL() : CoreMPL(BasicSchemeMPL::CIPHERSUITE_ID) {} 113 | bool AggregateVerify( 114 | const vector>& pubkeys, 115 | const vector>& messages, 116 | const vector& signature) override; 117 | 118 | bool AggregateVerify( 119 | const vector& pubkeys, 120 | const vector& messages, 121 | const Bytes& signature) override; 122 | 123 | bool AggregateVerify( 124 | const vector& pubkeys, 125 | const vector>& messages, 126 | const G2Element& signature) override; 127 | 128 | bool AggregateVerify( 129 | const vector& pubkeys, 130 | const vector& messages, 131 | const G2Element& signature) override; 132 | }; 133 | 134 | class AugSchemeMPL final : public CoreMPL { 135 | public: 136 | static const std::string CIPHERSUITE_ID; 137 | AugSchemeMPL() : CoreMPL(AugSchemeMPL::CIPHERSUITE_ID) {} 138 | 139 | G2Element Sign(const PrivateKey& seckey, const vector& message) 140 | override; 141 | 142 | G2Element Sign(const PrivateKey& seckey, const Bytes& message) override; 143 | 144 | // Used for prepending different augMessage 145 | G2Element Sign( 146 | const PrivateKey& seckey, 147 | const vector& message, 148 | const G1Element& prepend_pk); 149 | 150 | // Used for prepending different augMessage 151 | G2Element Sign( 152 | const PrivateKey& seckey, 153 | const Bytes& message, 154 | const G1Element& prepend_pk); 155 | 156 | bool Verify( 157 | const vector& pubkey, 158 | const vector& message, 159 | const vector& signature) override; 160 | 161 | bool Verify( 162 | const Bytes& pubkey, 163 | const Bytes& message, 164 | const Bytes& signature) override; 165 | 166 | bool Verify( 167 | const G1Element& pubkey, 168 | const vector& message, 169 | const G2Element& signature) override; 170 | 171 | bool Verify( 172 | const G1Element& pubkey, 173 | const Bytes& message, 174 | const G2Element& signature) override; 175 | 176 | bool AggregateVerify( 177 | const vector>& pubkeys, 178 | const vector>& messages, 179 | const vector& signature) override; 180 | 181 | bool AggregateVerify( 182 | const vector& pubkeys, 183 | const vector& messages, 184 | const Bytes& signature) override; 185 | 186 | bool AggregateVerify( 187 | const vector& pubkeys, 188 | const vector>& messages, 189 | const G2Element& signature) override; 190 | 191 | bool AggregateVerify( 192 | const vector& pubkeys, 193 | const vector& messages, 194 | const G2Element& signature) override; 195 | }; 196 | 197 | class PopSchemeMPL final : public CoreMPL { 198 | public: 199 | static const std::string CIPHERSUITE_ID; 200 | static const std::string POP_CIPHERSUITE_ID; 201 | PopSchemeMPL() : CoreMPL(PopSchemeMPL::CIPHERSUITE_ID) {} 202 | 203 | G2Element PopProve(const PrivateKey& seckey); 204 | 205 | bool PopVerify(const G1Element& pubkey, const G2Element& signature_proof); 206 | 207 | bool PopVerify(const vector& pubkey, const vector& proof); 208 | 209 | bool PopVerify(const Bytes& pubkey, const Bytes& proof); 210 | 211 | bool FastAggregateVerify( 212 | const vector& pubkeys, 213 | const vector& message, 214 | const G2Element& signature); 215 | 216 | bool FastAggregateVerify( 217 | const vector& pubkeys, 218 | const Bytes& message, 219 | const G2Element& signature); 220 | 221 | bool FastAggregateVerify( 222 | const vector>& pubkeys, 223 | const vector& message, 224 | const vector& signature); 225 | 226 | bool FastAggregateVerify( 227 | const vector& pubkeys, 228 | const Bytes& message, 229 | const Bytes& signature); 230 | }; 231 | 232 | } // end namespace bls 233 | 234 | #endif // SRC_BLSSCHEMES_HPP_ 235 | -------------------------------------------------------------------------------- /src/test-bench.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | 17 | #include "bls.hpp" 18 | #include "test-utils.hpp" 19 | 20 | using std::string; 21 | using std::vector; 22 | 23 | using namespace bls; 24 | 25 | std::vector wjbgetRandomSeed() 26 | { 27 | uint8_t buf[32]; 28 | 29 | for (int i = 0; i < 32; i++) buf[i] = rand(); 30 | 31 | std::vector ret(buf, buf + 32); 32 | return ret; 33 | } 34 | 35 | void benchSigs() 36 | { 37 | string testName = "Signing"; 38 | const int numIters = 5000; 39 | PrivateKey sk = AugSchemeMPL().KeyGen(getRandomSeed()); 40 | vector message1 = sk.GetG1Element().Serialize(); 41 | 42 | auto start = startStopwatch(); 43 | 44 | for (int i = 0; i < numIters; i++) { 45 | AugSchemeMPL().Sign(sk, message1); 46 | } 47 | endStopwatch(testName, start, numIters); 48 | } 49 | 50 | void benchVerification() 51 | { 52 | string testName = "Verification"; 53 | const int numIters = 10000; 54 | PrivateKey sk = AugSchemeMPL().KeyGen(getRandomSeed()); 55 | G1Element pk = sk.GetG1Element(); 56 | std::vector sigs; 57 | 58 | vector pkBytes = pk.Serialize(); 59 | for (int i = 0; i < numIters; i++) { 60 | uint8_t message[4]; 61 | Util::IntToFourBytes(message, i); 62 | vector messageBytes(message, message + 4); 63 | G2Element sig = AugSchemeMPL().Sign(sk, messageBytes); 64 | sigs.push_back(sig); 65 | } 66 | auto start = startStopwatch(); 67 | for (int i = 0; i < numIters; i++) { 68 | uint8_t message[4]; 69 | Util::IntToFourBytes(message, i); 70 | vector messageBytes(message, message + 4); 71 | bool ok = AugSchemeMPL().Verify(pk, messageBytes, sigs[i]); 72 | ASSERT(ok); 73 | } 74 | endStopwatch(testName, start, numIters); 75 | } 76 | 77 | void benchBatchVerification() 78 | { 79 | const int numIters = 100000; 80 | 81 | vector> sig_bytes; 82 | vector> pk_bytes; 83 | vector> ms; 84 | 85 | for (int i = 0; i < numIters; i++) { 86 | uint8_t message[4]; 87 | Util::IntToFourBytes(message, i); 88 | vector messageBytes(message, message + 4); 89 | PrivateKey sk = AugSchemeMPL().KeyGen(getRandomSeed()); 90 | G1Element pk = sk.GetG1Element(); 91 | sig_bytes.push_back(AugSchemeMPL().Sign(sk, messageBytes).Serialize()); 92 | pk_bytes.push_back(pk.Serialize()); 93 | ms.push_back(messageBytes); 94 | } 95 | 96 | vector pks; 97 | pks.reserve(numIters); 98 | 99 | auto start = startStopwatch(); 100 | for (auto const& pk : pk_bytes) { 101 | pks.emplace_back(G1Element::FromBytes(Bytes(pk))); 102 | } 103 | endStopwatch("Public key validation", start, numIters); 104 | 105 | vector sigs; 106 | sigs.reserve(numIters); 107 | 108 | start = startStopwatch(); 109 | for (auto const& sig : sig_bytes) { 110 | sigs.emplace_back(G2Element::FromBytes(Bytes(sig))); 111 | } 112 | endStopwatch("Signature validation", start, numIters); 113 | 114 | start = startStopwatch(); 115 | G2Element aggSig = AugSchemeMPL().Aggregate(sigs); 116 | endStopwatch("Aggregation", start, numIters); 117 | 118 | start = startStopwatch(); 119 | bool ok = AugSchemeMPL().AggregateVerify(pks, ms, aggSig); 120 | ASSERT(ok); 121 | endStopwatch("Batch verification", start, numIters); 122 | } 123 | 124 | void benchFastAggregateVerification() 125 | { 126 | const int numIters = 5000; 127 | 128 | vector sigs; 129 | vector pks; 130 | vector message = {1, 2, 3, 4, 5, 6, 7, 8}; 131 | vector pops; 132 | 133 | for (int i = 0; i < numIters; i++) { 134 | PrivateKey sk = PopSchemeMPL().KeyGen(getRandomSeed()); 135 | G1Element pk = sk.GetG1Element(); 136 | sigs.push_back(PopSchemeMPL().Sign(sk, message)); 137 | pops.push_back(PopSchemeMPL().PopProve(sk)); 138 | pks.push_back(pk); 139 | } 140 | 141 | auto start = startStopwatch(); 142 | G2Element aggSig = PopSchemeMPL().Aggregate(sigs); 143 | endStopwatch("PopScheme Aggregation", start, numIters); 144 | 145 | start = startStopwatch(); 146 | for (int i = 0; i < numIters; i++) { 147 | bool ok = PopSchemeMPL().PopVerify(pks[i], pops[i]); 148 | ASSERT(ok); 149 | } 150 | endStopwatch("PopScheme Proofs verification", start, numIters); 151 | 152 | start = startStopwatch(); 153 | bool ok = PopSchemeMPL().FastAggregateVerify(pks, message, aggSig); 154 | ASSERT(ok); 155 | endStopwatch("PopScheme verification", start, numIters); 156 | } 157 | 158 | int main(int argc, char* argv[]) 159 | { 160 | benchSigs(); 161 | benchVerification(); 162 | benchBatchVerification(); 163 | benchFastAggregateVerification(); 164 | } 165 | -------------------------------------------------------------------------------- /src/test-utils.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include "bls.hpp" 18 | 19 | using std::string; 20 | using std::vector; 21 | using std::cout; 22 | using std::endl; 23 | 24 | #ifdef _MSC_VER 25 | #define __PRETTY_FUNCTION__ __FUNCSIG__ 26 | #endif 27 | #define STR(x) #x 28 | #define ASSERT(x) if (!(x)) { printf("BLS assertion failed: (%s), function %s, file %s, line %d.\n", STR(x), __PRETTY_FUNCTION__, __FILE__, __LINE__); abort(); } 29 | 30 | std::chrono::time_point startStopwatch() { 31 | return std::chrono::steady_clock::now(); 32 | } 33 | 34 | void endStopwatch(string testName, 35 | std::chrono::time_point start, 36 | int numIters) { 37 | auto end = std::chrono::steady_clock::now(); 38 | auto now_ms = std::chrono::duration_cast( 39 | end - start); 40 | 41 | cout << endl << testName << endl; 42 | cout << "Total: " << numIters << " runs in " << now_ms.count() 43 | << " ms" << endl; 44 | cout << "Avg: " << now_ms.count() / static_cast(numIters) 45 | << " ms" << endl; 46 | } 47 | 48 | std::vector getRandomSeed() { 49 | uint8_t buf[32]; 50 | 51 | for (int i = 0; i < 32; i++) 52 | buf[i] = rand (); 53 | 54 | std::vector ret(buf, buf + 32); 55 | return ret; 56 | } 57 | -------------------------------------------------------------------------------- /src/util.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Chia Network Inc 2 | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef SRC_BLSUTIL_HPP_ 16 | #define SRC_BLSUTIL_HPP_ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace bls { 27 | 28 | class BLS; 29 | 30 | class Bytes { 31 | const uint8_t* pData; 32 | const size_t nSize; 33 | 34 | public: 35 | Bytes(const uint8_t* pDataIn, const size_t nSizeIn) 36 | : pData(pDataIn), nSize(nSizeIn) 37 | { 38 | } 39 | Bytes(const std::vector& vecBytes) 40 | : pData(vecBytes.data()), nSize(vecBytes.size()) 41 | { 42 | } 43 | template 44 | Bytes(const std::array& a) 45 | : pData(a.data()), nSize(N) 46 | { 47 | } 48 | 49 | inline const uint8_t* begin() const { return pData; } 50 | inline const uint8_t* end() const { return pData + nSize; } 51 | 52 | inline size_t size() const { return nSize; } 53 | 54 | const uint8_t& operator[](const int nIndex) const { return pData[nIndex]; } 55 | }; 56 | 57 | class Util { 58 | public: 59 | typedef void *(*SecureAllocCallback)(size_t); 60 | typedef void (*SecureFreeCallback)(void*); 61 | public: 62 | static void Hash256(uint8_t* output, const uint8_t* message, 63 | size_t messageLen) { 64 | blst_sha256(output, message, messageLen); 65 | } 66 | 67 | static void md_hmac(uint8_t *mac, const uint8_t *in, int in_len, const uint8_t *key, 68 | int key_len) { 69 | #define block_size 64 70 | #define RLC_MD_LEN 32 71 | uint8_t opad[block_size + RLC_MD_LEN]; 72 | uint8_t *ipad = (uint8_t *)malloc(block_size + in_len); 73 | uint8_t _key[block_size]; 74 | 75 | if (ipad == NULL) 76 | throw std::runtime_error("out of memory"); 77 | 78 | if (key_len > block_size) { 79 | Hash256(_key, key, key_len); 80 | key = _key; 81 | key_len = RLC_MD_LEN; 82 | } 83 | 84 | memcpy(_key, key, key_len); 85 | memset(_key + key_len, 0, block_size - key_len); 86 | key = _key; 87 | 88 | for (int i = 0; i < block_size; i++) { 89 | opad[i] = 0x5C ^ key[i]; 90 | ipad[i] = 0x36 ^ key[i]; 91 | } 92 | memcpy(ipad + block_size, in, in_len); 93 | Hash256(opad + block_size, ipad, block_size + in_len); 94 | Hash256(mac, opad, block_size + RLC_MD_LEN); 95 | 96 | free(ipad); 97 | } 98 | 99 | static std::string HexStr(const uint8_t* data, size_t len) { 100 | std::stringstream s; 101 | s << std::hex; 102 | for (size_t i=0; i < len; ++i) 103 | s << std::setw(2) << std::setfill('0') << static_cast(data[i]); 104 | return s.str(); 105 | } 106 | 107 | static std::string HexStr(const std::vector &data) { 108 | std::stringstream s; 109 | s << std::hex; 110 | for (size_t i=0; i < data.size(); ++i) 111 | s << std::setw(2) << std::setfill('0') << static_cast(data[i]); 112 | return s.str(); 113 | } 114 | 115 | /* 116 | * Securely allocates a portion of memory, using libsodium. This prevents 117 | * paging to disk, and zeroes out the memory when it's freed. 118 | */ 119 | template 120 | static T* SecAlloc(size_t numTs) { 121 | return static_cast(secureAllocCallback(sizeof(T) * numTs)); 122 | } 123 | 124 | /* 125 | * Frees memory allocated using SecAlloc. 126 | */ 127 | static void SecFree(void* ptr) { 128 | secureFreeCallback(ptr); 129 | } 130 | 131 | /* 132 | * Converts one hex character to an int. 133 | */ 134 | static uint8_t char2int(const char input) { 135 | if(input >= '0' && input <= '9') 136 | return input - '0'; 137 | if(input >= 'A' && input <= 'F') 138 | return input - 'A' + 10; 139 | if(input >= 'a' && input <= 'f') 140 | return input - 'a' + 10; 141 | throw std::invalid_argument("Invalid input string"); 142 | } 143 | 144 | /* 145 | * Converts a hex string into a vector of bytes. 146 | */ 147 | static std::vector HexToBytes(const std::string hex) { 148 | if (hex.size() % 2 != 0) { 149 | throw std::invalid_argument("Invalid input string, length must be multple of 2"); 150 | } 151 | std::vector ret = std::vector(); 152 | size_t start_at = 0; 153 | if (hex.rfind("0x", 0) == 0 || hex.rfind("0x", 0) == 0) { 154 | start_at = 2; 155 | } 156 | 157 | for (size_t i = start_at; i < hex.size(); i += 2) { 158 | ret.push_back(char2int(hex[i]) * 16 + char2int(hex[i+1])); 159 | } 160 | return ret; 161 | } 162 | 163 | /* 164 | * Converts a 32 bit int to bytes. 165 | */ 166 | static void IntToFourBytes(uint8_t* result, 167 | const uint32_t input) { 168 | for (size_t i = 0; i < 4; i++) { 169 | result[3 - i] = (input >> (i * 8)); 170 | } 171 | } 172 | 173 | /* 174 | * Converts a byte array to a 32 bit int. 175 | */ 176 | static uint32_t FourBytesToInt(const uint8_t* bytes) { 177 | uint32_t sum = 0; 178 | for (size_t i = 0; i < 4; i++) { 179 | uint32_t addend = bytes[i] << (8 * (3 - i)); 180 | sum += addend; 181 | } 182 | return sum; 183 | } 184 | 185 | static bool HasOnlyZeros(const Bytes& bytes) { 186 | return std::all_of(bytes.begin(), bytes.end(), [](uint8_t byte){ return byte == 0x00; }); 187 | } 188 | 189 | private: 190 | friend class BLS; 191 | static SecureAllocCallback secureAllocCallback; 192 | static SecureFreeCallback secureFreeCallback; 193 | }; 194 | } // end namespace bls 195 | #endif // SRC_BLSUTIL_HPP_ 196 | --------------------------------------------------------------------------------