├── .circleci └── config.yml ├── .dockerignore ├── .gitattributes ├── .github ├── actions │ └── setup │ │ └── action.yml └── workflows │ └── rust_ci.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md └── crates ├── threshold-bls-ffi ├── Cargo.toml ├── README.md ├── cross │ ├── Makefile │ ├── cargo-config.toml.template │ ├── create-ndk-standalone.sh │ └── threshold.h └── src │ ├── ffi.rs │ ├── jvm.rs │ ├── lib.rs │ └── wasm.rs └── threshold-bls ├── Cargo.toml ├── README.md └── src ├── curve ├── bls12381.rs ├── mod.rs └── zexe.rs ├── ecies.rs ├── group.rs ├── lib.rs ├── poly.rs ├── sig ├── blind.rs ├── bls.rs ├── mod.rs ├── sig.rs ├── tblind.rs └── tbls.rs └── test_vectors.rs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build-wasm: 5 | docker: 6 | - image: cimg/base:stable 7 | steps: 8 | - checkout 9 | - setup_remote_docker 10 | - run: 11 | name: Build WASM 12 | command: make wasm 13 | - store_artifacts: 14 | path: output/wasm 15 | 16 | build-jvm: 17 | docker: 18 | - image: cimg/base:stable 19 | steps: 20 | - checkout 21 | - setup_remote_docker 22 | - run: 23 | name: Build JVM 24 | command: make jvm 25 | - store_artifacts: 26 | path: output/jvm 27 | 28 | build-android: 29 | docker: 30 | - image: cimg/base:stable 31 | steps: 32 | - checkout 33 | - setup_remote_docker 34 | - run: 35 | name: Build Android 36 | command: make android 37 | - store_artifacts: 38 | path: output/android 39 | 40 | build-ios: 41 | macos: 42 | xcode: 15.1.0 43 | steps: 44 | - checkout 45 | - run: 46 | name: Install Rust 47 | command: | 48 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.62.0 49 | source "$HOME/.cargo/env" 50 | - run: 51 | name: Build iOS 52 | command: make ios 53 | - store_artifacts: 54 | path: output/ios 55 | 56 | workflows: 57 | version: 2 58 | build: 59 | jobs: 60 | - build-wasm 61 | - build-jvm 62 | - build-android 63 | - build-ios 64 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target/ 2 | output/ 3 | pkg/ 4 | crates/threshold-bls-ffi/pkg/ 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup Rust Action Environment 2 | 3 | description: Installs dependencies to run Rust jobs. 4 | 5 | inputs: 6 | components: 7 | description: Components to install in the rust toolchain 8 | required: false 9 | default: "" 10 | channel: 11 | description: The Rust channel release to use 12 | required: false 13 | default: "stable" 14 | prefix-key: 15 | description: The prefix key for the rust cache 16 | required: false 17 | default: "" 18 | 19 | runs: 20 | using: "composite" 21 | steps: 22 | # - name: Install Just 23 | # uses: taiki-e/install-action@just 24 | 25 | - name: Installs stable rust toolchain 26 | if: ${{ inputs.channel == 'stable' && inputs.components == '' }} 27 | uses: dtolnay/rust-toolchain@stable 28 | with: 29 | toolchain: 1.62 30 | 31 | - name: Installs stable rust toolchain 32 | if: ${{ inputs.channel == 'stable' && inputs.components != '' }} 33 | uses: dtolnay/rust-toolchain@stable 34 | with: 35 | toolchain: 1.62 36 | components: $COMPONENTS 37 | env: 38 | COMPONENTS: ${{ inputs.components }} 39 | 40 | # - name: Installs nightly rust toolchain 41 | # if: ${{ inputs.channel == 'nightly' && inputs.components == '' }} 42 | # uses: dtolnay/rust-toolchain@nightly 43 | 44 | # - name: Installs nightly rust toolchain 45 | # if: ${{ inputs.channel == 'nightly' && inputs.components != '' }} 46 | # uses: dtolnay/rust-toolchain@nightly 47 | # with: 48 | # components: $COMPONENTS 49 | # env: 50 | # COMPONENTS: ${{ inputs.components }} 51 | 52 | - name: Installs the rust cache 53 | uses: Swatinem/rust-cache@v2 54 | with: 55 | cache-on-failure: true 56 | prefix-key: $PREFIX_KEY 57 | env: 58 | PREFIX_KEY: ${{ inputs.prefix-key }} 59 | -------------------------------------------------------------------------------- /.github/workflows/rust_ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | on: 3 | push: 4 | branches: [ develop ] 5 | pull_request: 6 | env: 7 | CARGO_TERM_COLOR: always 8 | jobs: 9 | cargo-tests: 10 | runs-on: ubuntu-latest 11 | name: test 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v4 15 | - uses: ./.github/actions/setup 16 | with: 17 | components: rustfmt 18 | - uses: taiki-e/install-action@nextest 19 | - name: cargo test 20 | run: cargo nextest run --workspace --all-features 21 | 22 | cargo-lint: 23 | runs-on: ubuntu-latest 24 | name: lint 25 | steps: 26 | - name: Checkout sources 27 | uses: actions/checkout@v4 28 | - uses: ./.github/actions/setup 29 | with: 30 | channel: stable 31 | components: rustfmt 32 | - uses: ./.github/actions/setup 33 | with: 34 | channel: stable 35 | components: clippy 36 | - name: lint 37 | run: cargo clippy --all-targets --all-features -- -D warnings 38 | - name: fmt 39 | run: cargo fmt --all -- --check 40 | 41 | cargo-build: 42 | runs-on: ubuntu-latest 43 | name: build 44 | continue-on-error: true 45 | steps: 46 | - name: Checkout sources 47 | uses: actions/checkout@v4 48 | - uses: ./.github/actions/setup 49 | with: 50 | channel: stable 51 | - name: build 52 | run: cargo build --all-targets --all-features 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | pkg/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | 10 | #Added by cargo 11 | # 12 | #already existing elements were commented out 13 | 14 | /target 15 | /output 16 | #Cargo.lock 17 | 18 | NDK 19 | .cargo 20 | react-native 21 | 22 | solidity/cache 23 | solidity/build 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "crates/threshold-bls", 5 | "crates/threshold-bls-ffi", 6 | ] 7 | 8 | [profile.release] 9 | opt-level = 3 10 | lto = "thin" 11 | incremental = true 12 | 13 | # build all our deps in release mode 14 | [profile.dev.package."*"] 15 | opt-level = 3 16 | 17 | [profile.bench] 18 | opt-level = 3 19 | debug = false 20 | rpath = false 21 | lto = "thin" 22 | incremental = true 23 | debug-assertions = false 24 | 25 | 26 | [profile.test] 27 | opt-level = 3 28 | incremental = true 29 | debug-assertions = true 30 | debug = true 31 | 32 | [patch."https://github.com/scipr-lab/zexe"] 33 | algebra = { git = "https://github.com/celo-org/arkworks-v0.1.1-patch", package = "algebra", branch = "patch-rust-compat" } 34 | algebra-core = { git = "https://github.com/celo-org/arkworks-v0.1.1-patch", package = "algebra-core", branch = "patch-rust-compat" } 35 | algebra-core-derive = { git = "https://github.com/celo-org/arkworks-v0.1.1-patch", package = "algebra-core-derive", branch = "patch-rust-compat" } 36 | bench-utils = { git = "https://github.com/celo-org/arkworks-v0.1.1-patch", package = "bench-utils", branch = "patch-rust-compat" } 37 | crypto-primitives = { git = "https://github.com/celo-org/arkworks-v0.1.1-patch", package = "crypto-primitives", branch = "patch-rust-compat" } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG RUST_VERSION=1.62.0 2 | FROM ubuntu:20.04 3 | 4 | ARG RUST_VERSION 5 | 6 | # Set non-interactive mode for apt-get 7 | ENV DEBIAN_FRONTEND=noninteractive 8 | 9 | # Update package lists and install required dependencies 10 | RUN apt-get update && apt-get install -y \ 11 | curl \ 12 | build-essential \ 13 | git \ 14 | pkg-config \ 15 | libssl-dev \ 16 | ca-certificates \ 17 | unzip \ 18 | python 19 | 20 | # Install rustup without a default toolchain 21 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain ${RUST_VERSION} 22 | 23 | # Add Cargo's bin directory to the PATH 24 | ENV PATH="/root/.cargo/bin:${PATH}" 25 | 26 | # Install Rust and set it as the default toolchain 27 | RUN rustup toolchain install ${RUST_VERSION} && rustup default ${RUST_VERSION} 28 | 29 | # Install wasm-pack 30 | # There is no cargo-installable version of wasm-pack that will "just work" with Rust 1.41.0 today, because of unpinned upstream dependencies like log. 31 | # So we have to build it from source and pin the dependencies to the versions that work with Rust 1.41.0. 32 | RUN curl -L https://github.com/rustwasm/wasm-pack/archive/refs/tags/v0.8.1.tar.gz \ 33 | | tar xz -C /tmp && \ 34 | cd /tmp/wasm-pack-0.8.1 && \ 35 | sed -i 's/log = ".*"/log = "=0.4.14"/' Cargo.toml && \ 36 | cargo build --release && \ 37 | cp target/release/wasm-pack /usr/local/bin/ && \ 38 | rm -rf /tmp/wasm-pack-0.8.1 39 | 40 | # Install Android NDK 41 | RUN curl -L https://dl.google.com/android/repository/android-ndk-r21-linux-x86_64.zip -o /tmp/ndk.zip && \ 42 | unzip /tmp/ndk.zip -d /opt && \ 43 | mv /opt/android-ndk-r21 /opt/android-ndk && \ 44 | rm /tmp/ndk.zip 45 | 46 | WORKDIR /app 47 | 48 | COPY . . 49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RUST_VERSION ?= 1.62.0 2 | IMAGE_NAME = celo-bls-rust-$(subst .,_,$(RUST_VERSION)) 3 | OUTPUT_DIR = $(PWD)/output 4 | CARGO_CACHE_VOLUME = celo-bls-cargo-cache 5 | TARGET_CACHE_VOLUME = celo-bls-target-cache 6 | 7 | .PHONY: all clean build-docker-image wasm jvm ios android test test-cached create-cache-volumes 8 | 9 | all: clean build-docker-image wasm jvm ios android 10 | 11 | clean: 12 | rm -rf $(OUTPUT_DIR) 13 | 14 | build-docker-image: 15 | docker build --platform=linux/amd64 --build-arg RUST_VERSION=$(RUST_VERSION) -t $(IMAGE_NAME) . 16 | 17 | # ios builds cannot be run in docker, so we need to build it locally on Mac OS 18 | ios: 19 | mkdir -p $(OUTPUT_DIR)/ios 20 | cd crates/threshold-bls-ffi/cross && make ios 21 | mkdir -p $(OUTPUT_DIR)/ios 22 | cp crates/threshold-bls-ffi/cross/target/ios/libblind_threshold_bls.a $(OUTPUT_DIR)/ios/ 23 | rm -rf crates/threshold-bls-ffi/cross/target 24 | rm -rf target 25 | 26 | android: 27 | make build-docker-image 28 | mkdir -p $(OUTPUT_DIR)/android 29 | docker run --platform=linux/amd64 --rm \ 30 | -v $(OUTPUT_DIR)/android:/output/android \ 31 | -w /app/crates/threshold-bls-ffi \ 32 | $(IMAGE_NAME) \ 33 | sh -c "cd cross && export NDK_HOME=/opt/android-ndk && ./create-ndk-standalone.sh && \ 34 | make android" 35 | 36 | wasm: 37 | make build-docker-image 38 | mkdir -p $(OUTPUT_DIR)/wasm 39 | docker run --platform=linux/amd64 --rm \ 40 | -v $(OUTPUT_DIR)/wasm:/app/crates/threshold-bls-ffi/pkg \ 41 | -w /app/crates/threshold-bls-ffi \ 42 | $(IMAGE_NAME) \ 43 | wasm-pack build --target nodejs -- --features=wasm 44 | 45 | jvm: 46 | make build-docker-image 47 | mkdir -p $(OUTPUT_DIR)/jvm 48 | docker run --platform=linux/amd64 --rm \ 49 | -v $(OUTPUT_DIR)/jvm:/app/target \ 50 | -w /app/crates/threshold-bls-ffi \ 51 | $(IMAGE_NAME) \ 52 | cargo build --release --features=jvm 53 | 54 | test: 55 | make build-docker-image 56 | docker run --platform=linux/amd64 --rm -w /app ${IMAGE_NAME} cargo test --features wasm -- --nocapture 57 | 58 | # Create Docker volumes for caching Cargo and target directories 59 | create-cache-volumes: 60 | docker volume create $(CARGO_CACHE_VOLUME) 61 | docker volume create $(TARGET_CACHE_VOLUME) 62 | 63 | # Use cached volumes for faster testing 64 | test-cached: create-cache-volumes build-docker-image 65 | docker run --platform=linux/amd64 --rm \ 66 | -v $(CARGO_CACHE_VOLUME):/root/.cargo \ 67 | -v $(TARGET_CACHE_VOLUME):/app/target \ 68 | -v $(PWD):/app \ 69 | -w /app ${IMAGE_NAME} cargo test --features wasm -- --nocapture -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Threshold BLS Signatures

