├── .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