2 | 3 | ## Overview 4 | 5 | This crate provides libraries and command line interfaces for producing threshold BLS signatures. The signatures can also be [blind](https://en.wikipedia.org/wiki/Blind_signature) in order to preserve the privacy of the user asking for a signature from another set of parties. 6 | 7 | > **Note:** The DKG (Distributed Key Generation) crates have been removed from this repository as they were unstable and not necessary for the BLS crates to work. They were removed in commit [a911f40f0fd31fabae197016b87640f2fdcf1c9f]. 8 | 9 | ## Building with Docker 10 | 11 | The project includes a Makefile that supports building for multiple platforms using Docker. All built libraries are placed in the `output` directory, organized by platform. 12 | 13 | ### Clean Build 14 | 15 | To clean the output directory before building: 16 | ``` 17 | make clean 18 | ``` 19 | 20 | ### Build All Platforms 21 | ``` 22 | make all 23 | ``` 24 | This builds the libraries for all supported platforms. 25 | 26 | ### Running Tests 27 | 28 | To run tests: 29 | ``` 30 | make test 31 | ``` 32 | 33 | This runs the tests in a Docker container which is especially important for Apple Silicon (M1/M2/M3) Macs, as some dependencies have compatibility issues when running natively on ARM64 architecture. The Docker container provides an x86_64 / amd64 environment where all tests run successfully. 34 | 35 | ### WASM Build 36 | ``` 37 | make wasm 38 | ``` 39 | This builds WebAssembly bindings that can be used with Node.js and places them in `output/wasm`. 40 | 41 | ### JVM Build 42 | ``` 43 | make jvm 44 | ``` 45 | This builds JVM-compatible libraries and places them in `output/jvm`. 46 | 47 | ### iOS Build 48 | ``` 49 | make ios 50 | ``` 51 | This builds a universal static library for iOS (combining aarch64 and x86_64 architectures) and places it in `output/ios`. Note that iOS builds must be run on a macOS host as they cannot be built in Docker. 52 | 53 | ### Android Build 54 | ``` 55 | make android 56 | ``` 57 | This builds dynamic libraries for Android architectures (x86, x86_64, arm64-v8a, armeabi, and armeabi-v7a) and places them in `output/android`. 58 | 59 | ### Docker Build 60 | 61 | The docker image used for building the libraries can be built separately if needed: 62 | ``` 63 | make build-docker-image 64 | ``` 65 | 66 | ### Rust version 67 | 68 | Rust 1.62.0 is used by default and tested for all builds. If desired, you can build with a different Rust version by setting the RUST_VERSION env var: 69 | ``` 70 | make RUST_VERSION=1.56.1 71 | ``` 72 | 73 | ## Directory Structure 74 | 75 | This repository contains Rust crates that implement threshold BLS signatures. The high-level structure of the repository is as follows: 76 | 77 | - [`threshold-bls`](crates/threshold-bls): (blind) threshold BLS signatures for BLS12-381 and BLS12-377 78 | - [`threshold-bls-ffi`](crates/threshold-bls-ffi): FFI and WASM bindings to `threshold-bls` for cross platform interoperability 79 | 80 | ## Disclaimers 81 | 82 | **This software has not been audited. Use at your own risk.** 83 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "threshold-bls-ffi" 3 | version = "0.1.0" 4 | authors = ["Georgios Konstantopoulos "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["lib", "cdylib", "staticlib"] 9 | name = "blind_threshold_bls" 10 | 11 | [dependencies] 12 | threshold-bls = { path = "../threshold-bls", default-features = false, features = ["bls12_377"] } 13 | bls-crypto = { git = "https://github.com/celo-org/bls-zexe", rev = "879630a7d95794994e31c874934d04b3c5904892" } 14 | 15 | rand_core = { version = "0.5.1", default-features = false, optional = true } 16 | rand_chacha = { version = "0.2.2", default-features = false, optional = true } 17 | 18 | # Required for WASM interface 19 | wasm-bindgen = { version = "=0.2.62", optional = true } 20 | blake2 = { version = "0.10", default-features = false, optional = true } 21 | # getrandom = { version = "=0.2.9", default-features = false, optional = true, features = ["js"] } # breaking 22 | bincode = { version = "1.2.1", default-features = false } 23 | serde = { version = "=1.0.106", default-features = false, optional = true } 24 | # The `console_error_panic_hook` crate provides better debugging of panics by 25 | # logging them with `console.error`. This is great for development, but requires 26 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 27 | # code size when deploying. 28 | console_error_panic_hook = { version = "0.1.7", optional = true } 29 | 30 | cfg-if = "0.1" 31 | 32 | jni = { version = "0.21.1", optional = true } 33 | 34 | [features] 35 | # Build WASM bindings for use in JS environments 36 | # wasm = ["wasm-bindgen", "getrandom/js", "blake2", "rand_core", "rand_chacha", "serde"] 37 | wasm = ["wasm-bindgen", "blake2", "rand_core", "rand_chacha", "serde"] 38 | # Include a panic hook for printing panic messages to the JS console. 39 | wasm-debug = ["wasm", "console_error_panic_hook"] 40 | 41 | jvm = ["jni"] 42 | ffi = ["rand_core", "rand_chacha", "serde"] -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/README.md: -------------------------------------------------------------------------------- 1 | # Threshold BLS FFI 2 | 3 | ## FFI Bindings 4 | 5 | ``` 6 | cargo build --target 7 | ``` 8 | 9 | You can generate headerfiles via [`bindgen`]() 10 | 11 | ## WASM Bindings 12 | 13 | This library provides wasm bindings for signing under the `sig/wasm.rs` module. These can be built 14 | via the [`wasm-pack`](https://github.com/rustwasm/wasm-pack) tool. Depending on the platform you are 15 | targetting, you'll need to use a different build flag. 16 | 17 | ``` 18 | $ wasm-pack build --target nodejs -- --features=wasm 19 | ``` 20 | 21 | The bundled wasm package will be under the `pkg/` directory. You can then either pack and publish it 22 | with `wasm-pack`'s `pack` and `publish` commands, or manually import it in your application. 23 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/cross/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile - based on https://github.com/Terrahop/react-native-rust-demo 2 | 3 | NAME = libblind_threshold_bls 4 | LIB = $(NAME).a 5 | SO = $(NAME).so 6 | ARCHS_IOS = aarch64-apple-ios x86_64-apple-ios 7 | ARCHS_ANDROID = aarch64-linux-android armv7-linux-androideabi arm-linux-androideabi i686-linux-android x86_64-linux-android 8 | NDK_STANDALONE = ./NDK 9 | ANDROID_DEST = /output/android 10 | IOS_DEST = ./target/ios 11 | CARGO_PARAMS = --no-default-features $(if $(FEATURES),--features $(FEATURES)) 12 | 13 | TARGET_DIR = ../../../target 14 | 15 | all: android ios 16 | 17 | android-setup: 18 | rustup target add $(ARCHS_ANDROID) 19 | 20 | android-build: $(ARCHS_ANDROID) 21 | 22 | ios-setup: 23 | rustup target add $(ARCHS_IOS) 24 | 25 | ios: ios-setup $(LIB) 26 | 27 | clean: 28 | rm -rf $(IOS_DEST) 29 | rm -rf $(ANDROID_DEST) 30 | rm -rf $(TARGET_DIR) 31 | 32 | android: android-setup android-build 33 | mkdir -p $(ANDROID_DEST) 34 | mkdir -p $(ANDROID_DEST)/x86 35 | mkdir -p $(ANDROID_DEST)/x86_64 36 | mkdir -p $(ANDROID_DEST)/arm64-v8a 37 | mkdir -p $(ANDROID_DEST)/armeabi 38 | mkdir -p $(ANDROID_DEST)/armeabi-v7a 39 | 40 | cp $(TARGET_DIR)/i686-linux-android/release/$(SO) ${ANDROID_DEST}/x86/$(SO) 41 | cp $(TARGET_DIR)/x86_64-linux-android/release/$(SO) ${ANDROID_DEST}/x86_64/$(SO) 42 | cp $(TARGET_DIR)/aarch64-linux-android/release/$(SO) ${ANDROID_DEST}/arm64-v8a/$(SO) 43 | cp $(TARGET_DIR)/arm-linux-androideabi/release/$(SO) ${ANDROID_DEST}/armeabi/$(SO) 44 | cp $(TARGET_DIR)/armv7-linux-androideabi/release/$(SO) ${ANDROID_DEST}/armeabi-v7a/$(SO) 45 | 46 | aarch64-linux-android: 47 | PATH="$(PATH)":$(NDK_STANDALONE)/arm64/bin \ 48 | CC=$@-gcc \ 49 | CXX=$@-g++ \ 50 | cargo build $(CARGO_PARAMS) --target $@ --release --lib 51 | 52 | arm-linux-androideabi: 53 | PATH="$(PATH)":$(NDK_STANDALONE)/arm/bin \ 54 | CC=$@-gcc \ 55 | CXX=$@-g++ \ 56 | cargo build $(CARGO_PARAMS) --target $@ --release --lib 57 | 58 | armv7-linux-androideabi: 59 | PATH="$(PATH)":$(NDK_STANDALONE)/arm/bin \ 60 | CC=arm-linux-androideabi-gcc \ 61 | CXX=arm-linux-androideabi-g++ \ 62 | cargo build $(CARGO_PARAMS) --target $@ --release --lib 63 | 64 | i686-linux-android: 65 | PATH="$(PATH)":$(NDK_STANDALONE)/x86/bin \ 66 | CC=$@-gcc \ 67 | CXX=$@-g++ \ 68 | cargo build $(CARGO_PARAMS) --target $@ --release --lib 69 | 70 | x86_64-linux-android: 71 | PATH="$(PATH)":$(NDK_STANDALONE)/x86_64/bin \ 72 | CC=$@-gcc \ 73 | CXX=$@-g++ \ 74 | cargo build $(CARGO_PARAMS) --target $@ --release --lib 75 | 76 | .PHONY: $(ARCHS_IOS) 77 | $(ARCHS_IOS): %: 78 | cargo build $(CARGO_PARAMS) --target $@ --release --lib 79 | 80 | $(LIB): $(ARCHS_IOS) 81 | mkdir -p $(IOS_DEST) 82 | lipo -create -output $(IOS_DEST)/$(LIB) $(foreach arch,$(ARCHS_IOS),$(TARGET_DIR)/$(arch)/release/$(LIB)) 83 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/cross/cargo-config.toml.template: -------------------------------------------------------------------------------- 1 | [target.aarch64-linux-android] 2 | ar = "$PWD/NDK/arm64/bin/aarch64-linux-android-ar" 3 | linker = "$PWD/NDK/arm64/bin/aarch64-linux-android-gcc" 4 | 5 | [target.arm-linux-androideabi] 6 | ar = "$PWD/NDK/arm/bin/arm-linux-androideabi-ar" 7 | linker = "$PWD/NDK/arm/bin/arm-linux-androideabi-gcc" 8 | 9 | [target.armv7-linux-androideabi] 10 | ar = "$PWD/NDK/arm/bin/arm-linux-androideabi-ar" 11 | linker = "$PWD/NDK/arm/bin/arm-linux-androideabi-gcc" 12 | 13 | [target.i686-linux-android] 14 | ar = "$PWD/NDK/x86/bin/i686-linux-android-ar" 15 | linker = "$PWD/NDK/x86/bin/i686-linux-android-gcc" 16 | 17 | [target.x86_64-linux-android] 18 | ar = "$PWD/NDK/x86_64/bin/x86_64-linux-android-ar" 19 | linker = "$PWD/NDK/x86_64/bin/x86_64-linux-android-gcc" 20 | 21 | [build] 22 | rustflags = ["-C", "link-args=-Wl,-z,max-page-size=16384"] 23 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/cross/create-ndk-standalone.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # based on https://github.com/Terrahop/react-native-rust-demo 4 | 5 | set -euo pipefail 6 | 7 | if [ -d NDK ]; then 8 | printf '\e[33;1mStandalone NDK already exists... Delete the NDK folder to make a new one.\e[0m\n\n' 9 | printf '$ rm -rf NDK\n' 10 | exit 0 11 | fi 12 | 13 | MAKER="$NDK_HOME/build/tools/make_standalone_toolchain.py" 14 | echo 'Creating standalone NDK...' 15 | 16 | mkdir NDK 17 | cd NDK 18 | 19 | for ARCH in arm64 arm x86 x86_64; do 20 | echo "($ARCH)..." 21 | "$MAKER" --arch $ARCH --api 21 --install-dir $ARCH 22 | done 23 | 24 | echo 'Updating .cargo/config.toml...' 25 | 26 | cd .. 27 | mkdir -p .cargo 28 | sed 's|$PWD|'"${PWD}"'|g' cargo-config.toml.template > .cargo/config 29 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/cross/threshold.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /** 7 | * A BLS12-377 Keypair 8 | */ 9 | typedef struct Keypair Keypair; 10 | 11 | typedef struct Keys Keys; 12 | 13 | /** 14 | * A polynomial that is using a scalar for the variable x and a generic 15 | * element for the coefficients. The coefficients must be able to multiply 16 | * the type of the variable, which is always a scalar. 17 | */ 18 | typedef struct Poly_PrivateKey__PublicKey Poly_PrivateKey__PublicKey; 19 | 20 | typedef struct Share_PrivateKey Share_PrivateKey; 21 | 22 | /** 23 | * Blinding a message before requesting a signature requires the usage of a 24 | * private blinding factor that is called a Token. To unblind the signature 25 | * afterwards, one needs the same token as what the blinding method returned. 26 | * In this blind signature scheme, the token is simply a field element. 27 | */ 28 | typedef struct Token_PrivateKey Token_PrivateKey; 29 | 30 | /** 31 | * Data structure which is used to store buffers of varying length 32 | */ 33 | typedef struct { 34 | /** 35 | * Pointer to the message 36 | */ 37 | uint8_t *ptr; 38 | /** 39 | * The length of the buffer 40 | */ 41 | size_t len; 42 | } Buffer; 43 | 44 | typedef struct Private PrivateKey; 45 | 46 | typedef struct Public PublicKey; 47 | 48 | typedef struct Signature Signature; 49 | 50 | /** 51 | * Given a message and a seed, it will blind it and return the blinded message 52 | * 53 | * * message: A cleartext message which you want to blind 54 | * * seed: A 32 byte seed for randomness. You can get one securely via `crypto.randomBytes(32)` 55 | * * blinded_message: Pointer to the memory where the blinded message will be written to 56 | * 57 | * The `BlindedMessage.blinding_factor` should be saved for unblinding any 58 | * signatures on `BlindedMessage.message` 59 | * 60 | * # Safety 61 | * - If the same seed is used twice, the blinded result WILL be the same 62 | * 63 | * Returns true if successful, otherwise false. 64 | */ 65 | void blind(const Buffer *message, 66 | const Buffer *seed, 67 | Buffer *blinded_message_out, 68 | Token_PrivateKey **blinding_factor_out); 69 | 70 | /** 71 | * Combines a flattened vector of partial signatures to a single threshold signature 72 | * 73 | * # Safety 74 | * - This function does not check if the signatures are valid! 75 | */ 76 | bool combine(uintptr_t threshold, const Buffer *signatures, Buffer *asig); 77 | 78 | bool deserialize_privkey(const uint8_t *privkey_buf, PrivateKey **privkey); 79 | 80 | bool deserialize_pubkey(const uint8_t *pubkey_buf, PublicKey **pubkey); 81 | 82 | bool deserialize_sig(const uint8_t *sig_buf, Signature **sig); 83 | 84 | void destroy_privkey(PrivateKey *private_key); 85 | 86 | void destroy_pubkey(PublicKey *public_key); 87 | 88 | void destroy_sig(Signature *signature); 89 | 90 | void destroy_token(Token_PrivateKey *token); 91 | 92 | void free_vector(uint8_t *bytes, size_t len); 93 | 94 | /** 95 | * Generates a single private key from the provided seed. 96 | * 97 | * # Safety 98 | * 99 | * The seed MUST be at least 32 bytes long 100 | */ 101 | void keygen(const Buffer *seed, Keypair *keypair); 102 | 103 | /** 104 | * Gets the number of shares corresponding to the provided `Keys` pointer 105 | */ 106 | uintptr_t num_shares(const Keys *keys); 107 | 108 | /** 109 | * Signs the message with the provided **share** of the private key and returns the **partial** 110 | * signature. 111 | */ 112 | bool partial_sign(const Share_PrivateKey *share, const Buffer *message, Buffer *signature); 113 | 114 | /** 115 | * Verifies a partial signature against the public key corresponding to the secret shared 116 | * polynomial. 117 | */ 118 | bool partial_verify(const Poly_PrivateKey__PublicKey *polynomial, 119 | const Buffer *blinded_message, 120 | const Buffer *sig); 121 | 122 | /** 123 | * Gets a pointer to the polynomial corresponding to the provided `Keys` pointer 124 | */ 125 | const Poly_PrivateKey__PublicKey *polynomial_ptr(const Keys *keys); 126 | 127 | /** 128 | * Gets a pointer to the private key corresponding to the provided `KeyPair` pointer 129 | */ 130 | const PrivateKey *private_key_ptr(const Keypair *keypair); 131 | 132 | /** 133 | * Gets a pointer to the public key corresponding to the provided `KeyPair` pointer 134 | */ 135 | const PublicKey *public_key_ptr(const Keypair *keypair); 136 | 137 | void serialize_privkey(const PrivateKey *privkey, uint8_t **privkey_buf); 138 | 139 | void serialize_pubkey(const PublicKey *pubkey, uint8_t **pubkey_buf); 140 | 141 | void serialize_sig(const Signature *sig, uint8_t **sig_buf); 142 | 143 | /** 144 | * Gets the `index`'th share corresponding to the provided `Keys` pointer 145 | */ 146 | const Share_PrivateKey *share_ptr(const Keys *keys, uintptr_t index); 147 | 148 | /** 149 | * Signs the message with the provided private key and returns the signature 150 | * 151 | * # Throws 152 | * 153 | * - If signing fails 154 | */ 155 | bool sign(const PrivateKey *private_key, const Buffer *message, Buffer *signature); 156 | 157 | /** 158 | * Gets a pointer to the threshold public key corresponding to the provided `Keys` pointer 159 | */ 160 | const PublicKey *threshold_public_key_ptr(const Keys *keys); 161 | 162 | /** 163 | * Given a blinded signature and a blinding_factor used for blinding, it returns the signature 164 | * unblinded 165 | * 166 | * * blinded_signature: A message which has been blinded or a blind signature 167 | * * blinding_factor: The blinding_factor used to blind the message 168 | * * unblinded_signature: Pointer to the memory where the unblinded signature will be written to 169 | * 170 | * Returns true if successful, otherwise false. 171 | */ 172 | bool unblind(const Buffer *blinded_signature, 173 | const Token_PrivateKey *blinding_factor, 174 | Buffer *unblinded_signature); 175 | 176 | /** 177 | * Verifies the signature after it has been unblinded. Users will call this on the 178 | * threshold signature against the full public key 179 | * 180 | * * public_key: The public key used to sign the message 181 | * * message: The message which was signed 182 | * * signature: The signature which was produced on the message 183 | * 184 | * Returns true if successful, otherwise false. 185 | */ 186 | bool verify(const PublicKey *public_key, const Buffer *message, const Buffer *signature); 187 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/src/ffi.rs: -------------------------------------------------------------------------------- 1 | //! # BLS12-377 FFI Bindings for Blind Threshold Signatures. 2 | use rand_chacha::ChaChaRng; 3 | use rand_core::{RngCore, SeedableRng}; 4 | 5 | use serde::{de::DeserializeOwned, Serialize}; 6 | use threshold_bls::{ 7 | poly::{Idx as Index, Poly}, 8 | sig::{ 9 | BlindScheme, BlindThresholdScheme, Scheme, Share, SignatureScheme, ThresholdScheme, Token, 10 | }, 11 | }; 12 | 13 | use bls_crypto::ffi::Buffer; 14 | 15 | use crate::*; 16 | 17 | /////////////////////////////////////////////////////////////////////////// 18 | // User -> Library 19 | /////////////////////////////////////////////////////////////////////////// 20 | 21 | /// Given a message and a seed, it will blind it and return the blinded message 22 | /// 23 | /// * message: A cleartext message which you want to blind 24 | /// * seed: A 32 byte seed for randomness. You can get one securely via `crypto.randomBytes(32)` 25 | /// * blinded_message_out : Pointer to the memory where the blinded message will be written to 26 | /// * blinding_factor_out : Pointer to the object storing the blinding factor 27 | /// 28 | /// The `blinding_factor_out` should be saved for unblinding any 29 | /// signatures on `blinded_message_out`. It lives in-memory. 30 | /// 31 | /// You should use `free_vec` to free `blinded_message_out` and `destroy_token` to destroy 32 | /// `blinding_factor_out`. 33 | /// 34 | /// # Safety 35 | /// - If the same seed is used twice, the blinded result WILL be the same 36 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 37 | /// then the software will crash**. 38 | /// - If NULL pointers are passed, the function will return false 39 | /// 40 | /// Returns true if successful, otherwise false. 41 | #[no_mangle] 42 | pub unsafe extern "C" fn blind( 43 | message: *const Buffer, 44 | seed: *const Buffer, 45 | blinded_message_out: *mut Buffer, 46 | blinding_factor_out: *mut *mut Token, 47 | ) -> bool { 48 | if message.is_null() 49 | || seed.is_null() 50 | || blinded_message_out.is_null() 51 | || blinding_factor_out.is_null() 52 | { 53 | return false; 54 | } 55 | 56 | // convert the seed to randomness 57 | let seed = <&[u8]>::from(unsafe { &*seed }); 58 | let mut rng = get_rng(seed); 59 | 60 | // blind the message with this randomness 61 | let message = <&[u8]>::from(unsafe { &*message }); 62 | let (blinding_factor, blinded_message_bytes) = SigScheme::blind_msg(&message, &mut rng); 63 | 64 | unsafe { *blinded_message_out = Buffer::from(&blinded_message_bytes[..]) }; 65 | std::mem::forget(blinded_message_bytes); 66 | unsafe { *blinding_factor_out = Box::into_raw(Box::new(blinding_factor)) }; 67 | 68 | true 69 | } 70 | 71 | /// Given a blinded signature and a blinding_factor used for blinding, it returns the signature 72 | /// unblinded 73 | /// 74 | /// * blinded_signature: A message which has been blinded or a blind signature 75 | /// * blinding_factor: The blinding_factor used to blind the message 76 | /// * unblinded_signature: Pointer to the memory where the unblinded signature will be written to 77 | /// 78 | /// # Safety 79 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 80 | /// then the software will crash**. 81 | /// - If NULL pointers are passed, the function will return false 82 | /// 83 | /// Returns true if successful, otherwise false. 84 | #[no_mangle] 85 | pub unsafe extern "C" fn unblind( 86 | blinded_signature: *const Buffer, 87 | blinding_factor: *const Token, 88 | unblinded_signature: *mut Buffer, 89 | ) -> bool { 90 | if blinded_signature.is_null() || blinding_factor.is_null() || unblinded_signature.is_null() { 91 | return false; 92 | } 93 | 94 | let blinded_signature = <&[u8]>::from(unsafe { &*blinded_signature }); 95 | let blinding_factor = unsafe { &*blinding_factor }; 96 | 97 | let sig = match SigScheme::unblind_sig(blinding_factor, blinded_signature) { 98 | Ok(s) => s, 99 | Err(_) => return false, 100 | }; 101 | 102 | unsafe { *unblinded_signature = Buffer::from(&sig[..]) }; 103 | std::mem::forget(sig); 104 | 105 | true 106 | } 107 | 108 | /// Verifies the signature after it has been unblinded. Users will call this on the 109 | /// threshold signature against the full public key 110 | /// 111 | /// * public_key: The public key used to sign the message 112 | /// * message: The message which was signed 113 | /// * signature: The signature which was produced on the message 114 | /// 115 | /// # Safety 116 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 117 | /// then the software will crash**. 118 | /// - If NULL pointers are passed, the function will return false 119 | /// 120 | /// Returns true if successful, otherwise false. 121 | #[no_mangle] 122 | pub unsafe extern "C" fn verify( 123 | public_key: *const PublicKey, 124 | message: *const Buffer, 125 | signature: *const Buffer, 126 | ) -> bool { 127 | if public_key.is_null() || message.is_null() || signature.is_null() { 128 | return false; 129 | } 130 | 131 | let public_key = unsafe { &*public_key }; 132 | let message = <&[u8]>::from(unsafe { &*message }); 133 | 134 | // checks the signature on the message hash 135 | let signature = <&[u8]>::from(unsafe { &*signature }); 136 | SigScheme::verify(public_key, &message, signature).is_ok() 137 | } 138 | 139 | /////////////////////////////////////////////////////////////////////////// 140 | // Service -> Library 141 | /////////////////////////////////////////////////////////////////////////// 142 | 143 | /// Signs the message with the provided private key and returns the signature 144 | /// 145 | /// # Safety 146 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 147 | /// then the software will crash**. 148 | /// - If NULL pointers are passed, the function will return false 149 | /// 150 | /// Returns true if successful, otherwise false. 151 | #[no_mangle] 152 | pub unsafe extern "C" fn sign( 153 | private_key: *const PrivateKey, 154 | message: *const Buffer, 155 | signature: *mut Buffer, 156 | ) -> bool { 157 | if private_key.is_null() || message.is_null() || signature.is_null() { 158 | return false; 159 | } 160 | 161 | let private_key = unsafe { &*private_key }; 162 | let message = <&[u8]>::from(unsafe { &*message }); 163 | 164 | let sig = match SigScheme::sign(&private_key, &message) { 165 | Ok(s) => s, 166 | Err(_) => return false, 167 | }; 168 | 169 | unsafe { *signature = Buffer::from(&sig[..]) }; 170 | std::mem::forget(sig); 171 | 172 | true 173 | } 174 | 175 | /// Signs a *blinded* message with the provided private key and returns the signature 176 | /// 177 | /// # Safety 178 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 179 | /// then the software will crash**. 180 | /// - If NULL pointers are passed, the function will return false 181 | /// 182 | /// Returns true if successful, otherwise false. 183 | #[no_mangle] 184 | pub unsafe extern "C" fn sign_blinded_message( 185 | private_key: *const PrivateKey, 186 | message: *const Buffer, 187 | signature: *mut Buffer, 188 | ) -> bool { 189 | if private_key.is_null() || message.is_null() || signature.is_null() { 190 | return false; 191 | } 192 | 193 | let private_key = unsafe { &*private_key }; 194 | let message = <&[u8]>::from(unsafe { &*message }); 195 | 196 | let sig = match SigScheme::blind_sign(&private_key, &message) { 197 | Ok(s) => s, 198 | Err(_) => return false, 199 | }; 200 | 201 | unsafe { *signature = Buffer::from(&sig[..]) }; 202 | std::mem::forget(sig); 203 | 204 | true 205 | } 206 | 207 | /// Signs the message with the provided **share** of the private key and returns the **partial** 208 | /// signature. 209 | /// 210 | /// # Safety 211 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 212 | /// then the software will crash**. 213 | /// - If NULL pointers are passed, the function will return false 214 | /// 215 | /// Returns true if successful, otherwise false. 216 | #[no_mangle] 217 | pub unsafe extern "C" fn partial_sign( 218 | share: *const Share, 219 | message: *const Buffer, 220 | signature: *mut Buffer, 221 | ) -> bool { 222 | if share.is_null() || message.is_null() || signature.is_null() { 223 | return false; 224 | } 225 | 226 | let share = unsafe { &*share }; 227 | let message = unsafe { &*message }; 228 | let sig = match SigScheme::partial_sign(share, <&[u8]>::from(message)) { 229 | Ok(s) => s, 230 | Err(_) => return false, 231 | }; 232 | 233 | unsafe { *signature = Buffer::from(&sig[..]) }; 234 | std::mem::forget(sig); 235 | 236 | true 237 | } 238 | 239 | /// Signs a *blinded* message with the provided *share* of the private key and returns the 240 | /// *partial blind* signature. 241 | /// 242 | /// # Safety 243 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 244 | /// then the software will crash**. 245 | /// - If NULL pointers are passed, the function will return false 246 | /// 247 | /// Returns true if successful, otherwise false. 248 | #[no_mangle] 249 | pub unsafe extern "C" fn partial_sign_blinded_message( 250 | share: *const Share, 251 | blinded_message: *const Buffer, 252 | signature: *mut Buffer, 253 | ) -> bool { 254 | if share.is_null() || blinded_message.is_null() || signature.is_null() { 255 | return false; 256 | } 257 | 258 | let share = unsafe { &*share }; 259 | let message = unsafe { &*blinded_message }; 260 | let sig = match SigScheme::sign_blind_partial(share, <&[u8]>::from(message)) { 261 | Ok(s) => s, 262 | Err(_) => return false, 263 | }; 264 | 265 | unsafe { *signature = Buffer::from(&sig[..]) }; 266 | std::mem::forget(sig); 267 | 268 | true 269 | } 270 | 271 | /////////////////////////////////////////////////////////////////////////// 272 | // Combiner -> Library 273 | /////////////////////////////////////////////////////////////////////////// 274 | 275 | /// Verifies a partial signature against the public key corresponding to the secret shared 276 | /// polynomial. 277 | /// 278 | /// # Safety 279 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 280 | /// then the software will crash**. 281 | /// - If NULL pointers are passed, the function will return false 282 | /// 283 | /// Returns true if successful, otherwise false. 284 | #[no_mangle] 285 | pub unsafe extern "C" fn partial_verify( 286 | // TODO: The polynomial does not have a constant length type. Is it safe to not 287 | // pass any length parameter? 288 | polynomial: *const Poly, 289 | blinded_message: *const Buffer, 290 | signature: *const Buffer, 291 | ) -> bool { 292 | if polynomial.is_null() || blinded_message.is_null() || signature.is_null() { 293 | return false; 294 | } 295 | 296 | let polynomial = unsafe { &*polynomial }; 297 | let blinded_message = <&[u8]>::from(unsafe { &*blinded_message }); 298 | let signature = <&[u8]>::from(unsafe { &*signature }); 299 | 300 | SigScheme::partial_verify(&polynomial, blinded_message, signature).is_ok() 301 | } 302 | 303 | /// Verifies a partial *blinded* signature against the public key corresponding to the secret shared 304 | /// polynomial. 305 | /// 306 | /// # Safety 307 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 308 | /// then the software will crash**. 309 | /// - If NULL pointers are passed, the function will return false 310 | /// 311 | /// Returns true if successful, otherwise false. 312 | #[no_mangle] 313 | pub unsafe extern "C" fn partial_verify_blind_signature( 314 | // TODO: The polynomial does not have a constant length type. Is it safe to not 315 | // pass any length parameter? 316 | polynomial: *const Poly, 317 | blinded_message: *const Buffer, 318 | signature: *const Buffer, 319 | ) -> bool { 320 | if polynomial.is_null() || blinded_message.is_null() || signature.is_null() { 321 | return false; 322 | } 323 | 324 | let polynomial = unsafe { &*polynomial }; 325 | let blinded_message = <&[u8]>::from(unsafe { &*blinded_message }); 326 | let signature = <&[u8]>::from(unsafe { &*signature }); 327 | 328 | SigScheme::verify_blind_partial(&polynomial, blinded_message, signature).is_ok() 329 | } 330 | 331 | /// Combines a flattened vector of partial signatures to a single threshold signature 332 | /// 333 | /// # Safety 334 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 335 | /// then the software will crash**. 336 | /// - If NULL pointers are passed, the function will return false 337 | /// - This function does not check if the signatures are valid! 338 | /// 339 | /// Returns true if successful, otherwise false. 340 | #[no_mangle] 341 | pub unsafe extern "C" fn combine( 342 | threshold: usize, 343 | signatures: *const Buffer, 344 | asig: *mut Buffer, 345 | ) -> bool { 346 | if signatures.is_null() || asig.is_null() { 347 | return false; 348 | } 349 | 350 | // split the flattened vector to a Vec> where each element is a serialized signature 351 | let signatures = <&[u8]>::from(unsafe { &*signatures }); 352 | let sigs = signatures 353 | .chunks(PARTIAL_SIG_LENGTH) 354 | .map(|chunk| chunk.to_vec()) 355 | .collect::>>(); 356 | 357 | let signature = match SigScheme::aggregate(threshold, &sigs) { 358 | Ok(s) => s, 359 | Err(_) => return false, 360 | }; 361 | 362 | unsafe { *asig = Buffer::from(&signature[..]) }; 363 | std::mem::forget(signature); 364 | 365 | true 366 | } 367 | 368 | /////////////////////////////////////////////////////////////////////////// 369 | // Serialization 370 | /////////////////////////////////////////////////////////////////////////// 371 | 372 | #[no_mangle] 373 | /// Deserializes a public key from the provided buffer 374 | /// 375 | /// # Safety 376 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 377 | /// then the software will crash**. 378 | /// - If NULL pointers are passed, the function will return false 379 | /// 380 | /// Returns true if successful, otherwise false. 381 | pub unsafe extern "C" fn deserialize_pubkey( 382 | pubkey_buf: *const u8, 383 | pubkey: *mut *mut PublicKey, 384 | ) -> bool { 385 | deserialize(pubkey_buf, PUBKEY_LEN, pubkey) 386 | } 387 | 388 | #[no_mangle] 389 | /// Deserializes a private key from the provided buffer 390 | /// 391 | /// # Safety 392 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 393 | /// then the software will crash**. 394 | /// - If NULL pointers are passed, the function will return false 395 | /// 396 | /// Returns true if successful, otherwise false. 397 | pub unsafe extern "C" fn deserialize_privkey( 398 | privkey_buf: *const u8, 399 | privkey: *mut *mut PrivateKey, 400 | ) -> bool { 401 | deserialize(privkey_buf, PRIVKEY_LEN, privkey) 402 | } 403 | 404 | #[no_mangle] 405 | /// Deserializes a signature from the provided buffer 406 | /// 407 | /// # Safety 408 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 409 | /// then the software will crash**. 410 | /// - If NULL pointers are passed, the function will return false 411 | /// 412 | /// Returns true if successful, otherwise false. 413 | pub unsafe extern "C" fn deserialize_sig(sig_buf: *const u8, sig: *mut *mut Signature) -> bool { 414 | deserialize(sig_buf, SIGNATURE_LEN, sig) 415 | } 416 | 417 | #[no_mangle] 418 | /// Serializes a public key to the provided buffer 419 | /// 420 | /// # Safety 421 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 422 | /// then the software will crash**. 423 | /// - If NULL pointers are passed, the function will return false 424 | /// 425 | /// Returns true if successful, otherwise false. 426 | pub unsafe extern "C" fn serialize_pubkey( 427 | pubkey: *const PublicKey, 428 | pubkey_buf: *mut *mut u8, 429 | ) -> bool { 430 | serialize(pubkey, pubkey_buf) 431 | } 432 | 433 | #[no_mangle] 434 | /// Serializes a private key to the provided buffer 435 | /// 436 | /// # Safety 437 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 438 | /// then the software will crash**. 439 | /// - If NULL pointers are passed, the function will return false 440 | /// 441 | /// Returns true if successful, otherwise false. 442 | pub unsafe extern "C" fn serialize_privkey( 443 | privkey: *const PrivateKey, 444 | privkey_buf: *mut *mut u8, 445 | ) -> bool { 446 | serialize(privkey, privkey_buf) 447 | } 448 | 449 | #[no_mangle] 450 | /// Serializes a signature to the provided buffer 451 | /// 452 | /// # Safety 453 | /// - **This function will dereference the provided pointers. If any invalid pointers are passed 454 | /// then the software will crash**. 455 | /// - If NULL pointers are passed, the function will return false 456 | /// 457 | /// Returns true if successful, otherwise false. 458 | pub unsafe extern "C" fn serialize_sig(sig: *const Signature, sig_buf: *mut *mut u8) -> bool { 459 | serialize(sig, sig_buf) 460 | } 461 | 462 | unsafe fn deserialize( 463 | in_buf: *const u8, 464 | len: usize, 465 | out: *mut *mut T, 466 | ) -> bool { 467 | let buf = unsafe { std::slice::from_raw_parts(in_buf, len) }; 468 | 469 | let obj = if let Ok(res) = bincode::deserialize(&buf) { 470 | res 471 | } else { 472 | return false; 473 | }; 474 | 475 | unsafe { *out = Box::into_raw(Box::new(obj)) }; 476 | 477 | true 478 | } 479 | 480 | unsafe fn serialize(in_obj: *const T, out_bytes: *mut *mut u8) -> bool { 481 | let obj = unsafe { &*in_obj }; 482 | let mut marshalled = if let Ok(res) = bincode::serialize(obj) { 483 | res 484 | } else { 485 | return false; 486 | }; 487 | 488 | unsafe { 489 | *out_bytes = marshalled.as_mut_ptr(); 490 | }; 491 | std::mem::forget(marshalled); 492 | 493 | true 494 | } 495 | 496 | #[no_mangle] 497 | /// Frees the memory allocated for the blinding factor 498 | /// 499 | /// # Safety 500 | /// 501 | /// The pointer must point to a valid instance of the data type 502 | pub unsafe extern "C" fn destroy_token(token: *mut Token) { 503 | Box::from_raw(token); 504 | } 505 | 506 | #[no_mangle] 507 | /// Frees the memory allocated for the threshold keys helper 508 | /// 509 | /// # Safety 510 | /// 511 | /// The pointer must point to a valid instance of the data type 512 | pub unsafe extern "C" fn destroy_keys(keys: *mut Keys) { 513 | Box::from_raw(keys); 514 | } 515 | 516 | #[no_mangle] 517 | /// Frees the memory allocated for the keypair helper 518 | /// 519 | /// # Safety 520 | /// 521 | /// The pointer must point to a valid instance of the data type 522 | pub unsafe extern "C" fn destroy_keypair(keypair: *mut Keypair) { 523 | Box::from_raw(keypair); 524 | } 525 | 526 | #[no_mangle] 527 | /// Frees the memory allocated for a private key 528 | /// 529 | /// # Safety 530 | /// 531 | /// The pointer must point to a valid instance of the data type 532 | pub unsafe extern "C" fn destroy_privkey(private_key: *mut PrivateKey) { 533 | Box::from_raw(private_key); 534 | } 535 | 536 | #[no_mangle] 537 | /// Frees the memory allocated for a vector 538 | /// 539 | /// # Safety 540 | /// 541 | /// The pointer must point to a valid instance of the data type 542 | pub unsafe extern "C" fn free_vector(bytes: *mut u8, len: usize) { 543 | drop(unsafe { Vec::from_raw_parts(bytes, len as usize, len as usize) }); 544 | } 545 | 546 | #[no_mangle] 547 | /// Frees the memory allocated for a public key 548 | /// 549 | /// # Safety 550 | /// 551 | /// The pointer must point to a valid instance of the data type 552 | pub unsafe extern "C" fn destroy_pubkey(public_key: *mut PublicKey) { 553 | Box::from_raw(public_key); 554 | } 555 | 556 | #[no_mangle] 557 | /// Frees the memory allocated for a signature 558 | /// 559 | /// # Safety 560 | /// 561 | /// The pointer must point to a valid instance of the data type 562 | pub unsafe extern "C" fn destroy_sig(signature: *mut Signature) { 563 | Box::from_raw(signature); 564 | } 565 | 566 | /////////////////////////////////////////////////////////////////////////// 567 | // Helpers 568 | // 569 | // These should be exposed behind a helper module and should not be made part 570 | // of the public API 571 | /////////////////////////////////////////////////////////////////////////// 572 | 573 | /// Generates a t-of-n polynomial and private key shares 574 | /// 575 | /// The return value should be destroyed with `destroy_keys`. 576 | /// 577 | /// # Safety 578 | /// 579 | /// WARNING: This is a helper function for local testing of the library. Do not use 580 | /// in production, unless you trust the person that generated the keys. 581 | /// 582 | /// The seed MUST be at least 32 bytes long 583 | #[no_mangle] 584 | pub unsafe extern "C" fn threshold_keygen(n: usize, t: usize, seed: &[u8], keys: *mut *mut Keys) { 585 | let mut rng = get_rng(seed); 586 | let private = Poly::::new_from(t - 1, &mut rng); 587 | let shares = (0..n) 588 | .map(|i| private.eval(i as Index)) 589 | .map(|e| Share { 590 | index: e.index, 591 | private: e.value, 592 | }) 593 | .collect(); 594 | let polynomial: Poly = private.commit(); 595 | let threshold_public_key = polynomial.public_key().clone(); 596 | 597 | let keys_local = Keys { 598 | shares, 599 | polynomial, 600 | threshold_public_key, 601 | t, 602 | n, 603 | }; 604 | 605 | unsafe { 606 | *keys = Box::into_raw(Box::new(keys_local)); 607 | }; 608 | } 609 | 610 | /// Generates a single private key from the provided seed. 611 | /// 612 | /// The return value should be destroyed with `destroy_keypair`. 613 | /// 614 | /// # Safety 615 | /// 616 | /// The seed MUST be at least 32 bytes long 617 | #[no_mangle] 618 | pub unsafe extern "C" fn keygen(seed: *const Buffer, keypair: *mut *mut Keypair) { 619 | let seed = <&[u8]>::from(unsafe { &*seed }); 620 | let mut rng = get_rng(&seed); 621 | let (private, public) = SigScheme::keypair(&mut rng); 622 | let keypair_local = Keypair { private, public }; 623 | unsafe { *keypair = Box::into_raw(Box::new(keypair_local)) }; 624 | } 625 | 626 | /// Gets the `index`'th share corresponding to the provided `Keys` pointer 627 | /// 628 | /// The return value should be destroyed with `destroy_keys`. 629 | /// 630 | /// # Safety 631 | /// 632 | /// WARNING: This is a helper function for local testing of the library. Do not use 633 | /// in production, unless you trust the person that generated the keys. 634 | /// 635 | /// The seed MUST be at least 32 bytes long 636 | #[no_mangle] 637 | pub unsafe extern "C" fn share_ptr(keys: *const Keys, index: usize) -> *const Share { 638 | &(*keys).shares[index] as *const Share 639 | } 640 | 641 | /// Gets the number of shares corresponding to the provided `Keys` pointer 642 | /// 643 | /// # Safety 644 | /// The provided pointer will be dereferenced, so there must be valid data beneath it 645 | #[no_mangle] 646 | pub unsafe extern "C" fn num_shares(keys: *const Keys) -> usize { 647 | (*keys).shares.len() 648 | } 649 | 650 | /// Gets a pointer to the polynomial corresponding to the provided `Keys` pointer 651 | /// 652 | /// # Safety 653 | /// The provided pointer will be dereferenced, so there must be valid data beneath it 654 | #[no_mangle] 655 | pub unsafe extern "C" fn polynomial_ptr(keys: *const Keys) -> *const Poly { 656 | &(*keys).polynomial as *const Poly 657 | } 658 | 659 | /// Gets a pointer to the threshold public key corresponding to the provided `Keys` pointer 660 | /// 661 | /// # Safety 662 | /// The provided pointer will be dereferenced, so there must be valid data beneath it 663 | #[no_mangle] 664 | pub unsafe extern "C" fn threshold_public_key_ptr(keys: *const Keys) -> *const PublicKey { 665 | &(*keys).threshold_public_key as *const PublicKey 666 | } 667 | 668 | /// Gets a pointer to the public key corresponding to the provided `KeyPair` pointer 669 | /// 670 | /// # Safety 671 | /// The provided pointer will be dereferenced, so there must be valid data beneath it 672 | #[no_mangle] 673 | pub unsafe extern "C" fn public_key_ptr(keypair: *const Keypair) -> *const PublicKey { 674 | &(*keypair).public as *const PublicKey 675 | } 676 | 677 | /// Gets a pointer to the private key corresponding to the provided `KeyPair` pointer 678 | /// 679 | /// # Safety 680 | /// The provided pointer will be dereferenced, so there must be valid data beneath it 681 | #[no_mangle] 682 | pub unsafe extern "C" fn private_key_ptr(keypair: *const Keypair) -> *const PrivateKey { 683 | &(*keypair).private as *const PrivateKey 684 | } 685 | 686 | /// T-of-n threshold key parameters 687 | #[derive(Debug, Clone)] 688 | pub struct Keys { 689 | shares: Vec>, 690 | polynomial: Poly, 691 | threshold_public_key: PublicKey, 692 | pub t: usize, 693 | pub n: usize, 694 | } 695 | 696 | #[derive(Clone)] 697 | #[repr(C)] 698 | /// A BLS12-377 Keypair 699 | pub struct Keypair { 700 | /// The private key 701 | private: PrivateKey, 702 | /// The public key 703 | public: PublicKey, 704 | } 705 | 706 | fn get_rng(digest: &[u8]) -> impl RngCore { 707 | let seed = from_slice(digest); 708 | ChaChaRng::from_seed(seed) 709 | } 710 | 711 | fn from_slice(bytes: &[u8]) -> [u8; 32] { 712 | let mut array = [0; 32]; 713 | let bytes = &bytes[..array.len()]; // panics if not enough data 714 | array.copy_from_slice(bytes); 715 | array 716 | } 717 | 718 | // The general pattern in these FFI tests is: 719 | // 1. create a MaybeUninit pointer 720 | // 2. pass it to the function 721 | // 3. assert that the function call was successful 722 | // 4. assume the pointer is now initialized 723 | #[cfg(test)] 724 | mod tests { 725 | use super::*; 726 | use std::mem::MaybeUninit; 727 | 728 | #[test] 729 | fn threshold_verify_ffi() { 730 | threshold_verify_ffi_should_blind(true); 731 | threshold_verify_ffi_should_blind(false); 732 | } 733 | 734 | fn threshold_verify_ffi_should_blind(should_blind: bool) { 735 | let seed = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 736 | let msg = vec![1u8, 2, 3, 4, 6]; 737 | let user_seed = &b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"[..]; 738 | let empty_token = Token::new(); 739 | let partial_sign_fn = if should_blind { 740 | partial_sign_blinded_message 741 | } else { 742 | partial_sign 743 | }; 744 | let partial_verify_fn = if should_blind { 745 | partial_verify_blind_signature 746 | } else { 747 | partial_verify 748 | }; 749 | 750 | let (n, t) = (5, 3); 751 | let mut keys = MaybeUninit::<*mut Keys>::uninit(); 752 | unsafe { threshold_keygen(n, t, &seed[..], keys.as_mut_ptr()) }; 753 | let keys = unsafe { &*keys.assume_init() }; 754 | 755 | let (message_to_sign, blinding_factor) = if should_blind { 756 | let mut blinded_message = MaybeUninit::::uninit(); 757 | let mut blinding_factor = MaybeUninit::<*mut Token>::uninit(); 758 | unsafe { 759 | blind( 760 | &Buffer::from(msg.as_ref()), 761 | &Buffer::from(user_seed), 762 | blinded_message.as_mut_ptr(), 763 | blinding_factor.as_mut_ptr(), 764 | ) 765 | }; 766 | let blinded_message = unsafe { blinded_message.assume_init() }; 767 | let blinding_factor = unsafe { &*blinding_factor.assume_init() }; 768 | 769 | (blinded_message, blinding_factor) 770 | } else { 771 | (Buffer::from(&msg[..]), &empty_token) 772 | }; 773 | 774 | // 2. partially sign the blinded message 775 | let mut sigs = Vec::new(); 776 | for i in 0..t { 777 | let mut partial_sig = MaybeUninit::::uninit(); 778 | let ret = unsafe { 779 | partial_sign_fn( 780 | share_ptr(keys, i), 781 | &message_to_sign, 782 | partial_sig.as_mut_ptr(), 783 | ) 784 | }; 785 | assert!(ret); 786 | 787 | let partial_sig = unsafe { partial_sig.assume_init() }; 788 | sigs.push(partial_sig); 789 | } 790 | 791 | // 3. verify the partial signatures & concatenate them 792 | let public_key = unsafe { polynomial_ptr(keys) }; 793 | let mut concatenated = Vec::new(); 794 | for sig in &sigs { 795 | let sig_slice = <&[u8]>::from(sig); 796 | concatenated.extend_from_slice(sig_slice); 797 | let ret = unsafe { partial_verify_fn(public_key, &message_to_sign, sig) }; 798 | assert!(ret); 799 | } 800 | let concatenated = Buffer::from(&concatenated[..]); 801 | 802 | // 4. generate the threshold signature 803 | let mut asig = MaybeUninit::::uninit(); 804 | let ret = unsafe { combine(t, &concatenated, asig.as_mut_ptr()) }; 805 | assert!(ret); 806 | let asig = unsafe { asig.assume_init() }; 807 | 808 | // 5. unblind the threshold signature 809 | let asig = if should_blind { 810 | let mut unblinded = MaybeUninit::::uninit(); 811 | let ret = unsafe { unblind(&asig, blinding_factor, unblinded.as_mut_ptr()) }; 812 | assert!(ret); 813 | unsafe { unblinded.assume_init() } 814 | } else { 815 | asig 816 | }; 817 | 818 | // 6. verify the threshold signature against the public key 819 | let ret = unsafe { 820 | verify( 821 | threshold_public_key_ptr(keys), 822 | &Buffer::from(&msg[..]), 823 | &asig, 824 | ) 825 | }; 826 | assert!(ret); 827 | } 828 | 829 | #[test] 830 | fn verify_ffi() { 831 | verify_ffi_should_blind(true); 832 | verify_ffi_should_blind(false); 833 | } 834 | 835 | fn verify_ffi_should_blind(should_blind: bool) { 836 | let seed = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 837 | let msg = vec![1u8, 2, 3, 4, 6]; 838 | let user_seed = &b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"[..]; 839 | let empty_token = Token::new(); 840 | 841 | let sign_fn = if should_blind { 842 | sign_blinded_message 843 | } else { 844 | sign 845 | }; 846 | 847 | let mut keypair = MaybeUninit::<*mut Keypair>::uninit(); 848 | unsafe { keygen(&Buffer::from(&seed[..]), keypair.as_mut_ptr()) }; 849 | let keypair = unsafe { &*keypair.assume_init() }; 850 | 851 | let (message_to_sign, blinding_factor) = if should_blind { 852 | let mut blinded_message = MaybeUninit::::uninit(); 853 | let mut blinding_factor = MaybeUninit::<*mut Token>::uninit(); 854 | unsafe { 855 | blind( 856 | &Buffer::from(msg.as_ref()), 857 | &Buffer::from(user_seed), 858 | blinded_message.as_mut_ptr(), 859 | blinding_factor.as_mut_ptr(), 860 | ) 861 | }; 862 | let blinded_message = unsafe { blinded_message.assume_init() }; 863 | let blinding_factor = unsafe { &*blinding_factor.assume_init() }; 864 | 865 | (blinded_message, blinding_factor) 866 | } else { 867 | (Buffer::from(&msg[..]), &empty_token) 868 | }; 869 | 870 | let mut sig = MaybeUninit::::uninit(); 871 | let ret = unsafe { sign_fn(private_key_ptr(keypair), &message_to_sign, sig.as_mut_ptr()) }; 872 | assert!(ret); 873 | let sig = unsafe { sig.assume_init() }; 874 | 875 | let sig = if should_blind { 876 | let mut unblinded = MaybeUninit::::uninit(); 877 | let ret = unsafe { unblind(&sig, blinding_factor, unblinded.as_mut_ptr()) }; 878 | assert!(ret); 879 | 880 | unsafe { unblinded.assume_init() } 881 | } else { 882 | sig 883 | }; 884 | 885 | let ret = unsafe { verify(public_key_ptr(keypair), &Buffer::from(&msg[..]), &sig) }; 886 | assert!(ret); 887 | } 888 | 889 | #[test] 890 | fn private_key_serialization() { 891 | let seed = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 892 | 893 | let mut keypair = MaybeUninit::<*mut Keypair>::uninit(); 894 | unsafe { keygen(&Buffer::from(&seed[..]), keypair.as_mut_ptr()) }; 895 | let keypair = unsafe { &*keypair.assume_init() }; 896 | 897 | let private_key_ptr = unsafe { private_key_ptr(keypair) }; 898 | let private_key = unsafe { &*private_key_ptr }; 899 | let marshalled = bincode::serialize(private_key).unwrap(); 900 | 901 | let mut privkey_buf = MaybeUninit::<*mut u8>::uninit(); 902 | 903 | let ret = unsafe { serialize_privkey(private_key_ptr, privkey_buf.as_mut_ptr()) }; 904 | assert!(ret); 905 | 906 | let privkey_buf = unsafe { privkey_buf.assume_init() }; 907 | let message = unsafe { std::slice::from_raw_parts(privkey_buf, PRIVKEY_LEN) }; 908 | assert_eq!(marshalled, message); 909 | 910 | let unmarshalled: PrivateKey = bincode::deserialize(&message).unwrap(); 911 | assert_eq!(&unmarshalled, private_key); 912 | 913 | let mut de = MaybeUninit::<*mut PrivateKey>::uninit(); 914 | let ret = unsafe { deserialize_privkey(&message[0] as *const u8, de.as_mut_ptr()) }; 915 | assert!(ret); 916 | let de = unsafe { de.assume_init() }; 917 | 918 | assert_eq!(private_key, unsafe { &*de }); 919 | } 920 | 921 | #[test] 922 | fn public_key_serialization() { 923 | let seed = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 924 | 925 | let mut keypair = MaybeUninit::<*mut Keypair>::uninit(); 926 | unsafe { keygen(&Buffer::from(&seed[..]), keypair.as_mut_ptr()) }; 927 | let keypair = unsafe { &*keypair.assume_init() }; 928 | 929 | let public_key_ptr = unsafe { public_key_ptr(keypair) }; 930 | let public_key = unsafe { &*public_key_ptr }; 931 | 932 | let marshalled = bincode::serialize(public_key).unwrap(); 933 | 934 | let mut pubkey_buf = MaybeUninit::<*mut u8>::uninit(); 935 | 936 | let ret = unsafe { serialize_pubkey(public_key_ptr, pubkey_buf.as_mut_ptr()) }; 937 | assert!(ret); 938 | 939 | let pubkey_buf = unsafe { pubkey_buf.assume_init() }; 940 | // the serialized result 941 | let message = unsafe { std::slice::from_raw_parts(pubkey_buf, PUBKEY_LEN) }; 942 | assert_eq!(marshalled, message); 943 | 944 | let unmarshalled: PublicKey = bincode::deserialize(&message).unwrap(); 945 | assert_eq!(&unmarshalled, public_key); 946 | 947 | let mut de = MaybeUninit::<*mut PublicKey>::uninit(); 948 | let ret = unsafe { deserialize_pubkey(&message[0] as *const u8, de.as_mut_ptr()) }; 949 | assert!(ret); 950 | let de = unsafe { de.assume_init() }; 951 | 952 | assert_eq!(public_key, unsafe { &*de }); 953 | } 954 | } 955 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/src/jvm.rs: -------------------------------------------------------------------------------- 1 | use jni::objects::{JByteArray, JClass}; 2 | use jni::sys::jboolean; 3 | use jni::JNIEnv; 4 | 5 | use threshold_bls::sig::SignatureScheme; 6 | 7 | use crate::*; 8 | 9 | // This keeps Rust from "mangling" the name and making it unique for this 10 | // crate. 11 | #[no_mangle] 12 | pub extern "system" fn Java_org_celo_BlindThresholdBls_verify<'local>( 13 | env: JNIEnv<'local>, 14 | _class: JClass<'local>, 15 | pub_key: JByteArray<'local>, 16 | message: JByteArray<'local>, 17 | signature: JByteArray<'local>, 18 | ) -> jboolean { 19 | let pub_key_vec = env.convert_byte_array(&pub_key).unwrap(); 20 | let pub_key: PublicKey = bincode::deserialize(&pub_key_vec).unwrap(); 21 | let message = env.convert_byte_array(&message).unwrap(); 22 | let signature = env.convert_byte_array(&signature).unwrap(); 23 | 24 | jboolean::from(SigScheme::verify(&pub_key, &message, &signature).is_ok()) 25 | } 26 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | // add this so that we can be more explicit about unsafe calls inside unsafe functions 2 | #![allow(unused_unsafe)] 3 | 4 | extern crate cfg_if; 5 | 6 | cfg_if::cfg_if! { 7 | if #[cfg(feature = "wasm")] { 8 | pub mod wasm; 9 | } else if #[cfg(feature = "jvm")] { 10 | pub mod jvm; 11 | } else if #[cfg(feature = "ffi")] { 12 | pub mod ffi; 13 | pub(crate) type Signature = ::Signature; 14 | pub(crate) const PUBKEY_LEN: usize = 96; 15 | pub(crate) const PRIVKEY_LEN: usize = 32; 16 | } 17 | } 18 | 19 | use threshold_bls::{poly::Idx, schemes::bls12_377::G2Scheme as SigScheme, sig::Scheme}; 20 | 21 | #[allow(dead_code)] 22 | pub(crate) type PublicKey = ::Public; 23 | #[allow(dead_code)] 24 | pub(crate) type PrivateKey = ::Private; 25 | 26 | #[allow(dead_code)] 27 | pub(crate) const VEC_LENGTH: usize = 8; 28 | #[allow(dead_code)] 29 | pub(crate) const SIGNATURE_LEN: usize = 48; 30 | #[allow(dead_code)] 31 | pub(crate) const PARTIAL_SIG_LENGTH: usize = 32 | VEC_LENGTH + SIGNATURE_LEN + std::mem::size_of::(); 33 | -------------------------------------------------------------------------------- /crates/threshold-bls-ffi/src/wasm.rs: -------------------------------------------------------------------------------- 1 | //! # BLS12-377 WASM Bindings for Blind Threshold Signatures. 2 | use wasm_bindgen::prelude::*; 3 | 4 | use rand_chacha::ChaChaRng; 5 | use rand_core::{RngCore, SeedableRng}; 6 | 7 | use threshold_bls::{ 8 | poly::{Idx as Index, Poly}, 9 | sig::{ 10 | BlindScheme, BlindThresholdScheme, Scheme, Share, SignatureScheme, ThresholdScheme, Token, 11 | }, 12 | }; 13 | 14 | use crate::*; 15 | 16 | type Result = std::result::Result; 17 | 18 | /////////////////////////////////////////////////////////////////////////// 19 | // User -> Library 20 | /////////////////////////////////////////////////////////////////////////// 21 | 22 | #[wasm_bindgen] 23 | /// Given a message and a seed, it will blind it and return the blinded message 24 | /// 25 | /// * message: A cleartext message which you want to blind 26 | /// * seed: A 32 byte seed for randomness. You can get one securely via `crypto.randomBytes(32)` 27 | /// 28 | /// Returns a `BlindedMessage`. The `BlindedMessage.blinding_factor` should be saved for unblinding any 29 | /// signatures on `BlindedMessage.message` 30 | /// 31 | /// # Safety 32 | /// - If the same seed is used twice, the blinded result WILL be the same 33 | pub fn blind(message: Vec, seed: &[u8]) -> BlindedMessage { 34 | // convert the seed to randomness 35 | let mut rng = get_rng(seed); 36 | 37 | // blind the message with this randomness 38 | let (blinding_factor, blinded_message) = SigScheme::blind_msg(&message, &mut rng); 39 | 40 | // return the message and the blinding_factor used for blinding 41 | BlindedMessage { 42 | message: blinded_message, 43 | blinding_factor, 44 | } 45 | } 46 | 47 | #[wasm_bindgen] 48 | /// Given a blinded message and a blinding_factor used for blinding, it returns the message 49 | /// unblinded 50 | /// 51 | /// * blinded_message: A message which has been blinded or a blind signature 52 | /// * blinding_factor: The blinding_factor used to blind the message 53 | /// 54 | /// # Throws 55 | /// 56 | /// - If unblinding fails. 57 | pub fn unblind(blinded_signature: &[u8], blinding_factor_buf: &[u8]) -> Result> { 58 | let blinding_factor: Token = 59 | bincode::deserialize(blinding_factor_buf).map_err(|err| { 60 | JsValue::from_str(&format!("could not deserialize blinding factor {}", err)) 61 | })?; 62 | 63 | SigScheme::unblind_sig(&blinding_factor, blinded_signature) 64 | .map_err(|err| JsValue::from_str(&format!("could not unblind signature {}", err))) 65 | } 66 | 67 | #[wasm_bindgen] 68 | /// Verifies the signature after it has been unblinded. Users will call this on the 69 | /// threshold signature against the full public key 70 | /// 71 | /// * public_key: The public key used to sign the message 72 | /// * message: The message which was signed 73 | /// * signature: The signature which was produced on the message 74 | /// 75 | /// # Throws 76 | /// 77 | /// - If verification fails 78 | pub fn verify(public_key_buf: &[u8], message: &[u8], signature: &[u8]) -> Result<()> { 79 | let public_key: PublicKey = bincode::deserialize(public_key_buf) 80 | .map_err(|err| JsValue::from_str(&format!("could not deserialize public key {}", err)))?; 81 | 82 | // checks the signature on the message hash 83 | SigScheme::verify(&public_key, message, signature) 84 | .map_err(|err| JsValue::from_str(&format!("signature verification failed: {}", err))) 85 | } 86 | 87 | #[wasm_bindgen(js_name = verifyBlindSignature)] 88 | /// Verifies the signature after it has been unblinded without hashing. Users will call this on the 89 | /// threshold signature against the full public key 90 | /// 91 | /// * public_key: The public key used to sign the message 92 | /// * message: The message which was signed 93 | /// * signature: The signature which was produced on the message 94 | /// 95 | /// # Throws 96 | /// 97 | /// - If verification fails 98 | pub fn verify_blind_signature( 99 | public_key_buf: &[u8], 100 | message: &[u8], 101 | signature: &[u8], 102 | ) -> Result<()> { 103 | let public_key: PublicKey = bincode::deserialize(public_key_buf) 104 | .map_err(|err| JsValue::from_str(&format!("could not deserialize public key {}", err)))?; 105 | 106 | // checks the signature on the message hash 107 | SigScheme::blind_verify(&public_key, message, signature) 108 | .map_err(|err| JsValue::from_str(&format!("signature verification failed: {}", err))) 109 | } 110 | 111 | /////////////////////////////////////////////////////////////////////////// 112 | // Service -> Library 113 | /////////////////////////////////////////////////////////////////////////// 114 | 115 | #[wasm_bindgen] 116 | /// Signs the message with the provided private key and returns the signature 117 | /// 118 | /// # Throws 119 | /// 120 | /// - If signing fails 121 | pub fn sign(private_key_buf: &[u8], message: &[u8]) -> Result> { 122 | let private_key: PrivateKey = bincode::deserialize(private_key_buf) 123 | .map_err(|err| JsValue::from_str(&format!("could not deserialize private key {}", err)))?; 124 | 125 | SigScheme::sign(&private_key, message) 126 | .map_err(|err| JsValue::from_str(&format!("could not sign message: {}", err))) 127 | } 128 | 129 | #[wasm_bindgen(js_name = signBlindedMessage)] 130 | /// Signs the message with the provided private key without hashing and returns the signature 131 | /// 132 | /// # Throws 133 | /// 134 | /// - If signing fails 135 | pub fn sign_blinded_message(private_key_buf: &[u8], message: &[u8]) -> Result> { 136 | let private_key: PrivateKey = bincode::deserialize(private_key_buf) 137 | .map_err(|err| JsValue::from_str(&format!("could not deserialize private key {}", err)))?; 138 | 139 | SigScheme::blind_sign(&private_key, message) 140 | .map_err(|err| JsValue::from_str(&format!("could not sign message: {}", err))) 141 | } 142 | 143 | #[wasm_bindgen(js_name = partialSign)] 144 | /// Signs the message with the provided **share** of the private key and returns the **partial** 145 | /// signature. 146 | /// 147 | /// # Throws 148 | /// 149 | /// - If signing fails 150 | /// 151 | /// NOTE: This method must NOT be called with a PrivateKey which is not generated via a 152 | /// secret sharing scheme. 153 | pub fn partial_sign(share_buf: &[u8], message: &[u8]) -> Result> { 154 | let share: Share = bincode::deserialize(share_buf).map_err(|err| { 155 | JsValue::from_str(&format!("could not deserialize private key share {}", err)) 156 | })?; 157 | 158 | SigScheme::partial_sign(&share, message) 159 | .map_err(|err| JsValue::from_str(&format!("could not partially sign message: {}", err))) 160 | } 161 | 162 | #[wasm_bindgen(js_name = partialSignBlindedMessage)] 163 | /// Signs the message with the provided **share** of the private key and returns the **partial** 164 | /// signature. 165 | /// 166 | /// # Throws 167 | /// 168 | /// - If signing fails 169 | /// 170 | /// NOTE: This method must NOT be called with a PrivateKey which is not generated via a 171 | /// secret sharing scheme. 172 | pub fn partial_sign_blinded_message(share_buf: &[u8], message: &[u8]) -> Result> { 173 | let share: Share = bincode::deserialize(share_buf).map_err(|err| { 174 | JsValue::from_str(&format!("could not deserialize private key share {}", err)) 175 | })?; 176 | 177 | SigScheme::sign_blind_partial(&share, message) 178 | .map_err(|err| JsValue::from_str(&format!("could not partially sign message: {}", err))) 179 | } 180 | 181 | /////////////////////////////////////////////////////////////////////////// 182 | // Combiner -> Library 183 | /////////////////////////////////////////////////////////////////////////// 184 | 185 | #[wasm_bindgen(js_name = partialVerify)] 186 | /// Verifies a partial signature against the public key corresponding to the secret shared 187 | /// polynomial. 188 | /// 189 | /// # Throws 190 | /// 191 | /// - If verification fails 192 | pub fn partial_verify(polynomial_buf: &[u8], blinded_message: &[u8], sig: &[u8]) -> Result<()> { 193 | let polynomial: Poly = bincode::deserialize(polynomial_buf) 194 | .map_err(|err| JsValue::from_str(&format!("could not deserialize polynomial {}", err)))?; 195 | 196 | SigScheme::partial_verify(&polynomial, blinded_message, sig) 197 | .map_err(|err| JsValue::from_str(&format!("could not partially verify message: {}", err))) 198 | } 199 | 200 | #[wasm_bindgen(js_name = partialVerifyBlindSignature)] 201 | /// Verifies a partial *blind* signature against the public key corresponding to the secret shared 202 | /// polynomial. 203 | /// 204 | /// # Throws 205 | /// 206 | /// - If verification fails 207 | pub fn partial_verify_blind_signature( 208 | polynomial_buf: &[u8], 209 | blinded_message: &[u8], 210 | sig: &[u8], 211 | ) -> Result<()> { 212 | let polynomial: Poly = bincode::deserialize(polynomial_buf) 213 | .map_err(|err| JsValue::from_str(&format!("could not deserialize polynomial {}", err)))?; 214 | 215 | SigScheme::verify_blind_partial(&polynomial, blinded_message, sig) 216 | .map_err(|err| JsValue::from_str(&format!("could not partially verify message: {}", err))) 217 | } 218 | 219 | #[wasm_bindgen] 220 | /// Combines a flattened vector of partial signatures to a single threshold signature 221 | /// 222 | /// NOTE: Wasm-bindgen does not support Vec>, so this function accepts a flattened 223 | /// byte vector which it will parse in chunks for each signature. 224 | /// 225 | /// NOTE: If you are working with an array of Uint8Arrays In Javascript, the simplest 226 | /// way to flatten them is via: 227 | /// 228 | /// ```js 229 | /// function flatten(arr) { 230 | /// return Uint8Array.from(arr.reduce(function(a, b) { 231 | /// return Array.from(a).concat(Array.from(b)); 232 | /// }, [])); 233 | /// } 234 | /// ``` 235 | /// 236 | /// # Throws 237 | /// 238 | /// - If the aggregation fails 239 | /// 240 | /// # Safety 241 | /// 242 | /// - This function does not check if the signatures are valid! 243 | pub fn combine(threshold: usize, signatures: Vec) -> Result> { 244 | // break the flattened vector to a Vec> where each element is a serialized signature 245 | let sigs = signatures 246 | .chunks(PARTIAL_SIG_LENGTH) 247 | .map(|chunk| chunk.to_vec()) 248 | .collect::>>(); 249 | 250 | SigScheme::aggregate(threshold, &sigs) 251 | .map_err(|err| JsValue::from_str(&format!("could not aggregate sigs: {}", err,))) 252 | } 253 | 254 | /////////////////////////////////////////////////////////////////////////// 255 | // Helpers 256 | /////////////////////////////////////////////////////////////////////////// 257 | 258 | #[wasm_bindgen(js_name = thresholdKeygen)] 259 | /// Generates a t-of-n polynomial and private key shares 260 | /// 261 | /// # Safety 262 | /// 263 | /// WARNING: This is a helper function for local testing of the library. Do not use 264 | /// in production, unless you trust the person that generated the keys. 265 | /// 266 | /// The seed MUST be at least 32 bytes long 267 | pub fn threshold_keygen(n: usize, t: usize, seed: &[u8]) -> Keys { 268 | let mut rng = get_rng(seed); 269 | let private = Poly::::new_from(t - 1, &mut rng); 270 | let shares = (0..n) 271 | .map(|i| private.eval(i as Index)) 272 | .map(|e| Share { 273 | index: e.index, 274 | private: e.value, 275 | }) 276 | .collect(); 277 | let polynomial = private.commit(); 278 | Keys { 279 | shares, 280 | polynomial, 281 | t, 282 | n, 283 | } 284 | } 285 | 286 | #[wasm_bindgen(inspectable)] 287 | /// A blinded message along with the blinding_factor used to produce it 288 | pub struct BlindedMessage { 289 | /// The resulting blinded message 290 | message: Vec, 291 | /// The blinding_factor which was used to generate the blinded message. This will be used 292 | /// to unblind the signature received on the blinded message to a valid signature 293 | /// on the unblinded message 294 | blinding_factor: Token, 295 | } 296 | 297 | #[wasm_bindgen] 298 | impl BlindedMessage { 299 | #[wasm_bindgen(getter)] 300 | pub fn message(&self) -> Vec { 301 | self.message.clone() 302 | } 303 | 304 | #[wasm_bindgen(getter, js_name = blindingFactor)] 305 | pub fn blinding_factor(&self) -> Vec { 306 | bincode::serialize(&self.blinding_factor).expect("could not serialize blinding factor") 307 | } 308 | } 309 | 310 | #[wasm_bindgen] 311 | #[derive(Clone)] 312 | /// A BLS12-377 Keypair 313 | pub struct Keypair { 314 | /// The private key 315 | private: PrivateKey, 316 | /// The public key 317 | public: PublicKey, 318 | } 319 | 320 | // Need to implement custom getters if we want to return more than one value 321 | // and expose it https://rustwasm.github.io/wasm-bindgen/reference/attributes/on-rust-exports/getter-and-setter.html 322 | #[wasm_bindgen] 323 | impl Keypair { 324 | #[wasm_bindgen(getter, js_name = privateKey)] 325 | pub fn private_key(&self) -> Vec { 326 | bincode::serialize(&self.private).expect("could not serialize private key") 327 | } 328 | 329 | #[wasm_bindgen(getter, js_name = publicKey)] 330 | pub fn public_key(&self) -> Vec { 331 | bincode::serialize(&self.public).expect("could not serialize public key") 332 | } 333 | } 334 | 335 | /// Generates a single private key from the provided seed. 336 | /// 337 | /// # Safety 338 | /// 339 | /// The seed MUST be at least 32 bytes long 340 | #[wasm_bindgen] 341 | pub fn keygen(seed: Vec) -> Keypair { 342 | let mut rng = get_rng(&seed); 343 | let (private, public) = SigScheme::keypair(&mut rng); 344 | Keypair { private, public } 345 | } 346 | 347 | #[wasm_bindgen] 348 | pub struct Keys { 349 | shares: Vec>, 350 | polynomial: Poly, 351 | pub t: usize, 352 | pub n: usize, 353 | } 354 | 355 | #[wasm_bindgen] 356 | impl Keys { 357 | #[wasm_bindgen(js_name = getShare)] 358 | pub fn get_share(&self, index: usize) -> Vec { 359 | bincode::serialize(&self.shares[index]).expect("could not serialize share") 360 | } 361 | 362 | #[wasm_bindgen(js_name = numShares)] 363 | pub fn num_shares(&self) -> usize { 364 | self.shares.len() 365 | } 366 | 367 | #[wasm_bindgen(getter, js_name = polynomial)] 368 | pub fn polynomial(&self) -> Vec { 369 | bincode::serialize(&self.polynomial).expect("could not serialize polynomial") 370 | } 371 | 372 | #[wasm_bindgen(getter, js_name = thresholdPublicKey)] 373 | pub fn threshold_public_key(&self) -> Vec { 374 | bincode::serialize(&self.polynomial.public_key()) 375 | .expect("could not serialize threshold public key") 376 | } 377 | } 378 | 379 | fn get_rng(digest: &[u8]) -> impl RngCore { 380 | let seed = from_slice(digest); 381 | ChaChaRng::from_seed(seed) 382 | } 383 | 384 | fn from_slice(bytes: &[u8]) -> [u8; 32] { 385 | let mut array = [0; 32]; 386 | let bytes = &bytes[..array.len()]; // panics if not enough data 387 | array.copy_from_slice(bytes); 388 | array 389 | } 390 | 391 | #[cfg(test)] 392 | mod tests { 393 | use super::*; 394 | 395 | #[test] 396 | fn threshold_wasm() { 397 | threshold_wasm_should_blind(true); 398 | threshold_wasm_should_blind(false); 399 | } 400 | 401 | #[test] 402 | fn signing() { 403 | wasm_should_blind(true); 404 | wasm_should_blind(false); 405 | } 406 | 407 | fn wasm_should_blind(should_blind: bool) { 408 | let seed = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 409 | let keypair = keygen(seed.to_vec()); 410 | 411 | let msg = vec![1, 2, 3, 4, 6]; 412 | let key = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; 413 | 414 | let (message, token) = if should_blind { 415 | let ret = blind(msg.clone(), &key[..]); 416 | (ret.message.clone(), ret.blinding_factor()) 417 | } else { 418 | (msg.clone(), vec![]) 419 | }; 420 | 421 | let sign_fn = if should_blind { 422 | sign_blinded_message 423 | } else { 424 | sign 425 | }; 426 | 427 | let sig = sign_fn(&keypair.private_key(), &message).unwrap(); 428 | 429 | if should_blind { 430 | verify_blind_signature(&keypair.public_key(), &message, &sig).unwrap(); 431 | let unblinded = unblind(&sig, &token).unwrap(); 432 | verify(&keypair.public_key(), &msg, &unblinded).unwrap(); 433 | } else { 434 | verify(&keypair.public_key(), &msg, &sig).unwrap(); 435 | } 436 | } 437 | 438 | fn threshold_wasm_should_blind(should_blind: bool) { 439 | let (n, t) = (5, 3); 440 | let seed = b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 441 | let keys = threshold_keygen(n, t, &seed[..]); 442 | 443 | let msg = vec![1, 2, 3, 4, 6]; 444 | let key = b"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; 445 | 446 | let (message, token) = if should_blind { 447 | let ret = blind(msg.clone(), &key[..]); 448 | (ret.message.clone(), ret.blinding_factor()) 449 | } else { 450 | (msg.clone(), vec![]) 451 | }; 452 | 453 | let sign_fn = if should_blind { 454 | partial_sign_blinded_message 455 | } else { 456 | partial_sign 457 | }; 458 | 459 | let verify_fn = if should_blind { 460 | partial_verify_blind_signature 461 | } else { 462 | partial_verify 463 | }; 464 | 465 | let sigs = (0..t) 466 | .map(|i| sign_fn(&keys.get_share(i), &message).unwrap()) 467 | .collect::>>(); 468 | 469 | sigs.iter() 470 | .for_each(|sig| verify_fn(&keys.polynomial(), &message, sig).unwrap()); 471 | 472 | let concatenated = sigs.concat(); 473 | let asig = combine(3, concatenated).unwrap(); 474 | 475 | if should_blind { 476 | verify_blind_signature(&keys.threshold_public_key(), &message, &asig).unwrap(); 477 | let unblinded = unblind(&asig, &token).unwrap(); 478 | verify(&keys.threshold_public_key(), &msg, &unblinded).unwrap(); 479 | } else { 480 | verify(&keys.threshold_public_key(), &msg, &asig).unwrap(); 481 | } 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /crates/threshold-bls/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "threshold-bls" 3 | version = "0.1.0" 4 | authors = ["nikkolasg"] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["lib"] 9 | 10 | [dependencies] 11 | rand_core = { version = "0.5.1", default-features = false } 12 | rand = "0.7" 13 | rand_chacha = "0.2.2" 14 | serde = {version = "1.0.106", features = ["derive"] } 15 | 16 | # for ECIES 17 | chacha20poly1305 = "0.3" 18 | hkdf = "0.8" 19 | sha2 = "0.8" 20 | 21 | # bls12_381 22 | paired = { version = "0.18.0", features = ["serde"], optional = true } 23 | ff = { version = "0.2", package = "fff", optional = true } 24 | groupy = {version = "0.3.0", optional = true } 25 | 26 | 27 | # bls12_377 28 | algebra = { git = "https://github.com/celo-org/arkworks-v0.1.1-patch", package = "algebra", branch = "patch-rust-compat", features = ["bls12_377"], optional = true } 29 | bls-crypto = { git = "https://github.com/celo-org/bls-zexe", rev = "879630a7d95794994e31c874934d04b3c5904892", optional = true } 30 | thiserror = "1.0.15" 31 | bincode = "1.2.1" 32 | 33 | [features] 34 | default = ["bls12_381", "bls12_377"] 35 | bls12_377 = ["algebra", "bls-crypto"] 36 | bls12_381 = ["paired", "groupy", "ff"] 37 | 38 | [dev-dependencies] 39 | static_assertions = "1.1.0" 40 | proptest = "0.9.6" 41 | hex = "0.4.3" 42 | num-bigint = "0.4" 43 | serde_json = "1.0" 44 | -------------------------------------------------------------------------------- /crates/threshold-bls/README.md: -------------------------------------------------------------------------------- 1 | # BLS Threshold CryptoSignatures 2 | 3 | This library provides primitives for (blind) threshold cryptography. Currently supported 4 | curves are BLS12-377 and BLS12-381. 5 | 6 | **Work In Progress: DO NOT EXPECT ANY STABLE API NOW** 7 | 8 | ## Group functionality 9 | 10 | [`src/group.rs`](src/group.rs) contains the definitions of generic trait to work 11 | with scalars of prime fields and points on elliptic curves. The following 12 | `Element` trait allows to get a generic implementation of a polynomial with lagrange interpolation for both scalars and points. 13 | ```rust 14 | pub trait Element: Clone + fmt::Display + fmt::Debug + Eq { 15 | /// new MUST return the zero element of the group. 16 | fn new() -> Self; 17 | fn one() -> Self; 18 | fn add(&mut self, s2: &Self); 19 | fn mul(&mut self, mul: &RHS); 20 | fn pick(&mut self, rng: &mut R); 21 | fn zero() -> Self { 22 | Self::new() 23 | } 24 | } 25 | ``` 26 | 27 | There is an implementation of these traits using the curve BLS12-381 in 28 | [`src/bls12381.rs`](src/bls12381.rs). 29 | 30 | ## Polynomial functionality 31 | 32 | [`src/poly.rs`](src/poly.rs) contains the implementation of a polynomial 33 | suitable to be used for secret sharing schemes and the dkg protocol. It can 34 | evaluates shares and interpolate private and public shares to their 35 | corresponding polynomial. 36 | 37 | The following (from the [tests](src/poly.rs#L264)) shows how to interploate 38 | a set of private shares: 39 | 40 | ```rust 41 | use crate::bls12381::Scalar as Sc; 42 | fn interpolation() { 43 | let degree = 4; 44 | let threshold = degree + 1; 45 | let poly = Poly::::new(degree); 46 | let shares = (0..threshold) 47 | .map(|i| poly.eval(i as u64)) 48 | .collect::>>(); 49 | let recovered = Poly::::recover(threshold as usize, shares); 50 | let expected = poly.c[0]; 51 | let computed = recovered.c[0]; 52 | assert_eq!(expected, computed); 53 | } 54 | ``` 55 | 56 | ## Curve Implementations 57 | 58 | Curently there are two curves available, `BLS12 381` and `BLS 377`. By default they are enabled both, but you can select which one you want to use using 59 | the features `bls12_381` and `bls_377`. 60 | 61 | You can use them like this when adding the dependency to your `Cargo.toml` file. 62 | 63 | ```toml 64 | # Only bls12_381 65 | threshold = { version = "0.1", default-features = false, features = ["bls12_381"] } 66 | # Only bls12_377 67 | threshold = { version = "0.1", default-features = false, features = ["bls12_377"] } 68 | # Both 69 | threshold = { version = "0.1" } 70 | ``` 71 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/curve/bls12381.rs: -------------------------------------------------------------------------------- 1 | use crate::group::{self, Element, PairingCurve as PC, Point, Scalar as Sc}; 2 | use ff::{Field, PrimeField}; 3 | use groupy::CurveProjective; 4 | use paired::bls12_381::{Bls12, Fq12, Fr, FrRepr, G1 as PG1, G2 as PG2}; 5 | use paired::Engine; 6 | use rand_core::RngCore; 7 | use std::result::Result; 8 | use thiserror::Error; 9 | 10 | pub type Scalar = Fr; 11 | pub type G1 = PG1; 12 | pub type G2 = PG2; 13 | pub type GT = Fq12; 14 | 15 | #[derive(Debug, Error)] 16 | pub enum BellmanError { 17 | #[error("decoding: invalid length {0}/{1}")] 18 | InvalidLength(usize, usize), 19 | #[error("IO Error: {0}")] 20 | IoError(#[from] std::io::Error), 21 | #[error("Field Decoding Error: {0}")] 22 | PrimeFieldDecodingError(#[from] ff::PrimeFieldDecodingError), 23 | #[error("Group Decoding Error: {0}")] 24 | GroupDecodingError(#[from] groupy::GroupDecodingError), 25 | } 26 | 27 | impl Element for Scalar { 28 | type RHS = Fr; 29 | 30 | fn new() -> Self { 31 | ff::Field::zero() 32 | } 33 | 34 | fn one() -> Self { 35 | ff::Field::one() 36 | } 37 | fn add(&mut self, s2: &Self) { 38 | self.add_assign(s2); 39 | } 40 | fn mul(&mut self, mul: &Fr) { 41 | self.mul_assign(mul) 42 | } 43 | fn rand(rng: &mut R) -> Self { 44 | Fr::random(rng) 45 | } 46 | } 47 | 48 | /// Implementation of Scalar using field elements used in BLS12-381 49 | impl Sc for Scalar { 50 | fn set_int(&mut self, i: u64) { 51 | *self = Fr::from_repr(FrRepr::from(i)).unwrap(); 52 | } 53 | 54 | fn inverse(&self) -> Option { 55 | ff::Field::inverse(self) 56 | } 57 | 58 | fn negate(&mut self) { 59 | ff::Field::negate(self); 60 | } 61 | 62 | fn sub(&mut self, other: &Self) { 63 | self.sub_assign(other); 64 | } 65 | } 66 | 67 | /// G1 points can be multiplied by Fr elements 68 | impl Element for G1 { 69 | type RHS = Scalar; 70 | 71 | fn new() -> Self { 72 | groupy::CurveProjective::zero() 73 | } 74 | 75 | fn one() -> Self { 76 | groupy::CurveProjective::one() 77 | } 78 | 79 | fn rand(rng: &mut R) -> Self { 80 | G1::random(rng) 81 | } 82 | 83 | fn add(&mut self, s2: &Self) { 84 | self.add_assign(s2); 85 | } 86 | 87 | fn mul(&mut self, mul: &Scalar) { 88 | self.mul_assign(FrRepr::from(*mul)) 89 | } 90 | } 91 | 92 | impl Element for G2 { 93 | type RHS = Scalar; 94 | 95 | fn new() -> Self { 96 | groupy::CurveProjective::zero() 97 | } 98 | 99 | fn one() -> Self { 100 | groupy::CurveProjective::one() 101 | } 102 | 103 | fn rand(rng: &mut R) -> Self { 104 | G2::random(rng) 105 | } 106 | 107 | fn add(&mut self, s2: &Self) { 108 | self.add_assign(s2); 109 | } 110 | 111 | fn mul(&mut self, mul: &Scalar) { 112 | self.mul_assign(FrRepr::from(*mul)) 113 | } 114 | } 115 | 116 | /// Implementation of Point using G1 from BLS12-381 117 | impl Point for G1 { 118 | type Error = (); 119 | 120 | fn map(&mut self, data: &[u8]) -> Result<(), ()> { 121 | *self = G1::hash(data); 122 | Ok(()) 123 | } 124 | } 125 | 126 | /// Implementation of Point using G2 from BLS12-381 127 | impl Point for G2 { 128 | type Error = (); 129 | 130 | fn map(&mut self, data: &[u8]) -> Result<(), ()> { 131 | *self = G2::hash(data); 132 | Ok(()) 133 | } 134 | } 135 | 136 | impl Element for GT { 137 | type RHS = GT; 138 | 139 | fn new() -> Self { 140 | ff::Field::zero() 141 | } 142 | 143 | fn one() -> Self { 144 | ff::Field::one() 145 | } 146 | fn add(&mut self, s2: &Self) { 147 | self.add_assign(s2); 148 | } 149 | fn mul(&mut self, mul: >) { 150 | self.mul_assign(mul) 151 | } 152 | 153 | fn rand(rng: &mut R) -> Self { 154 | ff::Field::random(rng) 155 | } 156 | } 157 | 158 | /// alias to BLS12-381's G1 group 159 | pub type Curve = group::G1Curve; 160 | 161 | /// alias to BLS12-381's G2 Group 162 | pub type G2Curve = group::G2Curve; 163 | 164 | #[derive(Clone, Debug)] 165 | pub struct PairingCurve; 166 | 167 | impl PC for PairingCurve { 168 | type Scalar = Scalar; 169 | 170 | type G1 = G1; 171 | 172 | type G2 = G2; 173 | 174 | type GT = Fq12; 175 | 176 | fn pair(a: &Self::G1, b: &Self::G2) -> Self::GT { 177 | Bls12::pairing(a.into_affine(), b.into_affine()) 178 | } 179 | } 180 | 181 | #[cfg(test)] 182 | mod tests { 183 | use super::*; 184 | use rand::prelude::*; 185 | 186 | use serde::{de::DeserializeOwned, Serialize}; 187 | use static_assertions::assert_impl_all; 188 | 189 | assert_impl_all!(G1: Serialize, DeserializeOwned, Clone); 190 | assert_impl_all!(G2: Serialize, DeserializeOwned, Clone); 191 | assert_impl_all!(GT: Serialize, DeserializeOwned, Clone); 192 | assert_impl_all!(Scalar: Serialize, DeserializeOwned, Clone); 193 | 194 | // test if the element trait is usable 195 | fn add_two>(e1: &mut T, e2: &T) { 196 | e1.add(e2); 197 | e1.mul(e2); 198 | } 199 | 200 | #[test] 201 | fn basic_group() { 202 | let s = Scalar::rand(&mut thread_rng()); 203 | let mut e1 = s; 204 | let e2 = s; 205 | let mut s2 = s; 206 | s2.add(&s); 207 | s2.mul(&s); 208 | add_two(&mut e1, &e2); 209 | // p1 = s2 * G = (s+s)G 210 | let mut p1 = G1::new(); 211 | p1.mul(&s2); 212 | // p2 = sG + sG = s2 * G 213 | let mut p2 = G1::new(); 214 | p2.mul(&s); 215 | p2.add(&p2.clone()); 216 | assert_eq!(p1, p2); 217 | 218 | let mut ii = Scalar::new(); 219 | ii.set_int(4); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/curve/mod.rs: -------------------------------------------------------------------------------- 1 | /// Wrappers around the BLS12-381 curve from the [paired](http://docs.rs/paired) crate 2 | #[cfg(feature = "bls12_381")] 3 | pub mod bls12381; 4 | 5 | /// Wrappers around the BLS12-377 curve from [zexe](https://github.com/scipr-lab/zexe/tree/master/algebra/src/bls12_377) 6 | #[cfg(feature = "bls12_377")] 7 | pub mod zexe; 8 | 9 | use thiserror::Error; 10 | 11 | /// Error which unifies all curve specific errors from different libraries 12 | #[derive(Debug, Error)] 13 | pub enum CurveError { 14 | #[cfg(feature = "bls12_377")] 15 | #[error("Zexe Error: {0}")] 16 | BLS12_377(zexe::ZexeError), 17 | 18 | #[cfg(feature = "bls12_381")] 19 | #[error("Bellman Error: {0}")] 20 | BLS12_381(bls12381::BellmanError), 21 | } 22 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/curve/zexe.rs: -------------------------------------------------------------------------------- 1 | use crate::group::{self, Element, PairingCurve as PC, Point, Scalar as Sc}; 2 | use algebra::{ 3 | bls12_377 as zexe, 4 | curves::{AffineCurve, PairingEngine, ProjectiveCurve}, 5 | fields::Field, 6 | prelude::{One, UniformRand, Zero}, 7 | CanonicalDeserialize, CanonicalSerialize, ConstantSerializedSize, 8 | }; 9 | use bls_crypto::{ 10 | hash_to_curve::{try_and_increment::TryAndIncrement, HashToCurve}, 11 | hashers::DirectHasher, 12 | BLSError, SIG_DOMAIN, 13 | }; 14 | use rand_core::RngCore; 15 | use serde::{ 16 | de::{Error as DeserializeError, SeqAccess, Visitor}, 17 | ser::{Error as SerializationError, SerializeTuple}, 18 | Deserialize, Deserializer, Serialize, Serializer, 19 | }; 20 | use std::{ 21 | fmt, 22 | marker::PhantomData, 23 | ops::{AddAssign, MulAssign, Neg, SubAssign}, 24 | }; 25 | 26 | use thiserror::Error; 27 | 28 | #[derive(Debug, Error)] 29 | pub enum ZexeError { 30 | #[error("{0}")] 31 | SerializationError(#[from] algebra::SerializationError), 32 | #[error("{0}")] 33 | BLSError(#[from] BLSError), 34 | } 35 | 36 | // TODO(gakonst): Make this work with any PairingEngine. 37 | 38 | #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] 39 | pub struct Scalar( 40 | #[serde(deserialize_with = "deserialize_field")] 41 | #[serde(serialize_with = "serialize_field")] 42 | ::Fr, 43 | ); 44 | 45 | type ZG1 = ::G1Projective; 46 | 47 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 48 | pub struct G1( 49 | #[serde(deserialize_with = "deserialize_group")] 50 | #[serde(serialize_with = "serialize_group")] 51 | ZG1, 52 | ); 53 | 54 | type ZG2 = ::G2Projective; 55 | 56 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 57 | pub struct G2( 58 | #[serde(deserialize_with = "deserialize_group")] 59 | #[serde(serialize_with = "serialize_group")] 60 | ZG2, 61 | ); 62 | 63 | #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] 64 | pub struct GT( 65 | #[serde(deserialize_with = "deserialize_field")] 66 | #[serde(serialize_with = "serialize_field")] 67 | ::Fqk, 68 | ); 69 | 70 | impl Element for Scalar { 71 | type RHS = Scalar; 72 | 73 | fn new() -> Self { 74 | Self(Zero::zero()) 75 | } 76 | 77 | fn one() -> Self { 78 | Self(One::one()) 79 | } 80 | 81 | fn add(&mut self, s2: &Self) { 82 | self.0.add_assign(s2.0); 83 | } 84 | 85 | fn mul(&mut self, mul: &Scalar) { 86 | self.0.mul_assign(mul.0) 87 | } 88 | 89 | fn rand(rng: &mut R) -> Self { 90 | Self(zexe::Fr::rand(rng)) 91 | } 92 | } 93 | 94 | impl Sc for Scalar { 95 | fn set_int(&mut self, i: u64) { 96 | *self = Self(zexe::Fr::from(i)) 97 | } 98 | 99 | fn inverse(&self) -> Option { 100 | Some(Self(Field::inverse(&self.0)?)) 101 | } 102 | 103 | fn negate(&mut self) { 104 | *self = Self(self.0.neg()) 105 | } 106 | 107 | fn sub(&mut self, other: &Self) { 108 | self.0.sub_assign(other.0); 109 | } 110 | } 111 | 112 | impl fmt::Display for Scalar { 113 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 114 | write!(f, "{{{:?}}}", self.0) 115 | } 116 | } 117 | 118 | /// G1 points can be multiplied by Fr elements 119 | impl Element for G1 { 120 | type RHS = Scalar; 121 | 122 | fn new() -> Self { 123 | Self(Zero::zero()) 124 | } 125 | 126 | fn one() -> Self { 127 | Self(ZG1::prime_subgroup_generator()) 128 | } 129 | 130 | fn rand(rng: &mut R) -> Self { 131 | Self(ZG1::rand(rng)) 132 | } 133 | 134 | fn add(&mut self, s2: &Self) { 135 | self.0.add_assign(s2.0); 136 | } 137 | 138 | fn mul(&mut self, mul: &Scalar) { 139 | self.0.mul_assign(mul.0) 140 | } 141 | } 142 | 143 | /// Implementation of Point using G1 from BLS12-377 144 | impl Point for G1 { 145 | type Error = ZexeError; 146 | 147 | fn map(&mut self, data: &[u8]) -> Result<(), ZexeError> { 148 | let hasher = TryAndIncrement::new(&DirectHasher); 149 | 150 | let hash = hasher.hash(SIG_DOMAIN, data, &[])?; 151 | 152 | *self = Self(hash); 153 | 154 | Ok(()) 155 | } 156 | } 157 | 158 | impl fmt::Display for G1 { 159 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 160 | write!(f, "{{{:?}}}", self.0) 161 | } 162 | } 163 | 164 | /// G1 points can be multiplied by Fr elements 165 | impl Element for G2 { 166 | type RHS = Scalar; 167 | 168 | fn new() -> Self { 169 | Self(Zero::zero()) 170 | } 171 | 172 | fn one() -> Self { 173 | Self(ZG2::prime_subgroup_generator()) 174 | } 175 | 176 | fn rand(mut rng: &mut R) -> Self { 177 | Self(ZG2::rand(&mut rng)) 178 | } 179 | 180 | fn add(&mut self, s2: &Self) { 181 | self.0.add_assign(s2.0); 182 | } 183 | 184 | fn mul(&mut self, mul: &Scalar) { 185 | self.0.mul_assign(mul.0) 186 | } 187 | } 188 | 189 | /// Implementation of Point using G2 from BLS12-377 190 | impl Point for G2 { 191 | type Error = ZexeError; 192 | 193 | fn map(&mut self, data: &[u8]) -> Result<(), ZexeError> { 194 | let hasher = TryAndIncrement::new(&DirectHasher); 195 | 196 | let hash = hasher.hash(SIG_DOMAIN, data, &[])?; 197 | *self = Self(hash); 198 | 199 | Ok(()) 200 | } 201 | } 202 | 203 | impl fmt::Display for G2 { 204 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 205 | write!(f, "{{{:?}}}", self.0) 206 | } 207 | } 208 | 209 | impl Element for GT { 210 | type RHS = GT; 211 | 212 | fn new() -> Self { 213 | Self(Zero::zero()) 214 | } 215 | fn one() -> Self { 216 | Self(One::one()) 217 | } 218 | fn add(&mut self, s2: &Self) { 219 | self.0.add_assign(s2.0); 220 | } 221 | fn mul(&mut self, mul: >) { 222 | self.0.mul_assign(mul.0) 223 | } 224 | fn rand(rng: &mut R) -> Self { 225 | Self(zexe::Fq12::rand(rng)) 226 | } 227 | } 228 | 229 | impl fmt::Display for GT { 230 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 231 | write!(f, "{{{:?}}}", self.0) 232 | } 233 | } 234 | 235 | pub type G1Curve = group::G1Curve; 236 | pub type G2Curve = group::G2Curve; 237 | 238 | #[derive(Clone, Debug)] 239 | pub struct PairingCurve; 240 | 241 | impl PC for PairingCurve { 242 | type Scalar = Scalar; 243 | type G1 = G1; 244 | type G2 = G2; 245 | type GT = GT; 246 | 247 | fn pair(a: &Self::G1, b: &Self::G2) -> Self::GT { 248 | GT(::pairing(a.0, b.0)) 249 | } 250 | } 251 | 252 | // Serde implementations (ideally, these should be upstreamed to Zexe) 253 | 254 | fn deserialize_field<'de, D, C>(deserializer: D) -> Result 255 | where 256 | D: Deserializer<'de>, 257 | C: CanonicalDeserialize + ConstantSerializedSize, 258 | { 259 | struct FieldVisitor(PhantomData); 260 | 261 | impl<'de, C> Visitor<'de> for FieldVisitor 262 | where 263 | C: CanonicalDeserialize + ConstantSerializedSize, 264 | { 265 | type Value = C; 266 | 267 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 268 | formatter.write_str("a valid group element") 269 | } 270 | 271 | fn visit_seq(self, mut seq: S) -> Result 272 | where 273 | S: SeqAccess<'de>, 274 | { 275 | let len = C::SERIALIZED_SIZE; 276 | let bytes: Vec = (0..len) 277 | .map(|_| { 278 | seq.next_element()? 279 | .ok_or_else(|| DeserializeError::custom("could not read bytes")) 280 | }) 281 | .collect::, _>>()?; 282 | 283 | let res = C::deserialize(&mut &bytes[..]).map_err(DeserializeError::custom)?; 284 | Ok(res) 285 | } 286 | } 287 | 288 | let visitor = FieldVisitor(PhantomData); 289 | deserializer.deserialize_tuple(C::SERIALIZED_SIZE, visitor) 290 | } 291 | 292 | fn serialize_field(c: &C, s: S) -> Result 293 | where 294 | S: Serializer, 295 | C: CanonicalSerialize, 296 | { 297 | let len = c.serialized_size(); 298 | let mut bytes = Vec::with_capacity(len); 299 | c.serialize(&mut bytes) 300 | .map_err(SerializationError::custom)?; 301 | 302 | let mut tup = s.serialize_tuple(len)?; 303 | for byte in &bytes { 304 | tup.serialize_element(byte)?; 305 | } 306 | tup.end() 307 | } 308 | 309 | fn deserialize_group<'de, D, C>(deserializer: D) -> Result 310 | where 311 | D: Deserializer<'de>, 312 | C: ProjectiveCurve, 313 | C::Affine: CanonicalDeserialize + ConstantSerializedSize, 314 | { 315 | struct GroupVisitor(PhantomData); 316 | 317 | impl<'de, C> Visitor<'de> for GroupVisitor 318 | where 319 | C: ProjectiveCurve, 320 | C::Affine: CanonicalDeserialize + ConstantSerializedSize, 321 | { 322 | type Value = C; 323 | 324 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 325 | formatter.write_str("a valid group element") 326 | } 327 | 328 | fn visit_seq(self, mut seq: S) -> Result 329 | where 330 | S: SeqAccess<'de>, 331 | { 332 | let len = C::Affine::SERIALIZED_SIZE; 333 | let bytes: Vec = (0..len) 334 | .map(|_| { 335 | seq.next_element()? 336 | .ok_or_else(|| DeserializeError::custom("could not read bytes")) 337 | }) 338 | .collect::, _>>()?; 339 | 340 | let affine = 341 | C::Affine::deserialize(&mut &bytes[..]).map_err(DeserializeError::custom)?; 342 | Ok(affine.into_projective()) 343 | } 344 | } 345 | 346 | let visitor = GroupVisitor(PhantomData); 347 | deserializer.deserialize_tuple(C::Affine::SERIALIZED_SIZE, visitor) 348 | } 349 | 350 | fn serialize_group(c: &C, s: S) -> Result 351 | where 352 | S: Serializer, 353 | C: ProjectiveCurve, 354 | C::Affine: CanonicalSerialize, 355 | { 356 | let affine = c.into_affine(); 357 | let len = affine.serialized_size(); 358 | let mut bytes = Vec::with_capacity(len); 359 | affine 360 | .serialize(&mut bytes) 361 | .map_err(SerializationError::custom)?; 362 | 363 | let mut tup = s.serialize_tuple(len)?; 364 | for byte in &bytes { 365 | tup.serialize_element(byte)?; 366 | } 367 | tup.end() 368 | } 369 | 370 | #[cfg(test)] 371 | mod tests { 372 | use super::*; 373 | use serde::{de::DeserializeOwned, Serialize}; 374 | use static_assertions::assert_impl_all; 375 | 376 | assert_impl_all!(G1: Serialize, DeserializeOwned, Clone); 377 | assert_impl_all!(G2: Serialize, DeserializeOwned, Clone); 378 | assert_impl_all!(GT: Serialize, DeserializeOwned, Clone); 379 | assert_impl_all!(Scalar: Serialize, DeserializeOwned, Clone); 380 | 381 | #[test] 382 | fn serialize_group() { 383 | serialize_group_test::(48); 384 | serialize_group_test::(96); 385 | } 386 | 387 | fn serialize_group_test(size: usize) { 388 | let rng = &mut rand::thread_rng(); 389 | let sig = E::rand(rng); 390 | let ser = bincode::serialize(&sig).unwrap(); 391 | assert_eq!(ser.len(), size); 392 | 393 | let de: E = bincode::deserialize(&ser).unwrap(); 394 | assert_eq!(de, sig); 395 | } 396 | 397 | #[test] 398 | fn serialize_field() { 399 | serialize_field_test::(576); 400 | serialize_field_test::(32); 401 | } 402 | 403 | fn serialize_field_test(size: usize) { 404 | let rng = &mut rand::thread_rng(); 405 | let sig = E::rand(rng); 406 | let ser = bincode::serialize(&sig).unwrap(); 407 | assert_eq!(ser.len(), size); 408 | 409 | let de: E = bincode::deserialize(&ser).unwrap(); 410 | assert_eq!(de, sig); 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/ecies.rs: -------------------------------------------------------------------------------- 1 | //! # ECIES 2 | //! 3 | //! Implements an Elliptic Curve Integrated Encryption Scheme using SHA256 as the Key Derivation 4 | //! Function. 5 | //! 6 | //! # Examples 7 | //! 8 | //! ```rust 9 | //! use threshold_bls::{ 10 | //! ecies::{encrypt, decrypt}, 11 | //! curve::bls12381::G2Curve, 12 | //! group::{Curve, Element} 13 | //! }; 14 | //! 15 | //! let message = b"hello"; 16 | //! let rng = &mut rand::thread_rng(); 17 | //! let secret_key = ::Scalar::rand(rng); 18 | //! let mut public_key = ::Point::one(); 19 | //! public_key.mul(&secret_key); 20 | //! 21 | //! // encrypt the message with the receiver's public key 22 | //! let ciphertext = encrypt::(&public_key, &message[..], rng); 23 | //! 24 | //! // the receiver can then decrypt the ciphertext with their secret key 25 | //! let cleartext = decrypt(&secret_key, &ciphertext).unwrap(); 26 | //! 27 | //! assert_eq!(&message[..], &cleartext[..]); 28 | //! ``` 29 | 30 | use crate::group::{Curve, Element}; 31 | use rand_core::RngCore; 32 | use serde::{Deserialize, Serialize}; 33 | 34 | // crypto imports 35 | use chacha20poly1305::{ 36 | aead::{Aead, Error as AError, NewAead}, 37 | ChaCha20Poly1305, 38 | }; 39 | use hkdf::Hkdf; 40 | use sha2::Sha256; 41 | 42 | // Re-export error type 43 | pub use chacha20poly1305::aead::Error as EciesError; 44 | 45 | /// The nonce length 46 | const NONCE_LEN: usize = 12; 47 | 48 | /// The ephemeral key length 49 | const KEY_LEN: usize = 32; 50 | 51 | /// A domain separator 52 | const DOMAIN: [u8; 4] = [1, 9, 6, 9]; 53 | 54 | /// An ECIES encrypted cipher. Contains the ciphertext's bytes as well as the 55 | /// ephemeral public key 56 | #[derive(Debug, Clone, Serialize, Deserialize)] 57 | pub struct EciesCipher { 58 | /// The ciphertext which was encrypted 59 | aead: Vec, 60 | /// The ephemeral public key corresponding to the scalar which was used to 61 | /// encrypt the plaintext 62 | ephemeral: C::Point, 63 | /// The nonce used to encrypt the ciphertext 64 | nonce: [u8; NONCE_LEN], 65 | } 66 | 67 | /// Encrypts the message with a public key (curve point) and returns a ciphertext 68 | pub fn encrypt(to: &C::Point, msg: &[u8], rng: &mut R) -> EciesCipher { 69 | let eph_secret = C::Scalar::rand(rng); 70 | 71 | let mut ephemeral = C::Point::one(); 72 | ephemeral.mul(&eph_secret); 73 | 74 | // dh = eph(yG) = eph * public 75 | let mut dh = to.clone(); 76 | dh.mul(&eph_secret); 77 | 78 | // derive an ephemeral key from the public key 79 | let ephemeral_key = derive::(&dh); 80 | 81 | // instantiate the AEAD scheme 82 | let aead = ChaCha20Poly1305::new(ephemeral_key.into()); 83 | 84 | // generate a random nonce 85 | let mut nonce: [u8; NONCE_LEN] = [0u8; NONCE_LEN]; 86 | rng.fill_bytes(&mut nonce); 87 | 88 | // do the encryption 89 | let aead = aead 90 | .encrypt(&nonce.into(), &*msg) 91 | .expect("aead should not fail"); 92 | 93 | EciesCipher { 94 | aead, 95 | nonce, 96 | ephemeral, 97 | } 98 | } 99 | 100 | /// Decrypts the message with a secret key (curve scalar) and returns the cleartext 101 | pub fn decrypt(private: &C::Scalar, cipher: &EciesCipher) -> Result, AError> { 102 | // dh = private * (eph * G) = private * ephPublic 103 | let mut dh = cipher.ephemeral.clone(); 104 | dh.mul(private); 105 | 106 | let ephemeral_key = derive::(&dh); 107 | 108 | let aead = ChaCha20Poly1305::new((ephemeral_key).into()); 109 | 110 | aead.decrypt(&cipher.nonce.into(), &cipher.aead[..]) 111 | } 112 | 113 | /// Derives an ephemeral key from the provided public key 114 | fn derive(dh: &C::Point) -> [u8; KEY_LEN] { 115 | let serialized = bincode::serialize(dh).expect("could not serialize element"); 116 | 117 | // no salt is fine since we use ephemeral - static DH 118 | let h = Hkdf::::new(None, &serialized); 119 | let mut ephemeral_key = [0u8; KEY_LEN]; 120 | h.expand(&DOMAIN, &mut ephemeral_key) 121 | .expect("hkdf should not fail"); 122 | 123 | debug_assert!(ephemeral_key.len() == KEY_LEN); 124 | 125 | ephemeral_key 126 | } 127 | 128 | #[cfg(feature = "bls12_381")] 129 | #[cfg(test)] 130 | mod tests { 131 | use super::*; 132 | use crate::curve::bls12381::{Curve, Scalar, G1}; 133 | use rand::thread_rng; 134 | 135 | fn kp() -> (Scalar, G1) { 136 | let secret = Scalar::rand(&mut thread_rng()); 137 | let mut public = G1::one(); 138 | public.mul(&secret); 139 | (secret, public) 140 | } 141 | 142 | #[test] 143 | fn test_decryption() { 144 | let (s1, _) = kp(); 145 | let (s2, p2) = kp(); 146 | let data = vec![1, 2, 3, 4]; 147 | 148 | // decryption with the right key OK 149 | let mut cipher = encrypt::(&p2, &data, &mut thread_rng()); 150 | let deciphered = decrypt::(&s2, &cipher).unwrap(); 151 | assert_eq!(data, deciphered); 152 | 153 | // decrypting with wrong private key should fail 154 | decrypt::(&s1, &cipher).unwrap_err(); 155 | 156 | // having an invalid ciphertext should fail 157 | cipher.aead = vec![0; 32]; 158 | decrypt::(&s2, &cipher).unwrap_err(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/group.rs: -------------------------------------------------------------------------------- 1 | //! Traits for operating on Groups and Elliptic Curves. 2 | 3 | use rand_core::RngCore; 4 | use serde::{Deserialize, Serialize}; 5 | use std::fmt::{Debug, Display}; 6 | use std::marker::PhantomData; 7 | 8 | /// Element represents an element of a group with the additive notation 9 | /// which is also equipped with a multiplication transformation. 10 | /// Two implementations are for Scalar which forms a ring so RHS is the same 11 | /// and Point which can be multiplied by a scalar of its prime field. 12 | pub trait Element: 13 | Clone + Display + Debug + Eq + Serialize + for<'a> Deserialize<'a> + PartialEq + Send + Sync 14 | { 15 | /// The right-hand-side argument for multiplication 16 | type RHS; 17 | 18 | /// Returns the zero element of the group 19 | fn new() -> Self; 20 | 21 | /// Returns the one element of the group 22 | fn one() -> Self; 23 | 24 | /// Adds the RHS element to the LHS element in place 25 | fn add(&mut self, s2: &Self); 26 | 27 | /// Multiplies the LHS element by the RHS element in place 28 | fn mul(&mut self, mul: &Self::RHS); 29 | 30 | /// Samples a random element using the provided RNG 31 | fn rand(rng: &mut R) -> Self; 32 | 33 | /// Returns the zero element of the group 34 | fn zero() -> Self { 35 | Self::new() 36 | } 37 | } 38 | 39 | /// Scalar can be multiplied by only a Scalar, no other elements. 40 | pub trait Scalar: Element { 41 | fn set_int(&mut self, i: u64); 42 | fn inverse(&self) -> Option; 43 | fn negate(&mut self); 44 | fn sub(&mut self, other: &Self); 45 | // TODO 46 | } 47 | 48 | /// Basic point functionality that can be multiplied by a scalar 49 | pub trait Point: Element { 50 | /// Error which may occur while mapping to the group 51 | type Error: Debug; 52 | 53 | /// Maps the provided data to a group element 54 | fn map(&mut self, data: &[u8]) -> Result<(), ::Error>; 55 | } 56 | 57 | /// A group holds functionalities to create scalar and points related; it is 58 | /// similar to the Engine definition, just much more simpler. 59 | pub trait Curve: Clone + Debug + Send + Sync { 60 | /// The curve's scalar 61 | type Scalar: Scalar; 62 | 63 | /// The curve's point 64 | type Point: Point; 65 | 66 | /// scalar returns the identity element of the field. 67 | fn scalar() -> Self::Scalar { 68 | Self::Scalar::new() 69 | } 70 | 71 | /// point returns the default additive generator of the group. 72 | fn point() -> Self::Point { 73 | Self::Point::one() 74 | } 75 | } 76 | 77 | /// A curve equipped with a bilinear pairing operation. 78 | pub trait PairingCurve: Debug { 79 | type Scalar: Scalar; 80 | 81 | type G1: Point; 82 | 83 | type G2: Point; 84 | 85 | type GT: Element; 86 | 87 | /// Perfors a pairing operation between the 2 group elements 88 | fn pair(a: &Self::G1, b: &Self::G2) -> Self::GT; 89 | } 90 | 91 | #[derive(Debug, Clone, PartialEq)] 92 | /// Helper which binds together a scalar with a group type to form a curve 93 | pub struct CurveFrom { 94 | s: PhantomData, 95 | p: PhantomData

, 96 | } 97 | 98 | impl Curve for CurveFrom 99 | where 100 | S: Scalar, 101 | P: Point, 102 | { 103 | type Scalar = S; 104 | type Point = P; 105 | } 106 | 107 | pub(super) type G1Curve = CurveFrom<::Scalar, ::G1>; 108 | pub(super) type G2Curve = CurveFrom<::Scalar, ::G2>; 109 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Threshold BLS Signatures 2 | //! 3 | //! This crate provides implementations for BLS signatures on G1 and G2, with additional support 4 | //! for blind and threshold signing modes. 5 | //! 6 | //! ## Normal BLS Signatures 7 | //! 8 | //! ```rust 9 | //! // import the instantiated scheme and the traits for signing and generating keys 10 | //! use threshold_bls::{ 11 | //! schemes::bls12_381::G1Scheme as SigScheme, 12 | //! sig::{Scheme, SignatureScheme} 13 | //! }; 14 | //! 15 | //! let (private, public) = SigScheme::keypair(&mut rand::thread_rng()); 16 | //! let msg = b"hello"; 17 | //! let sig = SigScheme::sign(&private, &msg[..]).unwrap(); 18 | //! SigScheme::verify(&public, &msg[..], &sig).expect("signature should be verified"); 19 | //! ``` 20 | //! 21 | //! ## Blind Signatures 22 | //! 23 | //! Blind signatures are supported via an implementation based on this 24 | //! [paper](https://eprint.iacr.org/2018/733.pdf). 25 | //! 26 | //! The procedure is the same, but we import the [`SignatureSchemeExt`] because it requires 27 | //! signing the blinded message without hashing it. Note that verification is done in the same 28 | //! way as before on the unblinded signature and message. 29 | //! 30 | //! ```rust 31 | //! // import the instantiated scheme and the traits for signing and generating keys 32 | //! use threshold_bls::{ 33 | //! schemes::bls12_381::G1Scheme as SigScheme, 34 | //! sig::{Scheme, SignatureScheme, BlindScheme} 35 | //! }; 36 | //! 37 | //! let (private, public) = SigScheme::keypair(&mut rand::thread_rng()); 38 | //! let msg = b"hello"; 39 | //! 40 | //! // the blinding factor needs to be saved for unblinding later 41 | //! let (blinding_factor, blinded) = SigScheme::blind_msg(&msg[..], &mut rand::thread_rng()); 42 | //! 43 | //! // sign the blinded message 44 | //! let blinded_sig = SigScheme::blind_sign(&private, &blinded).unwrap(); 45 | //! // verify the blinded signature with the blinded message. This can be done 46 | //! // by any third party given the blinded signature & message, since they are 47 | //! // not private. 48 | //! SigScheme::blind_verify(&public, &blinded, &blinded_sig).expect("blinded signature should verify"); 49 | //! 50 | //! // unblind the signature 51 | //! let clear_sig = SigScheme::unblind_sig(&blinding_factor, &blinded_sig).expect("unblind should not fail"); 52 | //! 53 | //! SigScheme::verify(&public, &msg[..], &clear_sig).expect("signature should be verified"); 54 | //! ``` 55 | //! 56 | //! ## Threshold Signatures 57 | //! 58 | //! First a threshold keypair must be generated. This is done utilizing [polynomials](poly). 59 | //! Each share then proceeds to sign the message, to generate a partial signature. Once enough 60 | //! partial signatures are produced, they can be combined to a threshold signature, which can be 61 | //! verified against the threshold public key. Each partial signature can also be individually partially 62 | //! verified against the public polynomial. 63 | //! 64 | //! ```rust 65 | //! use threshold_bls::{ 66 | //! poly::{Poly, Idx}, 67 | //! schemes::bls12_381::G1Scheme as SigScheme, 68 | //! sig::{Scheme, SignatureScheme, ThresholdScheme, Share} 69 | //! }; 70 | //! 71 | //! let (n, t) = (5, 3); 72 | //! // create the private key polynomial 73 | //! let private_poly = Poly::<::Private>::new(t - 1); 74 | //! 75 | //! // Evaluate it at `n` points to generate the shares 76 | //! let shares = (0..n) 77 | //! .map(|i| { 78 | //! let eval = private_poly.eval(i as Idx); 79 | //! Share { 80 | //! index: eval.index, 81 | //! private: eval.value, 82 | //! } 83 | //! }) 84 | //! .collect::>(); 85 | //! 86 | //! // Get the public polynomial 87 | //! let public_poly = private_poly.commit(); 88 | //! let threshold_public_key = public_poly.public_key(); 89 | //! 90 | //! // Generate the partial signatures 91 | //! let msg = b"hello"; 92 | //! 93 | //! let partials = shares 94 | //! .iter() 95 | //! .map(|s| SigScheme::partial_sign(s, &msg[..]).unwrap()) 96 | //! .collect::>(); 97 | //! 98 | //! // each partial sig can be partially verified against the public polynomial 99 | //! partials.iter().for_each(|partial| { 100 | //! SigScheme::partial_verify(&public_poly, &msg[..], &partial).unwrap(); 101 | //! }); 102 | //! 103 | //! // generate the threshold sig 104 | //! let threshold_sig = SigScheme::aggregate(t, &partials).unwrap(); 105 | //! 106 | //! SigScheme::verify( 107 | //! &threshold_public_key, 108 | //! &msg[..], 109 | //! &threshold_sig 110 | //! ).unwrap(); 111 | //! ``` 112 | //! 113 | //! 114 | //! 115 | //! # Misc. Notes 116 | //! 117 | //! ### Supporting a new curve 118 | //! 119 | //! Curves are implemented in the [`curve`] module. In order to support a new curve, 120 | //! the trait [`PairingCurve`] must be implemented for it. This in turn requires that 121 | //! you define the pairing-friendly curve's `Scalar` and `G_T` fields, its 122 | //! G1 and G2 groups and implement the `Scalar`, `Element` and `Point` traits for them. 123 | //! For reference, use the [existing implementation of BLS12-377](bls12_377) which wraps the implementation 124 | //! from [Zexe](https://github.com/arkworks-rs/snark/). 125 | //! 126 | //! ### Switching Groups 127 | //! 128 | //! `G1Scheme` can be drop-in replaced with `G2Scheme` (and vice-versa) depending on which group you 129 | //! want keys and signatures to be in. 130 | //! 131 | //! Before: 132 | //! 133 | //! ```rust 134 | //! use threshold_bls::sig::G1Scheme as SigScheme; 135 | //! ``` 136 | //! 137 | //! After: 138 | //! 139 | //! ```rust 140 | //! use threshold_bls::sig::G2Scheme as SigScheme; 141 | //! ``` 142 | //! 143 | //! ## Features 144 | //! 145 | //! Curently there are two curves available, `BLS12 381` and `BLS 377`. By default they are both 146 | //! enabled both, but you can select which one you want to use using the features 147 | //! `bls12_381` and `bls_377`. 148 | //! 149 | //! You can use them like this when adding the dependency to your `Cargo.toml` file. 150 | //! 151 | //! Only BLS12-381: 152 | //! 153 | //! ```toml 154 | //! threshold-bls = { version = "0.1", default-features = false, features = ["bls12_381"] } 155 | //! ``` 156 | //! 157 | //! Only BLS12-377: 158 | //! 159 | //! ```toml 160 | //! threshold-bls = { version = "0.1", default-features = false, features = ["bls12_377"] } 161 | //! ``` 162 | //! 163 | //! Both: 164 | //! 165 | //! ```toml 166 | //! threshold-bls = { version = "0.1" } 167 | //! ``` 168 | //! 169 | //! [poly]: ./poly/index.html 170 | //! [bls12_377]: ./curve/zexe/index.html 171 | //! 172 | //! [`curve`]: ./curve/index.html 173 | //! [`SignatureSchemeExt`]: ./sig/trait.SignatureSchemeExt.html 174 | 175 | /// Curve implementations for the traits defined in the [`group`](group/index.html) module. 176 | pub mod curve; 177 | 178 | /// Elliptic Curve Integrated Encryption Scheme using SHA256 as the Key Derivation 179 | pub mod ecies; 180 | 181 | /// Definitions of generic traits with scalars of prime fields and points on elliptic curves. 182 | pub mod group; 183 | 184 | /// Implementation of a polynomial suitable to be used for secret sharing schemes and DKG 185 | /// protocols. It can evaluate and interpolate private and public shares to their corresponding 186 | /// polynomial. 187 | pub mod poly; 188 | 189 | /// BLS Signature implementations. Supports blind and threshold signatures. 190 | pub mod sig; 191 | 192 | /// Pre-instantiated signature schemes for each curve 193 | pub mod schemes { 194 | use crate::sig::{G1Scheme, G2Scheme}; 195 | 196 | #[cfg(feature = "bls12_381")] 197 | /// BLS12-381 Schemes 198 | pub mod bls12_381 { 199 | use crate::curve::bls12381::PairingCurve; 200 | 201 | pub use crate::curve::bls12381::{Curve as G1Curve, G2Curve}; 202 | 203 | /// Public Keys on G1, Signatures on G2 204 | pub type G1Scheme = super::G1Scheme; 205 | /// Public Keys on G2, Signatures on G1 206 | pub type G2Scheme = super::G2Scheme; 207 | } 208 | 209 | #[cfg(feature = "bls12_377")] 210 | /// BLS12-377 Schemes 211 | pub mod bls12_377 { 212 | use crate::curve::zexe::PairingCurve; 213 | pub use crate::curve::zexe::{G1Curve, G2Curve}; 214 | 215 | /// Public Keys on G1, Signatures on G2 216 | pub type G1Scheme = super::G1Scheme; 217 | /// Public Keys on G2, Signatures on G1 218 | pub type G2Scheme = super::G2Scheme; 219 | } 220 | } 221 | 222 | #[cfg(test)] 223 | mod test_vectors; 224 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/poly.rs: -------------------------------------------------------------------------------- 1 | use crate::group::{Curve, Element, Point, Scalar}; 2 | use rand_core::RngCore; 3 | use serde::{Deserialize, Serialize}; 4 | use std::{collections::BTreeMap, fmt}; 5 | use thiserror::Error; 6 | 7 | pub type PrivatePoly = Poly<::Scalar>; 8 | pub type PublicPoly = Poly<::Point>; 9 | 10 | pub type Idx = u32; 11 | 12 | #[derive(Debug, Clone, Serialize, Deserialize)] 13 | pub struct Eval { 14 | pub value: A, 15 | pub index: Idx, 16 | } 17 | 18 | impl fmt::Display for Eval { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | write!(f, "{{ idx: {}, value: {} }}", self.index, self.value) 21 | } 22 | } 23 | 24 | /// A polynomial that is using a scalar for the variable x and a generic 25 | /// element for the coefficients. The coefficients must be able to multiply 26 | /// the type of the variable, which is always a scalar. 27 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 28 | pub struct Poly(Vec); 29 | 30 | impl Poly { 31 | /// Returns the degree of the polynomial 32 | pub fn degree(&self) -> usize { 33 | // e.g. c_3 * x^3 + c_2 * x^2 + c_1 * x + c_0 34 | // ^ 4 coefficients correspond to a 3rd degree poly 35 | self.0.len() - 1 36 | } 37 | 38 | #[cfg(test)] 39 | /// Returns the number of coefficients 40 | fn len(&self) -> usize { 41 | self.0.len() 42 | } 43 | } 44 | 45 | impl Poly { 46 | /// Returns a new polynomial of the given degree where each coefficients is 47 | /// sampled at random from the given RNG. 48 | /// In the context of secret sharing, the threshold is the degree + 1. 49 | pub fn new_from(degree: usize, rng: &mut R) -> Self { 50 | let coeffs: Vec = (0..=degree).map(|_| C::rand(rng)).collect(); 51 | Self::from(coeffs) 52 | } 53 | 54 | /// get returns the given coefficient at the requested index. It will panic 55 | /// if the index is out of range,i.e. `if i > self.degree()`. 56 | pub fn get(&self, i: Idx) -> C { 57 | self.0[i as usize].clone() 58 | } 59 | 60 | /// set the given element at the specified index. The index 0 is the free 61 | /// coefficient of the polynomial. It panics if the index is out of range. 62 | pub fn set(&mut self, index: usize, value: C) { 63 | self.0[index] = value; 64 | } 65 | 66 | /// Returns a new polynomial of the given degree where each coefficients is 67 | /// sampled at random. 68 | /// 69 | /// In the context of secret sharing, the threshold is the degree + 1. 70 | pub fn new(degree: usize) -> Self { 71 | use rand::prelude::*; 72 | Self::new_from(degree, &mut thread_rng()) 73 | } 74 | 75 | /// Returns a polynomial from the given list of coefficients 76 | // TODO: implement the From<> trait 77 | // TODO fix semantics of zero: 78 | // it should be G1::zero() as only element 79 | pub fn zero() -> Self { 80 | Self::from(vec![C::zero()]) 81 | } 82 | 83 | fn is_zero(&self) -> bool { 84 | self.0.is_empty() || self.0.iter().all(|coeff| coeff == &C::zero()) 85 | } 86 | 87 | /// Performs polynomial addition in place 88 | pub fn add(&mut self, other: &Self) { 89 | // if we have a smaller degree we should pad with zeros 90 | if self.0.len() < other.0.len() { 91 | self.0.resize(other.0.len(), C::zero()) 92 | } 93 | 94 | self.0.iter_mut().zip(&other.0).for_each(|(a, b)| a.add(b)) 95 | } 96 | } 97 | 98 | #[derive(Debug, Error)] 99 | pub enum PolyError { 100 | #[error("Invalid recovery: only has {0}/{1} shares")] 101 | InvalidRecovery(usize, usize), 102 | #[error("Could not invert scalar")] 103 | NoInverse, 104 | } 105 | 106 | impl Poly 107 | where 108 | C: Element, 109 | C::RHS: Scalar, 110 | { 111 | /// Evaluates the polynomial at the specified value. 112 | pub fn eval(&self, i: Idx) -> Eval { 113 | let mut xi = C::RHS::new(); 114 | // +1 because we must never evaluate the polynomial at its first point 115 | // otherwise it reveals the "secret" value ! 116 | // TODO: maybe move that a layer above, to not mix ss scheme with poly. 117 | xi.set_int((i + 1).into()); 118 | 119 | let res = self.0.iter().rev().fold(C::zero(), |mut sum, coeff| { 120 | sum.mul(&xi); 121 | sum.add(coeff); 122 | sum 123 | }); 124 | 125 | Eval { 126 | value: res, 127 | index: i, 128 | } 129 | } 130 | 131 | /// Given at least `t` polynomial evaluations, it will recover the polynomial's 132 | /// constant term 133 | pub fn recover(t: usize, shares: Vec>) -> Result { 134 | let xs = Self::share_map(t, shares)?; 135 | 136 | // iterate over all indices and for each multiply the lagrange basis 137 | // with the value of the share 138 | let mut acc = C::zero(); 139 | for (i, xi) in &xs { 140 | let mut yi = xi.1.clone(); 141 | let mut num = C::RHS::one(); 142 | let mut den = C::RHS::one(); 143 | 144 | for (j, xj) in &xs { 145 | if i == j { 146 | continue; 147 | } 148 | 149 | // xj - 0 150 | num.mul(&xj.0); 151 | 152 | // 1 / (xj - xi) 153 | let mut tmp = xj.0.clone(); 154 | tmp.sub(&xi.0); 155 | den.mul(&tmp); 156 | } 157 | 158 | let inv = den.inverse().ok_or(PolyError::NoInverse)?; 159 | num.mul(&inv); 160 | yi.mul(&num); 161 | acc.add(&yi); 162 | } 163 | 164 | Ok(acc) 165 | } 166 | 167 | /// Given at least `t` polynomial evaluations, it will recover the entire polynomial 168 | pub fn full_recover(t: usize, shares: Vec>) -> Result { 169 | let xs = Self::share_map(t, shares)?; 170 | 171 | // iterate over all indices and for each multiply the lagrange basis 172 | // with the value of the share 173 | let res = xs 174 | .iter() 175 | // get the share and the lagrange basis 176 | .map(|(i, share)| (share, Poly::::lagrange_basis(*i, &xs))) 177 | // get the linear combination poly 178 | .map(|(share, basis)| { 179 | // calculate the linear combination coefficients 180 | let linear_coeffs = basis 181 | .0 182 | .into_iter() 183 | .map(move |c| { 184 | // y_j * L_y 185 | // TODO: Can we avoid allocating here? 186 | let mut s = share.1.clone(); 187 | s.mul(&c); 188 | s 189 | }) 190 | .collect::>(); 191 | 192 | Self::from(linear_coeffs) 193 | }) 194 | .fold(Self::zero(), |mut acc, poly| { 195 | acc.add(&poly); 196 | acc 197 | }); 198 | 199 | Ok(res) 200 | } 201 | 202 | fn share_map( 203 | t: usize, 204 | mut shares: Vec>, 205 | ) -> Result, PolyError> { 206 | if shares.len() < t { 207 | return Err(PolyError::InvalidRecovery(shares.len(), t)); 208 | } 209 | 210 | // first sort the shares as it can happens recovery happens for 211 | // non-correlated shares so the subset chosen becomes important 212 | shares.sort_by(|a, b| a.index.cmp(&b.index)); 213 | 214 | // convert the indexes of the shares into scalars 215 | let xs = shares 216 | .into_iter() 217 | .take(t) 218 | .fold(BTreeMap::new(), |mut m, sh| { 219 | let mut xi = C::RHS::new(); 220 | xi.set_int((sh.index + 1).into()); 221 | m.insert(sh.index, (xi, sh.value)); 222 | m 223 | }); 224 | 225 | debug_assert_eq!(xs.len(), t); 226 | 227 | Ok(xs) 228 | } 229 | 230 | /// Returns the constant term of the polynomial which can be interpreted as 231 | /// the threshold public key 232 | pub fn public_key(&self) -> &C { 233 | &self.0[0] 234 | } 235 | } 236 | 237 | impl From> for Poly { 238 | fn from(c: Vec) -> Self { 239 | Self(c) 240 | } 241 | } 242 | 243 | impl From> for Vec { 244 | fn from(poly: Poly) -> Self { 245 | poly.0 246 | } 247 | } 248 | 249 | impl> Poly { 250 | /// Performs the multiplication operation. 251 | /// 252 | /// Note this is a simple implementation that is suitable for secret sharing schemes, 253 | /// but may be inneficient for other purposes: the degree of the returned polynomial 254 | /// is always the greatest possible, regardless of the actual coefficients 255 | /// given. 256 | // TODO: Implement divide and conquer algorithm 257 | fn mul(&mut self, other: &Self) { 258 | if self.is_zero() || other.is_zero() { 259 | *self = Self::zero(); 260 | return; 261 | } 262 | 263 | let d3 = self.degree() + other.degree(); 264 | 265 | // need to initializes every coeff to zero first 266 | let mut coeffs = (0..=d3).map(|_| X::zero()).collect::>(); 267 | 268 | for (i, c1) in self.0.iter().enumerate() { 269 | for (j, c2) in other.0.iter().enumerate() { 270 | // c_ij += c1 * c2 271 | let mut tmp = X::one(); 272 | tmp.mul(c1); 273 | tmp.mul(c2); 274 | coeffs[i + j].add(&tmp); 275 | } 276 | } 277 | 278 | self.0 = coeffs; 279 | } 280 | 281 | /// Returns the scalar polynomial f(x) = x - c 282 | fn new_neg_constant(mut c: X) -> Poly { 283 | c.negate(); 284 | Poly::from(vec![c, X::one()]) 285 | } 286 | 287 | /// Computes the lagrange basis polynomial of index i 288 | fn lagrange_basis>(i: Idx, xs: &BTreeMap) -> Poly { 289 | let mut basis = Poly::::from(vec![X::one()]); 290 | 291 | // accumulator of the denominator values 292 | let mut acc = X::one(); 293 | 294 | // TODO remove that cloning due to borrowing issue with the map 295 | let xi = xs.get(&i).unwrap().clone().0; 296 | for (idx, sc) in xs.iter() { 297 | if *idx == i { 298 | continue; 299 | } 300 | 301 | // basis * (x - sc) 302 | let minus_sc = Poly::::new_neg_constant(sc.0.clone()); 303 | basis.mul(&minus_sc); 304 | 305 | // den = xi - sc 306 | let mut den = X::zero(); 307 | den.add(&xi); 308 | den.sub(&sc.0); 309 | 310 | // den = 1 / den 311 | den = den.inverse().unwrap(); 312 | 313 | // acc = acc * den 314 | acc.mul(&den); 315 | } 316 | 317 | // multiply all coefficients by the denominator 318 | basis.mul(&Poly::from(vec![acc])); 319 | basis 320 | } 321 | 322 | /// Commits the scalar polynomial to the group and returns a polynomial over 323 | /// the group 324 | /// 325 | /// This is done by multiplying each coefficient of the polynomial with the 326 | /// group's generator. 327 | pub fn commit>(&self) -> Poly

{ 328 | let commits = self 329 | .0 330 | .iter() 331 | .map(|c| { 332 | let mut commitment = P::one(); 333 | commitment.mul(c); 334 | commitment 335 | }) 336 | .collect::>(); 337 | 338 | Poly::

::from(commits) 339 | } 340 | } 341 | 342 | impl fmt::Display for Poly { 343 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 344 | let s = self 345 | .0 346 | .iter() 347 | .enumerate() 348 | .map(|(i, c)| format!("{}: {}", i, c)) 349 | .collect::>() 350 | .join(", "); 351 | write!(f, "[deg: {}, coeffs: [{}]]", self.degree(), s) 352 | } 353 | } 354 | 355 | #[cfg(feature = "bls12_381")] 356 | #[cfg(test)] 357 | pub mod tests { 358 | use super::*; 359 | use crate::curve::bls12381::Scalar as Sc; 360 | use crate::curve::bls12381::G1; 361 | use rand::prelude::*; 362 | 363 | #[test] 364 | fn poly_degree() { 365 | let s = 5; 366 | let p = Poly::::new(s); 367 | assert_eq!(p.0.len(), s + 1); 368 | assert_eq!(p.degree(), s); 369 | } 370 | 371 | #[test] 372 | fn add_zero() { 373 | let p1 = Poly::::new(3); 374 | let p2 = Poly::::zero(); 375 | let mut res = p1.clone(); 376 | res.add(&p2); 377 | assert_eq!(res, p1); 378 | 379 | let p1 = Poly::::zero(); 380 | let p2 = Poly::::new(3); 381 | let mut res = p1; 382 | res.add(&p2); 383 | assert_eq!(res, p2); 384 | } 385 | 386 | #[test] 387 | fn mul_by_zero() { 388 | let p1 = Poly::::new(3); 389 | let p2 = Poly::::zero(); 390 | let mut res = p1; 391 | res.mul(&p2); 392 | assert_eq!(res, Poly::::zero()); 393 | 394 | let p1 = Poly::::zero(); 395 | let p2 = Poly::::new(3); 396 | let mut res = p1; 397 | res.mul(&p2); 398 | assert_eq!(res, Poly::::zero()); 399 | } 400 | 401 | use proptest::prelude::*; 402 | 403 | proptest! { 404 | 405 | // the coefficients up to the smaller polynomial's degree should be summed up, 406 | // after that they should be the same as the largest one 407 | #[test] 408 | fn addition(deg1 in 0..100usize, deg2 in 0..100usize) { 409 | dbg!(deg1, deg2); 410 | let p1 = Poly::::new(deg1); 411 | let p2 = Poly::::new(deg2); 412 | let mut res = p1.clone(); 413 | res.add(&p2); 414 | 415 | let (larger, smaller) = if p1.degree() > p2.degree() { 416 | (&p1, &p2) 417 | } else { 418 | (&p2, &p1) 419 | }; 420 | 421 | for i in 0..larger.len() { 422 | if i < smaller.len() { 423 | let mut coeff_sum = p1.0[i]; 424 | coeff_sum.add(&p2.0[i]); 425 | assert_eq!(res.0[i], coeff_sum); 426 | } else { 427 | // (this code branch will never get hit when p1.length = p2.length) 428 | assert_eq!(res.0[i], larger.0[i]); 429 | } 430 | } 431 | 432 | // the result has the largest degree 433 | assert_eq!(res.degree(), larger.degree()); 434 | } 435 | 436 | 437 | #[test] 438 | fn interpolation(degree in 0..100usize, num_evals in 0..100usize) { 439 | let poly = Poly::::new(degree); 440 | let expected = poly.0[0]; 441 | 442 | let shares = (0..num_evals) 443 | .map(|i| poly.eval(i as Idx)) 444 | .collect::>(); 445 | 446 | let recovered_poly = Poly::::full_recover(num_evals, shares.clone()).unwrap(); 447 | let computed = recovered_poly.0[0]; 448 | 449 | let recovered_constant = Poly::::recover(num_evals, shares).unwrap(); 450 | 451 | // if we had enough evaluations we must get the correct term 452 | if num_evals > degree { 453 | assert_eq!(expected, computed); 454 | assert_eq!(expected, recovered_constant); 455 | } else { 456 | // if there were not enough evaluations, then the call will still succeed 457 | // but will return a mismatching recovered term 458 | assert_ne!(expected, computed); 459 | assert_ne!(expected, recovered_constant); 460 | } 461 | } 462 | 463 | #[test] 464 | fn eval(d in 0..100usize, idx in 0..(100 as Idx)) { 465 | let mut x = Sc::new(); 466 | x.set_int(idx as u64 + 1); 467 | 468 | let p1 = Poly::::new(d); 469 | let evaluation = p1.eval(idx).value; 470 | 471 | // Naively calculate \sum c_i * x^i 472 | let coeffs = p1.0; 473 | let mut sum = coeffs[0]; 474 | for (i, coeff) in coeffs.into_iter().enumerate().take(d + 1).skip(1) { 475 | let xi = pow(x, i); 476 | let mut var = coeff; 477 | var.mul(&xi); 478 | sum.add(&var); 479 | } 480 | 481 | assert_eq!(sum, evaluation); 482 | 483 | // helper to calculate the power of x 484 | fn pow(base: Sc, pow: usize) -> Sc { 485 | let mut res = Sc::one(); 486 | for _ in 0..pow { 487 | res.mul(&base) 488 | } 489 | res 490 | } 491 | } 492 | 493 | } 494 | 495 | #[test] 496 | fn interpolation_insufficient_shares() { 497 | let degree = 4; 498 | let threshold = degree + 1; 499 | let poly = Poly::::new(degree); 500 | 501 | // insufficient shares gathered 502 | let shares = (0..threshold - 1) 503 | .map(|i| poly.eval(i as Idx)) 504 | .collect::>(); 505 | 506 | Poly::::recover(threshold, shares.clone()).unwrap_err(); 507 | Poly::::full_recover(threshold, shares).unwrap_err(); 508 | } 509 | 510 | #[test] 511 | fn benchy() { 512 | use std::time::SystemTime; 513 | let degree = 49; 514 | let threshold = degree + 1; 515 | let poly = Poly::::new(degree); 516 | let shares = (0..threshold) 517 | .map(|i| poly.eval(i as Idx)) 518 | .collect::>>(); 519 | let now = SystemTime::now(); 520 | Poly::::recover(threshold as usize, shares).unwrap(); 521 | match now.elapsed() { 522 | Ok(e) => println!("single recover: time elapsed {:?}", e), 523 | Err(e) => panic!("{}", e), 524 | } 525 | let shares = (0..threshold) 526 | .map(|i| poly.eval(i as Idx)) 527 | .collect::>>(); 528 | 529 | let now = SystemTime::now(); 530 | Poly::::full_recover(threshold as usize, shares).unwrap(); 531 | match now.elapsed() { 532 | Ok(e) => println!("full_recover: time elapsed {:?}", e), 533 | Err(e) => panic!("{}", e), 534 | } 535 | } 536 | 537 | #[test] 538 | fn mul() { 539 | let d = 1; 540 | let p1 = Poly::::new(d); 541 | let p2 = Poly::::new(d); 542 | let mut p3 = p1.clone(); 543 | p3.mul(&p2); 544 | assert_eq!(p3.degree(), d + d); 545 | // f1 = c0 + c1 * x 546 | // f2 = d0 + d1 * x 547 | // l1 l2 l3 548 | // f3 = f1 * f2 = (c0*d0) + (c0*d1 + d0*c1) * x + (c1*d1) * x^2 549 | 550 | // f3(1) = l1 + l2 + l3 551 | let mut l1 = p1.0[0]; 552 | l1.mul(&p2.0[0]); 553 | 554 | // c0 * d1 555 | let mut l21 = p1.0[0]; 556 | l21.mul(&p2.0[1]); 557 | 558 | // d0 * c1 559 | let mut l22 = p1.0[1]; 560 | l22.mul(&p2.0[0]); 561 | let mut l2 = Sc::new(); 562 | l2.add(&l21); 563 | l2.add(&l22); 564 | let mut l3 = p1.0[1]; 565 | l3.mul(&p2.0[1]); 566 | 567 | let mut total = Sc::new(); 568 | total.add(&l1); 569 | total.add(&l2); 570 | total.add(&l3); 571 | let res = p3.eval(0); 572 | assert_eq!(total, res.value); 573 | } 574 | 575 | #[test] 576 | fn new_neg_constant() { 577 | let mut constant = Sc::rand(&mut thread_rng()); 578 | let p = Poly::::new_neg_constant(constant); 579 | 580 | constant.negate(); 581 | let v = vec![constant, Sc::one()]; 582 | let res = Poly::from(v); 583 | 584 | assert_eq!(res, p); 585 | } 586 | 587 | #[test] 588 | fn commit() { 589 | let secret = Poly::::new(5); 590 | 591 | let coeffs = secret.0.clone(); 592 | let commitment = coeffs 593 | .iter() 594 | .map(|coeff| { 595 | let mut p = G1::one(); 596 | p.mul(coeff); 597 | p 598 | }) 599 | .collect::>(); 600 | let commitment = Poly::from(commitment); 601 | 602 | assert_eq!(commitment, secret.commit::()); 603 | } 604 | } 605 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/sig/blind.rs: -------------------------------------------------------------------------------- 1 | use crate::group::{Element, Point, Scalar}; 2 | use crate::sig::bls::{common::BLSScheme, BLSError}; 3 | use crate::sig::{BlindScheme, Scheme}; 4 | use rand::RngCore; 5 | use serde::{Deserialize, Serialize}; 6 | use thiserror::Error; 7 | 8 | /// BlindError are errors which may be returned from a blind signature scheme 9 | #[derive(Debug, Error)] 10 | pub enum BlindError { 11 | /// InvalidToken is thrown out when the token used to unblind can not be 12 | /// inversed. This error should not happen if you use the Token that was 13 | /// returned by the blind operation. 14 | #[error("invalid token")] 15 | InvalidToken, 16 | 17 | /// Raised when (de)serialization fails 18 | #[error("could not deserialize: {0}")] 19 | BincodeError(#[from] bincode::Error), 20 | 21 | #[error("invalid signature verification: {0}")] 22 | SignatureError(#[from] BLSError), 23 | } 24 | 25 | /// Blinding a message before requesting a signature requires the usage of a 26 | /// private blinding factor that is called a Token. To unblind the signature 27 | /// afterwards, one needs the same token as what the blinding method returned. 28 | /// In this blind signature scheme, the token is simply a field element. 29 | #[derive(Clone, Debug, Serialize, Deserialize)] 30 | #[serde(bound = "S: Serialize + serde::de::DeserializeOwned")] 31 | pub struct Token(S); 32 | 33 | impl Default for Token { 34 | fn default() -> Self { 35 | Self::new() 36 | } 37 | } 38 | 39 | impl Token { 40 | /// Instantiates a token with the `Zero` element of the underlying scalar 41 | pub fn new() -> Self { 42 | Self(S::new()) 43 | } 44 | } 45 | 46 | /// The blinder follows the protocol described 47 | /// in this [paper](https://eprint.iacr.org/2018/733.pdf). 48 | impl BlindScheme for I 49 | where 50 | I: Scheme + BLSScheme, 51 | { 52 | type Token = Token; 53 | type Error = BlindError; 54 | 55 | fn blind_msg(msg: &[u8], rng: &mut R) -> (Self::Token, Vec) { 56 | let r = I::Private::rand(rng); 57 | 58 | let mut h = I::Signature::new(); 59 | 60 | // r * H(m) 61 | // XXX result from zexe API but it shouldn't 62 | h.map(msg).expect("could not map to the group"); 63 | h.mul(&r); 64 | 65 | let serialized = bincode::serialize(&h).expect("serialization should not fail"); 66 | (Token(r), serialized) 67 | } 68 | 69 | fn unblind_sig(t: &Self::Token, sigbuff: &[u8]) -> Result, Self::Error> { 70 | let mut sig: I::Signature = bincode::deserialize(sigbuff)?; 71 | 72 | // r^-1 * ( r * H(m)^x) = H(m)^x 73 | let ri = t.0.inverse().ok_or(BlindError::InvalidToken)?; 74 | sig.mul(&ri); 75 | 76 | let serialized = bincode::serialize(&sig)?; 77 | Ok(serialized) 78 | } 79 | 80 | fn blind_verify( 81 | public: &I::Public, 82 | blinded_msg: &[u8], 83 | blinded_sig: &[u8], 84 | ) -> Result<(), Self::Error> { 85 | // message point 86 | let blinded_msg: I::Signature = bincode::deserialize(blinded_msg)?; 87 | // signature point 88 | let blinded_sig: I::Signature = bincode::deserialize(blinded_sig)?; 89 | 90 | if !I::final_exp(public, &blinded_sig, &blinded_msg) { 91 | return Err(BlindError::from(BLSError::InvalidSig)); 92 | } 93 | Ok(()) 94 | } 95 | 96 | fn blind_sign(private: &I::Private, blinded_msg: &[u8]) -> Result, Self::Error> { 97 | // (r * H(m))^x 98 | let mut hm: I::Signature = bincode::deserialize(blinded_msg)?; 99 | hm.mul(private); 100 | Ok(bincode::serialize(&hm)?) 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | #[cfg(feature = "bls12_381")] 106 | mod tests { 107 | use super::*; 108 | use crate::curve::bls12381::PairingCurve as PCurve; 109 | use crate::sig::bls::{G1Scheme, G2Scheme}; 110 | use crate::sig::SignatureScheme; 111 | use rand::thread_rng; 112 | 113 | #[cfg(feature = "bls12_381")] 114 | #[test] 115 | fn blind_g1() { 116 | blind_test::>(); 117 | } 118 | 119 | #[cfg(feature = "bls12_381")] 120 | #[test] 121 | fn blind_g2() { 122 | blind_test::>(); 123 | } 124 | 125 | fn blind_test() 126 | where 127 | B: BlindScheme + SignatureScheme, 128 | { 129 | let (private, public) = B::keypair(&mut thread_rng()); 130 | let msg = vec![1, 9, 6, 9]; 131 | 132 | let (token, blinded) = B::blind_msg(&msg, &mut thread_rng()); 133 | 134 | // signs the blinded message w/o hashing 135 | let blinded_sig = B::blind_sign(&private, &blinded).unwrap(); 136 | B::blind_verify(&public, &blinded, &blinded_sig).unwrap(); 137 | 138 | let clear_sig = B::unblind_sig(&token, &blinded_sig).expect("unblind should go well"); 139 | B::verify(&public, &msg, &clear_sig).unwrap(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/sig/bls.rs: -------------------------------------------------------------------------------- 1 | use crate::group::{Element, PairingCurve, Point}; 2 | use crate::sig::{Scheme, SignatureScheme}; 3 | use std::{fmt::Debug, marker::PhantomData}; 4 | use thiserror::Error; 5 | 6 | /// BLSError are thrown out when using the BLS signature scheme. 7 | #[derive(Debug, Error)] 8 | pub enum BLSError { 9 | /// InvalidSig is raised when the validation routine of the BLS algorithm 10 | /// does not finish successfully,i.e. it is an invalid signature. 11 | #[error("invalid signature")] 12 | InvalidSig, 13 | 14 | #[error("could not hash to curve")] 15 | HashingError, 16 | 17 | #[error("could not deserialize: {0}")] 18 | DeserializationError(#[from] bincode::Error), 19 | } 20 | 21 | // private module workaround to avoid leaking a private 22 | // trait into a public trait 23 | // see https://github.com/rust-lang/rust/issues/34537 24 | // XXX another way to pull it off without this hack? 25 | pub mod common { 26 | use super::*; 27 | 28 | /// BLSScheme is an internal trait that encompasses the common work between a 29 | /// BLS signature over G1 or G2. 30 | pub trait BLSScheme: Scheme { 31 | /// Returns sig = msg^{private}. The message MUST be hashed before this call. 32 | fn internal_sign( 33 | private: &Self::Private, 34 | msg: &[u8], 35 | should_hash: bool, 36 | ) -> Result, BLSError> { 37 | let mut h = if should_hash { 38 | let mut h = Self::Signature::new(); 39 | h.map(msg).map_err(|_| BLSError::HashingError)?; 40 | h 41 | } else { 42 | bincode::deserialize_from(msg)? 43 | }; 44 | 45 | h.mul(private); 46 | 47 | let serialized = bincode::serialize(&h)?; 48 | Ok(serialized) 49 | } 50 | 51 | fn internal_verify( 52 | public: &Self::Public, 53 | msg: &[u8], 54 | sig_bytes: &[u8], 55 | should_hash: bool, 56 | ) -> Result<(), BLSError> { 57 | let sig: Self::Signature = bincode::deserialize_from(sig_bytes)?; 58 | 59 | let h = if should_hash { 60 | let mut h = Self::Signature::new(); 61 | h.map(msg).map_err(|_| BLSError::HashingError)?; 62 | h 63 | } else { 64 | bincode::deserialize_from(msg)? 65 | }; 66 | 67 | let success = Self::final_exp(public, &sig, &h); 68 | if !success { 69 | return Err(BLSError::InvalidSig); 70 | } 71 | 72 | Ok(()) 73 | } 74 | 75 | /// Performs the final exponentiation for the BLS sig scheme 76 | fn final_exp(p: &Self::Public, sig: &Self::Signature, hm: &Self::Signature) -> bool; 77 | } 78 | 79 | impl SignatureScheme for T 80 | where 81 | T: BLSScheme, 82 | { 83 | type Error = BLSError; 84 | 85 | fn sign(private: &Self::Private, msg: &[u8]) -> Result, Self::Error> { 86 | T::internal_sign(private, msg, true) 87 | } 88 | 89 | /// Verifies the signature by the provided public key 90 | fn verify( 91 | public: &Self::Public, 92 | msg_bytes: &[u8], 93 | sig_bytes: &[u8], 94 | ) -> Result<(), Self::Error> { 95 | T::internal_verify(public, msg_bytes, sig_bytes, true) 96 | } 97 | } 98 | } 99 | 100 | /// G1Scheme implements the BLS signature scheme with G1 as private / public 101 | /// keys and G2 as signature elements over the given pairing curve. 102 | #[derive(Clone, Debug)] 103 | pub struct G1Scheme { 104 | m: PhantomData, 105 | } 106 | 107 | impl Scheme for G1Scheme 108 | where 109 | C: PairingCurve, 110 | { 111 | type Private = C::Scalar; 112 | type Public = C::G1; 113 | type Signature = C::G2; 114 | } 115 | 116 | impl common::BLSScheme for G1Scheme 117 | where 118 | C: PairingCurve, 119 | { 120 | fn final_exp(p: &Self::Public, sig: &Self::Signature, hm: &Self::Signature) -> bool { 121 | // e(g1,sig) == e(pub, H(m)) 122 | // e(g1,H(m))^x == e(g1,H(m))^x 123 | let left = C::pair(&C::G1::one(), sig); 124 | let right = C::pair(p, hm); 125 | left == right 126 | } 127 | } 128 | 129 | /// G2Scheme implements the BLS signature scheme with G2 as private / public 130 | /// keys and G1 as signature elements over the given pairing curve. 131 | #[derive(Clone, Debug)] 132 | pub struct G2Scheme { 133 | m: PhantomData, 134 | } 135 | 136 | impl Scheme for G2Scheme 137 | where 138 | C: PairingCurve, 139 | { 140 | type Private = C::Scalar; 141 | type Public = C::G2; 142 | type Signature = C::G1; 143 | } 144 | 145 | impl common::BLSScheme for G2Scheme 146 | where 147 | C: PairingCurve, 148 | { 149 | fn final_exp(p: &Self::Public, sig: &Self::Signature, hm: &Self::Signature) -> bool { 150 | // e(sig,g2) == e(H(m),pub) 151 | // e(H(m),g2)^x == e(H(m),g2)^x 152 | let left = C::pair(sig, &Self::Public::one()); 153 | let right = C::pair(hm, p); 154 | left == right 155 | } 156 | } 157 | 158 | #[cfg(feature = "bls12_381")] 159 | #[cfg(test)] 160 | mod tests { 161 | use super::*; 162 | use crate::curve::bls12381::{Curve as G1Curve, G2Curve, PairingCurve as PCurve}; 163 | use crate::group::Curve; 164 | use rand::prelude::*; 165 | 166 | fn keypair() -> (C::Scalar, C::Point) { 167 | let private = C::Scalar::rand(&mut thread_rng()); 168 | let mut public = C::Point::one(); 169 | public.mul(&private); 170 | (private, public) 171 | } 172 | 173 | #[test] 174 | fn nbls_g2() { 175 | let (private, public) = keypair::(); 176 | let msg = vec![1, 9, 6, 9]; 177 | let sig = G2Scheme::::sign(&private, &msg).unwrap(); 178 | G2Scheme::::verify(&public, &msg, &sig).expect("that should not happen"); 179 | } 180 | 181 | #[test] 182 | fn nbls_g1() { 183 | let (private, public) = keypair::(); 184 | let msg = vec![1, 9, 6, 9]; 185 | let sig = G1Scheme::::sign(&private, &msg).unwrap(); 186 | G1Scheme::::verify(&public, &msg, &sig).expect("that should not happen"); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/sig/mod.rs: -------------------------------------------------------------------------------- 1 | mod blind; 2 | pub use blind::{BlindError, Token}; 3 | 4 | mod bls; 5 | pub use bls::{BLSError, G1Scheme, G2Scheme}; 6 | 7 | mod tblind; 8 | pub use tblind::BlindThresholdError; 9 | 10 | mod tbls; 11 | pub use tbls::{Share, ThresholdError}; 12 | 13 | #[allow(clippy::module_inception)] 14 | mod sig; 15 | pub use sig::*; 16 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/sig/sig.rs: -------------------------------------------------------------------------------- 1 | pub use super::tbls::Share; // import and re-export it for easier access 2 | use crate::{ 3 | group::{Element, Point, Scalar}, 4 | poly::Poly, 5 | }; 6 | use rand_core::RngCore; 7 | use serde::{de::DeserializeOwned, Serialize}; 8 | use std::{error::Error, fmt::Debug}; 9 | 10 | /// The `Scheme` trait contains the basic information of the groups over 11 | /// which the signing operations takes places and a way to create a valid key 12 | /// pair. 13 | /// 14 | /// The Scheme trait is necessary to implement for "simple" signature scheme as 15 | /// well for threshold based signature scheme. 16 | pub trait Scheme: Debug { 17 | /// `Private` represents the field over which private keys are represented. 18 | type Private: Scalar; 19 | /// `Public` represents the group over which the public keys are 20 | /// represented. 21 | type Public: Point + Serialize + DeserializeOwned; 22 | /// `Signature` represents the group over which the signatures are reresented. 23 | type Signature: Point + Serialize + DeserializeOwned; 24 | 25 | /// Returns a new fresh keypair usable by the scheme. 26 | fn keypair(rng: &mut R) -> (Self::Private, Self::Public) { 27 | let private = Self::Private::rand(rng); 28 | 29 | let mut public = Self::Public::one(); 30 | public.mul(&private); 31 | 32 | (private, public) 33 | } 34 | } 35 | 36 | /// SignatureScheme is the trait that defines the operations of a sinature 37 | /// scheme, namely `sign` and `verify`. Below is an example of using the 38 | /// signature scheme based on BLS, using the BLS12-381 curves. 39 | /// 40 | /// ``` 41 | /// # #[cfg(feature = "bls12_381")] 42 | /// # { 43 | /// use rand::prelude::*; 44 | /// use threshold_bls::{sig::{SignatureScheme, Scheme, G2Scheme}, group::{Element, Point}}; 45 | /// use threshold_bls::curve::bls12381::PairingCurve as PC; 46 | /// 47 | /// let msg = vec![1,9,6,9]; 48 | /// let (private,public) = G2Scheme::::keypair(&mut thread_rng()); 49 | /// let signature = G2Scheme::::sign(&private,&msg).unwrap(); 50 | /// match G2Scheme::::verify(&public, &msg, &signature) { 51 | /// Ok(_) => println!("signature is correct!"), 52 | /// Err(e) => println!("signature is invalid: {}",e), 53 | /// }; 54 | /// # } 55 | /// ``` 56 | /// Note signature scheme handles the format of the signature itself. 57 | pub trait SignatureScheme: Scheme { 58 | /// Error produced when signing a message 59 | type Error: Error; 60 | 61 | /// Signs the message with the provided private key and returns a serialized signature 62 | fn sign(private: &Self::Private, msg: &[u8]) -> Result, Self::Error>; 63 | 64 | /// Verifies that the signature on the provided message was produced by the public key 65 | fn verify(public: &Self::Public, msg: &[u8], sig: &[u8]) -> Result<(), Self::Error>; 66 | } 67 | 68 | /// BlindScheme is a signature scheme where the message can be blinded before 69 | /// signing so the signer does not know the real message. The signature can 70 | /// later be "unblinded" as to reveal a valid signature over the initial 71 | /// message. 72 | /// 73 | /// ``` 74 | /// # #[cfg(feature = "bls12_381")] 75 | /// # { 76 | /// use rand::prelude::*; 77 | /// use threshold_bls::{ 78 | /// sig::{BlindScheme,SignatureScheme, Scheme, G2Scheme}, 79 | /// group::{Element, Point} 80 | /// }; 81 | /// use threshold_bls::curve::bls12381::PairingCurve as PC; 82 | /// 83 | /// let msg = vec![1,9,6,9]; 84 | /// let (private,public) = G2Scheme::::keypair(&mut thread_rng()); 85 | /// // we first blind the message so the signers don't know the real underlying 86 | /// // message they are signing. 87 | /// let (token, blinded_msg) = G2Scheme::::blind_msg(&msg,&mut thread_rng()); 88 | /// // this method is called by the signers, that sign blindly. 89 | /// let blinded_sig = G2Scheme::::blind_sign(&private,&blinded_msg).unwrap(); 90 | /// // this method can be called by a third party that is able to verify if a 91 | /// // blinded signature is a a valid one even without having access to the 92 | /// // clear message. 93 | /// G2Scheme::::blind_verify(&public,&blinded_msg,&blinded_sig) 94 | /// .expect("blinded signatures should be correct"); 95 | 96 | /// // the owner of the message can then unblind the signature to reveal a 97 | /// // regular signature that can be verified using the regular method of the 98 | /// // SignatureScheme. 99 | /// let clear_sig = G2Scheme::::unblind_sig(&token,&blinded_sig).unwrap(); 100 | /// match G2Scheme::::verify(&public, &msg, &clear_sig) { 101 | /// Ok(_) => println!("signature is correct!"), 102 | /// Err(e) => println!("signature is invalid: {}",e), 103 | /// }; 104 | /// # } 105 | /// ``` 106 | pub trait BlindScheme: Scheme { 107 | /// The blinding factor which will be used to unblind the message 108 | type Token: Serialize + DeserializeOwned; 109 | 110 | /// Error during blinding or unblinding 111 | type Error: Error; 112 | 113 | /// Blinds the provided message using randomness from the provided RNG and returns 114 | /// the blinding factor and the blinded message. 115 | fn blind_msg(msg: &[u8], rng: &mut R) -> (Self::Token, Vec); 116 | 117 | /// Given the blinding factor that was used to blind the provided message, it will 118 | /// unblind it and return the cleartext message 119 | fn unblind_sig(t: &Self::Token, blinded_message: &[u8]) -> Result, Self::Error>; 120 | 121 | /// blind_sign is the method that signs the given blinded message and 122 | /// returns a blinded signature. 123 | fn blind_sign(private: &Self::Private, blinded_msg: &[u8]) -> Result, Self::Error>; 124 | 125 | /// blind_verify takes the blinded message and the blinded signature and 126 | /// checks if the latter is a valid signature by the provided public key. One must then unblind the signature so 127 | /// it can be verified using the regular `SignatureScheme::verify` method. 128 | fn blind_verify( 129 | public: &Self::Public, 130 | blinded_msg: &[u8], 131 | blinded_sig: &[u8], 132 | ) -> Result<(), Self::Error>; 133 | } 134 | 135 | /// Partial is simply an alias to denote a partial signature. 136 | pub type Partial = Vec; 137 | 138 | /// ThresholdScheme is a threshold-based `t-n` signature scheme. The security of 139 | /// such a scheme means at least `t` participants are required produce a "partial 140 | /// signature" to then produce a regular signature. 141 | /// The `dkg-core` module allows participants to create a distributed private/public key 142 | /// that can be used with implementations `ThresholdScheme`. 143 | pub trait ThresholdScheme: Scheme { 144 | /// Error produced when partially signing, aggregating or verifying 145 | type Error: Error; 146 | 147 | /// Partially signs a message with a share of the private key 148 | fn partial_sign(private: &Share, msg: &[u8]) -> Result; 149 | 150 | /// Verifies a partial signature on a message against the public polynomial 151 | fn partial_verify( 152 | public: &Poly, 153 | msg: &[u8], 154 | partial: &[u8], 155 | ) -> Result<(), Self::Error>; 156 | 157 | /// Aggregates all partials signature together. Note that this method does 158 | /// not verify if the partial signatures are correct or not; it only 159 | /// aggregates them. 160 | fn aggregate(threshold: usize, partials: &[Partial]) -> Result, Self::Error>; 161 | } 162 | 163 | /// BlindThreshold is ThresholdScheme that allows to verify a partially blinded 164 | /// signature as well blinded message, to aggregate them into one blinded signature 165 | /// such that it can be unblinded after and verified as a regular signature. 166 | pub trait BlindThresholdScheme: BlindScheme { 167 | type Error: Error; 168 | 169 | /// sign_blind_partial partially signs a blinded message and returns a 170 | /// partial blind signature over it. 171 | fn sign_blind_partial( 172 | private: &Share, 173 | blinded_msg: &[u8], 174 | ) -> Result::Error>; 175 | 176 | /// Given the blinding factor that was used to blind a message that was blind partially 177 | /// signed, it will unblind it and return the cleartext signature 178 | fn unblind_partial_sig( 179 | t: &Self::Token, 180 | partial: &[u8], 181 | ) -> Result::Error>; 182 | 183 | /// verify_blind_partial checks if a given blinded partial signature is 184 | /// correct given the blinded message. This can be called by any third party 185 | /// given the two parameters which are not private (since they are blinded). 186 | fn verify_blind_partial( 187 | public: &Poly, 188 | blind_msg: &[u8], 189 | blind_partial: &[u8], 190 | ) -> Result<(), ::Error>; 191 | } 192 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/sig/tblind.rs: -------------------------------------------------------------------------------- 1 | use crate::poly::{Eval, Poly}; 2 | use crate::sig::tbls::Share; 3 | use crate::sig::{BlindScheme, BlindThresholdScheme, Partial, ThresholdScheme}; 4 | use thiserror::Error; 5 | 6 | #[derive(Debug, Error)] 7 | // TODO: Can we get rid of this static lifetime bound? 8 | /// Errors associated with partially unblinding a signature 9 | pub enum BlindThresholdError { 10 | /// Raised when unblinding fails 11 | #[error(transparent)] 12 | BlindError(E), 13 | 14 | /// Raised when (de)serialization fails 15 | #[error(transparent)] 16 | BincodeError(#[from] bincode::Error), 17 | } 18 | 19 | impl BlindThresholdScheme for T 20 | where 21 | T: 'static + ThresholdScheme + BlindScheme, 22 | { 23 | type Error = BlindThresholdError<::Error>; 24 | 25 | fn sign_blind_partial( 26 | private: &Share, 27 | blinded_msg: &[u8], 28 | ) -> Result::Error> { 29 | let sig = Self::blind_sign(&private.private, blinded_msg) 30 | .map_err(BlindThresholdError::BlindError)?; 31 | let partial = Eval { 32 | value: sig, 33 | index: private.index, 34 | }; 35 | bincode::serialize(&partial).map_err(BlindThresholdError::BincodeError) 36 | } 37 | 38 | fn unblind_partial_sig( 39 | t: &Self::Token, 40 | partial: &[u8], 41 | ) -> Result::Error> { 42 | // deserialize the sig 43 | let partial: Eval> = bincode::deserialize(partial)?; 44 | 45 | let partially_unblinded = 46 | Self::unblind_sig(t, &partial.value).map_err(BlindThresholdError::BlindError)?; 47 | let partially_unblinded = Eval { 48 | index: partial.index, 49 | value: partially_unblinded, 50 | }; 51 | bincode::serialize(&partially_unblinded).map_err(BlindThresholdError::BincodeError) 52 | } 53 | 54 | fn verify_blind_partial( 55 | public: &Poly, 56 | blind_msg: &[u8], 57 | blind_partial: &[u8], 58 | ) -> Result<(), ::Error> { 59 | let blinded_partial: Eval> = bincode::deserialize(blind_partial)?; 60 | let public_i = public.eval(blinded_partial.index); 61 | Self::blind_verify(&public_i.value, blind_msg, &blinded_partial.value) 62 | .map_err(BlindThresholdError::BlindError) 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | #[cfg(feature = "bls12_381")] 70 | use crate::curve::bls12381::PairingCurve as PCurve; 71 | #[cfg(feature = "bls12_377")] 72 | use crate::curve::zexe::PairingCurve as Zexe; 73 | use crate::poly::{Idx, Poly}; 74 | use crate::sig::{ 75 | bls::{G1Scheme, G2Scheme}, 76 | tbls::Share, 77 | SignatureScheme, 78 | }; 79 | use rand::thread_rng; 80 | 81 | fn shares( 82 | n: usize, 83 | t: usize, 84 | ) -> (Vec>, Poly) { 85 | let private = Poly::::new(t - 1); 86 | let shares = (0..n) 87 | .map(|i| private.eval(i as Idx)) 88 | .map(|e| Share { 89 | index: e.index, 90 | private: e.value, 91 | }) 92 | .collect(); 93 | (shares, private.commit()) 94 | } 95 | 96 | #[cfg(feature = "bls12_377")] 97 | #[test] 98 | fn tblind_g1_zexe_unblind() { 99 | tblind_test::>(); 100 | } 101 | 102 | #[cfg(feature = "bls12_377")] 103 | #[test] 104 | fn tblind_g2_zexe_unblind() { 105 | tblind_test::>(); 106 | } 107 | 108 | #[cfg(feature = "bls12_381")] 109 | #[test] 110 | fn tblind_g1_bellman_unblind() { 111 | tblind_test::>(); 112 | } 113 | 114 | #[cfg(feature = "bls12_381")] 115 | #[test] 116 | fn tblind_g2_bellman_unblind() { 117 | tblind_test::>(); 118 | } 119 | 120 | fn tblind_test() 121 | where 122 | B: BlindThresholdScheme + SignatureScheme + ThresholdScheme, 123 | { 124 | let n = 5; 125 | let thr = 4; 126 | let (shares, public) = shares::(n, thr); 127 | let msg = vec![1, 9, 6, 9]; 128 | 129 | // blind the msg 130 | let (token, blinded) = B::blind_msg(&msg, &mut thread_rng()); 131 | 132 | // partially sign it 133 | let partials: Vec<_> = shares 134 | .iter() 135 | .map(|share| B::sign_blind_partial(share, &blinded).unwrap()) 136 | .collect(); 137 | 138 | // verify if each blind partial signatures is correct 139 | assert!(!partials 140 | .iter() 141 | .any(|p| B::verify_blind_partial(&public, &blinded, p).is_err())); 142 | 143 | // unblind each partial sig 144 | let unblindeds_partials: Vec<_> = partials 145 | .iter() 146 | .map(|p| B::unblind_partial_sig(&token, p).unwrap()) 147 | .collect(); 148 | 149 | // aggregate & verify the unblinded partials 150 | let final_sig1 = B::aggregate(thr, &unblindeds_partials).unwrap(); 151 | B::verify(public.public_key(), &msg, &final_sig1).unwrap(); 152 | 153 | // Another method is to aggregate the blinded partials directly. This 154 | // can be done by a third party 155 | let blinded_final = B::aggregate(thr, &partials).unwrap(); 156 | 157 | // unblind the aggregated signature 158 | let final_sig2 = B::unblind_sig(&token, &blinded_final).unwrap(); 159 | 160 | // verify the final signature 161 | B::verify(public.public_key(), &msg, &final_sig2).unwrap(); 162 | assert_eq!(final_sig1, final_sig2); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/sig/tbls.rs: -------------------------------------------------------------------------------- 1 | //! Threshold Signatures implementation for any type which implements 2 | //! [`SignatureScheme`](../trait.SignatureScheme.html) 3 | use crate::poly::{Eval, Idx, Poly, PolyError}; 4 | use crate::sig::{Partial, SignatureScheme, ThresholdScheme}; 5 | use serde::{Deserialize, Serialize}; 6 | use thiserror::Error; 7 | 8 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 9 | /// A private share which is part of the threshold signing key 10 | pub struct Share { 11 | /// The share's index in the polynomial 12 | pub index: Idx, 13 | /// The scalar corresponding to the share's secret 14 | pub private: S, 15 | } 16 | 17 | /// Errors associated with threshold signing, verification and aggregation. 18 | #[derive(Debug, Error)] 19 | pub enum ThresholdError { 20 | /// PolyError is raised when the public key could not be recovered 21 | #[error("could not recover public key: {0}")] 22 | PolyError(PolyError), 23 | 24 | /// BincodeError is raised when there is an error in (de)serialization 25 | #[error(transparent)] 26 | BincodeError(#[from] bincode::Error), 27 | 28 | /// SignatureError is raised when there is an error in threshold signing 29 | #[error("signing error {0}")] 30 | SignatureError(I::Error), 31 | 32 | /// NotEnoughPartialSignatures is raised if the signatures provided for aggregation 33 | /// were fewer than the threshold 34 | #[error("not enough partial signatures: {0}/{1}")] 35 | NotEnoughPartialSignatures(usize, usize), 36 | } 37 | 38 | impl ThresholdScheme for I { 39 | type Error = ThresholdError; 40 | 41 | fn partial_sign( 42 | private: &Share, 43 | msg: &[u8], 44 | ) -> Result, ::Error> { 45 | let sig = Self::sign(&private.private, msg).map_err(ThresholdError::SignatureError)?; 46 | let partial = Eval { 47 | value: sig, 48 | index: private.index, 49 | }; 50 | let ret = bincode::serialize(&partial)?; 51 | Ok(ret) 52 | } 53 | 54 | fn partial_verify( 55 | public: &Poly, 56 | msg: &[u8], 57 | partial: &[u8], 58 | ) -> Result<(), ::Error> { 59 | let partial: Eval> = bincode::deserialize(partial)?; 60 | 61 | let public_i = public.eval(partial.index); 62 | 63 | Self::verify(&public_i.value, msg, &partial.value).map_err(ThresholdError::SignatureError) 64 | } 65 | 66 | fn aggregate( 67 | threshold: usize, 68 | partials: &[Partial], 69 | ) -> Result, ::Error> { 70 | if threshold > partials.len() { 71 | return Err(ThresholdError::NotEnoughPartialSignatures( 72 | partials.len(), 73 | threshold, 74 | )); 75 | } 76 | 77 | let valid_partials: Vec> = partials 78 | .iter() 79 | .map(|partial| { 80 | let eval: Eval> = bincode::deserialize(partial)?; 81 | let sig = bincode::deserialize(&eval.value)?; 82 | Ok(Eval { 83 | index: eval.index, 84 | value: sig, 85 | }) 86 | }) 87 | .collect::::Error>>()?; 88 | 89 | let recovered_sig = Poly::::recover(threshold, valid_partials) 90 | .map_err(ThresholdError::PolyError)?; 91 | Ok(bincode::serialize(&recovered_sig).expect("could not serialize")) 92 | } 93 | } 94 | 95 | #[cfg(feature = "bls12_381")] 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | use crate::{ 100 | curve::bls12381::PairingCurve as PCurve, 101 | sig::{ 102 | bls::{G1Scheme, G2Scheme}, 103 | Scheme, SignatureScheme, 104 | }, 105 | }; 106 | 107 | type ShareCreator = fn( 108 | usize, 109 | usize, 110 | ) -> ( 111 | Vec::Private>>, 112 | Poly<::Public>, 113 | ); 114 | 115 | fn shares(n: usize, t: usize) -> (Vec>, Poly) { 116 | let private = Poly::::new(t - 1); 117 | let shares = (0..n) 118 | .map(|i| private.eval(i as Idx)) 119 | .map(|e| Share { 120 | index: e.index, 121 | private: e.value, 122 | }) 123 | .collect(); 124 | (shares, private.commit()) 125 | } 126 | 127 | fn test_threshold_scheme(creator: ShareCreator) { 128 | let threshold = 4; 129 | let (shares, public) = creator(5, threshold); 130 | let msg = vec![1, 9, 6, 9]; 131 | 132 | let partials: Vec<_> = shares 133 | .iter() 134 | .map(|s| T::partial_sign(s, &msg).unwrap()) 135 | .collect(); 136 | 137 | assert!(!partials 138 | .iter() 139 | .any(|p| T::partial_verify(&public, &msg, p).is_err())); 140 | let final_sig = T::aggregate(threshold, &partials).unwrap(); 141 | 142 | T::verify(public.public_key(), &msg, &final_sig).unwrap(); 143 | } 144 | 145 | #[test] 146 | fn threshold_g1() { 147 | type S = G1Scheme; 148 | test_threshold_scheme::(shares::); 149 | } 150 | 151 | #[test] 152 | fn threshold_g2() { 153 | type S = G2Scheme; 154 | test_threshold_scheme::(shares::); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /crates/threshold-bls/src/test_vectors.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[cfg(feature = "bls12_381")] 4 | mod bls12_381_vectors { 5 | use crate::curve::bls12381::{Scalar, G1}; 6 | use crate::poly::{Idx, Poly}; 7 | use crate::schemes::bls12_381::G1Scheme; 8 | use crate::sig::{Scheme, Share, SignatureScheme, ThresholdScheme}; 9 | use rand::SeedableRng; 10 | use rand_chacha::ChaChaRng; 11 | 12 | type PrivateKey = Scalar; 13 | type PublicKey = G1; 14 | 15 | // Test vectors with fixed seed 16 | const SEED: [u8; 32] = [42u8; 32]; 17 | const MESSAGES: [&[u8; 32]; 3] = [&[0x00; 32], &[0x56; 32], &[0xab; 32]]; 18 | 19 | const EXPECTED_PRIVATE_KEYS: [&str; 3] = [ 20 | "013abcff335703fb2f065483dba2cb40526f21e871c10d5f4e8ed5ddd079e750", 21 | "8ae9e96e9b11b58272914cb8d508ae9523ef57f70e786ee2f0f35ba484c82945", 22 | "28e3388629b01282d49de4ac4f3451690195bb569ef73c7de722d9f04e4d405e", 23 | ]; 24 | 25 | const EXPECTED_PUBLIC_KEYS: [&str; 3] = [ 26 | "b22bcac0cbc3fa2c720784b0bde1d8a678bc35d8bb3deecff85ae6a13416d95e82ed18e08ed0f864cd90cb0cb018a39a", 27 | "b1ad7db5da4d357d7e680229f2a5e099ef10ee87489c0f685431c56d67a2b0890bacef1097047d77a326af3465be2b70", 28 | "a8b33f1d332eb0d593bcaee2ce201b0c4a47823b0b6cc10ebbdbeeb9466ac87e937afdd9ce4a085198630d3518caf25d", 29 | ]; 30 | 31 | const EXPECTED_SIGNATURES: [[&str; 3]; 3] = [ 32 | // First private key with each message 33 | [ 34 | "6000000000000000b5f821ce57ee9a9a1c79f8ff4197b2c6c63adbdd3a2d6e771cd5f6748347d33ae5fa290c67115e24c5e8d9a47f19788e065b3a65bb90c71eddf200276496a5cde7dab19cb10f7180dd505f54eebec6433441f35ebc79d1986fc285ba13893f4f", 35 | "6000000000000000851cab1c75c2f5fa67dac6ebb65712a798e5ed293d890ef8b59dd1ebb2a315175c01dd718316ea98e51ef47d915424b31471a4119eb3f3007e48843e3544c192376d54f6cdf376699311c51aad547bf092285bad478c2268df4cd81d7d00cdef", 36 | "6000000000000000b91f177e50083ebd0a5b711da530578d73c623c0be88469b02e96a620a597d7f980c0b19f1dbef16918e564fb8fe37050a7ce3b7b8c2b01718a9b98900db97f75d302bd7bdcba2d8979dba714230afca74d65d34ee24a8f96c3b787d2f0a531e", 37 | ], 38 | // Second private key with each message 39 | [ 40 | "6000000000000000b5607557b0ef065ecc35869cd263221bf40c964d835490d62e7576ea24940f1a0599754e91b1371bbf9dc9c631cc03010c739bf8e1a113a458e99304dcaa29ead1799cb095eb0d9c38a16077f825a31f64b22e15708791cc12baae57a73a96d7", 41 | "6000000000000000aadc03db054c6a8931f1ea9d345464fa029b1caf32bff9b5c1cfbfb45884da77b6ec861817ab7c34c93c3b617e78527111f3450b6a1a6444fd698f78ea3a7e827aba2cf2d1aa984dbfdd649c9424351ac130888954f27667ef0361f50db06049", 42 | "60000000000000008475a405e7e30f0350a5cdd1e0b4c36fdd17d5f315e1be2c06ed6b0695d5983af344cb388d0db2621a8832f5f171cb910058b99d2d84b0141b0f0edf19d996e9cb592c0a50600d59358623f5114efb766e93d77df02ce6836550e1a8ed06b0f1" 43 | ], 44 | // Third private key with each message 45 | [ 46 | "6000000000000000b0f192df6e929b2d52f8c952baa6731e97e38a3232cdaf854abb958856eca7bd5d416343dc843232e59cb81a2993844f14841b07f274c78cd3e1cd60c716feebce7b1d1134048c7dd0d8305d896b9ebcd841fc395729a520478052ec4a53f11c", 47 | "60000000000000008536c724d442e299a0b4ef97e3249d0b27749b30d33a5c5c7930a00279085d7498ed4e42fe56e399e096e8944f5d70d2171236df5397ada1cb69038517f2f360f174ff8271ff2e59039afb39c3f1c3ed6b4c7a05f1788b4c4958450439e30e15", 48 | "600000000000000089c78160bb51cd0bd63e580ac1563944ffd33e416d4bae7a91e366ca5fe7601da74de5f43faf8d9e228faeb47587496a0c4c2cf75662aef4b3ec87b61a298a46f2bc89a57b8cd43aa8949f30ec9284367884a1afe1bc61cfa81068dc91a6bb50" 49 | ] 50 | ]; 51 | 52 | const EXPECTED_AGGREGATED_SIGNATURES: [&str; 3] = [ 53 | "6000000000000000b5f821ce57ee9a9a1c79f8ff4197b2c6c63adbdd3a2d6e771cd5f6748347d33ae5fa290c67115e24c5e8d9a47f19788e065b3a65bb90c71eddf200276496a5cde7dab19cb10f7180dd505f54eebec6433441f35ebc79d1986fc285ba13893f4f", 54 | "6000000000000000851cab1c75c2f5fa67dac6ebb65712a798e5ed293d890ef8b59dd1ebb2a315175c01dd718316ea98e51ef47d915424b31471a4119eb3f3007e48843e3544c192376d54f6cdf376699311c51aad547bf092285bad478c2268df4cd81d7d00cdef", 55 | "6000000000000000b91f177e50083ebd0a5b711da530578d73c623c0be88469b02e96a620a597d7f980c0b19f1dbef16918e564fb8fe37050a7ce3b7b8c2b01718a9b98900db97f75d302bd7bdcba2d8979dba714230afca74d65d34ee24a8f96c3b787d2f0a531e" 56 | ]; 57 | 58 | // Create a deterministic RNG for reproducible key generation 59 | fn get_deterministic_rng() -> ChaChaRng { 60 | ChaChaRng::from_seed(SEED) 61 | } 62 | 63 | fn get_keypair(index: usize) -> (PrivateKey, PublicKey) { 64 | let mut rng = get_deterministic_rng(); 65 | // Skip to the desired index 66 | for _ in 0..index { 67 | G1Scheme::keypair(&mut rng); 68 | } 69 | G1Scheme::keypair(&mut rng) 70 | } 71 | 72 | #[test] 73 | fn sign_and_verify() { 74 | // Test basic signing and verification with deterministic outputs 75 | for i in 0..3 { 76 | let (privkey, pubkey) = get_keypair(i); 77 | 78 | let priv_hex = hex::encode(bincode::serialize(&privkey).unwrap()); 79 | let pub_hex = hex::encode(bincode::serialize(&pubkey).unwrap()); 80 | 81 | assert_eq!( 82 | priv_hex, EXPECTED_PRIVATE_KEYS[i], 83 | "Private key {} mismatch", 84 | i 85 | ); 86 | assert_eq!( 87 | pub_hex, EXPECTED_PUBLIC_KEYS[i], 88 | "Public key {} mismatch", 89 | i 90 | ); 91 | 92 | for (j, &msg) in MESSAGES.iter().enumerate() { 93 | // Sign the message 94 | let sig = G1Scheme::sign(&privkey, msg).expect("Error signing"); 95 | let sig_hex = hex::encode(bincode::serialize(&sig).unwrap()); 96 | assert_eq!( 97 | sig_hex, EXPECTED_SIGNATURES[i][j], 98 | "Signature for key[{}] and message[{}] mismatch", 99 | i, j 100 | ); 101 | 102 | assert!( 103 | G1Scheme::verify(&pubkey, msg, &sig).is_ok(), 104 | "Signature verification failed for key[{}] and message[{}]", 105 | i, 106 | j 107 | ); 108 | } 109 | } 110 | } 111 | 112 | #[test] 113 | fn test_signature_aggregation() { 114 | // Test threshold signatures with deterministic outputs 115 | for (msg_idx, &msg) in MESSAGES.iter().enumerate() { 116 | let n = 3; 117 | 118 | // Create private keys using deterministic generation 119 | let private_keys: Vec = (0..n) 120 | .map(get_keypair) 121 | .map(|(priv_key, _)| priv_key) 122 | .collect(); 123 | 124 | // Create a private polynomial from our private keys 125 | let private_poly = Poly::::from(private_keys); 126 | 127 | // Generate the shares from the polynomial 128 | let shares = (0..n) 129 | .map(|i| { 130 | let eval = private_poly.eval(i as Idx); 131 | Share { 132 | index: eval.index, 133 | private: eval.value, 134 | } 135 | }) 136 | .collect::>>(); 137 | 138 | // Get the public polynomial 139 | let public_poly = private_poly.commit(); 140 | let threshold_pubkey = public_poly.public_key(); 141 | 142 | // Generate partial signatures 143 | let partials = shares 144 | .iter() 145 | .map(|s| G1Scheme::partial_sign(s, msg).unwrap()) 146 | .collect::>(); 147 | 148 | // Verify each partial signature 149 | for (i, partial) in partials.iter().enumerate() { 150 | assert!( 151 | G1Scheme::partial_verify(&public_poly, msg, partial).is_ok(), 152 | "Partial signature verification failed for share {}", 153 | i 154 | ); 155 | } 156 | 157 | // Aggregate signatures 158 | let aggregated = G1Scheme::aggregate(n, &partials).expect("Failed to aggregate"); 159 | let agg_hex = hex::encode(bincode::serialize(&aggregated).unwrap()); 160 | assert_eq!( 161 | agg_hex, EXPECTED_AGGREGATED_SIGNATURES[msg_idx], 162 | "Aggregated signature for message {} mismatch", 163 | msg_idx 164 | ); 165 | 166 | assert!( 167 | G1Scheme::verify(threshold_pubkey, msg, &aggregated).is_ok(), 168 | "Aggregated signature verification failed for message {}", 169 | msg_idx 170 | ); 171 | } 172 | } 173 | } 174 | 175 | #[cfg(feature = "bls12_377")] 176 | #[cfg(test)] 177 | mod bls12_377_vectors { 178 | use crate::poly::{Idx, Poly}; 179 | use crate::schemes::bls12_377::G2Scheme; 180 | use crate::sig::{Scheme, Share, SignatureScheme, ThresholdScheme}; 181 | use rand::SeedableRng; 182 | use rand_chacha::ChaChaRng; 183 | 184 | // Define concrete types to avoid ambiguity 185 | type PrivateKey = ::Private; 186 | type PublicKey = ::Public; 187 | 188 | // Test vectors with fixed seed 189 | const SEED: [u8; 32] = [42u8; 32]; 190 | const MESSAGES: [&[u8; 32]; 3] = [&[0x00; 32], &[0x56; 32], &[0xab; 32]]; 191 | 192 | // Expected outputs for each operation with seed [42; 32] 193 | const EXPECTED_PRIVATE_KEYS: [&str; 3] = [ 194 | "d3538152f5f1570c1f21a6246665637c11153594e4220eec8e8d72be00706303", 195 | "7ca094ae6cbf48061b12121ef92160eafe996bc2d8a9a1336b00801120a87211", 196 | "922be46d5fa7d1bd43152a3cab829dc32ed1076e4c2be136f4e8846895557b02", 197 | ]; 198 | 199 | const EXPECTED_PUBLIC_KEYS: [&str; 3] = [ 200 | "203e261e640d22e0daed558229493c95fb5016c1c8dc5a22dfedb22f14fcb2958466446beb9c309dffa4b4ba52894000f9cab42d8570af09233b9ba06c0fb1c266a866f0e6384edbf176fcb76619f01da297480be4d7b8551b8fa3b9a08e1100", 201 | "82ad47f04edc28f22a3c47e2aaf642912d9b710c57c254ef78081a39f2eaeec97a95873789f15555503a1f885fea0e0190826f11ac8e09339391dcf499347db8cd48ae453b0882ea652177b6f73bb673b1381ed7cd91c8f6a0763f4dc9754901", 202 | "1a95b30e8dee662342c87ca88914890fba6b376344fbf8669abb4ce5ea62ac1dbd2935320efe5c6ebe5ceee9534187002ae20deb4445768a023d8d499fb1f49608942e0047ec82993fe6447a2abace036352eb6878fae17d5c0b992c7ba74800", 203 | ]; 204 | 205 | const EXPECTED_SIGNATURES: [[&str; 3]; 3] = [ 206 | // First private key with each message 207 | [ 208 | "3000000000000000e2dac33c610f0a247a82c98324188070164206c0151be6a98d8666e63a36c3804bfcb682f6764ad2307406292f2d2281", 209 | "3000000000000000f07921d52176cc4f6528005c19eab9d230900b531780e9ddfd1d13f020cf4b559a96a6909c33bfb16bd0e3ca0cdf5d01", 210 | "30000000000000007711c1febaa06af30f0c9bb19e942b642d21354c25ad392b1c4c3cf4eb0375809afa05bf7defaa17684202e036cfd480", 211 | ], 212 | // Second private key with each message 213 | [ 214 | "30000000000000001b4827907e476d060382874380fbd600605662c7eb80de9d0fc55cfc26a3efea6f596f18bc13e91353b87f4ec4c2b980", 215 | "30000000000000007cfb12face29af9d65638bd4430f5d95f10796eaaba9c91d594685780dda98f3870ed1d01a13356a308cdc24312f5a01", 216 | "30000000000000006dd334350a2e502b2b2880c9d58c1c3281dc4343b36d57de0541f29c2f77d29c39c4475a2368a6a4dff9702e5ba29d81", 217 | ], 218 | // Third private key with each message 219 | [ 220 | "30000000000000005eb1a349270e162e0e7c2fc027759d751e91765d27693591273a25737616516d431fddad37e94fe45714e5caca370b01", 221 | "3000000000000000d4829581cdd53438721641e1a6893ef92e62dbbd3bca21cb5dd74b6c685c9aaf905b81c544fb3b07031a917602647101", 222 | "3000000000000000bfff68378c234eeb81604dfe320c09cdacb0c7521cb0563d79789a1ba217397998be539204f4d6b0077511f0c14a0600", 223 | ], 224 | ]; 225 | 226 | const EXPECTED_AGGREGATED_SIGNATURES: [&str; 3] = [ 227 | "3000000000000000e2dac33c610f0a247a82c98324188070164206c0151be6a98d8666e63a36c3804bfcb682f6764ad2307406292f2d2281", 228 | "3000000000000000f07921d52176cc4f6528005c19eab9d230900b531780e9ddfd1d13f020cf4b559a96a6909c33bfb16bd0e3ca0cdf5d01", 229 | "30000000000000007711c1febaa06af30f0c9bb19e942b642d21354c25ad392b1c4c3cf4eb0375809afa05bf7defaa17684202e036cfd480", 230 | ]; 231 | 232 | // Create a deterministic RNG for reproducible key generation 233 | fn get_deterministic_rng() -> ChaChaRng { 234 | ChaChaRng::from_seed(SEED) 235 | } 236 | 237 | fn get_keypair(index: usize) -> (PrivateKey, PublicKey) { 238 | let mut rng = get_deterministic_rng(); 239 | // Skip to the desired index 240 | for _ in 0..index { 241 | G2Scheme::keypair(&mut rng); 242 | } 243 | G2Scheme::keypair(&mut rng) 244 | } 245 | 246 | #[test] 247 | fn sign_and_verify() { 248 | // Test basic signing and verification with deterministic outputs 249 | for i in 0..3 { 250 | let (privkey, pubkey) = get_keypair(i); 251 | 252 | let priv_hex = hex::encode(bincode::serialize(&privkey).unwrap()); 253 | let pub_hex = hex::encode(bincode::serialize(&pubkey).unwrap()); 254 | 255 | assert_eq!( 256 | priv_hex, EXPECTED_PRIVATE_KEYS[i], 257 | "Private key {} mismatch", 258 | i 259 | ); 260 | assert_eq!( 261 | pub_hex, EXPECTED_PUBLIC_KEYS[i], 262 | "Public key {} mismatch", 263 | i 264 | ); 265 | 266 | for (j, &msg) in MESSAGES.iter().enumerate() { 267 | // Sign the message 268 | let sig = G2Scheme::sign(&privkey, msg).expect("Error signing"); 269 | let sig_hex = hex::encode(bincode::serialize(&sig).unwrap()); 270 | assert_eq!( 271 | sig_hex, EXPECTED_SIGNATURES[i][j], 272 | "Signature for key[{}] and message[{}] mismatch", 273 | i, j 274 | ); 275 | 276 | assert!( 277 | G2Scheme::verify(&pubkey, msg, &sig).is_ok(), 278 | "Signature verification failed for key[{}] and message[{}]", 279 | i, 280 | j 281 | ); 282 | } 283 | } 284 | } 285 | 286 | #[test] 287 | fn test_signature_aggregation() { 288 | // Test threshold signatures with deterministic outputs 289 | for (msg_idx, &msg) in MESSAGES.iter().enumerate() { 290 | let n = 3; 291 | 292 | // Create private keys using deterministic generation 293 | let private_keys: Vec = (0..n) 294 | .map(get_keypair) 295 | .map(|(priv_key, _)| priv_key) 296 | .collect(); 297 | 298 | // Create a private polynomial from our private keys 299 | let private_poly = Poly::::from(private_keys); 300 | 301 | // Generate the shares from the polynomial 302 | let shares = (0..n) 303 | .map(|i| { 304 | let eval = private_poly.eval(i as Idx); 305 | Share { 306 | index: eval.index, 307 | private: eval.value, 308 | } 309 | }) 310 | .collect::>>(); 311 | 312 | // Get the public polynomial 313 | let public_poly = private_poly.commit(); 314 | let threshold_pubkey = public_poly.public_key(); 315 | 316 | // Generate partial signatures 317 | let partials = shares 318 | .iter() 319 | .map(|s| G2Scheme::partial_sign(s, msg).unwrap()) 320 | .collect::>(); 321 | 322 | // Verify each partial signature 323 | for (i, partial) in partials.iter().enumerate() { 324 | assert!( 325 | G2Scheme::partial_verify(&public_poly, msg, partial).is_ok(), 326 | "Partial signature verification failed for share {}", 327 | i 328 | ); 329 | } 330 | 331 | // Aggregate signatures 332 | let aggregated = G2Scheme::aggregate(n, &partials).expect("Failed to aggregate"); 333 | let agg_hex = hex::encode(bincode::serialize(&aggregated).unwrap()); 334 | assert_eq!( 335 | agg_hex, EXPECTED_AGGREGATED_SIGNATURES[msg_idx], 336 | "Aggregated signature for message {} mismatch", 337 | msg_idx 338 | ); 339 | 340 | assert!( 341 | G2Scheme::verify(threshold_pubkey, msg, &aggregated).is_ok(), 342 | "Aggregated signature verification failed for message {}", 343 | msg_idx 344 | ); 345 | } 346 | } 347 | } 348 | } 349 | --------------------------------------------------------------------------------