├── .github ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── coverage.yaml │ ├── main.yml │ └── release-drafter.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── DEVELOPERS.md ├── Dockerfile ├── LICENCE ├── LICENCE.MIT ├── LICENSE.Apache-2.0 ├── README.md ├── benches ├── bench.rs └── frost.rs ├── cloudbuild.yaml ├── codecov.yml ├── rfcs └── 0001-messages.md ├── src ├── batch.rs ├── constants.rs ├── error.rs ├── frost.rs ├── frost │ ├── redjubjub.rs │ ├── redjubjub │ │ ├── README.md │ │ ├── dkg.md │ │ └── keys │ │ │ ├── dkg.rs │ │ │ └── repairable.rs │ ├── redpallas.rs │ └── redpallas │ │ ├── README.md │ │ ├── dkg.md │ │ └── keys │ │ ├── dkg.rs │ │ └── repairable.rs ├── hash.rs ├── lib.rs ├── messages.rs ├── orchard.rs ├── orchard │ └── tests.rs ├── sapling.rs ├── scalar_mul.rs ├── scalar_mul │ └── tests.rs ├── signature.rs ├── signing_key.rs └── verification_key.rs ├── tests ├── batch.rs ├── bincode.rs ├── frost_redjubjub.rs ├── frost_redpallas.rs ├── librustzcash_vectors.rs ├── proptests.proptest-regressions ├── proptests.rs └── smallorder.rs └── zcash-frost-audit-report-20210323.pdf /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | timezone: America/New_York 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: cargo 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | timezone: America/New_York 14 | open-pull-requests-limit: 10 15 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | prerelease: true 2 | categories: 3 | - title: 'Features' 4 | labels: 5 | - 'feature' 6 | - 'enhancement' 7 | - title: 'Bug Fixes' 8 | labels: 9 | - 'fix' 10 | - 'bugfix' 11 | - 'bug' 12 | - title: 'Maintenance' 13 | label: 'chore' 14 | change-template: '- $TITLE (#$NUMBER)' 15 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 16 | template: | 17 | ## Changes 18 | 19 | $CHANGES 20 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yaml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | branches: 7 | - main 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | 14 | coverage: 15 | name: Coverage (+nightly) 16 | timeout-minutes: 30 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4.2.2 21 | with: 22 | persist-credentials: false 23 | 24 | - uses: actions-rs/toolchain@v1.0.7 25 | with: 26 | toolchain: nightly 27 | override: true 28 | profile: minimal 29 | components: llvm-tools-preview 30 | 31 | - name: Install cargo-llvm-cov cargo command 32 | run: cargo install cargo-llvm-cov 33 | 34 | - name: Run tests 35 | run: cargo llvm-cov --lcov --no-report 36 | 37 | - name: Generate coverage report 38 | run: cargo llvm-cov --lcov --no-run --output-path lcov.info 39 | 40 | - name: Upload coverage report to Codecov 41 | uses: codecov/codecov-action@v5.1.2 42 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test_msrv: 7 | name: build on MSRV 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4.2.2 11 | - uses: dtolnay/rust-toolchain@1.65.0 12 | # Don't use --all-features because `frost` has a higher MSRV and it's non-default. 13 | # Also don't run tests because some dev-dependencies have higher MSRVs. 14 | - run: cargo build 15 | test_nightly: 16 | name: test on nightly 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4.2.2 20 | - uses: dtolnay/rust-toolchain@nightly 21 | # Update dependencies since we commit the lockfile 22 | - run: cargo update --verbose 23 | - run: cargo test --all-features 24 | build_no_std: 25 | name: build with no_std 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4.2.2 29 | - uses: dtolnay/rust-toolchain@master 30 | with: 31 | toolchain: stable 32 | targets: thumbv6m-none-eabi 33 | - run: cargo build --no-default-features --target thumbv6m-none-eabi 34 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # Drafts your next Release notes as Pull Requests are merged into main 13 | - uses: release-drafter/release-drafter@v6 14 | with: 15 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml 16 | config-name: release-drafter.yml 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | *~ 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Entries are listed in reverse chronological order. 4 | 5 | ## 0.5.1 6 | 7 | * MSRV is now 1.65.0 8 | * Refactor & optimize the NAF (#63) 9 | * Updated `frost-rerandomized` to 0.6.0 (#67) 10 | 11 | ## 0.5.0 12 | 13 | * Add Pallas and Jubjub ciphersuites and FROST support (#33) 14 | * Migrate to `group` 0.13, `jubjub` 0.10, `pasta_curves` 0.5 (#44) 15 | 16 | ## 0.4.0 17 | 18 | * MSRV is now 1.60.0 (note: this was noticed after the crate was released) 19 | * port improvements from Zebra (#40) 20 | * clippy fixes; remove old FROST code (#32) 21 | * Update `criterion` requirement from 0.3 to 0.4 (#29) 22 | * Label Zcash consensus rules in `reddsa` (#27) 23 | * Fix alloc feature (#28) 24 | * fix category (no\_std -> no-std) (#25) 25 | 26 | ## 0.3.0 27 | 28 | * Migrate to `group` 0.12, `jubjub` 0.9, `pasta_curves` 0.4 29 | * Added support for `no-std` builds, via new (default-enabled) `std` and `alloc` 30 | feature flags. Module `batch` is supported on `alloc` feature only. Module 31 | `frost` is supported on `std` feature only. 32 | 33 | ## 0.2.0 34 | 35 | * MSRV is now 1.56.0 36 | * Migrate to `pasta_curves` 0.3, `blake2b_simd` 1, removed unneeded `digest` (#10) 37 | * Update the include_str support to fix CI on nightly (#12) 38 | 39 | ## 0.1.0 40 | 41 | Initial release of the `reddsa` crate, extracted from `redjubjub`. Changes 42 | relative to `redjubjub 0.4.0`: 43 | 44 | * Generalised the codebase, to enable usage for both RedJubjub and RedPallas. 45 | 46 | * Introduce `SpendAuth: SigType` and `Binding: SigType` traits. 47 | * The prior `SpendAuth` and `Binding` enums have been renamed to 48 | `sapling::{SpendAuth, Binding}`. 49 | * Added `orchard::{SpendAuth, Binding}` enums. 50 | 51 | * Migrated to `group 0.11`, `jubjub 0.8`. 52 | 53 | * Fixed a bug where small-order verification keys (including the identity) were 54 | handled inconsistently: the `VerificationKey` parsing logic rejected them, but 55 | the identity `VerificationKey` could be produced from the zero `SigningKey`. 56 | The behaviour is now to consistently accept all small-order verification keys, 57 | matching the RedDSA specification. 58 | 59 | * Downstream users who currently rely on the inconsistent behaviour (for e.g. 60 | consensus compatibility, either explicitly wanting to reject small-order 61 | verification keys, or on the belief that this crate implemented the RedDSA 62 | specification) should continue to use previous versions of this crate, until 63 | they can either move the checks into their own code, or migrate their 64 | consensus rules to match the RedDSA specification. 65 | 66 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reddsa" 3 | edition = "2021" 4 | rust-version = "1.65" 5 | # Refer to DEVELOPERS.md for guidance on making new releases. 6 | version = "0.5.1" 7 | authors = [ 8 | "Henry de Valence ", 9 | "Deirdre Connolly ", 10 | "Chelsea Komlo ", 11 | "Jack Grigg ", 12 | "Conrado Gouvea ", 13 | ] 14 | readme = "README.md" 15 | license = "MIT OR Apache-2.0" 16 | repository = "https://github.com/ZcashFoundation/reddsa" 17 | categories = ["cryptography", "no-std"] 18 | keywords = ["cryptography", "crypto", "zcash"] 19 | description = "A standalone implementation of the RedDSA signature scheme." 20 | 21 | [package.metadata.docs.rs] 22 | features = ["nightly"] 23 | 24 | [dependencies] 25 | blake2b_simd = { version = "1", default-features = false } 26 | byteorder = { version = "1.5", default-features = false } 27 | group = { version = "0.13", default-features = false } 28 | hex = { version = "0.4", optional = true, default-features = false, features = ["alloc"] } 29 | jubjub = { version = "0.10", default-features = false } 30 | pasta_curves = { version = "0.5", default-features = false } 31 | rand_core = { version = "0.6", default-features = false } 32 | serde = { version = "1", optional = true, features = ["derive"] } 33 | thiserror = { version = "2.0", optional = true } 34 | frost-rerandomized = { version = "2.1.0", optional = true, default-features = false, features = ["serialization", "cheater-detection"] } 35 | 36 | [dependencies.zeroize] 37 | version = "1" 38 | features = ["zeroize_derive"] 39 | optional = true 40 | 41 | [dev-dependencies] 42 | bincode = "1" 43 | criterion = "0.5" 44 | hex = "0.4.3" 45 | proptest-derive = "0.5" 46 | lazy_static = "1.5" 47 | proptest = "1.6" 48 | rand = "0.8" 49 | rand_chacha = "0.3" 50 | serde_json = "1.0" 51 | num-bigint = "0.4.6" 52 | num-traits = "0.2.19" 53 | frost-rerandomized = { version = "2.1.0", features = ["test-impl"] } 54 | 55 | # `alloc` is only used in test code 56 | [dev-dependencies.pasta_curves] 57 | version = "0.5" 58 | default-features = false 59 | features = ["alloc"] 60 | 61 | [features] 62 | std = ["blake2b_simd/std", "thiserror", "zeroize", "alloc", "frost-rerandomized?/std", 63 | "serde"] # conditional compilation for serde not complete (issue #9) 64 | alloc = ["hex"] 65 | nightly = [] 66 | frost = ["frost-rerandomized", "alloc"] 67 | serde = ["dep:serde", "frost-rerandomized?/serde"] 68 | default = ["std"] 69 | 70 | [[bench]] 71 | name = "bench" 72 | harness = false 73 | 74 | [[bench]] 75 | name = "frost" 76 | harness = false 77 | -------------------------------------------------------------------------------- /DEVELOPERS.md: -------------------------------------------------------------------------------- 1 | ## Release Checklist 2 | 3 | - Bump version in Cargo.toml 4 | - Update CHANGELOG.md 5 | - Ensure the MSRV in Cargo.toml (`rust-version` key) is equal to the MSRV being 6 | tested (main.yml) 7 | - Update locked dependencies: `cargo update`. Run `cargo test --all-features` 8 | to check if anything breaks. If that happens, see next section. 9 | - Test if it's publishable: `cargo publish --dry-run` 10 | - Open a PR with the version bump and changelog update, wait for review and merge 11 | - Tag a new release in GitHub: https://github.com/ZcashFoundation/reddsa/releases/new 12 | - Create a tag with the version (e.g. `0.5.1`) 13 | - Name: e.g. `0.5.1` 14 | - Paste the changelog for the version 15 | - Publish: `cargo publish` 16 | 17 | 18 | ## FROST 19 | 20 | FROST support is optional and enabled by the `frost` feature. It's not currently 21 | bound by the crate MSRV at this moment. 22 | 23 | 24 | ## If something breaks 25 | 26 | If testing broke after running `cargo update`, first determine if it's a 27 | test-only dependency or not. Run `cargo build`. If that works, 28 | then it's probably a test-only dependency, and you can avoid updating that 29 | specific dependency (leave a old version in the lockfile). Otherwise investigate 30 | why it caused build to fail. 31 | 32 | If the "test on nightly" test failed, then either there is some bug in the code 33 | or some dependency update caused it to fail. Investigate and if it's the latter, 34 | you can either downgrade in the lockfile or try to workaround it. 35 | 36 | If the "build with no_std" test failed, then some change was introduced that 37 | depended on the std-library. You will probably need to fix this by changing 38 | to some no_std dependency, or gating the code so it only compiles when 39 | `std` is enabled. 40 | 41 | If one of the dependencies bumped its MSRV, we might require a MSRV bump too: 42 | 43 | - Double check if the dependency is not a test-only dependency. (The MSRV 44 | test in CI only builds the library but does not test it, to prevent 45 | a test-only dependency MSRV bump from breaking it.) 46 | - If it's not a test-only dependency, check if the main consumers of the 47 | library are OK with a MSRV bump. I usually ask ECC devs. 48 | - If it's OK, bump it in Cargo.toml and main.yml. 49 | - If not, you will need to find some workaround. 50 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:stretch as base 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y --no-install-recommends \ 5 | make cmake g++ gcc 6 | 7 | RUN mkdir /reddsa 8 | WORKDIR /reddsa 9 | 10 | ENV RUST_BACKTRACE 1 11 | ENV CARGO_HOME /reddsa/.cargo/ 12 | 13 | # Copy local code to the container image. 14 | # Assumes that we are in the git repo. 15 | 16 | COPY . . 17 | 18 | RUN cargo fetch --verbose 19 | 20 | COPY . . 21 | 22 | RUN rustc -V; cargo -V; rustup -V; cargo test --all && cargo build --release 23 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | This software is licensed optionally under either the MIT license or Apache 2.0 2 | license, the full text of which may be found respectively in the LICENCE.MIT and 3 | LICENCE.Apache-2.0 files contained within this software distribution. 4 | 5 | ============================================================================== 6 | 7 | Portions of reddsa are taken from curve25519-dalek, which can be found at 8 | , under the following 9 | license. This implementation does NOT use the portions of curve25519-dalek 10 | which were originally derived from Adam Langley's Go edwards25519 11 | implementation, and, as such, that portion of the curve25519-dalek license is 12 | omitted here. 13 | 14 | ============================================================================== 15 | 16 | Copyright (c) 2016-2021 Isis Agora Lovecruft, Henry de Valence. All rights reserved. 17 | 18 | Redistribution and use in source and binary forms, with or without 19 | modification, are permitted provided that the following conditions are 20 | met: 21 | 22 | 1. Redistributions of source code must retain the above copyright 23 | notice, this list of conditions and the following disclaimer. 24 | 25 | 2. Redistributions in binary form must reproduce the above copyright 26 | notice, this list of conditions and the following disclaimer in the 27 | documentation and/or other materials provided with the distribution. 28 | 29 | 3. Neither the name of the copyright holder nor the names of its 30 | contributors may be used to endorse or promote products derived from 31 | this software without specific prior written permission. 32 | 33 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 34 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 35 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 36 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 37 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 39 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 40 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 41 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 42 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 43 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44 | -------------------------------------------------------------------------------- /LICENCE.MIT: -------------------------------------------------------------------------------- 1 | Copyright 2019-2021 Zcash Foundation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /LICENSE.Apache-2.0: -------------------------------------------------------------------------------- 1 | Copyright 2019-2021 Zcash Foundation 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A minimal [RedDSA][reddsa] implementation for use in Zcash. 2 | 3 | Two specializations of RedDSA are used in Zcash: RedJubjub and 4 | RedPallas. For each of these, two parameterizations are used, one for 5 | `BindingSig` and one for `SpendAuthSig`. This library distinguishes 6 | these in the type system, using the [sealed] `SigType` trait as a 7 | type-level enum. 8 | 9 | In addition to the `Signature`, `SigningKey`, `VerificationKey` types, 10 | the library also provides `VerificationKeyBytes`, a [refinement] of a 11 | `[u8; 32]` indicating that bytes represent an encoding of a RedDSA 12 | verification key. This allows the `VerificationKey` type to cache 13 | verification checks related to the verification key encoding. 14 | For all specializations of RedDSA used in Zcash, encodings of signing 15 | and verification keys are 32 bytes. 16 | 17 | ## Examples 18 | 19 | Creating a `BindingSig`, serializing and deserializing it, and 20 | verifying the signature: 21 | 22 | ```rust 23 | # use std::convert::TryFrom; 24 | use rand::thread_rng; 25 | use reddsa::*; 26 | 27 | let msg = b"Hello!"; 28 | 29 | // Generate a secret key and sign the message 30 | let sk = SigningKey::::new(thread_rng()); 31 | let sig = sk.sign(thread_rng(), msg); 32 | 33 | // Types can be converted to raw byte arrays using From/Into 34 | let sig_bytes: [u8; 64] = sig.into(); 35 | let pk_bytes: [u8; 32] = VerificationKey::from(&sk).into(); 36 | 37 | // Deserialize and verify the signature. 38 | let sig: Signature = sig_bytes.into(); 39 | assert!( 40 | VerificationKey::try_from(pk_bytes) 41 | .and_then(|pk| pk.verify(msg, &sig)) 42 | .is_ok() 43 | ); 44 | ``` 45 | 46 | ## FROST 47 | 48 | You can enable ZIP-312 re-randomized FROST support with the `frost` feature. 49 | This is still experimental since ZIP-312 is still a draft. 50 | 51 | ## docs 52 | 53 | ```shell,no_run 54 | cargo doc --features "nightly" --open 55 | ``` 56 | 57 | ## Developers guide 58 | 59 | See [DEVELOPERS.md](DEVELOPERS.md). 60 | 61 | 62 | [reddsa]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa 63 | [zebra]: https://github.com/ZcashFoundation/zebra 64 | [refinement]: https://en.wikipedia.org/wiki/Refinement_type 65 | [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed 66 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; 2 | 3 | use rand::{thread_rng, Rng}; 4 | use reddsa::*; 5 | use std::convert::TryFrom; 6 | 7 | enum Item { 8 | SpendAuth { 9 | vk_bytes: VerificationKeyBytes, 10 | sig: Signature, 11 | }, 12 | Binding { 13 | vk_bytes: VerificationKeyBytes, 14 | sig: Signature, 15 | }, 16 | } 17 | 18 | fn sigs_with_distinct_keys() -> impl Iterator { 19 | std::iter::repeat_with(|| { 20 | let mut rng = thread_rng(); 21 | let msg = b"Bench"; 22 | match rng.gen::() % 2 { 23 | 0 => { 24 | let sk = SigningKey::::new(thread_rng()); 25 | let vk_bytes = VerificationKey::from(&sk).into(); 26 | let sig = sk.sign(thread_rng(), &msg[..]); 27 | Item::SpendAuth { vk_bytes, sig } 28 | } 29 | 1 => { 30 | let sk = SigningKey::::new(thread_rng()); 31 | let vk_bytes = VerificationKey::from(&sk).into(); 32 | let sig = sk.sign(thread_rng(), &msg[..]); 33 | Item::Binding { vk_bytes, sig } 34 | } 35 | _ => panic!(), 36 | } 37 | }) 38 | } 39 | 40 | fn bench_batch_verify(c: &mut Criterion) { 41 | let mut group = c.benchmark_group("Batch Verification"); 42 | for &n in [8usize, 16, 24, 32, 40, 48, 56, 64].iter() { 43 | group.throughput(Throughput::Elements(n as u64)); 44 | 45 | let sigs = sigs_with_distinct_keys().take(n).collect::>(); 46 | 47 | group.bench_with_input( 48 | BenchmarkId::new("Unbatched verification", n), 49 | &sigs, 50 | |b, sigs| { 51 | b.iter(|| { 52 | for item in sigs.iter() { 53 | let msg = b"Bench"; 54 | match item { 55 | Item::SpendAuth { vk_bytes, sig } => { 56 | let _ = VerificationKey::try_from(*vk_bytes) 57 | .and_then(|vk| vk.verify(msg, sig)); 58 | } 59 | Item::Binding { vk_bytes, sig } => { 60 | let _ = VerificationKey::try_from(*vk_bytes) 61 | .and_then(|vk| vk.verify(msg, sig)); 62 | } 63 | } 64 | } 65 | }) 66 | }, 67 | ); 68 | 69 | group.bench_with_input( 70 | BenchmarkId::new("Batched verification", n), 71 | &sigs, 72 | |b, sigs| { 73 | b.iter(|| { 74 | let mut batch = batch::Verifier::new(); 75 | for item in sigs.iter() { 76 | let msg = b"Bench"; 77 | match item { 78 | Item::SpendAuth { vk_bytes, sig } => { 79 | batch.queue(batch::Item::from_spendauth(*vk_bytes, *sig, msg)); 80 | } 81 | Item::Binding { vk_bytes, sig } => { 82 | batch.queue(batch::Item::from_binding(*vk_bytes, *sig, msg)); 83 | } 84 | } 85 | } 86 | batch.verify(thread_rng()) 87 | }) 88 | }, 89 | ); 90 | } 91 | group.finish(); 92 | } 93 | 94 | criterion_group!(benches, bench_batch_verify); 95 | criterion_main!(benches); 96 | -------------------------------------------------------------------------------- /benches/frost.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 2 | use rand::thread_rng; 3 | use reddsa::frost::redpallas::PallasBlake2b512; 4 | 5 | use std::collections::BTreeMap; 6 | 7 | use rand_core::{CryptoRng, RngCore}; 8 | 9 | use frost_rerandomized::frost_core::Ciphersuite; 10 | use frost_rerandomized::{frost_core as frost, RandomizedParams}; 11 | 12 | /// Benchmark FROST signing with the specified ciphersuite. 13 | fn bench_rerandomized_sign< 14 | C: Ciphersuite + frost_rerandomized::RandomizedCiphersuite, 15 | R: RngCore + CryptoRng + Clone, 16 | >( 17 | c: &mut Criterion, 18 | name: &str, 19 | mut rng: &mut R, 20 | ) { 21 | let mut group = c.benchmark_group(format!("Rerandomized FROST Signing {name}")); 22 | for &n in [3u16, 10, 100, 1000].iter() { 23 | let max_signers = n; 24 | let min_signers = (n * 2 + 2) / 3; 25 | 26 | group.bench_with_input( 27 | BenchmarkId::new("Key Generation with Dealer", max_signers), 28 | &(max_signers, min_signers), 29 | |b, (max_signers, min_signers)| { 30 | let mut rng = rng.clone(); 31 | b.iter(|| { 32 | frost::keys::generate_with_dealer::( 33 | *max_signers, 34 | *min_signers, 35 | frost::keys::IdentifierList::Default, 36 | &mut rng, 37 | ) 38 | .unwrap(); 39 | }) 40 | }, 41 | ); 42 | 43 | let (shares, pubkeys) = frost::keys::generate_with_dealer::( 44 | max_signers, 45 | min_signers, 46 | frost::keys::IdentifierList::Default, 47 | rng, 48 | ) 49 | .unwrap(); 50 | 51 | // Verifies the secret shares from the dealer 52 | let mut key_packages: BTreeMap, frost::keys::KeyPackage> = 53 | BTreeMap::new(); 54 | 55 | for (k, v) in shares { 56 | key_packages.insert(k, frost::keys::KeyPackage::try_from(v).unwrap()); 57 | } 58 | 59 | group.bench_with_input( 60 | BenchmarkId::new("Round 1", min_signers), 61 | &key_packages, 62 | |b, key_packages| { 63 | let mut rng = rng.clone(); 64 | b.iter(|| { 65 | let participant_identifier = 1u16.try_into().expect("should be nonzero"); 66 | frost::round1::commit( 67 | key_packages 68 | .get(&participant_identifier) 69 | .unwrap() 70 | .signing_share(), 71 | &mut rng, 72 | ); 73 | }) 74 | }, 75 | ); 76 | 77 | let mut nonces: BTreeMap<_, _> = BTreeMap::new(); 78 | let mut commitments: BTreeMap<_, _> = BTreeMap::new(); 79 | 80 | for participant_index in 1..=min_signers { 81 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 82 | let (nonce, commitment) = frost::round1::commit( 83 | key_packages 84 | .get(&participant_identifier) 85 | .unwrap() 86 | .signing_share(), 87 | &mut rng, 88 | ); 89 | nonces.insert(participant_identifier, nonce); 90 | commitments.insert(participant_identifier, commitment); 91 | } 92 | 93 | let message = "message to sign".as_bytes(); 94 | let signing_package = frost::SigningPackage::new(commitments, message); 95 | let randomizer_params = frost_rerandomized::RandomizedParams::new( 96 | pubkeys.verifying_key(), 97 | &signing_package, 98 | &mut rng, 99 | ) 100 | .unwrap(); 101 | let randomizer = *randomizer_params.randomizer(); 102 | 103 | group.bench_with_input( 104 | BenchmarkId::new("Round 2", min_signers), 105 | &( 106 | key_packages.clone(), 107 | nonces.clone(), 108 | signing_package.clone(), 109 | ), 110 | |b, (key_packages, nonces, signing_package)| { 111 | b.iter(|| { 112 | let participant_identifier = 1u16.try_into().expect("should be nonzero"); 113 | let key_package = key_packages.get(&participant_identifier).unwrap(); 114 | let nonces_to_use = &nonces.get(&participant_identifier).unwrap(); 115 | frost_rerandomized::sign( 116 | signing_package, 117 | nonces_to_use, 118 | key_package, 119 | *randomizer_params.randomizer(), 120 | ) 121 | .unwrap(); 122 | }) 123 | }, 124 | ); 125 | 126 | let mut signature_shares = BTreeMap::new(); 127 | for participant_identifier in nonces.keys() { 128 | let key_package = key_packages.get(participant_identifier).unwrap(); 129 | let nonces_to_use = &nonces.get(participant_identifier).unwrap(); 130 | let signature_share = frost_rerandomized::sign( 131 | &signing_package, 132 | nonces_to_use, 133 | key_package, 134 | *randomizer_params.randomizer(), 135 | ) 136 | .unwrap(); 137 | signature_shares.insert(*key_package.identifier(), signature_share); 138 | } 139 | 140 | group.bench_with_input( 141 | BenchmarkId::new("Aggregate", min_signers), 142 | &(signing_package.clone(), signature_shares.clone(), pubkeys), 143 | |b, (signing_package, signature_shares, pubkeys)| { 144 | b.iter(|| { 145 | // We want to include the time to generate the randomizer 146 | // params for the Coordinator. Since Aggregate is the only 147 | // Coordinator timing, we include it here. 148 | let randomizer_params = 149 | RandomizedParams::from_randomizer(pubkeys.verifying_key(), randomizer); 150 | frost_rerandomized::aggregate( 151 | signing_package, 152 | signature_shares, 153 | pubkeys, 154 | &randomizer_params, 155 | ) 156 | .unwrap(); 157 | }) 158 | }, 159 | ); 160 | } 161 | group.finish(); 162 | } 163 | 164 | fn bench_sign_redpallas(c: &mut Criterion) { 165 | let mut rng = thread_rng(); 166 | 167 | frost_rerandomized::frost_core::benches::bench_sign::( 168 | c, 169 | "redpallas", 170 | &mut rng, 171 | ); 172 | } 173 | 174 | fn bench_rerandomized_sign_redpallas(c: &mut Criterion) { 175 | let mut rng = thread_rng(); 176 | 177 | bench_rerandomized_sign::(c, "redpallas", &mut rng); 178 | } 179 | 180 | criterion_group!( 181 | benches, 182 | bench_sign_redpallas, 183 | bench_rerandomized_sign_redpallas 184 | ); 185 | criterion_main!(benches); 186 | -------------------------------------------------------------------------------- /cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - name: 'gcr.io/kaniko-project/executor:latest' 3 | args: 4 | - --destination=gcr.io/$PROJECT_ID/$BRANCH_NAME 5 | - --cache=true 6 | - --cache-ttl=24h 7 | 8 | options: 9 | machineType: 'N1_HIGHCPU_32' 10 | 11 | timeout: 3600s # One hour for all steps. 12 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "50...100" 3 | status: 4 | project: 5 | default: 6 | informational: true 7 | patch: 8 | default: 9 | informational: true 10 | 11 | parsers: 12 | gcov: 13 | branch_detection: 14 | conditional: yes 15 | loop: yes 16 | method: yes 17 | macro: yes 18 | 19 | comment: 20 | layout: "diff" 21 | # post a coverage comment, even if coverage hasn't changed 22 | require_changes: false 23 | 24 | github_checks: 25 | annotations: false 26 | -------------------------------------------------------------------------------- /rfcs/0001-messages.md: -------------------------------------------------------------------------------- 1 | # FROST messages 2 | 3 | Proposes a message layout to exchange information between participants of a FROST setup using the [jubjub](https://github.com/zkcrypto/jubjub) curve. 4 | 5 | ## Motivation 6 | 7 | Currently FROST library is complete for 2 round signatures with a dealer/aggregator setup. 8 | This proposal acknowledges that specific features, additions and upgrades will need to be made when DKG is implemented. 9 | 10 | Assuming all participants have a FROST library available, we need to define message structures in a way that data can be exchanged between participants. The proposal is a collection of data types so each side can do all the actions needed for a real life situation. 11 | 12 | ## Definitions 13 | 14 | - `dealer` - Participant who distributes the initial package to all the other participants. 15 | - `aggregator` - Participant in charge of collecting all the signatures from the other participants and generating the final group signature. 16 | - `signer` - Participant that will receive the initial package, sign and send the signature to the aggregator to receive the final group signature. 17 | 18 | Note: In this RFC we consider the above 3 participants to be different. `dealer` and `aggregator` have specific hard coded `ParticipantId`s, so for example a `dealer` can't be a `signer`. This is not a protocol limitation but a specific rule introduced in this document. 19 | 20 | ## Guide-level explanation 21 | 22 | We propose a message separated in 2 parts, a header and a payload: 23 | 24 | ```rust 25 | /// The data required to serialize a frost message. 26 | struct Message { 27 | header: Header, 28 | payload: Payload, 29 | } 30 | ``` 31 | 32 | `Header` will look as follows: 33 | 34 | ```rust 35 | /// The data required to serialize the common header fields for every message. 36 | /// 37 | /// Note: the `msg_type` is derived from the `payload` enum variant. 38 | struct Header { 39 | version: MsgVersion, 40 | sender: ParticipantId, 41 | receiver: ParticipantId, 42 | } 43 | ``` 44 | 45 | While `Payload` will be defined as: 46 | 47 | ```rust 48 | /// The data required to serialize the payload for a message. 49 | enum Payload { 50 | SharePackage(messages::SharePackage), 51 | SigningCommitments(messages::SigningCommitments), 52 | SigningPackage(messages::SigningPackage), 53 | SignatureShare(messages::SignatureShare), 54 | AggregateSignature(messages::AggregateSignature), 55 | } 56 | ``` 57 | 58 | All the messages and new types will be defined in a new file `src/frost/messages.rs` 59 | 60 | ## Reference-level explanation 61 | 62 | Here we explore in detail the header types and all the message payloads. 63 | 64 | ### Header 65 | 66 | Fields of the header define new types. Proposed implementation for them is as follows: 67 | 68 | ```rust 69 | /// The numeric values used to identify each `Payload` variant during serialization. 70 | #[repr(u8)] 71 | #[non_exhaustive] 72 | enum MsgType { 73 | SharePackage, 74 | SigningCommitments, 75 | SigningPackage, 76 | SignatureShare, 77 | AggregateSignature, 78 | } 79 | 80 | /// The numeric values used to identify the protocol version during serialization. 81 | struct MsgVersion(u8); 82 | 83 | const BASIC_FROST_SERIALIZATION: MsgVersion = MsgVersion(0); 84 | 85 | /// The numeric values used to identify each participant during serialization. 86 | /// 87 | /// In the `frost` module, participant ID `0` should be invalid. 88 | /// But in serialization, we want participants to be indexed from `0..n`, 89 | /// where `n` is the number of participants. 90 | /// This helps us look up their shares and commitments in serialized arrays. 91 | /// So in serialization, we assign the dealer and aggregator the highest IDs, 92 | /// and mark those IDs as invalid for signers. Then we serialize the 93 | /// participants in numeric order of their FROST IDs. 94 | /// 95 | /// "When performing Shamir secret sharing, a polynomial `f(x)` is used to generate 96 | /// each party’s share of the secret. The actual secret is `f(0)` and the party with 97 | /// ID `i` will be given a share with value `f(i)`. 98 | /// Since a DKG may be implemented in the future, we recommend that the ID `0` be declared invalid." 99 | /// https://raw.githubusercontent.com/ZcashFoundation/redjubjub/main/zcash-frost-audit-report-20210323.pdf#d 100 | enum ParticipantId { 101 | /// A serialized participant ID for a signer. 102 | /// 103 | /// Must be less than or equal to `MAX_SIGNER_PARTICIPANT_ID`. 104 | Signer(u64), 105 | /// The fixed participant ID for the dealer. 106 | Dealer, 107 | /// The fixed participant ID for the aggregator. 108 | Aggregator, 109 | } 110 | 111 | /// The fixed participant ID for the dealer. 112 | const DEALER_PARTICIPANT_ID: u64 = u64::MAX - 1; 113 | 114 | /// The fixed participant ID for the aggregator. 115 | const AGGREGATOR_PARTICIPANT_ID: u64 = u64::MAX; 116 | 117 | /// The maximum `ParticipantId::Signer` in this serialization format. 118 | /// 119 | /// We reserve two participant IDs for the dealer and aggregator. 120 | const MAX_SIGNER_PARTICIPANT_ID: u64 = u64::MAX - 2; 121 | ``` 122 | 123 | ### Payloads 124 | 125 | Each payload defines a new message: 126 | 127 | ```rust 128 | /// The data required to serialize `frost::SharePackage`. 129 | /// 130 | /// The dealer sends this message to each signer for this round. 131 | /// With this, the signer should be able to build a `SharePackage` and use 132 | /// the `sign()` function. 133 | /// 134 | /// Note: `frost::SharePackage.public` can be calculated from `secret_share`. 135 | struct messages::SharePackage { 136 | /// The public signing key that represents the entire group: 137 | /// `frost::SharePackage.group_public`. 138 | group_public: VerificationKey, 139 | /// This participant's secret key share: `frost::SharePackage.share.value`. 140 | secret_share: frost::Secret, 141 | /// The commitments to the coefficients for our secret polynomial _f_, 142 | /// used to generate participants' key shares. Participants use these to perform 143 | /// verifiable secret sharing. 144 | /// Share packages that contain duplicate or missing `ParticipantId`s are invalid. 145 | /// `ParticipantId`s must be serialized in ascending numeric order. 146 | share_commitment: BTreeMap, 147 | } 148 | 149 | /// The data required to serialize `frost::SigningCommitments`. 150 | /// 151 | /// Each signer must send this message to the aggregator. 152 | /// A signing commitment from the first round of the signing protocol. 153 | struct messages::SigningCommitments { 154 | /// The hiding point: `frost::SigningCommitments.hiding` 155 | hiding: frost::Commitment, 156 | /// The binding point: `frost::SigningCommitments.binding` 157 | binding: frost::Commitment, 158 | } 159 | 160 | /// The data required to serialize `frost::SigningPackage`. 161 | /// 162 | /// The aggregator decides what message is going to be signed and 163 | /// sends it to each signer with all the commitments collected. 164 | struct messages::SigningPackage { 165 | /// The collected commitments for each signer as an ordered map of 166 | /// unique participant identifiers: `frost::SigningPackage.signing_commitments` 167 | /// 168 | /// Signing packages that contain duplicate or missing `ParticipantId`s are invalid. 169 | /// `ParticipantId`s must be serialized in ascending numeric order. 170 | signing_commitments: BTreeMap, 171 | /// The message to be signed: `frost::SigningPackage.message`. 172 | /// 173 | /// Each signer should perform protocol-specific verification on the message. 174 | message: Vec, 175 | } 176 | 177 | /// The data required to serialize `frost::SignatureShare`. 178 | /// 179 | /// Each signer sends their signatures to the aggregator who is going to collect them 180 | /// and generate a final spend signature. 181 | struct messages::SignatureShare { 182 | /// This participant's signature over the message: `frost::SignatureShare.signature` 183 | signature: frost::SignatureResponse, 184 | } 185 | 186 | /// The data required to serialize a successful output from `frost::aggregate()`. 187 | /// 188 | /// The final signature is broadcasted by the aggregator to all signers. 189 | struct messages::AggregateSignature { 190 | /// The aggregated group commitment: `Signature.r_bytes` returned by `frost::aggregate` 191 | group_commitment: frost::GroupCommitment, 192 | /// A plain Schnorr signature created by summing all the signature shares: 193 | /// `Signature.s_bytes` returned by `frost::aggregate` 194 | schnorr_signature: frost::SignatureResponse, 195 | } 196 | ``` 197 | 198 | ## Validation 199 | 200 | Validation is implemented to each new data type as needed. This will ensure the creation of valid messages before they are send and right after they are received. We create a trait for this as follows: 201 | 202 | ```rust 203 | pub trait Validate { 204 | fn validate(&self) -> Result<&Self, MsgErr>; 205 | } 206 | ``` 207 | 208 | And we implement where needed. For example, in the header, sender and receiver can't be the same: 209 | 210 | ```rust 211 | impl Validate for Header { 212 | fn validate(&self) -> Result<&Self, MsgErr> { 213 | if self.sender.0 == self.receiver.0 { 214 | return Err(MsgErr::SameSenderAndReceiver); 215 | } 216 | Ok(self) 217 | } 218 | } 219 | ``` 220 | 221 | This will require to have validation error messages as: 222 | 223 | ```rust 224 | use thiserror::Error; 225 | 226 | #[derive(Clone, Error, Debug)] 227 | pub enum MsgErr { 228 | #[error("sender and receiver are the same")] 229 | SameSenderAndReceiver, 230 | } 231 | ``` 232 | 233 | Then to create a valid `Header` in the sender side we call: 234 | 235 | ```rust 236 | let header = Validate::validate(&Header { 237 | .. 238 | }).expect("a valid header"); 239 | ``` 240 | 241 | The receiver side will validate the header using the same method. Instead of panicking the error can be ignored to don't crash and keep waiting for other (potentially valid) messages. 242 | 243 | ```rust 244 | if let Ok(header) = msg.header.validate() { 245 | .. 246 | } 247 | ``` 248 | 249 | ### Rules 250 | 251 | The following rules must be implemented: 252 | 253 | #### Header 254 | 255 | - `version` must be a supported version. 256 | - `sender` and `receiver` can't be the same. 257 | - The `ParticipantId` variants of `sender` and `receiver` must match the message type. 258 | 259 | #### Payloads 260 | 261 | - Each jubjub type must be validated during deserialization. 262 | - `share_commitments`: 263 | - Length must be less than or equal to `MAX_SIGNER_PARTICIPANT_ID`. 264 | - Length must be at least `MIN_SIGNERS` (`2` signers). 265 | - Duplicate `ParticipantId`s are invalid. This is implicit in the use of `BTreeMap` during serialization, but must be checked during deserialization. 266 | - Commitments must be serialized in ascending numeric `ParticipantId` order. This is the order of `BTreeMap.iter` during serialization, but must be checked during deserialization. 267 | - `signing_commitments`: 268 | - Length must be less than or equal to `MAX_SIGNER_PARTICIPANT_ID`. 269 | - Length must be at least `MIN_THRESHOLD` (`2` required signers). 270 | - Signing packages that contain duplicate `ParticipantId`s are invalid. This is implicit in the use of `BTreeMap` during serialization, but must be checked during deserialization. 271 | - Signing packages must serialize in ascending numeric `ParticipantId` order. This is the order of `BTreeMap.iter` during serialization, but must be checked during deserialization.. 272 | - `message`: signed messages have a protocol-specific length limit. For Zcash, that limit is the maximum network protocol message length: `2^21` bytes (2 MB). 273 | 274 | ## Serialization/Deserialization 275 | 276 | Each message struct needs to serialize to bytes representation before it is sent through the wire and must deserialize to the same struct (round trip) on the receiver side. We use `serde` and macro derivations (`Serialize` and `Deserialize`) to automatically implement where possible. 277 | 278 | This will require deriving serde in several types defined in `frost.rs`. 279 | Manual implementation of serialization/deserialization will be located at a new mod `src/frost/serialize.rs`. 280 | 281 | ### Byte order 282 | 283 | Each byte chunk specified below is in little-endian order unless is specified otherwise. 284 | 285 | Multi-byte integers **must not** be used for serialization, because they have different byte orders on different platforms. 286 | 287 | ### Header 288 | 289 | The `Header` part of the message is 18 bytes total: 290 | 291 | Bytes | Field name | Data type 292 | ------|------------|----------- 293 | 1 | version | u8 294 | 1 | msg_type | u8 295 | 8 | sender | u64 296 | 8 | receiver | u64 297 | 298 | ### Frost types 299 | 300 | The FROST types we will be using in the messages can be represented always as a primitive type. For serialization/deserialization purposes: 301 | 302 | - `Commitment` = `AffinePoint` 303 | - `Secret` = `Scalar` 304 | - `GroupCommitment` = `AffinePoint` 305 | - `SignatureResponse` = `Scalar` 306 | 307 | ### Primitive types 308 | 309 | `Payload`s use data types that we need to specify first. We have 3 primitive types inside the payload messages: 310 | 311 | #### `Scalar` 312 | 313 | `jubjub::Scalar` is a an alias for `jubjub::Fr`. We use `Scalar::to_bytes` and `Scalar::from_bytes` to get a 32-byte little-endian canonical representation. See https://github.com/zkcrypto/bls12_381/blob/main/src/scalar.rs#L260 and https://github.com/zkcrypto/bls12_381/blob/main/src/scalar.rs#L232 314 | 315 | #### `AffinePoint` 316 | 317 | Much of the math in FROST is done using `jubjub::ExtendedPoint`. But for message exchange `jubjub::AffinePoint`s are a better choice, as their byte representation is smaller. 318 | 319 | Conversion from one type to the other is trivial: 320 | 321 | https://docs.rs/jubjub/0.6.0/jubjub/struct.AffinePoint.html#impl-From%3CExtendedPoint%3E 322 | https://docs.rs/jubjub/0.6.0/jubjub/struct.ExtendedPoint.html#impl-From%3CAffinePoint%3E 323 | 324 | We use `AffinePoint::to_bytes` and `AffinePoint::from_bytes` to get a 32-byte little-endian canonical representation. See https://github.com/zkcrypto/jubjub/blob/main/src/lib.rs#L443 325 | 326 | #### VerificationKey 327 | 328 | `redjubjub::VerificationKey`s can be serialized and deserialized using `<[u8; 32]>::from` and `VerificationKey::from`. See https://github.com/ZcashFoundation/redjubjub/blob/main/src/verification_key.rs#L80-L90 and https://github.com/ZcashFoundation/redjubjub/blob/main/src/verification_key.rs#L114-L121. 329 | 330 | ### Payload 331 | 332 | Payload part of the message is variable in size and depends on message type. 333 | 334 | #### `SharePackage` 335 | 336 | Bytes | Field name | Data type 337 | ----------------|------------------|----------- 338 | 32 | group_public | VerificationKey 339 | 32 | secret_share | Share 340 | 1 | participants | u8 341 | (8+32)*participants | share_commitment | BTreeMap 342 | 343 | #### `SigningCommitments` 344 | 345 | Bytes | Field name | Data type 346 | --------|---------------------|----------- 347 | 32 | hiding | Commitment 348 | 32 | binding | Commitment 349 | 350 | #### `SigningPackage` 351 | 352 | Bytes | Field name | Data type 353 | -----------------------|--------------------|----------- 354 | 1 | participants | u8 355 | (8+32+32)*participants | signing_commitments| BTreeMap 356 | 8 | message_length | u64 357 | message_length | message | Vec\ 358 | 359 | 360 | #### `SignatureShare` 361 | 362 | Bytes | Field name | Data type 363 | ------|------------|----------- 364 | 32 | signature | SignatureResponse 365 | 366 | #### `AggregateSignature` 367 | 368 | Bytes | Field name | Data type 369 | ------|------------------|----------- 370 | 32 | group_commitment | GroupCommitment 371 | 32 | schnorr_signature| SignatureResponse 372 | 373 | ## Not included 374 | 375 | The following are a few things this RFC is not considering: 376 | 377 | - The RFC does not describe implementation-specific issues - it is focused on message structure and serialization. 378 | - Implementations using this serialization should handle missing messages using timeouts or similar protocol-specific mechanisms. 379 | - This is particularly important for `SigningPackage`s, which only need a threshold of participants to continue. 380 | - Messages larger than 4 GB are not supported on 32-bit platforms. 381 | - Implementations should validate that message lengths are lower than a protocol-specific maximum length, then allocate message memory. 382 | - Implementations should distinguish between FROST messages from different signature schemes using implementation-specific mechanisms. 383 | 384 | ### State-Based Validation 385 | 386 | The following validation rules should be checked by the implementation: 387 | 388 | - `share_commitments`: The number of participants in each round is set by the length of `share_commitments`. 389 | - If `sender` and `receiver` are a `ParticipantId::Signer`, they must be less than the number of participants in this round. 390 | - The length of `signing_commitments` must be less than or equal to the number of participants in this round. 391 | - `signing_commitments`: Signing packages that contain missing `ParticipantId`s are invalid 392 | - Note: missing participants are supported by this serialization format. 393 | But implementations can require all participants to fully participate in each round. 394 | 395 | If the implementation knows the number of key shares, it should re-check all the validation rules involving `MAX_SIGNER_PARTICIPANT_ID` using that lower limit. 396 | 397 | ## Testing plan 398 | 399 | ### Test Vectors 400 | 401 | #### Conversion on Test Vectors 402 | 403 | - Test conversion from `frost` to `message` on a test vector 404 | 1. Implement the Rust `message` struct 405 | 2. Implement conversion from and to the `frost` type 406 | 3. Do a round-trip test from `frost` to `message` on a test vector 407 | - Test conversion from `message` to bytes on a test vector 408 | 1. Implement conversion from and to the `message` type 409 | 2. Do a round-trip test from `message` to bytes on a test vector 410 | 411 | #### Signing Rounds on Test Vectors 412 | 413 | - Test signing using `frost` types on a test vector 414 | 1. Implement a single round of `frost` signing using a test vector 415 | - Test signing using `message` types on a test vector 416 | - Test signing using byte vectors on a test vector 417 | 418 | ### Property Tests 419 | 420 | #### Conversion Property Tests 421 | 422 | - Create property tests for each message 423 | - Test round-trip conversion from `frost` to `message` types 424 | - Test round-trip serialization and deserialization for each `message` type 425 | 426 | #### Signing Round Property Tests 427 | 428 | - Create property tests for signing rounds 429 | - Test a signing round with `frost` types 430 | - Test a signing round with `message` types 431 | - Test a signing round with byte vectors 432 | -------------------------------------------------------------------------------- /src/batch.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - Deirdre Connolly 9 | // - Henry de Valence 10 | 11 | //! Performs batch RedDSA signature verification. 12 | //! 13 | //! Batch verification asks whether *all* signatures in some set are valid, 14 | //! rather than asking whether *each* of them is valid. This allows sharing 15 | //! computations among all signature verifications, performing less work overall 16 | //! at the cost of higher latency (the entire batch must complete), complexity of 17 | //! caller code (which must assemble a batch of signatures across work-items), 18 | //! and loss of the ability to easily pinpoint failing signatures. 19 | //! 20 | 21 | use alloc::vec::Vec; 22 | use core::convert::TryFrom; 23 | 24 | use group::{ 25 | cofactor::CofactorGroup, 26 | ff::{Field, PrimeField}, 27 | GroupEncoding, 28 | }; 29 | use rand_core::{CryptoRng, RngCore}; 30 | 31 | use crate::{private::SealedScalar, scalar_mul::VartimeMultiscalarMul, *}; 32 | 33 | /// Shim to generate a random 128 bit value in a `[u64; 4]`, without 34 | /// importing `rand`. 35 | /// 36 | /// The final 128 bits are zero. 37 | fn gen_128_bits(mut rng: R) -> [u64; 4] { 38 | let mut bytes = [0u64; 4]; 39 | bytes[0] = rng.next_u64(); 40 | bytes[1] = rng.next_u64(); 41 | bytes 42 | } 43 | 44 | /// Inner type of a batch verification item. 45 | /// 46 | /// This struct exists to allow batch processing to be decoupled from the 47 | /// lifetime of the message. This is useful when using the batch verification 48 | /// API in an async context 49 | /// 50 | /// The different enum variants are for the different signature types which use 51 | /// different basepoints for computation: SpendAuth and Binding signatures. 52 | #[derive(Clone, Debug)] 53 | enum Inner> { 54 | /// A RedDSA signature using the SpendAuth generator group element. 55 | SpendAuth { 56 | vk_bytes: VerificationKeyBytes, 57 | sig: Signature, 58 | c: S::Scalar, 59 | }, 60 | /// A RedDSA signature using the Binding generator group element. 61 | Binding { 62 | vk_bytes: VerificationKeyBytes, 63 | sig: Signature, 64 | c: B::Scalar, 65 | }, 66 | } 67 | 68 | /// A batch verification item. 69 | /// 70 | /// This struct exists to allow batch processing to be decoupled from the 71 | /// lifetime of the message. This is useful when using the batch verification API 72 | /// in an async context. 73 | #[derive(Clone, Debug)] 74 | pub struct Item> { 75 | inner: Inner, 76 | } 77 | 78 | impl> Item { 79 | /// Create a batch item from a `SpendAuth` signature. 80 | pub fn from_spendauth>( 81 | vk_bytes: VerificationKeyBytes, 82 | sig: Signature, 83 | msg: &M, 84 | ) -> Self { 85 | // Compute c now to avoid dependency on the msg lifetime. 86 | let c = HStar::::default() 87 | .update(&sig.r_bytes[..]) 88 | .update(&vk_bytes.bytes[..]) 89 | .update(msg) 90 | .finalize(); 91 | Self { 92 | inner: Inner::SpendAuth { vk_bytes, sig, c }, 93 | } 94 | } 95 | 96 | /// Create a batch item from a `Binding` signature. 97 | pub fn from_binding>( 98 | vk_bytes: VerificationKeyBytes, 99 | sig: Signature, 100 | msg: &M, 101 | ) -> Self { 102 | // Compute c now to avoid dependency on the msg lifetime. 103 | let c = HStar::::default() 104 | .update(&sig.r_bytes[..]) 105 | .update(&vk_bytes.bytes[..]) 106 | .update(msg) 107 | .finalize(); 108 | Self { 109 | inner: Inner::Binding { vk_bytes, sig, c }, 110 | } 111 | } 112 | 113 | /// Perform non-batched verification of this `Item`. 114 | /// 115 | /// This is useful (in combination with `Item::clone`) for implementing fallback 116 | /// logic when batch verification fails. In contrast to 117 | /// [`VerificationKey::verify`](crate::VerificationKey::verify), which requires 118 | /// borrowing the message data, the `Item` type is unlinked from the lifetime of 119 | /// the message. 120 | #[allow(non_snake_case)] 121 | pub fn verify_single(self) -> Result<(), Error> { 122 | match self.inner { 123 | Inner::Binding { vk_bytes, sig, c } => { 124 | VerificationKey::::try_from(vk_bytes).and_then(|vk| vk.verify_prehashed(&sig, c)) 125 | } 126 | Inner::SpendAuth { vk_bytes, sig, c } => { 127 | VerificationKey::::try_from(vk_bytes).and_then(|vk| vk.verify_prehashed(&sig, c)) 128 | } 129 | } 130 | } 131 | } 132 | 133 | /// A batch verification context. 134 | pub struct Verifier> { 135 | /// Signature data queued for verification. 136 | signatures: Vec>, 137 | } 138 | 139 | impl> Default for Verifier { 140 | fn default() -> Self { 141 | Verifier { signatures: vec![] } 142 | } 143 | } 144 | 145 | impl> Verifier { 146 | /// Construct a new batch verifier. 147 | pub fn new() -> Verifier { 148 | Verifier::default() 149 | } 150 | 151 | /// Queue an Item for verification. 152 | pub fn queue>>(&mut self, item: I) { 153 | self.signatures.push(item.into()); 154 | } 155 | 156 | /// Perform batch verification, returning `Ok(())` if all signatures were 157 | /// valid and `Err` otherwise. 158 | /// 159 | /// The batch verification equation is: 160 | /// 161 | /// h_G * ( -[sum(z_i * s_i)]P_G + sum(\[z_i\]R_i) + sum([z_i * c_i]VK_i) ) = 0_G 162 | /// 163 | /// as given in https://zips.z.cash/protocol/protocol.pdf#reddsabatchvalidate 164 | /// (the terms are split out so that we can use multiscalar multiplication speedups). 165 | /// 166 | /// where for each signature i, 167 | /// - VK_i is the verification key; 168 | /// - R_i is the signature's R value; 169 | /// - s_i is the signature's s value; 170 | /// - c_i is the hash of the message and other data; 171 | /// - z_i is a random 128-bit Scalar; 172 | /// - h_G is the cofactor of the group; 173 | /// - P_G is the generator of the subgroup; 174 | /// 175 | /// Since RedDSA uses different subgroups for different types 176 | /// of signatures, SpendAuth's and Binding's, we need to have yet 177 | /// another point and associated scalar accumulator for all the 178 | /// signatures of each type in our batch, but we can still 179 | /// amortize computation nicely in one multiscalar multiplication: 180 | /// 181 | /// h_G * ( [-sum(z_i * s_i): i_type == SpendAuth]P_SpendAuth + [-sum(z_i * s_i): i_type == Binding]P_Binding + sum(\[z_i\]R_i) + sum([z_i * c_i]VK_i) ) = 0_G 182 | /// 183 | /// As follows elliptic curve scalar multiplication convention, 184 | /// scalar variables are lowercase and group point variables 185 | /// are uppercase. This does not exactly match the RedDSA 186 | /// notation in the [protocol specification §B.1][ps]. 187 | /// 188 | /// [ps]: https://zips.z.cash/protocol/protocol.pdf#reddsabatchverify 189 | #[allow(non_snake_case)] 190 | pub fn verify(self, mut rng: R) -> Result<(), Error> { 191 | // https://p.z.cash/TCR:bad-txns-orchard-binding-signature-invalid?partial 192 | let n = self.signatures.len(); 193 | 194 | let mut VK_coeffs = Vec::with_capacity(n); 195 | let mut VKs = Vec::with_capacity(n); 196 | let mut R_coeffs = Vec::with_capacity(self.signatures.len()); 197 | let mut Rs = Vec::with_capacity(self.signatures.len()); 198 | let mut P_spendauth_coeff = S::Scalar::ZERO; 199 | let mut P_binding_coeff = B::Scalar::ZERO; 200 | 201 | for item in self.signatures.iter() { 202 | let (s_bytes, r_bytes, c) = match item.inner { 203 | Inner::SpendAuth { sig, c, .. } => (sig.s_bytes, sig.r_bytes, c), 204 | Inner::Binding { sig, c, .. } => (sig.s_bytes, sig.r_bytes, c), 205 | }; 206 | 207 | let s = { 208 | // XXX-jubjub: should not use CtOption here 209 | let mut repr = ::Repr::default(); 210 | repr.as_mut().copy_from_slice(&s_bytes); 211 | let maybe_scalar = S::Scalar::from_repr(repr); 212 | if maybe_scalar.is_some().into() { 213 | maybe_scalar.unwrap() 214 | } else { 215 | return Err(Error::InvalidSignature); 216 | } 217 | }; 218 | 219 | let R = { 220 | // XXX-jubjub: should not use CtOption here 221 | // XXX-jubjub: inconsistent ownership in from_bytes 222 | let mut repr = ::Repr::default(); 223 | repr.as_mut().copy_from_slice(&r_bytes); 224 | let maybe_point = S::Point::from_bytes(&repr); 225 | if maybe_point.is_some().into() { 226 | maybe_point.unwrap() 227 | } else { 228 | return Err(Error::InvalidSignature); 229 | } 230 | }; 231 | 232 | let VK = match item.inner { 233 | Inner::SpendAuth { vk_bytes, .. } => { 234 | VerificationKey::::try_from(vk_bytes.bytes)?.point 235 | } 236 | Inner::Binding { vk_bytes, .. } => { 237 | VerificationKey::::try_from(vk_bytes.bytes)?.point 238 | } 239 | }; 240 | 241 | let z = S::Scalar::from_raw(gen_128_bits(&mut rng)); 242 | 243 | let P_coeff = z * s; 244 | match item.inner { 245 | Inner::SpendAuth { .. } => { 246 | P_spendauth_coeff -= P_coeff; 247 | } 248 | Inner::Binding { .. } => { 249 | P_binding_coeff -= P_coeff; 250 | } 251 | }; 252 | 253 | R_coeffs.push(z); 254 | Rs.push(R); 255 | 256 | VK_coeffs.push(S::Scalar::ZERO + (z * c)); 257 | VKs.push(VK); 258 | } 259 | 260 | use core::iter::once; 261 | 262 | let scalars = once(&P_spendauth_coeff) 263 | .chain(once(&P_binding_coeff)) 264 | .chain(VK_coeffs.iter()) 265 | .chain(R_coeffs.iter()); 266 | 267 | let basepoints = [S::basepoint(), B::basepoint()]; 268 | let points = basepoints.iter().chain(VKs.iter()).chain(Rs.iter()); 269 | 270 | let check = S::Point::vartime_multiscalar_mul(scalars, points); 271 | 272 | if check.is_small_order().into() { 273 | Ok(()) 274 | } else { 275 | Err(Error::InvalidSignature) 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - Henry de Valence 9 | 10 | /// The byte-encoding of the basepoint for `SpendAuthSig`. 11 | // Extracted ad-hoc from librustzcash 12 | // XXX add tests for this value. 13 | pub const SPENDAUTHSIG_BASEPOINT_BYTES: [u8; 32] = [ 14 | 48, 181, 242, 170, 173, 50, 86, 48, 188, 221, 219, 206, 77, 103, 101, 109, 5, 253, 28, 194, 15 | 208, 55, 187, 83, 117, 182, 233, 109, 158, 1, 161, 215, 16 | ]; 17 | 18 | /// The byte-encoding of the basepoint for `BindingSig`. 19 | // Extracted ad-hoc from librustzcash 20 | // XXX add tests for this value. 21 | pub const BINDINGSIG_BASEPOINT_BYTES: [u8; 32] = [ 22 | 139, 106, 11, 56, 185, 250, 174, 60, 59, 128, 59, 71, 176, 241, 70, 173, 80, 171, 34, 30, 110, 23 | 42, 251, 230, 219, 222, 69, 203, 169, 211, 129, 237, 24 | ]; 25 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - Deirdre Connolly 9 | // - Henry de Valence 10 | 11 | use core::fmt; 12 | 13 | /// An error related to RedDSA signatures. 14 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 15 | pub enum Error { 16 | /// The encoding of a signing key was malformed. 17 | MalformedSigningKey, 18 | /// The encoding of a verification key was malformed. 19 | MalformedVerificationKey, 20 | /// Signature verification failed. 21 | InvalidSignature, 22 | } 23 | 24 | #[cfg(feature = "std")] 25 | impl std::error::Error for Error {} 26 | 27 | impl fmt::Display for Error { 28 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 | match self { 30 | Self::MalformedSigningKey => write!(f, "Malformed signing key encoding."), 31 | Self::MalformedVerificationKey => write!(f, "Malformed verification key encoding."), 32 | Self::InvalidSignature => write!(f, "Invalid signature."), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/frost.rs: -------------------------------------------------------------------------------- 1 | //! RedDSA with FROST. 2 | 3 | pub mod redjubjub; 4 | pub mod redpallas; 5 | -------------------------------------------------------------------------------- /src/frost/redjubjub.rs: -------------------------------------------------------------------------------- 1 | //! Rerandomized FROST with Jubjub curve. 2 | #![allow(non_snake_case)] 3 | #![deny(missing_docs)] 4 | 5 | use alloc::collections::BTreeMap; 6 | 7 | use group::GroupEncoding; 8 | #[cfg(feature = "alloc")] 9 | use group::{ff::Field as FFField, ff::PrimeField}; 10 | 11 | // Re-exports in our public API 12 | #[cfg(feature = "serde")] 13 | pub use frost_rerandomized::frost_core::serde; 14 | pub use frost_rerandomized::frost_core::{ 15 | self as frost, Ciphersuite, Field, FieldError, Group, GroupError, 16 | }; 17 | use frost_rerandomized::RandomizedCiphersuite; 18 | pub use rand_core; 19 | 20 | use rand_core::{CryptoRng, RngCore}; 21 | 22 | use crate::{hash::HStar, private::Sealed, sapling}; 23 | 24 | /// An error type for the FROST(Jubjub, BLAKE2b-512) ciphersuite. 25 | pub type Error = frost_rerandomized::frost_core::Error; 26 | 27 | /// An implementation of the FROST(Jubjub, BLAKE2b-512) ciphersuite scalar field. 28 | #[derive(Clone, Copy)] 29 | pub struct JubjubScalarField; 30 | 31 | impl Field for JubjubScalarField { 32 | type Scalar = jubjub::Scalar; 33 | 34 | type Serialization = [u8; 32]; 35 | 36 | fn zero() -> Self::Scalar { 37 | Self::Scalar::zero() 38 | } 39 | 40 | fn one() -> Self::Scalar { 41 | Self::Scalar::one() 42 | } 43 | 44 | fn invert(scalar: &Self::Scalar) -> Result { 45 | // [`Jubjub::Scalar`]'s Eq/PartialEq does a constant-time comparison using 46 | // `ConstantTimeEq` 47 | if *scalar == ::zero() { 48 | Err(FieldError::InvalidZeroScalar) 49 | } else { 50 | Ok(Self::Scalar::invert(scalar).unwrap()) 51 | } 52 | } 53 | 54 | fn random(rng: &mut R) -> Self::Scalar { 55 | Self::Scalar::random(rng) 56 | } 57 | 58 | fn serialize(scalar: &Self::Scalar) -> Self::Serialization { 59 | scalar.to_bytes() 60 | } 61 | 62 | fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization { 63 | Self::serialize(scalar) 64 | } 65 | 66 | fn deserialize(buf: &Self::Serialization) -> Result { 67 | match Self::Scalar::from_repr(*buf).into() { 68 | Some(s) => Ok(s), 69 | None => Err(FieldError::MalformedScalar), 70 | } 71 | } 72 | } 73 | 74 | /// An implementation of the FROST(Jubjub, BLAKE2b-512) ciphersuite group. 75 | #[derive(Clone, Copy, PartialEq, Eq)] 76 | pub struct JubjubGroup; 77 | 78 | impl Group for JubjubGroup { 79 | type Field = JubjubScalarField; 80 | 81 | type Element = jubjub::ExtendedPoint; 82 | 83 | type Serialization = [u8; 32]; 84 | 85 | fn cofactor() -> ::Scalar { 86 | Self::Field::one() 87 | } 88 | 89 | fn identity() -> Self::Element { 90 | Self::Element::identity() 91 | } 92 | 93 | fn generator() -> Self::Element { 94 | sapling::SpendAuth::basepoint() 95 | } 96 | 97 | fn serialize(element: &Self::Element) -> Result { 98 | if *element == Self::identity() { 99 | return Err(GroupError::InvalidIdentityElement); 100 | } 101 | Ok(element.to_bytes()) 102 | } 103 | 104 | fn deserialize(buf: &Self::Serialization) -> Result { 105 | let point = Self::Element::from_bytes(buf); 106 | 107 | match Option::::from(point) { 108 | Some(point) => { 109 | if point == Self::identity() { 110 | Err(GroupError::InvalidIdentityElement) 111 | } else if point.is_torsion_free().into() { 112 | Ok(point) 113 | } else { 114 | Err(GroupError::InvalidNonPrimeOrderElement) 115 | } 116 | } 117 | None => Err(GroupError::MalformedElement), 118 | } 119 | } 120 | } 121 | 122 | /// An implementation of the FROST(Jubjub, BLAKE2b-512) ciphersuite. 123 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 124 | pub struct JubjubBlake2b512; 125 | 126 | impl Ciphersuite for JubjubBlake2b512 { 127 | const ID: &'static str = "FROST(Jubjub, BLAKE2b-512)"; 128 | 129 | type Group = JubjubGroup; 130 | 131 | type HashOutput = [u8; 64]; 132 | 133 | type SignatureSerialization = [u8; 64]; 134 | 135 | /// H1 for FROST(Jubjub, BLAKE2b-512) 136 | fn H1(m: &[u8]) -> <::Field as Field>::Scalar { 137 | HStar::::new(b"FROST_RedJubjubR") 138 | .update(m) 139 | .finalize() 140 | } 141 | 142 | /// H2 for FROST(Jubjub, BLAKE2b-512) 143 | fn H2(m: &[u8]) -> <::Field as Field>::Scalar { 144 | HStar::::default().update(m).finalize() 145 | } 146 | 147 | /// H3 for FROST(Jubjub, BLAKE2b-512) 148 | fn H3(m: &[u8]) -> <::Field as Field>::Scalar { 149 | HStar::::new(b"FROST_RedJubjubN") 150 | .update(m) 151 | .finalize() 152 | } 153 | 154 | /// H4 for FROST(Jubjub, BLAKE2b-512) 155 | fn H4(m: &[u8]) -> Self::HashOutput { 156 | let mut state = blake2b_simd::Params::new() 157 | .hash_length(64) 158 | .personal(b"FROST_RedJubjubM") 159 | .to_state(); 160 | *state.update(m).finalize().as_array() 161 | } 162 | 163 | /// H5 for FROST(Jubjub, BLAKE2b-512) 164 | fn H5(m: &[u8]) -> Self::HashOutput { 165 | let mut state = blake2b_simd::Params::new() 166 | .hash_length(64) 167 | .personal(b"FROST_RedJubjubC") 168 | .to_state(); 169 | *state.update(m).finalize().as_array() 170 | } 171 | 172 | /// HDKG for FROST(Jubjub, BLAKE2b-512) 173 | fn HDKG(m: &[u8]) -> Option<<::Field as Field>::Scalar> { 174 | Some( 175 | HStar::::new(b"FROST_RedJubjubD") 176 | .update(m) 177 | .finalize(), 178 | ) 179 | } 180 | 181 | /// HID for FROST(Jubjub, BLAKE2b-512) 182 | fn HID(m: &[u8]) -> Option<<::Field as Field>::Scalar> { 183 | Some( 184 | HStar::::new(b"FROST_RedJubjubI") 185 | .update(m) 186 | .finalize(), 187 | ) 188 | } 189 | } 190 | 191 | impl RandomizedCiphersuite for JubjubBlake2b512 { 192 | fn hash_randomizer(m: &[u8]) -> Option<<::Field as Field>::Scalar> { 193 | Some( 194 | HStar::::new(b"FROST_RedJubjubA") 195 | .update(m) 196 | .finalize(), 197 | ) 198 | } 199 | } 200 | 201 | // Shorthand alias for the ciphersuite 202 | type J = JubjubBlake2b512; 203 | 204 | /// A FROST(Jubjub, BLAKE2b-512) participant identifier. 205 | pub type Identifier = frost::Identifier; 206 | 207 | /// FROST(Jubjub, BLAKE2b-512) keys, key generation, key shares. 208 | pub mod keys { 209 | use alloc::collections::BTreeMap; 210 | 211 | use super::*; 212 | 213 | /// The identifier list to use when generating key shares. 214 | pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, J>; 215 | 216 | /// Allows all participants' keys to be generated using a central, trusted 217 | /// dealer. 218 | pub fn generate_with_dealer( 219 | max_signers: u16, 220 | min_signers: u16, 221 | identifiers: IdentifierList, 222 | mut rng: RNG, 223 | ) -> Result<(BTreeMap, PublicKeyPackage), Error> { 224 | frost::keys::generate_with_dealer(max_signers, min_signers, identifiers, &mut rng) 225 | } 226 | 227 | /// Splits an existing key into FROST shares. 228 | /// 229 | /// This is identical to [`generate_with_dealer`] but receives an existing key 230 | /// instead of generating a fresh one. This is useful in scenarios where 231 | /// the key needs to be generated externally or must be derived from e.g. a 232 | /// seed phrase. 233 | pub fn split( 234 | key: &SigningKey, 235 | max_signers: u16, 236 | min_signers: u16, 237 | identifiers: IdentifierList, 238 | rng: &mut R, 239 | ) -> Result<(BTreeMap, PublicKeyPackage), Error> { 240 | frost::keys::split(key, max_signers, min_signers, identifiers, rng) 241 | } 242 | 243 | /// Secret and public key material generated by a dealer performing 244 | /// [`generate_with_dealer`]. 245 | /// 246 | /// # Security 247 | /// 248 | /// To derive a FROST(Jubjub, BLAKE2b-512) keypair, the receiver of the [`SecretShare`] *must* call 249 | /// .into(), which under the hood also performs validation. 250 | pub type SecretShare = frost::keys::SecretShare; 251 | 252 | /// A secret scalar value representing a signer's share of the group secret. 253 | pub type SigningShare = frost::keys::SigningShare; 254 | 255 | /// A public group element that represents a single signer's public verification share. 256 | pub type VerifyingShare = frost::keys::VerifyingShare; 257 | 258 | /// A FROST(Jubjub, BLAKE2b-512) keypair, which can be generated either by a trusted dealer or using 259 | /// a DKG. 260 | /// 261 | /// When using a central dealer, [`SecretShare`]s are distributed to 262 | /// participants, who then perform verification, before deriving 263 | /// [`KeyPackage`]s, which they store to later use during signing. 264 | pub type KeyPackage = frost::keys::KeyPackage; 265 | 266 | /// Public data that contains all the signers' public keys as well as the 267 | /// group public key. 268 | /// 269 | /// Used for verification purposes before publishing a signature. 270 | pub type PublicKeyPackage = frost::keys::PublicKeyPackage; 271 | 272 | /// Contains the commitments to the coefficients for our secret polynomial _f_, 273 | /// used to generate participants' key shares. 274 | /// 275 | /// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which 276 | /// themselves are scalars) for a secret polynomial f, where f is used to 277 | /// generate each ith participant's key share f(i). Participants use this set of 278 | /// commitments to perform verifiable secret sharing. 279 | /// 280 | /// Note that participants MUST be assured that they have the *same* 281 | /// [`VerifiableSecretSharingCommitment`], either by performing pairwise comparison, or by using 282 | /// some agreed-upon public location for publication, where each participant can 283 | /// ensure that they received the correct (and same) value. 284 | pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; 285 | 286 | pub mod dkg; 287 | pub mod repairable; 288 | } 289 | 290 | /// FROST(Jubjub, BLAKE2b-512) Round 1 functionality and types. 291 | pub mod round1 { 292 | use frost_rerandomized::frost_core::keys::SigningShare; 293 | 294 | use super::*; 295 | /// Comprised of FROST(Jubjub, BLAKE2b-512) hiding and binding nonces. 296 | /// 297 | /// Note that [`SigningNonces`] must be used *only once* for a signing 298 | /// operation; re-using nonces will result in leakage of a signer's long-lived 299 | /// signing key. 300 | pub type SigningNonces = frost::round1::SigningNonces; 301 | 302 | /// Published by each participant in the first round of the signing protocol. 303 | /// 304 | /// This step can be batched if desired by the implementation. Each 305 | /// SigningCommitment can be used for exactly *one* signature. 306 | pub type SigningCommitments = frost::round1::SigningCommitments; 307 | 308 | /// A commitment to a signing nonce share. 309 | pub type NonceCommitment = frost::round1::NonceCommitment; 310 | 311 | /// Performed once by each participant selected for the signing operation. 312 | /// 313 | /// Generates the signing nonces and commitments to be used in the signing 314 | /// operation. 315 | pub fn commit( 316 | secret: &SigningShare, 317 | rng: &mut RNG, 318 | ) -> (SigningNonces, SigningCommitments) 319 | where 320 | RNG: CryptoRng + RngCore, 321 | { 322 | frost::round1::commit::(secret, rng) 323 | } 324 | } 325 | 326 | /// Generated by the coordinator of the signing operation and distributed to 327 | /// each signing party. 328 | pub type SigningPackage = frost::SigningPackage; 329 | 330 | /// FROST(Jubjub, BLAKE2b-512) Round 2 functionality and types, for signature share generation. 331 | pub mod round2 { 332 | use super::*; 333 | 334 | /// A FROST(Jubjub, BLAKE2b-512) participant's signature share, which the Coordinator will aggregate with all other signer's 335 | /// shares into the joint signature. 336 | pub type SignatureShare = frost::round2::SignatureShare; 337 | 338 | /// Performed once by each participant selected for the signing operation. 339 | /// 340 | /// Receives the message to be signed and a set of signing commitments and a set 341 | /// of randomizing commitments to be used in that signing operation, including 342 | /// that for this participant. 343 | /// 344 | /// Assumes the participant has already determined which nonce corresponds with 345 | /// the commitment that was assigned by the coordinator in the SigningPackage. 346 | pub fn sign( 347 | signing_package: &SigningPackage, 348 | signer_nonces: &round1::SigningNonces, 349 | key_package: &keys::KeyPackage, 350 | randomizer: Randomizer, 351 | ) -> Result { 352 | frost_rerandomized::sign(signing_package, signer_nonces, key_package, randomizer) 353 | } 354 | } 355 | 356 | /// A Schnorr signature on FROST(Jubjub, BLAKE2b-512). 357 | pub type Signature = frost_rerandomized::frost_core::Signature; 358 | 359 | /// Randomized parameters for a signing instance of randomized FROST. 360 | pub type RandomizedParams = frost_rerandomized::RandomizedParams; 361 | 362 | /// A randomizer. A random scalar which is used to randomize the key. 363 | pub type Randomizer = frost_rerandomized::Randomizer; 364 | 365 | /// Verifies each FROST(Jubjub, BLAKE2b-512) participant's signature share, and if all are valid, 366 | /// aggregates the shares into a signature to publish. 367 | /// 368 | /// Resulting signature is compatible with verification of a plain Schnorr 369 | /// signature. 370 | /// 371 | /// This operation is performed by a coordinator that can communicate with all 372 | /// the signing participants before publishing the final signature. The 373 | /// coordinator can be one of the participants or a semi-trusted third party 374 | /// (who is trusted to not perform denial of service attacks, but does not learn 375 | /// any secret information). Note that because the coordinator is trusted to 376 | /// report misbehaving parties in order to avoid publishing an invalid 377 | /// signature, if the coordinator themselves is a signer and misbehaves, they 378 | /// can avoid that step. However, at worst, this results in a denial of 379 | /// service attack due to publishing an invalid signature. 380 | pub fn aggregate( 381 | signing_package: &SigningPackage, 382 | signature_shares: &BTreeMap, 383 | pubkeys: &keys::PublicKeyPackage, 384 | randomized_params: &RandomizedParams, 385 | ) -> Result { 386 | frost_rerandomized::aggregate( 387 | signing_package, 388 | signature_shares, 389 | pubkeys, 390 | randomized_params, 391 | ) 392 | } 393 | 394 | /// A signing key for a Schnorr signature on FROST(Jubjub, BLAKE2b-512). 395 | pub type SigningKey = frost_rerandomized::frost_core::SigningKey; 396 | 397 | /// A valid verifying key for Schnorr signatures on FROST(Jubjub, BLAKE2b-512). 398 | pub type VerifyingKey = frost_rerandomized::frost_core::VerifyingKey; 399 | -------------------------------------------------------------------------------- /src/frost/redjubjub/README.md: -------------------------------------------------------------------------------- 1 | An implementation of Schnorr signatures on the Jubjub curve for both single and threshold numbers 2 | of signers (FROST). 3 | 4 | ## Example: key generation with trusted dealer and FROST signing 5 | 6 | Creating a key with a trusted dealer and splitting into shares; then signing a message 7 | and aggregating the signature. Note that the example just simulates a distributed 8 | scenario in a single thread and it abstracts away any communication between peers. 9 | 10 | 11 | ```rust 12 | use reddsa::frost::redjubjub as frost; 13 | use rand::thread_rng; 14 | use std::collections::HashMap; 15 | 16 | let mut rng = thread_rng(); 17 | let max_signers = 5; 18 | let min_signers = 3; 19 | let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?; 20 | 21 | // Verifies the secret shares from the dealer and stores them in a HashMap. 22 | // In practice, each KeyPackage must be sent to its respective participant 23 | // through a confidential and authenticated channel. 24 | let key_packages: HashMap<_, _> = shares 25 | .into_iter() 26 | .map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?))) 27 | .collect::>()?; 28 | 29 | let mut nonces = HashMap::new(); 30 | let mut commitments = HashMap::new(); 31 | 32 | //////////////////////////////////////////////////////////////////////////// 33 | // Round 1: generating nonces and signing commitments for each participant 34 | //////////////////////////////////////////////////////////////////////////// 35 | 36 | // In practice, each iteration of this loop will be executed by its respective participant. 37 | for participant_index in 1..(min_signers as u16 + 1) { 38 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 39 | // Generate one (1) nonce and one SigningCommitments instance for each 40 | // participant, up to _threshold_. 41 | let (nonce, commitment) = frost::round1::commit( 42 | participant_identifier, 43 | key_packages[&participant_identifier].secret_share(), 44 | &mut rng, 45 | ); 46 | // In practice, the nonces and commitments must be sent to the coordinator 47 | // (or to every other participant if there is no coordinator) using 48 | // an authenticated channel. 49 | nonces.insert(participant_identifier, nonce); 50 | commitments.insert(participant_identifier, commitment); 51 | } 52 | 53 | // This is what the signature aggregator / coordinator needs to do: 54 | // - decide what message to sign 55 | // - take one (unused) commitment per signing participant 56 | let mut signature_shares = Vec::new(); 57 | let message = "message to sign".as_bytes(); 58 | let comms = commitments.clone().into_values().collect(); 59 | // In practice, the SigningPackage must be sent to all participants 60 | // involved in the current signing (at least min_signers participants), 61 | // using an authenticated channel (and confidential if the message is secret). 62 | let signing_package = frost::SigningPackage::new(comms, message.to_vec()); 63 | 64 | //////////////////////////////////////////////////////////////////////////// 65 | // Round 2: each participant generates their signature share 66 | //////////////////////////////////////////////////////////////////////////// 67 | 68 | // In practice, each iteration of this loop will be executed by its respective participant. 69 | for participant_identifier in nonces.keys() { 70 | let key_package = &key_packages[participant_identifier]; 71 | 72 | let nonces_to_use = &nonces[participant_identifier]; 73 | 74 | // Each participant generates their signature share. 75 | let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?; 76 | 77 | // In practice, the signature share must be sent to the Coordinator 78 | // using an authenticated channel. 79 | signature_shares.push(signature_share); 80 | } 81 | 82 | //////////////////////////////////////////////////////////////////////////// 83 | // Aggregation: collects the signing shares from all participants, 84 | // generates the final signature. 85 | //////////////////////////////////////////////////////////////////////////// 86 | 87 | // Aggregate (also verifies the signature shares) 88 | let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?; 89 | 90 | // Check that the threshold signature can be verified by the group public 91 | // key (the verification key). 92 | assert!(pubkeys 93 | .group_public 94 | .verify(message, &group_signature) 95 | .is_ok()); 96 | 97 | # Ok::<(), frost::Error>(()) 98 | ``` 99 | -------------------------------------------------------------------------------- /src/frost/redjubjub/dkg.md: -------------------------------------------------------------------------------- 1 | # Distributed Key Generation (DKG) 2 | 3 | The DKG module supports generating FROST key shares in a distributed manner, 4 | without a trusted dealer. 5 | 6 | Before starting, each participant needs an unique identifier, which can be built from 7 | a `u16`. The process in which these identifiers are allocated is up to the application. 8 | 9 | The distributed key generation process has 3 parts, with 2 communication rounds 10 | between them, in which each participant needs to send a "package" to every other 11 | participant. In the first round, each participant sends the same package 12 | (a [`round1::Package`]) to every other. In the second round, each receiver gets 13 | their own package (a [`round2::Package`]). 14 | 15 | Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] 16 | that MUST be kept secret. Between part 2 and 3, each participant needs to hold 17 | onto a [`round2::SecretPackage`]. 18 | 19 | After the third part, each participant will get a [`KeyPackage`] with their 20 | long-term secret share that must be kept secret, and a [`PublicKeyPackage`] 21 | that is public (and will be the same between all participants). With those 22 | they can proceed to sign messages with FROST. 23 | 24 | 25 | ## Example 26 | 27 | ```rust 28 | # // ANCHOR: dkg_import 29 | use rand::thread_rng; 30 | use std::collections::BTreeMap; 31 | 32 | use reddsa::frost::redjubjub as frost; 33 | 34 | let mut rng = thread_rng(); 35 | 36 | let max_signers = 5; 37 | let min_signers = 3; 38 | # // ANCHOR_END: dkg_import 39 | 40 | //////////////////////////////////////////////////////////////////////////// 41 | // Key generation, Round 1 42 | //////////////////////////////////////////////////////////////////////////// 43 | 44 | // Keep track of each participant's round 1 secret package. 45 | // In practice each participant will keep its copy; no one 46 | // will have all the participant's packages. 47 | let mut round1_secret_packages = BTreeMap::new(); 48 | 49 | // Keep track of all round 1 packages sent to the given participant. 50 | // This is used to simulate the broadcast; in practice the packages 51 | // will be sent through some communication channel. 52 | let mut received_round1_packages = BTreeMap::new(); 53 | 54 | // For each participant, perform the first part of the DKG protocol. 55 | // In practice, each participant will perform this on their own environments. 56 | for participant_index in 1..=max_signers { 57 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 58 | # // ANCHOR: dkg_part1 59 | let (round1_secret_package, round1_package) = frost::keys::dkg::part1( 60 | participant_identifier, 61 | max_signers, 62 | min_signers, 63 | &mut rng, 64 | )?; 65 | # // ANCHOR_END: dkg_part1 66 | 67 | // Store the participant's secret package for later use. 68 | // In practice each participant will store it in their own environment. 69 | round1_secret_packages.insert(participant_identifier, round1_secret_package); 70 | 71 | // "Send" the round 1 package to all other participants. In this 72 | // test this is simulated using a BTreeMap; in practice this will be 73 | // sent through some communication channel. 74 | for receiver_participant_index in 1..=max_signers { 75 | if receiver_participant_index == participant_index { 76 | continue; 77 | } 78 | let receiver_participant_identifier: frost::Identifier = receiver_participant_index 79 | .try_into() 80 | .expect("should be nonzero"); 81 | received_round1_packages 82 | .entry(receiver_participant_identifier) 83 | .or_insert_with(BTreeMap::new) 84 | .insert(participant_identifier, round1_package.clone()); 85 | } 86 | } 87 | 88 | //////////////////////////////////////////////////////////////////////////// 89 | // Key generation, Round 2 90 | //////////////////////////////////////////////////////////////////////////// 91 | 92 | // Keep track of each participant's round 2 secret package. 93 | // In practice each participant will keep its copy; no one 94 | // will have all the participant's packages. 95 | let mut round2_secret_packages = BTreeMap::new(); 96 | 97 | // Keep track of all round 2 packages sent to the given participant. 98 | // This is used to simulate the broadcast; in practice the packages 99 | // will be sent through some communication channel. 100 | let mut received_round2_packages = BTreeMap::new(); 101 | 102 | // For each participant, perform the second part of the DKG protocol. 103 | // In practice, each participant will perform this on their own environments. 104 | for participant_index in 1..=max_signers { 105 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 106 | let round1_secret_package = round1_secret_packages 107 | .remove(&participant_identifier) 108 | .unwrap(); 109 | let round1_packages = &received_round1_packages[&participant_identifier]; 110 | # // ANCHOR: dkg_part2 111 | let (round2_secret_package, round2_packages) = 112 | frost::keys::dkg::part2(round1_secret_package, round1_packages)?; 113 | # // ANCHOR_END: dkg_part2 114 | 115 | // Store the participant's secret package for later use. 116 | // In practice each participant will store it in their own environment. 117 | round2_secret_packages.insert(participant_identifier, round2_secret_package); 118 | 119 | // "Send" the round 2 package to all other participants. In this 120 | // test this is simulated using a BTreeMap; in practice this will be 121 | // sent through some communication channel. 122 | // Note that, in contrast to the previous part, here each other participant 123 | // gets its own specific package. 124 | for (receiver_identifier, round2_package) in round2_packages { 125 | received_round2_packages 126 | .entry(receiver_identifier) 127 | .or_insert_with(BTreeMap::new) 128 | .insert(participant_identifier, round2_package); 129 | } 130 | } 131 | 132 | //////////////////////////////////////////////////////////////////////////// 133 | // Key generation, final computation 134 | //////////////////////////////////////////////////////////////////////////// 135 | 136 | // Keep track of each participant's long-lived key package. 137 | // In practice each participant will keep its copy; no one 138 | // will have all the participant's packages. 139 | let mut key_packages = BTreeMap::new(); 140 | 141 | // Keep track of each participant's public key package. 142 | // In practice, if there is a Coordinator, only they need to store the set. 143 | // If there is not, then all candidates must store their own sets. 144 | // All participants will have the same exact public key package. 145 | let mut pubkey_packages = BTreeMap::new(); 146 | 147 | // For each participant, perform the third part of the DKG protocol. 148 | // In practice, each participant will perform this on their own environments. 149 | for participant_index in 1..=max_signers { 150 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 151 | let round2_secret_package = &round2_secret_packages[&participant_identifier]; 152 | let round1_packages = &received_round1_packages[&participant_identifier]; 153 | let round2_packages = &received_round2_packages[&participant_identifier]; 154 | # // ANCHOR: dkg_part3 155 | let (key_package, pubkey_package) = frost::keys::dkg::part3( 156 | round2_secret_package, 157 | round1_packages, 158 | round2_packages, 159 | )?; 160 | # // ANCHOR_END: dkg_part3 161 | key_packages.insert(participant_identifier, key_package); 162 | pubkey_packages.insert(participant_identifier, pubkey_package); 163 | } 164 | 165 | // With its own key package and the pubkey package, each participant can now proceed 166 | // to sign with FROST. 167 | # Ok::<(), frost::Error>(()) 168 | ``` 169 | -------------------------------------------------------------------------------- /src/frost/redjubjub/keys/dkg.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../dkg.md")] 2 | use super::*; 3 | 4 | /// DKG Round 1 structures. 5 | pub mod round1 { 6 | use super::*; 7 | 8 | /// The secret package that must be kept in memory by the participant 9 | /// between the first and second parts of the DKG protocol (round 1). 10 | /// 11 | /// # Security 12 | /// 13 | /// This package MUST NOT be sent to other participants! 14 | pub type SecretPackage = frost::keys::dkg::round1::SecretPackage; 15 | 16 | /// The package that must be broadcast by each participant to all other participants 17 | /// between the first and second parts of the DKG protocol (round 1). 18 | pub type Package = frost::keys::dkg::round1::Package; 19 | } 20 | 21 | /// DKG Round 2 structures. 22 | pub mod round2 { 23 | use super::*; 24 | 25 | /// The secret package that must be kept in memory by the participant 26 | /// between the second and third parts of the DKG protocol (round 2). 27 | /// 28 | /// # Security 29 | /// 30 | /// This package MUST NOT be sent to other participants! 31 | pub type SecretPackage = frost::keys::dkg::round2::SecretPackage; 32 | 33 | /// A package that must be sent by each participant to some other participants 34 | /// in Round 2 of the DKG protocol. Note that there is one specific package 35 | /// for each specific recipient, in contrast to Round 1. 36 | /// 37 | /// # Security 38 | /// 39 | /// The package must be sent on an *confidential* and *authenticated* channel. 40 | pub type Package = frost::keys::dkg::round2::Package; 41 | } 42 | 43 | /// Performs the first part of the distributed key generation protocol 44 | /// for the given participant. 45 | /// 46 | /// It returns the [`round1::SecretPackage`] that must be kept in memory 47 | /// by the participant for the other steps, and the [`round1::Package`] that 48 | /// must be sent to other participants. 49 | pub fn part1( 50 | identifier: Identifier, 51 | max_signers: u16, 52 | min_signers: u16, 53 | mut rng: R, 54 | ) -> Result<(round1::SecretPackage, round1::Package), Error> { 55 | frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) 56 | } 57 | 58 | /// Performs the second part of the distributed key generation protocol 59 | /// for the participant holding the given [`round1::SecretPackage`], 60 | /// given the received [`round1::Package`]s received from the other participants. 61 | /// 62 | /// It returns the [`round2::SecretPackage`] that must be kept in memory 63 | /// by the participant for the final step, and the [`round2::Package`]s that 64 | /// must be sent to other participants. 65 | pub fn part2( 66 | secret_package: round1::SecretPackage, 67 | round1_packages: &BTreeMap, 68 | ) -> Result<(round2::SecretPackage, BTreeMap), Error> { 69 | frost::keys::dkg::part2(secret_package, round1_packages) 70 | } 71 | 72 | /// Performs the third and final part of the distributed key generation protocol 73 | /// for the participant holding the given [`round2::SecretPackage`], 74 | /// given the received [`round1::Package`]s and [`round2::Package`]s received from 75 | /// the other participants. 76 | /// 77 | /// It returns the [`KeyPackage`] that has the long-lived key share for the 78 | /// participant, and the [`PublicKeyPackage`]s that has public information 79 | /// about all participants; both of which are required to compute FROST 80 | /// signatures. 81 | pub fn part3( 82 | round2_secret_package: &round2::SecretPackage, 83 | round1_packages: &BTreeMap, 84 | round2_packages: &BTreeMap, 85 | ) -> Result<(KeyPackage, PublicKeyPackage), Error> { 86 | frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages) 87 | } 88 | -------------------------------------------------------------------------------- /src/frost/redjubjub/keys/repairable.rs: -------------------------------------------------------------------------------- 1 | //! Repairable Threshold Scheme 2 | //! 3 | //! Implements the Repairable Threshold Scheme (RTS) from . 4 | //! The RTS is used to help a signer (participant) repair their lost share. This is achieved 5 | //! using a subset of the other signers know here as `helpers`. 6 | 7 | use alloc::collections::BTreeMap; 8 | 9 | use jubjub::Scalar; 10 | 11 | use crate::frost::redjubjub::{ 12 | frost, Ciphersuite, CryptoRng, Error, Identifier, JubjubBlake2b512, RngCore, 13 | }; 14 | 15 | use super::{SecretShare, VerifiableSecretSharingCommitment}; 16 | 17 | /// Step 1 of RTS. 18 | /// 19 | /// Generates the "delta" values from `helper_i` to help `participant` recover their share 20 | /// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` 21 | /// is the share of `helper_i`. 22 | /// 23 | /// Returns a HashMap mapping which value should be sent to which participant. 24 | pub fn repair_share_step_1( 25 | helpers: &[Identifier], 26 | share_i: &SecretShare, 27 | rng: &mut R, 28 | participant: Identifier, 29 | ) -> Result, Error> { 30 | frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) 31 | } 32 | 33 | /// Step 2 of RTS. 34 | /// 35 | /// Generates the `sigma` values from all `deltas` received from `helpers` 36 | /// to help `participant` recover their share. 37 | /// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. 38 | /// 39 | /// Returns a scalar 40 | pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { 41 | frost::keys::repairable::repair_share_step_2::(deltas_j) 42 | } 43 | 44 | /// Step 3 of RTS 45 | /// 46 | /// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` 47 | /// is made up of the `identifier`and `commitment` of the `participant` as well as the 48 | /// `value` which is the `SigningShare`. 49 | pub fn repair_share_step_3( 50 | sigmas: &[Scalar], 51 | identifier: Identifier, 52 | commitment: &VerifiableSecretSharingCommitment, 53 | ) -> SecretShare { 54 | frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) 55 | } 56 | -------------------------------------------------------------------------------- /src/frost/redpallas.rs: -------------------------------------------------------------------------------- 1 | //! Rerandomized FROST with Pallas curve. 2 | //! 3 | //! This also re-exposes the FROST functions already parametrized with the 4 | //! Pallas curve. Note that if you use the generic frost-core functions instead, 5 | //! you will not get public keys with guaranteed even Y coordinate, and will 6 | //! need to convert them using the [`EvenY`] trait; see its documentation for 7 | //! details. 8 | #![allow(non_snake_case)] 9 | #![deny(missing_docs)] 10 | 11 | use alloc::collections::BTreeMap; 12 | 13 | use frost_rerandomized::RandomizedCiphersuite; 14 | use group::GroupEncoding; 15 | #[cfg(feature = "alloc")] 16 | use group::{ff::Field as FFField, ff::PrimeField, Group as FFGroup}; 17 | use pasta_curves::pallas; 18 | 19 | // Re-exports in our public API 20 | #[cfg(feature = "serde")] 21 | pub use frost_rerandomized::frost_core::serde; 22 | pub use frost_rerandomized::frost_core::{ 23 | self as frost, Ciphersuite, Field, FieldError, Group, GroupError, 24 | }; 25 | pub use rand_core; 26 | 27 | use rand_core::{CryptoRng, RngCore}; 28 | 29 | use crate::{hash::HStar, orchard, private::Sealed}; 30 | 31 | /// An error type for the FROST(Pallas, BLAKE2b-512) ciphersuite. 32 | pub type Error = frost_rerandomized::frost_core::Error; 33 | 34 | /// An implementation of the FROST(Pallas, BLAKE2b-512) ciphersuite scalar field. 35 | #[derive(Clone, Copy)] 36 | pub struct PallasScalarField; 37 | 38 | impl Field for PallasScalarField { 39 | type Scalar = pallas::Scalar; 40 | 41 | type Serialization = [u8; 32]; 42 | 43 | fn zero() -> Self::Scalar { 44 | Self::Scalar::zero() 45 | } 46 | 47 | fn one() -> Self::Scalar { 48 | Self::Scalar::one() 49 | } 50 | 51 | fn invert(scalar: &Self::Scalar) -> Result { 52 | // [`pallas::Scalar`]'s Eq/PartialEq does a constant-time comparison using 53 | // `ConstantTimeEq` 54 | if *scalar == ::zero() { 55 | Err(FieldError::InvalidZeroScalar) 56 | } else { 57 | Ok(Self::Scalar::invert(scalar).unwrap()) 58 | } 59 | } 60 | 61 | fn random(rng: &mut R) -> Self::Scalar { 62 | Self::Scalar::random(rng) 63 | } 64 | 65 | fn serialize(scalar: &Self::Scalar) -> Self::Serialization { 66 | // to_repr() endianess is implementation-specific, but this is OK since 67 | // it is specific to [`pallas::Scalar`] which uses little-endian and that 68 | // is what we want. 69 | scalar.to_repr() 70 | } 71 | 72 | fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization { 73 | Self::serialize(scalar) 74 | } 75 | 76 | fn deserialize(buf: &Self::Serialization) -> Result { 77 | match pallas::Scalar::from_repr(*buf).into() { 78 | Some(s) => Ok(s), 79 | None => Err(FieldError::MalformedScalar), 80 | } 81 | } 82 | } 83 | 84 | /// An implementation of the FROST(Pallas, BLAKE2b-512) ciphersuite group. 85 | #[derive(Clone, Copy, PartialEq, Eq)] 86 | pub struct PallasGroup; 87 | 88 | impl Group for PallasGroup { 89 | type Field = PallasScalarField; 90 | 91 | type Element = pallas::Point; 92 | 93 | type Serialization = [u8; 32]; 94 | 95 | fn cofactor() -> ::Scalar { 96 | Self::Field::one() 97 | } 98 | 99 | fn identity() -> Self::Element { 100 | Self::Element::identity() 101 | } 102 | 103 | fn generator() -> Self::Element { 104 | orchard::SpendAuth::basepoint() 105 | } 106 | 107 | fn serialize(element: &Self::Element) -> Result { 108 | if *element == Self::identity() { 109 | return Err(GroupError::InvalidIdentityElement); 110 | } 111 | Ok(element.to_bytes()) 112 | } 113 | 114 | fn deserialize(buf: &Self::Serialization) -> Result { 115 | let point = Self::Element::from_bytes(buf); 116 | 117 | match Option::::from(point) { 118 | Some(point) => { 119 | if point == Self::identity() { 120 | Err(GroupError::InvalidIdentityElement) 121 | } else { 122 | Ok(point) 123 | } 124 | } 125 | None => Err(GroupError::MalformedElement), 126 | } 127 | } 128 | } 129 | 130 | /// An implementation of the FROST(Pallas, BLAKE2b-512) ciphersuite. 131 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 132 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 133 | #[cfg_attr(feature = "serde", serde(crate = "self::serde"))] 134 | pub struct PallasBlake2b512; 135 | 136 | impl Ciphersuite for PallasBlake2b512 { 137 | const ID: &'static str = "FROST(Pallas, BLAKE2b-512)"; 138 | 139 | type Group = PallasGroup; 140 | 141 | type HashOutput = [u8; 64]; 142 | 143 | type SignatureSerialization = [u8; 64]; 144 | 145 | /// H1 for FROST(Pallas, BLAKE2b-512) 146 | fn H1(m: &[u8]) -> <::Field as Field>::Scalar { 147 | HStar::::new(b"FROST_RedPallasR") 148 | .update(m) 149 | .finalize() 150 | } 151 | 152 | /// H2 for FROST(Pallas, BLAKE2b-512) 153 | fn H2(m: &[u8]) -> <::Field as Field>::Scalar { 154 | HStar::::default().update(m).finalize() 155 | } 156 | 157 | /// H3 for FROST(Pallas, BLAKE2b-512) 158 | fn H3(m: &[u8]) -> <::Field as Field>::Scalar { 159 | HStar::::new(b"FROST_RedPallasN") 160 | .update(m) 161 | .finalize() 162 | } 163 | 164 | /// H4 for FROST(Pallas, BLAKE2b-512) 165 | fn H4(m: &[u8]) -> Self::HashOutput { 166 | let mut state = blake2b_simd::Params::new() 167 | .hash_length(64) 168 | .personal(b"FROST_RedPallasM") 169 | .to_state(); 170 | *state.update(m).finalize().as_array() 171 | } 172 | 173 | /// H5 for FROST(Pallas, BLAKE2b-512) 174 | fn H5(m: &[u8]) -> Self::HashOutput { 175 | let mut state = blake2b_simd::Params::new() 176 | .hash_length(64) 177 | .personal(b"FROST_RedPallasC") 178 | .to_state(); 179 | *state.update(m).finalize().as_array() 180 | } 181 | 182 | /// HDKG for FROST(Pallas, BLAKE2b-512) 183 | fn HDKG(m: &[u8]) -> Option<<::Field as Field>::Scalar> { 184 | Some( 185 | HStar::::new(b"FROST_RedPallasD") 186 | .update(m) 187 | .finalize(), 188 | ) 189 | } 190 | 191 | /// HID for FROST(Pallas, BLAKE2b-512) 192 | fn HID(m: &[u8]) -> Option<<::Field as Field>::Scalar> { 193 | Some( 194 | HStar::::new(b"FROST_RedPallasI") 195 | .update(m) 196 | .finalize(), 197 | ) 198 | } 199 | } 200 | 201 | impl RandomizedCiphersuite for PallasBlake2b512 { 202 | fn hash_randomizer(m: &[u8]) -> Option<<::Field as Field>::Scalar> { 203 | Some( 204 | HStar::::new(b"FROST_RedPallasA") 205 | .update(m) 206 | .finalize(), 207 | ) 208 | } 209 | } 210 | 211 | // Shorthand alias for the ciphersuite 212 | type P = PallasBlake2b512; 213 | 214 | /// A FROST(Pallas, BLAKE2b-512) participant identifier. 215 | pub type Identifier = frost::Identifier

; 216 | 217 | /// FROST(Pallas, BLAKE2b-512) keys, key generation, key shares. 218 | pub mod keys { 219 | use alloc::{collections::BTreeMap, vec::Vec}; 220 | 221 | use super::*; 222 | 223 | /// The identifier list to use when generating key shares. 224 | pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, P>; 225 | 226 | /// Allows all participants' keys to be generated using a central, trusted 227 | /// dealer. 228 | pub fn generate_with_dealer( 229 | max_signers: u16, 230 | min_signers: u16, 231 | identifiers: IdentifierList, 232 | mut rng: RNG, 233 | ) -> Result<(BTreeMap, PublicKeyPackage), Error> { 234 | Ok(into_even_y(frost::keys::generate_with_dealer( 235 | max_signers, 236 | min_signers, 237 | identifiers, 238 | &mut rng, 239 | )?)) 240 | } 241 | 242 | /// Splits an existing key into FROST shares. 243 | /// 244 | /// This is identical to [`generate_with_dealer`] but receives an existing key 245 | /// instead of generating a fresh one. This is useful in scenarios where 246 | /// the key needs to be generated externally or must be derived from e.g. a 247 | /// seed phrase. 248 | pub fn split( 249 | key: &SigningKey, 250 | max_signers: u16, 251 | min_signers: u16, 252 | identifiers: IdentifierList, 253 | rng: &mut R, 254 | ) -> Result<(BTreeMap, PublicKeyPackage), Error> { 255 | Ok(into_even_y(frost::keys::split( 256 | key, 257 | max_signers, 258 | min_signers, 259 | identifiers, 260 | rng, 261 | )?)) 262 | } 263 | 264 | /// Secret and public key material generated by a dealer performing 265 | /// [`generate_with_dealer`]. 266 | /// 267 | /// # Security 268 | /// 269 | /// To derive a FROST(Pallas, BLAKE2b-512) keypair, the receiver of the [`SecretShare`] *must* call 270 | /// .into(), which under the hood also performs validation. 271 | pub type SecretShare = frost::keys::SecretShare

; 272 | 273 | /// A secret scalar value representing a signer's share of the group secret. 274 | pub type SigningShare = frost::keys::SigningShare

; 275 | 276 | /// A public group element that represents a single signer's public verification share. 277 | pub type VerifyingShare = frost::keys::VerifyingShare

; 278 | 279 | /// A FROST(Pallas, BLAKE2b-512) keypair, which can be generated either by a trusted dealer or using 280 | /// a DKG. 281 | /// 282 | /// When using a central dealer, [`SecretShare`]s are distributed to 283 | /// participants, who then perform verification, before deriving 284 | /// [`KeyPackage`]s, which they store to later use during signing. 285 | pub type KeyPackage = frost::keys::KeyPackage

; 286 | 287 | /// Public data that contains all the signers' public keys as well as the 288 | /// group public key. 289 | /// 290 | /// Used for verification purposes before publishing a signature. 291 | pub type PublicKeyPackage = frost::keys::PublicKeyPackage

; 292 | 293 | /// Contains the commitments to the coefficients for our secret polynomial _f_, 294 | /// used to generate participants' key shares. 295 | /// 296 | /// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which 297 | /// themselves are scalars) for a secret polynomial f, where f is used to 298 | /// generate each ith participant's key share f(i). Participants use this set of 299 | /// commitments to perform verifiable secret sharing. 300 | /// 301 | /// Note that participants MUST be assured that they have the *same* 302 | /// [`VerifiableSecretSharingCommitment`], either by performing pairwise comparison, or by using 303 | /// some agreed-upon public location for publication, where each participant can 304 | /// ensure that they received the correct (and same) value. 305 | pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment

; 306 | 307 | /// Trait for ensuring the group public key has an even Y coordinate. 308 | /// 309 | /// In the [Zcash spec][1], Orchard spend authorizing keys (which are then 310 | /// ones where FROST applies) are generated so that their matching public 311 | /// keys have a even Y coordinate. 312 | /// 313 | /// This trait is used to enable this procedure, by changing the private and 314 | /// public keys to ensure that the public key has a even Y coordinate. This 315 | /// is done by simply negating both keys if Y is even (in a field, negating 316 | /// is equivalent to computing p - x where p is the prime modulus. Since p 317 | /// is odd, if x is odd then the result will be even). Fortunately this 318 | /// works even after Shamir secret sharing, in the individual signing and 319 | /// verifying shares, since it's linear. 320 | /// 321 | /// [1]: https://zips.z.cash/protocol/protocol.pdf#orchardkeycomponents 322 | pub trait EvenY { 323 | /// Return if the given type has a group public key with an even Y 324 | /// coordinate. 325 | fn has_even_y(&self) -> bool; 326 | 327 | /// Convert the given type to make sure the group public key has an even 328 | /// Y coordinate. `is_even` can be specified if evenness was already 329 | /// determined beforehand. Returns a boolean indicating if the original 330 | /// type had an even Y, and a (possibly converted) value with even Y. 331 | fn into_even_y(self, is_even: Option) -> Self; 332 | } 333 | 334 | impl EvenY for PublicKeyPackage { 335 | fn has_even_y(&self) -> bool { 336 | let verifying_key = self.verifying_key(); 337 | match verifying_key.serialize() { 338 | Ok(verifying_key_serialized) => verifying_key_serialized[31] & 0x80 == 0, 339 | // If serialization fails then it's the identity point, which has even Y 340 | Err(_) => true, 341 | } 342 | } 343 | 344 | fn into_even_y(self, is_even: Option) -> Self { 345 | let is_even = is_even.unwrap_or_else(|| self.has_even_y()); 346 | if !is_even { 347 | // Negate verifying key 348 | let verifying_key = VerifyingKey::new(-self.verifying_key().to_element()); 349 | // Recreate verifying share map with negated VerifyingShares 350 | // values. 351 | let verifying_shares: BTreeMap<_, _> = self 352 | .verifying_shares() 353 | .iter() 354 | .map(|(i, vs)| { 355 | let vs = VerifyingShare::new(-vs.to_element()); 356 | (*i, vs) 357 | }) 358 | .collect(); 359 | PublicKeyPackage::new(verifying_shares, verifying_key) 360 | } else { 361 | self 362 | } 363 | } 364 | } 365 | 366 | impl EvenY for SecretShare { 367 | fn has_even_y(&self) -> bool { 368 | let key_package: KeyPackage = self 369 | .clone() 370 | .try_into() 371 | .expect("Should work; expected to be called in freshly generated SecretShares"); 372 | key_package.has_even_y() 373 | } 374 | 375 | fn into_even_y(self, is_even: Option) -> Self { 376 | let is_even = is_even.unwrap_or_else(|| self.has_even_y()); 377 | if !is_even { 378 | // Negate SigningShare 379 | let signing_share = SigningShare::new(-self.signing_share().to_scalar()); 380 | // Negate VerifiableSecretSharingCommitment by negating each 381 | // coefficient in it. TODO: remove serialization roundtrip 382 | // workaround after required functions are added to frost-core 383 | let coefficients: Vec<_> = self 384 | .commitment() 385 | .coefficients() 386 | .iter() 387 | .map(|e| { 388 | ::Group::serialize(&-e.value()) 389 | .expect("none of the coefficient commitments are the identity") 390 | }) 391 | .collect(); 392 | let commitments = VerifiableSecretSharingCommitment::deserialize(coefficients) 393 | .expect("Should work since they were just serialized"); 394 | SecretShare::new(*self.identifier(), signing_share, commitments) 395 | } else { 396 | self 397 | } 398 | } 399 | } 400 | 401 | impl EvenY for KeyPackage { 402 | fn has_even_y(&self) -> bool { 403 | let pubkey = self.verifying_key(); 404 | match pubkey.serialize() { 405 | Ok(pubkey_serialized) => pubkey_serialized[31] & 0x80 == 0, 406 | // If serialization fails then it's the identity point, which has even Y 407 | Err(_) => true, 408 | } 409 | } 410 | 411 | fn into_even_y(self, is_even: Option) -> Self { 412 | let is_even = is_even.unwrap_or_else(|| self.has_even_y()); 413 | if !is_even { 414 | // Negate all components 415 | let verifying_key = VerifyingKey::new(-self.verifying_key().to_element()); 416 | let signing_share = SigningShare::new(-self.signing_share().to_scalar()); 417 | let verifying_share = VerifyingShare::new(-self.verifying_share().to_element()); 418 | KeyPackage::new( 419 | *self.identifier(), 420 | signing_share, 421 | verifying_share, 422 | verifying_key, 423 | *self.min_signers(), 424 | ) 425 | } else { 426 | self 427 | } 428 | } 429 | } 430 | 431 | // Helper function which calls into_even_y() on the return values of 432 | // keygen/split functions. 433 | fn into_even_y( 434 | (secret_shares, public_key_package): (BTreeMap, PublicKeyPackage), 435 | ) -> (BTreeMap, PublicKeyPackage) { 436 | let is_even = public_key_package.has_even_y(); 437 | let public_key_package = public_key_package.into_even_y(Some(is_even)); 438 | let secret_shares = secret_shares 439 | .iter() 440 | .map(|(i, s)| (*i, s.clone().into_even_y(Some(is_even)))) 441 | .collect(); 442 | (secret_shares, public_key_package) 443 | } 444 | 445 | pub mod dkg; 446 | pub mod repairable; 447 | } 448 | 449 | /// FROST(Pallas, BLAKE2b-512) Round 1 functionality and types. 450 | pub mod round1 { 451 | use frost_rerandomized::frost_core::keys::SigningShare; 452 | 453 | use super::*; 454 | /// Comprised of FROST(Pallas, BLAKE2b-512) hiding and binding nonces. 455 | /// 456 | /// Note that [`SigningNonces`] must be used *only once* for a signing 457 | /// operation; re-using nonces will result in leakage of a signer's long-lived 458 | /// signing key. 459 | pub type SigningNonces = frost::round1::SigningNonces

; 460 | 461 | /// Published by each participant in the first round of the signing protocol. 462 | /// 463 | /// This step can be batched if desired by the implementation. Each 464 | /// SigningCommitment can be used for exactly *one* signature. 465 | pub type SigningCommitments = frost::round1::SigningCommitments

; 466 | 467 | /// A commitment to a signing nonce share. 468 | pub type NonceCommitment = frost::round1::NonceCommitment

; 469 | 470 | /// Performed once by each participant selected for the signing operation. 471 | /// 472 | /// Generates the signing nonces and commitments to be used in the signing 473 | /// operation. 474 | pub fn commit( 475 | secret: &SigningShare

, 476 | rng: &mut RNG, 477 | ) -> (SigningNonces, SigningCommitments) 478 | where 479 | RNG: CryptoRng + RngCore, 480 | { 481 | frost::round1::commit::(secret, rng) 482 | } 483 | } 484 | 485 | /// Generated by the coordinator of the signing operation and distributed to 486 | /// each signing party. 487 | pub type SigningPackage = frost::SigningPackage

; 488 | 489 | /// FROST(Pallas, BLAKE2b-512) Round 2 functionality and types, for signature share generation. 490 | pub mod round2 { 491 | use super::*; 492 | 493 | /// A FROST(Pallas, BLAKE2b-512) participant's signature share, which the Coordinator will aggregate with all other signer's 494 | /// shares into the joint signature. 495 | pub type SignatureShare = frost::round2::SignatureShare

; 496 | 497 | /// Performed once by each participant selected for the signing operation. 498 | /// 499 | /// Receives the message to be signed and a set of signing commitments and a set 500 | /// of randomizing commitments to be used in that signing operation, including 501 | /// that for this participant. 502 | /// 503 | /// Assumes the participant has already determined which nonce corresponds with 504 | /// the commitment that was assigned by the coordinator in the SigningPackage. 505 | pub fn sign( 506 | signing_package: &SigningPackage, 507 | signer_nonces: &round1::SigningNonces, 508 | key_package: &keys::KeyPackage, 509 | randomizer: Randomizer, 510 | ) -> Result { 511 | frost_rerandomized::sign(signing_package, signer_nonces, key_package, randomizer) 512 | } 513 | } 514 | 515 | /// A Schnorr signature on FROST(Pallas, BLAKE2b-512). 516 | pub type Signature = frost_rerandomized::frost_core::Signature

; 517 | 518 | /// Randomized parameters for a signing instance of randomized FROST. 519 | pub type RandomizedParams = frost_rerandomized::RandomizedParams

; 520 | 521 | /// A randomizer. A random scalar which is used to randomize the key. 522 | pub type Randomizer = frost_rerandomized::Randomizer

; 523 | 524 | /// Verifies each FROST(Pallas, BLAKE2b-512) participant's signature share, and if all are valid, 525 | /// aggregates the shares into a signature to publish. 526 | /// 527 | /// Resulting signature is compatible with verification of a plain Schnorr 528 | /// signature. 529 | /// 530 | /// This operation is performed by a coordinator that can communicate with all 531 | /// the signing participants before publishing the final signature. The 532 | /// coordinator can be one of the participants or a semi-trusted third party 533 | /// (who is trusted to not perform denial of service attacks, but does not learn 534 | /// any secret information). Note that because the coordinator is trusted to 535 | /// report misbehaving parties in order to avoid publishing an invalid 536 | /// signature, if the coordinator themselves is a signer and misbehaves, they 537 | /// can avoid that step. However, at worst, this results in a denial of 538 | /// service attack due to publishing an invalid signature. 539 | pub fn aggregate( 540 | signing_package: &SigningPackage, 541 | signature_shares: &BTreeMap, 542 | pubkeys: &keys::PublicKeyPackage, 543 | randomized_params: &RandomizedParams, 544 | ) -> Result { 545 | frost_rerandomized::aggregate( 546 | signing_package, 547 | signature_shares, 548 | pubkeys, 549 | randomized_params, 550 | ) 551 | } 552 | 553 | /// A signing key for a Schnorr signature on FROST(Pallas, BLAKE2b-512). 554 | pub type SigningKey = frost_rerandomized::frost_core::SigningKey

; 555 | 556 | /// A valid verifying key for Schnorr signatures on FROST(Pallas, BLAKE2b-512). 557 | pub type VerifyingKey = frost_rerandomized::frost_core::VerifyingKey

; 558 | -------------------------------------------------------------------------------- /src/frost/redpallas/README.md: -------------------------------------------------------------------------------- 1 | An implementation of Schnorr signatures on the Pallas curve for both single and threshold numbers 2 | of signers (FROST). 3 | 4 | ## Example: key generation with trusted dealer and FROST signing 5 | 6 | Creating a key with a trusted dealer and splitting into shares; then signing a message 7 | and aggregating the signature. Note that the example just simulates a distributed 8 | scenario in a single thread and it abstracts away any communication between peers. 9 | 10 | 11 | ```rust 12 | use reddsa::frost::redpallas as frost; 13 | use rand::thread_rng; 14 | use std::collections::HashMap; 15 | 16 | let mut rng = thread_rng(); 17 | let max_signers = 5; 18 | let min_signers = 3; 19 | let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?; 20 | 21 | // Verifies the secret shares from the dealer and stores them in a HashMap. 22 | // In practice, each KeyPackage must be sent to its respective participant 23 | // through a confidential and authenticated channel. 24 | let key_packages: HashMap<_, _> = shares 25 | .into_iter() 26 | .map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?))) 27 | .collect::>()?; 28 | 29 | let mut nonces = HashMap::new(); 30 | let mut commitments = HashMap::new(); 31 | 32 | //////////////////////////////////////////////////////////////////////////// 33 | // Round 1: generating nonces and signing commitments for each participant 34 | //////////////////////////////////////////////////////////////////////////// 35 | 36 | // In practice, each iteration of this loop will be executed by its respective participant. 37 | for participant_index in 1..(min_signers as u16 + 1) { 38 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 39 | // Generate one (1) nonce and one SigningCommitments instance for each 40 | // participant, up to _threshold_. 41 | let (nonce, commitment) = frost::round1::commit( 42 | participant_identifier, 43 | key_packages[&participant_identifier].secret_share(), 44 | &mut rng, 45 | ); 46 | // In practice, the nonces and commitments must be sent to the coordinator 47 | // (or to every other participant if there is no coordinator) using 48 | // an authenticated channel. 49 | nonces.insert(participant_identifier, nonce); 50 | commitments.insert(participant_identifier, commitment); 51 | } 52 | 53 | // This is what the signature aggregator / coordinator needs to do: 54 | // - decide what message to sign 55 | // - take one (unused) commitment per signing participant 56 | let mut signature_shares = Vec::new(); 57 | let message = "message to sign".as_bytes(); 58 | let comms = commitments.clone().into_values().collect(); 59 | // In practice, the SigningPackage must be sent to all participants 60 | // involved in the current signing (at least min_signers participants), 61 | // using an authenticated channel (and confidential if the message is secret). 62 | let signing_package = frost::SigningPackage::new(comms, message.to_vec()); 63 | 64 | //////////////////////////////////////////////////////////////////////////// 65 | // Round 2: each participant generates their signature share 66 | //////////////////////////////////////////////////////////////////////////// 67 | 68 | // In practice, each iteration of this loop will be executed by its respective participant. 69 | for participant_identifier in nonces.keys() { 70 | let key_package = &key_packages[participant_identifier]; 71 | 72 | let nonces_to_use = &nonces[participant_identifier]; 73 | 74 | // Each participant generates their signature share. 75 | let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?; 76 | 77 | // In practice, the signature share must be sent to the Coordinator 78 | // using an authenticated channel. 79 | signature_shares.push(signature_share); 80 | } 81 | 82 | //////////////////////////////////////////////////////////////////////////// 83 | // Aggregation: collects the signing shares from all participants, 84 | // generates the final signature. 85 | //////////////////////////////////////////////////////////////////////////// 86 | 87 | // Aggregate (also verifies the signature shares) 88 | let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?; 89 | 90 | // Check that the threshold signature can be verified by the group public 91 | // key (the verification key). 92 | assert!(pubkeys 93 | .group_public 94 | .verify(message, &group_signature) 95 | .is_ok()); 96 | 97 | # Ok::<(), frost::Error>(()) 98 | ``` 99 | -------------------------------------------------------------------------------- /src/frost/redpallas/dkg.md: -------------------------------------------------------------------------------- 1 | # Distributed Key Generation (DKG) 2 | 3 | The DKG module supports generating FROST key shares in a distributed manner, 4 | without a trusted dealer. 5 | 6 | Before starting, each participant needs an unique identifier, which can be built from 7 | a `u16`. The process in which these identifiers are allocated is up to the application. 8 | 9 | The distributed key generation process has 3 parts, with 2 communication rounds 10 | between them, in which each participant needs to send a "package" to every other 11 | participant. In the first round, each participant sends the same package 12 | (a [`round1::Package`]) to every other. In the second round, each receiver gets 13 | their own package (a [`round2::Package`]). 14 | 15 | Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] 16 | that MUST be kept secret. Between part 2 and 3, each participant needs to hold 17 | onto a [`round2::SecretPackage`]. 18 | 19 | After the third part, each participant will get a [`KeyPackage`] with their 20 | long-term secret share that must be kept secret, and a [`PublicKeyPackage`] 21 | that is public (and will be the same between all participants). With those 22 | they can proceed to sign messages with FROST. 23 | 24 | 25 | ## Example 26 | 27 | ```rust 28 | # // ANCHOR: dkg_import 29 | use rand::thread_rng; 30 | use std::collections::BTreeMap; 31 | 32 | use reddsa::frost::redpallas as frost; 33 | 34 | let mut rng = thread_rng(); 35 | 36 | let max_signers = 5; 37 | let min_signers = 3; 38 | # // ANCHOR_END: dkg_import 39 | 40 | //////////////////////////////////////////////////////////////////////////// 41 | // Key generation, Round 1 42 | //////////////////////////////////////////////////////////////////////////// 43 | 44 | // Keep track of each participant's round 1 secret package. 45 | // In practice each participant will keep its copy; no one 46 | // will have all the participant's packages. 47 | let mut round1_secret_packages = BTreeMap::new(); 48 | 49 | // Keep track of all round 1 packages sent to the given participant. 50 | // This is used to simulate the broadcast; in practice the packages 51 | // will be sent through some communication channel. 52 | let mut received_round1_packages = BTreeMap::new(); 53 | 54 | // For each participant, perform the first part of the DKG protocol. 55 | // In practice, each participant will perform this on their own environments. 56 | for participant_index in 1..=max_signers { 57 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 58 | # // ANCHOR: dkg_part1 59 | let (round1_secret_package, round1_package) = frost::keys::dkg::part1( 60 | participant_identifier, 61 | max_signers, 62 | min_signers, 63 | &mut rng, 64 | )?; 65 | # // ANCHOR_END: dkg_part1 66 | 67 | // Store the participant's secret package for later use. 68 | // In practice each participant will store it in their own environment. 69 | round1_secret_packages.insert(participant_identifier, round1_secret_package); 70 | 71 | // "Send" the round 1 package to all other participants. In this 72 | // test this is simulated using a BTreeMap; in practice this will be 73 | // sent through some communication channel. 74 | for receiver_participant_index in 1..=max_signers { 75 | if receiver_participant_index == participant_index { 76 | continue; 77 | } 78 | let receiver_participant_identifier: frost::Identifier = receiver_participant_index 79 | .try_into() 80 | .expect("should be nonzero"); 81 | received_round1_packages 82 | .entry(receiver_participant_identifier) 83 | .or_insert_with(BTreeMap::new) 84 | .insert(participant_identifier, round1_package.clone()); 85 | } 86 | } 87 | 88 | //////////////////////////////////////////////////////////////////////////// 89 | // Key generation, Round 2 90 | //////////////////////////////////////////////////////////////////////////// 91 | 92 | // Keep track of each participant's round 2 secret package. 93 | // In practice each participant will keep its copy; no one 94 | // will have all the participant's packages. 95 | let mut round2_secret_packages = BTreeMap::new(); 96 | 97 | // Keep track of all round 2 packages sent to the given participant. 98 | // This is used to simulate the broadcast; in practice the packages 99 | // will be sent through some communication channel. 100 | let mut received_round2_packages = BTreeMap::new(); 101 | 102 | // For each participant, perform the second part of the DKG protocol. 103 | // In practice, each participant will perform this on their own environments. 104 | for participant_index in 1..=max_signers { 105 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 106 | let round1_secret_package = round1_secret_packages 107 | .remove(&participant_identifier) 108 | .unwrap(); 109 | let round1_packages = &received_round1_packages[&participant_identifier]; 110 | # // ANCHOR: dkg_part2 111 | let (round2_secret_package, round2_packages) = 112 | frost::keys::dkg::part2(round1_secret_package, round1_packages)?; 113 | # // ANCHOR_END: dkg_part2 114 | 115 | // Store the participant's secret package for later use. 116 | // In practice each participant will store it in their own environment. 117 | round2_secret_packages.insert(participant_identifier, round2_secret_package); 118 | 119 | // "Send" the round 2 package to all other participants. In this 120 | // test this is simulated using a BTreeMap; in practice this will be 121 | // sent through some communication channel. 122 | // Note that, in contrast to the previous part, here each other participant 123 | // gets its own specific package. 124 | for (receiver_identifier, round2_package) in round2_packages { 125 | received_round2_packages 126 | .entry(receiver_identifier) 127 | .or_insert_with(BTreeMap::new) 128 | .insert(participant_identifier, round2_package); 129 | } 130 | } 131 | 132 | //////////////////////////////////////////////////////////////////////////// 133 | // Key generation, final computation 134 | //////////////////////////////////////////////////////////////////////////// 135 | 136 | // Keep track of each participant's long-lived key package. 137 | // In practice each participant will keep its copy; no one 138 | // will have all the participant's packages. 139 | let mut key_packages = BTreeMap::new(); 140 | 141 | // Keep track of each participant's public key package. 142 | // In practice, if there is a Coordinator, only they need to store the set. 143 | // If there is not, then all candidates must store their own sets. 144 | // All participants will have the same exact public key package. 145 | let mut pubkey_packages = BTreeMap::new(); 146 | 147 | // For each participant, perform the third part of the DKG protocol. 148 | // In practice, each participant will perform this on their own environments. 149 | for participant_index in 1..=max_signers { 150 | let participant_identifier = participant_index.try_into().expect("should be nonzero"); 151 | let round2_secret_package = &round2_secret_packages[&participant_identifier]; 152 | let round1_packages = &received_round1_packages[&participant_identifier]; 153 | let round2_packages = &received_round2_packages[&participant_identifier]; 154 | # // ANCHOR: dkg_part3 155 | let (key_package, pubkey_package) = frost::keys::dkg::part3( 156 | round2_secret_package, 157 | round1_packages, 158 | round2_packages, 159 | )?; 160 | # // ANCHOR_END: dkg_part3 161 | key_packages.insert(participant_identifier, key_package); 162 | pubkey_packages.insert(participant_identifier, pubkey_package); 163 | } 164 | 165 | // With its own key package and the pubkey package, each participant can now proceed 166 | // to sign with FROST. 167 | # Ok::<(), frost::Error>(()) 168 | ``` 169 | -------------------------------------------------------------------------------- /src/frost/redpallas/keys/dkg.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../dkg.md")] 2 | use super::*; 3 | 4 | /// DKG Round 1 structures. 5 | pub mod round1 { 6 | use super::*; 7 | 8 | /// The secret package that must be kept in memory by the participant 9 | /// between the first and second parts of the DKG protocol (round 1). 10 | /// 11 | /// # Security 12 | /// 13 | /// This package MUST NOT be sent to other participants! 14 | pub type SecretPackage = frost::keys::dkg::round1::SecretPackage

; 15 | 16 | /// The package that must be broadcast by each participant to all other participants 17 | /// between the first and second parts of the DKG protocol (round 1). 18 | pub type Package = frost::keys::dkg::round1::Package

; 19 | } 20 | 21 | /// DKG Round 2 structures. 22 | pub mod round2 { 23 | use super::*; 24 | 25 | /// The secret package that must be kept in memory by the participant 26 | /// between the second and third parts of the DKG protocol (round 2). 27 | /// 28 | /// # Security 29 | /// 30 | /// This package MUST NOT be sent to other participants! 31 | pub type SecretPackage = frost::keys::dkg::round2::SecretPackage

; 32 | 33 | /// A package that must be sent by each participant to some other participants 34 | /// in Round 2 of the DKG protocol. Note that there is one specific package 35 | /// for each specific recipient, in contrast to Round 1. 36 | /// 37 | /// # Security 38 | /// 39 | /// The package must be sent on an *confidential* and *authenticated* channel. 40 | pub type Package = frost::keys::dkg::round2::Package

; 41 | } 42 | 43 | /// Performs the first part of the distributed key generation protocol 44 | /// for the given participant. 45 | /// 46 | /// It returns the [`round1::SecretPackage`] that must be kept in memory 47 | /// by the participant for the other steps, and the [`round1::Package`] that 48 | /// must be sent to other participants. 49 | pub fn part1( 50 | identifier: Identifier, 51 | max_signers: u16, 52 | min_signers: u16, 53 | mut rng: R, 54 | ) -> Result<(round1::SecretPackage, round1::Package), Error> { 55 | frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) 56 | } 57 | 58 | /// Performs the second part of the distributed key generation protocol 59 | /// for the participant holding the given [`round1::SecretPackage`], 60 | /// given the received [`round1::Package`]s received from the other participants. 61 | /// 62 | /// It returns the [`round2::SecretPackage`] that must be kept in memory 63 | /// by the participant for the final step, and the [`round2::Package`]s that 64 | /// must be sent to other participants. 65 | pub fn part2( 66 | secret_package: round1::SecretPackage, 67 | round1_packages: &BTreeMap, 68 | ) -> Result<(round2::SecretPackage, BTreeMap), Error> { 69 | frost::keys::dkg::part2(secret_package, round1_packages) 70 | } 71 | 72 | /// Performs the third and final part of the distributed key generation protocol 73 | /// for the participant holding the given [`round2::SecretPackage`], 74 | /// given the received [`round1::Package`]s and [`round2::Package`]s received from 75 | /// the other participants. 76 | /// 77 | /// It returns the [`KeyPackage`] that has the long-lived key share for the 78 | /// participant, and the [`PublicKeyPackage`]s that has public information 79 | /// about all participants; both of which are required to compute FROST 80 | /// signatures. 81 | pub fn part3( 82 | round2_secret_package: &round2::SecretPackage, 83 | round1_packages: &BTreeMap, 84 | round2_packages: &BTreeMap, 85 | ) -> Result<(KeyPackage, PublicKeyPackage), Error> { 86 | let (key_package, public_key_package) = 87 | frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages)?; 88 | let is_even = public_key_package.has_even_y(); 89 | Ok(( 90 | key_package.into_even_y(Some(is_even)), 91 | public_key_package.into_even_y(Some(is_even)), 92 | )) 93 | } 94 | -------------------------------------------------------------------------------- /src/frost/redpallas/keys/repairable.rs: -------------------------------------------------------------------------------- 1 | //! Repairable Threshold Scheme 2 | //! 3 | //! Implements the Repairable Threshold Scheme (RTS) from . 4 | //! The RTS is used to help a signer (participant) repair their lost share. This is achieved 5 | //! using a subset of the other signers know here as `helpers`. 6 | 7 | use alloc::collections::BTreeMap; 8 | 9 | use pasta_curves::pallas::Scalar; 10 | 11 | use crate::frost::redpallas::{ 12 | frost, Ciphersuite, CryptoRng, Error, Identifier, PallasBlake2b512, RngCore, 13 | }; 14 | 15 | use super::{SecretShare, VerifiableSecretSharingCommitment}; 16 | 17 | /// Step 1 of RTS. 18 | /// 19 | /// Generates the "delta" values from `helper_i` to help `participant` recover their share 20 | /// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` 21 | /// is the share of `helper_i`. 22 | /// 23 | /// Returns a BTreeMap mapping which value should be sent to which participant. 24 | pub fn repair_share_step_1( 25 | helpers: &[Identifier], 26 | share_i: &SecretShare, 27 | rng: &mut R, 28 | participant: Identifier, 29 | ) -> Result, Error> { 30 | frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) 31 | } 32 | 33 | /// Step 2 of RTS. 34 | /// 35 | /// Generates the `sigma` values from all `deltas` received from `helpers` 36 | /// to help `participant` recover their share. 37 | /// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. 38 | /// 39 | /// Returns a scalar 40 | pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { 41 | frost::keys::repairable::repair_share_step_2::(deltas_j) 42 | } 43 | 44 | /// Step 3 of RTS 45 | /// 46 | /// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` 47 | /// is made up of the `identifier`and `commitment` of the `participant` as well as the 48 | /// `value` which is the `SigningShare`. 49 | pub fn repair_share_step_3( 50 | sigmas: &[Scalar], 51 | identifier: Identifier, 52 | commitment: &VerifiableSecretSharingCommitment, 53 | ) -> SecretShare { 54 | frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) 55 | } 56 | -------------------------------------------------------------------------------- /src/hash.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - Deirdre Connolly 9 | // - Henry de Valence 10 | 11 | use core::marker::PhantomData; 12 | 13 | use blake2b_simd::{Params, State}; 14 | 15 | use crate::{private::SealedScalar, SigType}; 16 | 17 | /// Provides H^star, the hash-to-scalar function used by RedDSA. 18 | pub struct HStar { 19 | state: State, 20 | _marker: PhantomData, 21 | } 22 | 23 | impl Default for HStar { 24 | fn default() -> Self { 25 | let state = Params::new() 26 | .hash_length(64) 27 | .personal(T::H_STAR_PERSONALIZATION) 28 | .to_state(); 29 | Self { 30 | state, 31 | _marker: PhantomData, 32 | } 33 | } 34 | } 35 | 36 | impl HStar { 37 | // Only used by FROST code 38 | #[allow(unused)] 39 | pub(crate) fn new(personalization_string: &[u8]) -> Self { 40 | let state = Params::new() 41 | .hash_length(64) 42 | .personal(personalization_string) 43 | .to_state(); 44 | Self { 45 | state, 46 | _marker: PhantomData, 47 | } 48 | } 49 | 50 | /// Add `data` to the hash, and return `Self` for chaining. 51 | pub fn update(&mut self, data: impl AsRef<[u8]>) -> &mut Self { 52 | self.state.update(data.as_ref()); 53 | self 54 | } 55 | 56 | /// Consume `self` to compute the hash output. 57 | pub fn finalize(&self) -> T::Scalar { 58 | T::Scalar::from_bytes_wide(self.state.finalize().as_array()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - Deirdre Connolly 9 | // - Henry de Valence 10 | 11 | #![no_std] 12 | #![deny(missing_docs)] 13 | #![doc = include_str!("../README.md")] 14 | 15 | //! Docs require the `nightly` feature until RFC 1990 lands. 16 | 17 | #[cfg(feature = "alloc")] 18 | #[macro_use] 19 | extern crate alloc; 20 | #[cfg(feature = "std")] 21 | extern crate std; 22 | 23 | #[cfg(feature = "alloc")] 24 | pub mod batch; 25 | mod constants; 26 | mod error; 27 | #[cfg(feature = "frost")] 28 | pub mod frost; 29 | mod hash; 30 | pub mod orchard; 31 | pub mod sapling; 32 | #[cfg(feature = "alloc")] 33 | mod scalar_mul; 34 | pub(crate) mod signature; 35 | mod signing_key; 36 | mod verification_key; 37 | 38 | /// An element of the protocol's scalar field used for randomization of public and secret keys. 39 | pub type Randomizer = >::Scalar; 40 | 41 | use hash::HStar; 42 | 43 | pub use error::Error; 44 | pub use signature::Signature; 45 | pub use signing_key::SigningKey; 46 | pub use verification_key::{VerificationKey, VerificationKeyBytes}; 47 | 48 | /// Abstracts over different RedDSA parameter choices, [`Binding`] 49 | /// and [`SpendAuth`]. 50 | /// 51 | /// As described [at the end of §5.4.6][concretereddsa] of the Zcash 52 | /// protocol specification, the generator used in RedDSA is left as 53 | /// an unspecified parameter, chosen differently for each of 54 | /// `BindingSig` and `SpendAuthSig`. 55 | /// 56 | /// To handle this, we encode the parameter choice as a genuine type 57 | /// parameter. 58 | /// 59 | /// [concretereddsa]: https://zips.z.cash/protocol/protocol.pdf#concretereddsa 60 | pub trait SigType: private::Sealed {} 61 | 62 | /// A trait corresponding to `BindingSig` in Zcash protocols. 63 | pub trait Binding: SigType {} 64 | 65 | /// A trait corresponding to `SpendAuthSig` in Zcash protocols. 66 | pub trait SpendAuth: SigType {} 67 | 68 | pub(crate) mod private { 69 | use super::*; 70 | 71 | pub trait SealedScalar { 72 | fn from_bytes_wide(bytes: &[u8; 64]) -> Self; 73 | fn from_raw(val: [u64; 4]) -> Self; 74 | } 75 | 76 | impl SealedScalar for jubjub::Scalar { 77 | fn from_bytes_wide(bytes: &[u8; 64]) -> Self { 78 | jubjub::Scalar::from_bytes_wide(bytes) 79 | } 80 | fn from_raw(val: [u64; 4]) -> Self { 81 | jubjub::Scalar::from_raw(val) 82 | } 83 | } 84 | 85 | pub trait Sealed: 86 | Copy + Clone + Default + Eq + PartialEq + core::fmt::Debug 87 | { 88 | const H_STAR_PERSONALIZATION: &'static [u8; 16]; 89 | type Scalar: group::ff::PrimeField + SealedScalar; 90 | 91 | // `Point: VartimeMultiscalarMul` is conditioned by `alloc` feature flag 92 | // This is fine because `Sealed` is an internal trait. 93 | #[cfg(feature = "alloc")] 94 | type Point: group::cofactor::CofactorCurve 95 | + scalar_mul::VartimeMultiscalarMul; 96 | #[cfg(not(feature = "alloc"))] 97 | type Point: group::cofactor::CofactorCurve; 98 | 99 | fn basepoint() -> T::Point; 100 | } 101 | impl Sealed for sapling::Binding { 102 | const H_STAR_PERSONALIZATION: &'static [u8; 16] = b"Zcash_RedJubjubH"; 103 | type Point = jubjub::ExtendedPoint; 104 | type Scalar = jubjub::Scalar; 105 | 106 | fn basepoint() -> jubjub::ExtendedPoint { 107 | jubjub::AffinePoint::from_bytes(constants::BINDINGSIG_BASEPOINT_BYTES) 108 | .unwrap() 109 | .into() 110 | } 111 | } 112 | impl Sealed for sapling::SpendAuth { 113 | const H_STAR_PERSONALIZATION: &'static [u8; 16] = b"Zcash_RedJubjubH"; 114 | type Point = jubjub::ExtendedPoint; 115 | type Scalar = jubjub::Scalar; 116 | 117 | fn basepoint() -> jubjub::ExtendedPoint { 118 | jubjub::AffinePoint::from_bytes(constants::SPENDAUTHSIG_BASEPOINT_BYTES) 119 | .unwrap() 120 | .into() 121 | } 122 | } 123 | } 124 | 125 | /// Return the given byte array as a hex-encoded string. 126 | #[cfg(feature = "alloc")] 127 | pub(crate) fn hex_if_possible(bytes: &[u8]) -> alloc::string::String { 128 | hex::encode(bytes) 129 | } 130 | 131 | /// Return the given byte array. 132 | #[cfg(not(feature = "alloc"))] 133 | pub(crate) fn hex_if_possible(bytes: &[u8]) -> &[u8] { 134 | bytes 135 | } 136 | -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | //! The FROST communication messages specified in [RFC-001] 2 | //! 3 | //! [RFC-001]: https://github.com/ZcashFoundation/redjubjub/blob/main/rfcs/0001-messages.md 4 | 5 | use crate::{frost, signature, verification_key, SpendAuth}; 6 | use group::GroupEncoding; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | use alloc::vec::Vec; 10 | use std::{collections::BTreeMap, convert::TryInto}; 11 | 12 | #[cfg(test)] 13 | use proptest_derive::Arbitrary; 14 | 15 | #[cfg(test)] 16 | mod arbitrary; 17 | mod constants; 18 | mod serialize; 19 | #[cfg(test)] 20 | mod tests; 21 | mod validate; 22 | 23 | /// Define our own `Secret` type instead of using [`frost::Secret`]. 24 | /// 25 | /// The serialization design specifies that `Secret` is a [`jubjub::Scalar`] that uses: 26 | /// "a 32-byte little-endian canonical representation". 27 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] 28 | #[cfg_attr(test, derive(Arbitrary))] 29 | pub struct Secret([u8; 32]); 30 | 31 | /// Define our own `Commitment` type instead of using [`frost::Commitment`]. 32 | /// 33 | /// The serialization design specifies that `Commitment` is an [`jubjub::AffinePoint`] that uses: 34 | /// "a 32-byte little-endian canonical representation". 35 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Copy)] 36 | #[cfg_attr(test, derive(Arbitrary))] 37 | pub struct Commitment([u8; 32]); 38 | 39 | impl From> for Commitment { 40 | fn from(value: frost::Commitment) -> Commitment { 41 | // TODO(str4d): We need to either enforce somewhere that these messages are only 42 | // used with curves that have 32-byte encodings, or make the curve a parameter of 43 | // the encoding. This will be easier once const_evaluatable_checked stabilises. 44 | Commitment(value.0.to_bytes().as_ref().try_into().unwrap()) 45 | } 46 | } 47 | 48 | /// Define our own `GroupCommitment` type instead of using [`frost::GroupCommitment`]. 49 | /// 50 | /// The serialization design specifies that `GroupCommitment` is an [`jubjub::AffinePoint`] that uses: 51 | /// "a 32-byte little-endian canonical representation". 52 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] 53 | #[cfg_attr(test, derive(Arbitrary))] 54 | pub struct GroupCommitment([u8; 32]); 55 | 56 | /// Define our own `SignatureResponse` type instead of using [`frost::SignatureResponse`]. 57 | /// 58 | /// The serialization design specifies that `SignatureResponse` is a [`jubjub::Scalar`] that uses: 59 | /// "a 32-byte little-endian canonical representation". 60 | #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] 61 | #[cfg_attr(test, derive(Arbitrary))] 62 | pub struct SignatureResponse([u8; 32]); 63 | 64 | impl From> for SignatureResponse { 65 | fn from(value: signature::Signature) -> SignatureResponse { 66 | SignatureResponse(value.s_bytes) 67 | } 68 | } 69 | 70 | impl From> for GroupCommitment { 71 | fn from(value: signature::Signature) -> GroupCommitment { 72 | GroupCommitment(value.r_bytes) 73 | } 74 | } 75 | 76 | /// Define our own `VerificationKey` type instead of using [`verification_key::VerificationKey`]. 77 | /// 78 | /// The serialization design specifies that `VerificationKey` is a [`verification_key::VerificationKeyBytes`] that uses: 79 | /// "a 32-byte little-endian canonical representation". 80 | #[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)] 81 | #[cfg_attr(test, derive(Arbitrary))] 82 | pub struct VerificationKey([u8; 32]); 83 | 84 | impl From> for VerificationKey { 85 | fn from(value: verification_key::VerificationKey) -> VerificationKey { 86 | VerificationKey(<[u8; 32]>::from(value)) 87 | } 88 | } 89 | 90 | /// The data required to serialize a frost message. 91 | #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 92 | #[cfg_attr(test, derive(Arbitrary))] 93 | pub struct Message { 94 | header: Header, 95 | payload: Payload, 96 | } 97 | 98 | /// The data required to serialize the common header fields for every message. 99 | /// 100 | /// Note: the `msg_type` is derived from the `payload` enum variant. 101 | #[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)] 102 | pub struct Header { 103 | version: MsgVersion, 104 | sender: ParticipantId, 105 | receiver: ParticipantId, 106 | } 107 | 108 | /// The data required to serialize the payload for a message. 109 | #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 110 | #[cfg_attr(test, derive(Arbitrary))] 111 | pub enum Payload { 112 | SharePackage(SharePackage), 113 | SigningCommitments(SigningCommitments), 114 | SigningPackage(SigningPackage), 115 | SignatureShare(SignatureShare), 116 | AggregateSignature(AggregateSignature), 117 | } 118 | 119 | /// The numeric values used to identify each [`Payload`] variant during serialization. 120 | // TODO: spec says `#[repr(u8)]` but it is incompatible with `bincode` 121 | // manual serialization and deserialization is needed. 122 | #[repr(u32)] 123 | #[non_exhaustive] 124 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 125 | enum MsgType { 126 | SharePackage, 127 | SigningCommitments, 128 | SigningPackage, 129 | SignatureShare, 130 | AggregateSignature, 131 | } 132 | 133 | /// The numeric values used to identify the protocol version during serialization. 134 | #[derive(PartialEq, Serialize, Deserialize, Debug, Clone, Copy)] 135 | pub struct MsgVersion(u8); 136 | 137 | /// The numeric values used to identify each participant during serialization. 138 | /// 139 | /// In the `frost` module, participant ID `0` should be invalid. 140 | /// But in serialization, we want participants to be indexed from `0..n`, 141 | /// where `n` is the number of participants. 142 | /// This helps us look up their shares and commitments in serialized arrays. 143 | /// So in serialization, we assign the dealer and aggregator the highest IDs, 144 | /// and mark those IDs as invalid for signers. 145 | /// 146 | /// "When performing Shamir secret sharing, a polynomial `f(x)` is used to generate 147 | /// each party’s share of the secret. The actual secret is `f(0)` and the party with 148 | /// ID `i` will be given a share with value `f(i)`. 149 | /// Since a DKG may be implemented in the future, we recommend that the ID `0` be declared invalid." 150 | /// https://raw.githubusercontent.com/ZcashFoundation/redjubjub/main/zcash-frost-audit-report-20210323.pdf#d 151 | #[derive(PartialEq, Eq, Hash, PartialOrd, Debug, Copy, Clone, Ord)] 152 | pub enum ParticipantId { 153 | /// A serialized participant ID for a signer. 154 | /// 155 | /// Must be less than or equal to [`constants::MAX_SIGNER_PARTICIPANT_ID`]. 156 | Signer(u64), 157 | /// The fixed participant ID for the dealer as defined in [`constants::DEALER_PARTICIPANT_ID`]. 158 | Dealer, 159 | /// The fixed participant ID for the aggregator as defined in [`constants::AGGREGATOR_PARTICIPANT_ID`]. 160 | Aggregator, 161 | } 162 | 163 | impl From for u64 { 164 | fn from(value: ParticipantId) -> u64 { 165 | match value { 166 | // An id of `0` is invalid in frost. 167 | ParticipantId::Signer(id) => id + 1, 168 | ParticipantId::Dealer => constants::DEALER_PARTICIPANT_ID, 169 | ParticipantId::Aggregator => constants::AGGREGATOR_PARTICIPANT_ID, 170 | } 171 | } 172 | } 173 | 174 | /// The data required to serialize [`frost::SharePackage`]. 175 | /// 176 | /// The dealer sends this message to each signer for this round. 177 | /// With this, the signer should be able to build a [`SharePackage`] and use 178 | /// the [`frost::sign()`] function. 179 | /// 180 | /// Note: [`frost::SharePackage::public`] can be calculated from [`SharePackage::secret_share`]. 181 | #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 182 | #[cfg_attr(test, derive(Arbitrary))] 183 | pub struct SharePackage { 184 | /// The public signing key that represents the entire group: 185 | /// [`frost::SharePackage::group_public`]. 186 | group_public: VerificationKey, 187 | /// This participant's secret key share: [`frost::SharePackage::share`]. 188 | secret_share: Secret, 189 | /// The commitments to the coefficients for our secret polynomial _f_, 190 | /// used to generate participants' key shares. Participants use these to perform 191 | /// verifiable secret sharing. 192 | /// Share packages that contain duplicate or missing [`ParticipantId`]s are invalid. 193 | /// [`ParticipantId`]s must be serialized in ascending numeric order. 194 | share_commitment: BTreeMap, 195 | } 196 | 197 | /// The data required to serialize [`frost::SigningCommitments`]. 198 | /// 199 | /// Each signer must send this message to the aggregator. 200 | /// A signing commitment from the first round of the signing protocol. 201 | #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 202 | #[cfg_attr(test, derive(Arbitrary))] 203 | pub struct SigningCommitments { 204 | /// The hiding point: [`frost::SigningCommitments::hiding`] 205 | hiding: Commitment, 206 | /// The binding point: [`frost::SigningCommitments::binding`] 207 | binding: Commitment, 208 | } 209 | 210 | /// The data required to serialize [`frost::SigningPackage`]. 211 | /// 212 | /// The aggregator decides what message is going to be signed and 213 | /// sends it to each signer with all the commitments collected. 214 | #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 215 | #[cfg_attr(test, derive(Arbitrary))] 216 | pub struct SigningPackage { 217 | /// The collected commitments for each signer as a hashmap of 218 | /// unique participant identifiers: [`frost::SigningPackage::signing_commitments`] 219 | /// 220 | /// Signing packages that contain duplicate or missing [`ParticipantId`]s are invalid. 221 | signing_commitments: BTreeMap, 222 | /// The message to be signed: [`frost::SigningPackage::message`]. 223 | /// 224 | /// Each signer should perform protocol-specific verification on the message. 225 | message: Vec, 226 | } 227 | 228 | impl From for frost::SigningPackage { 229 | fn from(value: SigningPackage) -> frost::SigningPackage { 230 | let mut signing_commitments = Vec::new(); 231 | for (participant_id, commitment) in &value.signing_commitments { 232 | // TODO(str4d): This will be so much nicer once const_evaluatable_checked 233 | // stabilises, and `GroupEncoding::from_bytes` can take the array directly. 234 | let mut hiding_repr = ::Repr::default(); 235 | let mut binding_repr = ::Repr::default(); 236 | hiding_repr.as_mut().copy_from_slice(&commitment.hiding.0); 237 | binding_repr.as_mut().copy_from_slice(&commitment.binding.0); 238 | 239 | let s = frost::SigningCommitments { 240 | index: u64::from(*participant_id), 241 | // TODO: The `from_bytes()` response is a `CtOption` so we have to `unwrap()` 242 | hiding: S::Point::from_bytes(&hiding_repr).unwrap(), 243 | binding: S::Point::from_bytes(&binding_repr).unwrap(), 244 | }; 245 | signing_commitments.push(s); 246 | } 247 | 248 | frost::SigningPackage { 249 | signing_commitments, 250 | message: value.message, 251 | } 252 | } 253 | } 254 | 255 | /// The data required to serialize [`frost::SignatureShare`]. 256 | /// 257 | /// Each signer sends their signatures to the aggregator who is going to collect them 258 | /// and generate a final spend signature. 259 | #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 260 | #[cfg_attr(test, derive(Arbitrary))] 261 | pub struct SignatureShare { 262 | /// This participant's signature over the message: [`frost::SignatureShare::signature`] 263 | signature: SignatureResponse, 264 | } 265 | 266 | /// The data required to serialize a successful output from [`frost::aggregate()`]. 267 | /// 268 | /// The final signature is broadcasted by the aggregator to all signers. 269 | #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] 270 | #[cfg_attr(test, derive(Arbitrary))] 271 | pub struct AggregateSignature { 272 | /// The aggregated group commitment: [`signature::Signature::r_bytes`] returned by [`frost::aggregate()`] 273 | group_commitment: GroupCommitment, 274 | /// A plain Schnorr signature created by summing all the signature shares: 275 | /// [`signature::Signature::s_bytes`] returned by [`frost::aggregate()`] 276 | schnorr_signature: SignatureResponse, 277 | } 278 | -------------------------------------------------------------------------------- /src/orchard.rs: -------------------------------------------------------------------------------- 1 | //! Signature types for the Orchard protocol. 2 | 3 | #[cfg(feature = "alloc")] 4 | use alloc::vec::Vec; 5 | #[cfg(feature = "alloc")] 6 | use core::borrow::Borrow; 7 | 8 | use group::GroupEncoding; 9 | #[cfg(feature = "alloc")] 10 | use group::{ff::PrimeField, Group}; 11 | use pasta_curves::pallas; 12 | 13 | use crate::{private, SigType}; 14 | 15 | #[cfg(feature = "alloc")] 16 | use crate::scalar_mul::{LookupTable5, NonAdjacentForm, VartimeMultiscalarMul}; 17 | 18 | #[cfg(test)] 19 | mod tests; 20 | 21 | /// The byte-encoding of the basepoint for the Orchard `SpendAuthSig` on the [Pallas curve][pallasandvesta]. 22 | /// 23 | /// [pallasandvesta]: https://zips.z.cash/protocol/nu5.pdf#pallasandvesta 24 | // Reproducible by pallas::Point::hash_to_curve("z.cash:Orchard")(b"G").to_bytes() 25 | const ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES: [u8; 32] = [ 26 | 99, 201, 117, 184, 132, 114, 26, 141, 12, 161, 112, 123, 227, 12, 127, 12, 95, 68, 95, 62, 124, 27 | 24, 141, 59, 6, 214, 241, 40, 179, 35, 85, 183, 28 | ]; 29 | 30 | /// The byte-encoding of the basepoint for the Orchard `BindingSig` on the Pallas curve. 31 | // Reproducible by pallas::Point::hash_to_curve("z.cash:Orchard-cv")(b"r").to_bytes() 32 | const ORCHARD_BINDINGSIG_BASEPOINT_BYTES: [u8; 32] = [ 33 | 145, 90, 60, 136, 104, 198, 195, 14, 47, 128, 144, 238, 69, 215, 110, 64, 72, 32, 141, 234, 91, 34 | 35, 102, 79, 187, 9, 164, 15, 85, 68, 244, 7, 35 | ]; 36 | 37 | /// A type variable corresponding to Zcash's `OrchardSpendAuthSig`. 38 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 39 | pub enum SpendAuth {} 40 | // This should not exist, but is necessary to use zeroize::DefaultIsZeroes. 41 | impl Default for SpendAuth { 42 | fn default() -> Self { 43 | unimplemented!() 44 | } 45 | } 46 | impl SigType for SpendAuth {} 47 | impl super::SpendAuth for SpendAuth {} 48 | 49 | /// A type variable corresponding to Zcash's `OrchardBindingSig`. 50 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 51 | pub enum Binding {} 52 | // This should not exist, but is necessary to use zeroize::DefaultIsZeroes. 53 | impl Default for Binding { 54 | fn default() -> Self { 55 | unimplemented!() 56 | } 57 | } 58 | impl SigType for Binding {} 59 | impl super::Binding for Binding {} 60 | 61 | impl private::SealedScalar for pallas::Scalar { 62 | fn from_bytes_wide(bytes: &[u8; 64]) -> Self { 63 | >::from_uniform_bytes(bytes) 64 | } 65 | fn from_raw(val: [u64; 4]) -> Self { 66 | pallas::Scalar::from_raw(val) 67 | } 68 | } 69 | impl private::Sealed for SpendAuth { 70 | const H_STAR_PERSONALIZATION: &'static [u8; 16] = b"Zcash_RedPallasH"; 71 | type Point = pallas::Point; 72 | type Scalar = pallas::Scalar; 73 | 74 | fn basepoint() -> pallas::Point { 75 | pallas::Point::from_bytes(&ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES).unwrap() 76 | } 77 | } 78 | impl private::Sealed for Binding { 79 | const H_STAR_PERSONALIZATION: &'static [u8; 16] = b"Zcash_RedPallasH"; 80 | type Point = pallas::Point; 81 | type Scalar = pallas::Scalar; 82 | 83 | fn basepoint() -> pallas::Point { 84 | pallas::Point::from_bytes(&ORCHARD_BINDINGSIG_BASEPOINT_BYTES).unwrap() 85 | } 86 | } 87 | 88 | #[cfg(feature = "alloc")] 89 | impl NonAdjacentForm for pallas::Scalar { 90 | fn inner_to_bytes(&self) -> [u8; 32] { 91 | self.to_repr() 92 | } 93 | 94 | /// The NAF length for Pallas is 255 since Pallas' order is about 2254 + 95 | /// 2125.1. 96 | fn naf_length() -> usize { 97 | 255 98 | } 99 | } 100 | 101 | #[cfg(feature = "alloc")] 102 | impl<'a> From<&'a pallas::Point> for LookupTable5 { 103 | #[allow(non_snake_case)] 104 | fn from(A: &'a pallas::Point) -> Self { 105 | let mut Ai = [*A; 8]; 106 | let A2 = A.double(); 107 | for i in 0..7 { 108 | Ai[i + 1] = A2 + Ai[i]; 109 | } 110 | // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A] 111 | LookupTable5(Ai) 112 | } 113 | } 114 | 115 | #[cfg(feature = "alloc")] 116 | impl VartimeMultiscalarMul for pallas::Point { 117 | type Scalar = pallas::Scalar; 118 | type Point = pallas::Point; 119 | 120 | #[allow(non_snake_case)] 121 | fn optional_multiscalar_mul(scalars: I, points: J) -> Option 122 | where 123 | I: IntoIterator, 124 | I::Item: Borrow, 125 | J: IntoIterator>, 126 | { 127 | let nafs: Vec<_> = scalars 128 | .into_iter() 129 | .map(|c| c.borrow().non_adjacent_form(5)) 130 | .collect(); 131 | 132 | let lookup_tables = points 133 | .into_iter() 134 | .map(|P_opt| P_opt.map(|P| LookupTable5::::from(&P))) 135 | .collect::>>()?; 136 | 137 | let mut r = pallas::Point::identity(); 138 | let naf_size = Self::Scalar::naf_length(); 139 | 140 | for i in (0..naf_size).rev() { 141 | let mut t = r.double(); 142 | 143 | for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) { 144 | #[allow(clippy::comparison_chain)] 145 | if naf[i] > 0 { 146 | t += lookup_table.select(naf[i] as usize); 147 | } else if naf[i] < 0 { 148 | t -= lookup_table.select(-naf[i] as usize); 149 | } 150 | } 151 | 152 | r = t; 153 | } 154 | 155 | Some(r) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/orchard/tests.rs: -------------------------------------------------------------------------------- 1 | use std::println; 2 | 3 | use crate::scalar_mul::{self, VartimeMultiscalarMul}; 4 | use alloc::vec::Vec; 5 | use group::ff::Field; 6 | use group::{ff::PrimeField, GroupEncoding}; 7 | use rand::thread_rng; 8 | 9 | use pasta_curves::arithmetic::CurveExt; 10 | use pasta_curves::pallas; 11 | 12 | #[test] 13 | fn orchard_spendauth_basepoint() { 14 | use super::ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES; 15 | assert_eq!( 16 | pallas::Point::hash_to_curve("z.cash:Orchard")(b"G").to_bytes(), 17 | ORCHARD_SPENDAUTHSIG_BASEPOINT_BYTES 18 | ); 19 | } 20 | 21 | #[test] 22 | fn orchard_binding_basepoint() { 23 | use super::ORCHARD_BINDINGSIG_BASEPOINT_BYTES; 24 | assert_eq!( 25 | pallas::Point::hash_to_curve("z.cash:Orchard-cv")(b"r").to_bytes(), 26 | ORCHARD_BINDINGSIG_BASEPOINT_BYTES 27 | ); 28 | } 29 | 30 | /// Generates test vectors for [`test_pallas_vartime_multiscalar_mul`]. 31 | // #[test] 32 | #[allow(dead_code)] 33 | fn gen_pallas_test_vectors() { 34 | use group::Group; 35 | 36 | let rng = thread_rng(); 37 | 38 | let scalars = [ 39 | pallas::Scalar::random(rng.clone()), 40 | pallas::Scalar::random(rng.clone()), 41 | ]; 42 | println!("Scalars:"); 43 | for scalar in scalars { 44 | println!("{:?}", scalar.to_repr()); 45 | } 46 | 47 | let points = [ 48 | pallas::Point::random(rng.clone()), 49 | pallas::Point::random(rng), 50 | ]; 51 | println!("Points:"); 52 | for point in points { 53 | println!("{:?}", point.to_bytes()); 54 | } 55 | 56 | let res = pallas::Point::vartime_multiscalar_mul(scalars, points); 57 | println!("Result:"); 58 | println!("{:?}", res.to_bytes()); 59 | } 60 | 61 | /// Checks if the vartime multiscalar multiplication on Pallas produces the expected product. 62 | /// The test vectors were generated by [`gen_pallas_test_vectors`]. 63 | #[test] 64 | fn test_pallas_vartime_multiscalar_mul() { 65 | let scalars: [[u8; 32]; 2] = [ 66 | [ 67 | 235, 211, 155, 231, 188, 225, 161, 143, 148, 66, 177, 18, 246, 175, 177, 55, 1, 185, 68 | 115, 175, 208, 12, 252, 5, 168, 198, 26, 166, 129, 252, 158, 8, 69 | ], 70 | [ 71 | 1, 8, 55, 59, 168, 56, 248, 199, 77, 230, 228, 96, 35, 65, 191, 56, 137, 226, 161, 184, 72 | 105, 223, 98, 166, 248, 160, 156, 74, 18, 228, 122, 44, 73 | ], 74 | ]; 75 | 76 | let points: [[u8; 32]; 2] = [ 77 | [ 78 | 81, 113, 73, 111, 90, 141, 91, 248, 252, 201, 109, 74, 99, 75, 11, 228, 152, 144, 254, 79 | 104, 240, 69, 211, 23, 201, 128, 236, 187, 233, 89, 59, 133, 80 | ], 81 | [ 82 | 177, 3, 100, 162, 246, 15, 81, 236, 51, 73, 69, 43, 45, 202, 226, 99, 27, 58, 133, 52, 83 | 231, 244, 125, 221, 88, 155, 192, 4, 164, 102, 34, 143, 84 | ], 85 | ]; 86 | 87 | let expected_product: [u8; 32] = [ 88 | 68, 54, 98, 93, 238, 28, 229, 186, 127, 154, 101, 209, 216, 214, 66, 45, 141, 210, 70, 119, 89 | 100, 245, 164, 155, 213, 45, 126, 17, 199, 8, 84, 143, 90 | ]; 91 | 92 | let scalars: Vec = scalars 93 | .into_iter() 94 | .map(|s| { 95 | pallas::Scalar::from_repr_vartime(s).expect("Could not deserialize a `pallas::Scalar`.") 96 | }) 97 | .collect(); 98 | 99 | let points: Vec = points 100 | .into_iter() 101 | .map(|p| pallas::Point::from_bytes(&p).expect("Could not deserialize a `pallas::Point`.")) 102 | .collect(); 103 | 104 | let expected_product = pallas::Point::from_bytes(&expected_product) 105 | .expect("Could not deserialize a `pallas::Point`."); 106 | 107 | let product = pallas::Point::vartime_multiscalar_mul(scalars, points); 108 | assert_eq!(expected_product, product); 109 | } 110 | 111 | /// Tests the non-adjacent form for a Pallas scalar. 112 | #[test] 113 | fn test_non_adjacent_form() { 114 | let rng = thread_rng(); 115 | 116 | let scalar = pallas::Scalar::random(rng); 117 | scalar_mul::tests::test_non_adjacent_form_for_scalar(5, scalar); 118 | } 119 | -------------------------------------------------------------------------------- /src/sapling.rs: -------------------------------------------------------------------------------- 1 | //! Signature types for the Sapling protocol. 2 | 3 | use super::SigType; 4 | 5 | /// A type variable corresponding to Zcash's Sapling `SpendAuthSig`. 6 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 7 | pub enum SpendAuth {} 8 | // This should not exist, but is necessary to use zeroize::DefaultIsZeroes. 9 | impl Default for SpendAuth { 10 | fn default() -> Self { 11 | unimplemented!() 12 | } 13 | } 14 | impl SigType for SpendAuth {} 15 | impl super::SpendAuth for SpendAuth {} 16 | 17 | /// A type variable corresponding to Zcash's Sapling `BindingSig`. 18 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 19 | pub enum Binding {} 20 | // This should not exist, but is necessary to use zeroize::DefaultIsZeroes. 21 | impl Default for Binding { 22 | fn default() -> Self { 23 | unimplemented!() 24 | } 25 | } 26 | impl SigType for Binding {} 27 | impl super::Binding for Binding {} 28 | -------------------------------------------------------------------------------- /src/scalar_mul.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // Copyright (c) 2017-2021 isis agora lovecruft, Henry de Valence 6 | // See LICENSE for licensing information. 7 | // 8 | // Authors: 9 | // - isis agora lovecruft 10 | // - Henry de Valence 11 | // - Deirdre Connolly 12 | 13 | //! Traits and types that support variable-time multiscalar multiplication. 14 | 15 | use alloc::vec::Vec; 16 | use core::{borrow::Borrow, fmt::Debug}; 17 | 18 | use jubjub::{ExtendedNielsPoint, ExtendedPoint}; 19 | 20 | #[cfg(test)] 21 | pub(crate) mod tests; 22 | 23 | /// A trait for variable-time multiscalar multiplication without precomputation. 24 | pub trait VartimeMultiscalarMul { 25 | /// The type of scalar being multiplied, e.g., `jubjub::Scalar`. 26 | type Scalar; 27 | /// The type of point being multiplied, e.g., `jubjub::AffinePoint`. 28 | type Point; 29 | 30 | /// Given an iterator of public scalars and an iterator of 31 | /// `Option`s of points, compute either `Some(Q)`, where 32 | /// $$ 33 | /// Q = c\_1 P\_1 + \cdots + c\_n P\_n, 34 | /// $$ 35 | /// if all points were `Some(P_i)`, or else return `None`. 36 | fn optional_multiscalar_mul(scalars: I, points: J) -> Option 37 | where 38 | I: IntoIterator, 39 | I::Item: Borrow, 40 | J: IntoIterator>; 41 | 42 | /// Given an iterator of public scalars and an iterator of 43 | /// public points, compute 44 | /// $$ 45 | /// Q = c\_1 P\_1 + \cdots + c\_n P\_n, 46 | /// $$ 47 | /// using variable-time operations. 48 | /// 49 | /// It is an error to call this function with two iterators of different lengths. 50 | fn vartime_multiscalar_mul(scalars: I, points: J) -> Self::Point 51 | where 52 | I: IntoIterator, 53 | I::Item: Borrow, 54 | J: IntoIterator, 55 | J::Item: Borrow, 56 | Self::Point: Clone, 57 | { 58 | Self::optional_multiscalar_mul( 59 | scalars, 60 | points.into_iter().map(|p| Some(p.borrow().clone())), 61 | ) 62 | .unwrap() 63 | } 64 | } 65 | 66 | /// Produces the non-adjacent form (NAF) of a 32-byte scalar. 67 | pub trait NonAdjacentForm { 68 | /// Returns the scalar represented as a little-endian byte array. 69 | fn inner_to_bytes(&self) -> [u8; 32]; 70 | 71 | /// Returns the number of coefficients in the NAF. 72 | /// 73 | /// Claim: The length of the NAF requires at most one more coefficient than the length of the 74 | /// binary representation of the scalar. [^1] 75 | /// 76 | /// This trait works with scalars of at most 256 binary bits, so the default implementation 77 | /// returns 257. However, some (sub)groups' orders don't reach 256 bits and their scalars don't 78 | /// need the full 256 bits. Setting the corresponding NAF length for a particular curve will 79 | /// speed up the multiscalar multiplication since the number of loop iterations required for the 80 | /// multiplication is equal to the length of the NAF. 81 | /// 82 | /// [^1]: The proof is left as an exercise to the reader. 83 | fn naf_length() -> usize { 84 | 257 85 | } 86 | 87 | /// Computes the width-`w` non-adjacent form (width-`w` NAF) of the scalar. 88 | /// 89 | /// Thanks to [`curve25519-dalek`]. 90 | /// 91 | /// [`curve25519-dalek`]: https://github.com/dalek-cryptography/curve25519-dalek/blob/3e189820da03cc034f5fa143fc7b2ccb21fffa5e/src/scalar.rs#L907 92 | fn non_adjacent_form(&self, w: usize) -> Vec { 93 | // required by the NAF definition 94 | debug_assert!(w >= 2); 95 | // required so that the NAF digits fit in i8 96 | debug_assert!(w <= 8); 97 | 98 | use byteorder::{ByteOrder, LittleEndian}; 99 | 100 | let naf_length = Self::naf_length(); 101 | let mut naf = vec![0; naf_length]; 102 | 103 | let mut x_u64 = [0u64; 5]; 104 | LittleEndian::read_u64_into(&self.inner_to_bytes(), &mut x_u64[0..4]); 105 | 106 | let width = 1 << w; 107 | let window_mask = width - 1; 108 | 109 | let mut pos = 0; 110 | let mut carry = 0; 111 | 112 | while pos < naf_length { 113 | // Construct a buffer of bits of the scalar, starting at bit `pos` 114 | let u64_idx = pos / 64; 115 | let bit_idx = pos % 64; 116 | let bit_buf: u64 = if bit_idx < 64 - w { 117 | // This window's bits are contained in a single u64 118 | x_u64[u64_idx] >> bit_idx 119 | } else { 120 | // Combine the current u64's bits with the bits from the next u64 121 | (x_u64[u64_idx] >> bit_idx) | (x_u64[1 + u64_idx] << (64 - bit_idx)) 122 | }; 123 | 124 | // Add the carry into the current window 125 | let window = carry + (bit_buf & window_mask); 126 | 127 | if window & 1 == 0 { 128 | // If the window value is even, preserve the carry and continue. 129 | // Why is the carry preserved? 130 | // If carry == 0 and window & 1 == 0, then the next carry should be 0 131 | // If carry == 1 and window & 1 == 0, then bit_buf & 1 == 1 so the next carry should be 1 132 | pos += 1; 133 | continue; 134 | } 135 | 136 | if window < width / 2 { 137 | carry = 0; 138 | naf[pos] = window as i8; 139 | } else { 140 | carry = 1; 141 | naf[pos] = (window as i8).wrapping_sub(width as i8); 142 | } 143 | 144 | pos += w; 145 | } 146 | 147 | naf 148 | } 149 | } 150 | 151 | impl NonAdjacentForm for jubjub::Scalar { 152 | fn inner_to_bytes(&self) -> [u8; 32] { 153 | self.to_bytes() 154 | } 155 | 156 | /// The NAF length for Jubjub is 253 since Jubjub's order is about 2251.85. 157 | fn naf_length() -> usize { 158 | 253 159 | } 160 | } 161 | 162 | /// Holds odd multiples 1A, 3A, ..., 15A of a point A. 163 | #[derive(Copy, Clone)] 164 | pub(crate) struct LookupTable5(pub(crate) [T; 8]); 165 | 166 | impl LookupTable5 { 167 | /// Given public, odd \\( x \\) with \\( 0 < x < 2^4 \\), return \\(xA\\). 168 | pub fn select(&self, x: usize) -> T { 169 | debug_assert_eq!(x & 1, 1); 170 | debug_assert!(x < 16); 171 | 172 | self.0[x / 2] 173 | } 174 | } 175 | 176 | impl Debug for LookupTable5 { 177 | fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { 178 | write!(f, "LookupTable5({:?})", self.0) 179 | } 180 | } 181 | 182 | impl<'a> From<&'a ExtendedPoint> for LookupTable5 { 183 | #[allow(non_snake_case)] 184 | fn from(A: &'a ExtendedPoint) -> Self { 185 | let mut Ai = [A.to_niels(); 8]; 186 | let A2 = A.double(); 187 | for i in 0..7 { 188 | Ai[i + 1] = (A2 + Ai[i]).to_niels(); 189 | } 190 | // Now Ai = [A, 3A, 5A, 7A, 9A, 11A, 13A, 15A] 191 | LookupTable5(Ai) 192 | } 193 | } 194 | 195 | impl VartimeMultiscalarMul for ExtendedPoint { 196 | type Scalar = jubjub::Scalar; 197 | type Point = ExtendedPoint; 198 | 199 | /// Variable-time multiscalar multiplication using a non-adjacent form of 200 | /// width (5). 201 | /// 202 | /// The non-adjacent form has signed, odd digits. Using only odd digits 203 | /// halves the table size (since we only need odd multiples), or gives fewer 204 | /// additions for the same table size. 205 | /// 206 | /// As the name implies, the runtime varies according to the values of the 207 | /// inputs, thus is not safe for computing over secret data, but is great 208 | /// for computing over public data, such as validating signatures. 209 | #[allow(non_snake_case)] 210 | fn optional_multiscalar_mul(scalars: I, points: J) -> Option 211 | where 212 | I: IntoIterator, 213 | I::Item: Borrow, 214 | J: IntoIterator>, 215 | { 216 | let nafs: Vec<_> = scalars 217 | .into_iter() 218 | .map(|c| c.borrow().non_adjacent_form(5)) 219 | .collect(); 220 | 221 | let lookup_tables = points 222 | .into_iter() 223 | .map(|P_opt| P_opt.map(|P| LookupTable5::::from(&P))) 224 | .collect::>>()?; 225 | 226 | let mut r = ExtendedPoint::identity(); 227 | let naf_size = Self::Scalar::naf_length(); 228 | 229 | for i in (0..naf_size).rev() { 230 | let mut t = r.double(); 231 | 232 | for (naf, lookup_table) in nafs.iter().zip(lookup_tables.iter()) { 233 | #[allow(clippy::comparison_chain)] 234 | if naf[i] > 0 { 235 | t += lookup_table.select(naf[i] as usize); 236 | } else if naf[i] < 0 { 237 | t -= lookup_table.select(-naf[i] as usize); 238 | } 239 | } 240 | 241 | r = t; 242 | } 243 | 244 | Some(r) 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/scalar_mul/tests.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use group::{ff::Field, GroupEncoding}; 3 | use num_bigint::BigInt; 4 | use num_traits::Zero; 5 | use rand::thread_rng; 6 | 7 | use crate::scalar_mul::VartimeMultiscalarMul; 8 | 9 | use super::NonAdjacentForm; 10 | 11 | /// Generates test vectors for [`test_jubjub_vartime_multiscalar_mul`]. 12 | // #[test] 13 | #[allow(dead_code)] 14 | fn gen_jubjub_test_vectors() { 15 | use group::Group; 16 | use std::println; 17 | 18 | let rng = thread_rng(); 19 | 20 | let scalars = [ 21 | jubjub::Scalar::random(rng.clone()), 22 | jubjub::Scalar::random(rng.clone()), 23 | ]; 24 | println!("Scalars:"); 25 | for scalar in scalars { 26 | println!("{:?}", scalar.to_bytes()); 27 | } 28 | 29 | let points = [ 30 | jubjub::ExtendedPoint::random(rng.clone()), 31 | jubjub::ExtendedPoint::random(rng), 32 | ]; 33 | println!("Points:"); 34 | for point in points { 35 | println!("{:?}", point.to_bytes()); 36 | } 37 | 38 | let res = jubjub::ExtendedPoint::vartime_multiscalar_mul(scalars, points); 39 | println!("Result:"); 40 | println!("{:?}", res.to_bytes()); 41 | } 42 | 43 | /// Checks if the vartime multiscalar multiplication on Jubjub produces the expected product. 44 | /// The test vectors were generated by [`gen_jubjub_test_vectors`]. 45 | #[test] 46 | fn test_jubjub_vartime_multiscalar_mul() { 47 | let scalars: [[u8; 32]; 2] = [ 48 | [ 49 | 147, 209, 135, 83, 133, 175, 29, 28, 22, 161, 0, 220, 100, 218, 103, 47, 134, 242, 49, 50 | 19, 254, 204, 107, 185, 189, 155, 33, 110, 100, 141, 59, 0, 51 | ], 52 | [ 53 | 138, 136, 196, 249, 144, 2, 9, 103, 233, 93, 253, 46, 181, 12, 41, 158, 62, 201, 35, 54 | 198, 108, 139, 136, 78, 210, 12, 1, 223, 231, 22, 92, 13, 55 | ], 56 | ]; 57 | 58 | let points: [[u8; 32]; 2] = [ 59 | [ 60 | 93, 252, 67, 45, 63, 170, 103, 247, 53, 37, 164, 250, 32, 210, 38, 71, 162, 68, 205, 61 | 176, 116, 46, 209, 66, 131, 209, 107, 193, 210, 153, 222, 31, 62 | ], 63 | [ 64 | 139, 112, 204, 231, 187, 141, 159, 122, 210, 164, 7, 162, 185, 171, 47, 199, 5, 33, 80, 65 | 207, 129, 24, 165, 90, 204, 253, 38, 27, 55, 86, 225, 52, 66 | ], 67 | ]; 68 | 69 | let expected_product: [u8; 32] = [ 70 | 64, 228, 212, 168, 76, 90, 248, 218, 86, 22, 182, 130, 227, 52, 170, 88, 220, 193, 166, 71 | 131, 180, 48, 148, 72, 212, 148, 212, 240, 77, 244, 91, 213, 72 | ]; 73 | 74 | let scalars: Vec = scalars 75 | .into_iter() 76 | .map(|s| jubjub::Scalar::from_bytes(&s).expect("Could not deserialize a `jubjub::Scalar`.")) 77 | .collect(); 78 | 79 | let points: Vec = points 80 | .into_iter() 81 | .map(|p| { 82 | jubjub::ExtendedPoint::from_bytes(&p) 83 | .expect("Could not deserialize a `jubjub::ExtendedPoint`.") 84 | }) 85 | .collect(); 86 | 87 | let expected_product = jubjub::ExtendedPoint::from_bytes(&expected_product) 88 | .expect("Could not deserialize a `jubjub::ExtendedPoint`."); 89 | 90 | let product = jubjub::ExtendedPoint::vartime_multiscalar_mul(scalars, points); 91 | assert_eq!(expected_product, product); 92 | } 93 | 94 | /// Tests the non-adjacent form for a Jubjub scalar. 95 | #[test] 96 | fn test_non_adjacent_form() { 97 | let rng = thread_rng(); 98 | 99 | let scalar = jubjub::Scalar::random(rng); 100 | test_non_adjacent_form_for_scalar(5, scalar); 101 | } 102 | 103 | /// Tests the non-adjacent form for a particular scalar. 104 | pub(crate) fn test_non_adjacent_form_for_scalar(w: usize, scalar: Scalar) { 105 | let naf = scalar.non_adjacent_form(w); 106 | let naf_length = Scalar::naf_length(); 107 | 108 | // Check that the computed w-NAF has the intended length. 109 | assert_eq!(naf.len(), naf_length); 110 | 111 | let w = u32::try_from(w).expect("The window `w` did not fit into `u32`."); 112 | 113 | // `bound` <- 2^(w-1) 114 | let bound = 2_i32.pow(w - 1); 115 | 116 | // `valid_coeffs` <- a range of odd integers from -2^(w-1) to 2^(w-1) 117 | let valid_coeffs: Vec = (-bound..bound).filter(|x| x.rem_euclid(2) == 1).collect(); 118 | 119 | let mut reconstructed_scalar: BigInt = Zero::zero(); 120 | 121 | // Reconstruct the original scalar, and check two general invariants for any w-NAF along the 122 | // way. 123 | let mut i = 0; 124 | while i < naf_length { 125 | if naf[i] != 0 { 126 | // In a w-NAF, every nonzero coefficient `naf[i]` is an odd signed integer with 127 | // -2^(w-1) < `naf[i]` < 2^(w-1). 128 | assert!(valid_coeffs.contains(&i32::from(naf[i]))); 129 | 130 | // Incrementally keep reconstructing the original scalar. 131 | reconstructed_scalar += naf[i] * BigInt::from(2).pow(i.try_into().unwrap()); 132 | 133 | // In a w-NAF, at most one of any `w` consecutive coefficients is nonzero. 134 | for _ in 1..w { 135 | i += 1; 136 | if i >= naf_length { 137 | break; 138 | } 139 | assert_eq!(naf[i], 0) 140 | } 141 | } 142 | 143 | i += 1; 144 | } 145 | 146 | // Check that the reconstructed scalar is not negative, and convert it to little-endian bytes. 147 | let reconstructed_scalar = reconstructed_scalar 148 | .to_biguint() 149 | .expect("The reconstructed scalar is negative.") 150 | .to_bytes_le(); 151 | 152 | // Check that the reconstructed scalar is not too big. 153 | assert!(reconstructed_scalar.len() <= 32); 154 | 155 | // Convert the reconstructed scalar to a fixed byte array so we can compare it with the orginal 156 | // scalar. 157 | let mut reconstructed_scalar_bytes: [u8; 32] = [0; 32]; 158 | for (i, byte) in reconstructed_scalar.iter().enumerate() { 159 | reconstructed_scalar_bytes[i] = *byte; 160 | } 161 | 162 | // Check that the reconstructed scalar matches the original one. 163 | assert_eq!(reconstructed_scalar_bytes, scalar.inner_to_bytes()); 164 | } 165 | -------------------------------------------------------------------------------- /src/signature.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - Henry de Valence 9 | // - Conrado Gouvea 10 | 11 | //! RedDSA Signatures 12 | use core::{fmt, marker::PhantomData}; 13 | 14 | use crate::{hex_if_possible, SigType}; 15 | 16 | /// A RedDSA signature. 17 | #[derive(Copy, Clone, Eq, PartialEq)] 18 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 19 | pub struct Signature { 20 | pub(crate) r_bytes: [u8; 32], 21 | pub(crate) s_bytes: [u8; 32], 22 | pub(crate) _marker: PhantomData, 23 | } 24 | 25 | impl fmt::Debug for Signature { 26 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 | f.debug_struct("Signature") 28 | .field("r_bytes", &hex_if_possible(&self.r_bytes)) 29 | .field("s_bytes", &hex_if_possible(&self.s_bytes)) 30 | .finish() 31 | } 32 | } 33 | 34 | impl From<[u8; 64]> for Signature { 35 | fn from(bytes: [u8; 64]) -> Signature { 36 | let mut r_bytes = [0; 32]; 37 | r_bytes.copy_from_slice(&bytes[0..32]); 38 | let mut s_bytes = [0; 32]; 39 | s_bytes.copy_from_slice(&bytes[32..64]); 40 | Signature { 41 | r_bytes, 42 | s_bytes, 43 | _marker: PhantomData, 44 | } 45 | } 46 | } 47 | 48 | impl From> for [u8; 64] { 49 | fn from(sig: Signature) -> [u8; 64] { 50 | let mut bytes = [0; 64]; 51 | bytes[0..32].copy_from_slice(&sig.r_bytes[..]); 52 | bytes[32..64].copy_from_slice(&sig.s_bytes[..]); 53 | bytes 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/signing_key.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - Deirdre Connolly 9 | // - Henry de Valence 10 | 11 | use core::{ 12 | convert::{TryFrom, TryInto}, 13 | marker::PhantomData, 14 | }; 15 | 16 | use crate::{ 17 | private::SealedScalar, Error, Randomizer, SigType, Signature, SpendAuth, VerificationKey, 18 | }; 19 | 20 | use group::{ff::PrimeField, GroupEncoding}; 21 | use rand_core::{CryptoRng, RngCore}; 22 | 23 | /// A RedDSA signing key. 24 | #[derive(Copy, Clone, Debug)] 25 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 26 | #[cfg_attr(feature = "serde", serde(try_from = "SerdeHelper"))] 27 | #[cfg_attr(feature = "serde", serde(into = "SerdeHelper"))] 28 | #[cfg_attr(feature = "serde", serde(bound = "T: SigType"))] 29 | pub struct SigningKey { 30 | sk: T::Scalar, 31 | pk: VerificationKey, 32 | } 33 | 34 | impl From<&SigningKey> for VerificationKey { 35 | fn from(sk: &SigningKey) -> VerificationKey { 36 | sk.pk 37 | } 38 | } 39 | 40 | impl From> for [u8; 32] { 41 | fn from(sk: SigningKey) -> [u8; 32] { 42 | sk.sk.to_repr().as_ref().try_into().unwrap() 43 | } 44 | } 45 | 46 | impl TryFrom<[u8; 32]> for SigningKey { 47 | type Error = Error; 48 | 49 | fn try_from(bytes: [u8; 32]) -> Result { 50 | // XXX-jubjub: this should not use CtOption 51 | let mut repr = ::Repr::default(); 52 | repr.as_mut().copy_from_slice(&bytes); 53 | let maybe_sk = T::Scalar::from_repr(repr); 54 | if maybe_sk.is_some().into() { 55 | let sk = maybe_sk.unwrap(); 56 | let pk = VerificationKey::from(&sk); 57 | Ok(SigningKey { sk, pk }) 58 | } else { 59 | Err(Error::MalformedSigningKey) 60 | } 61 | } 62 | } 63 | 64 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 65 | struct SerdeHelper([u8; 32]); 66 | 67 | impl TryFrom for SigningKey { 68 | type Error = Error; 69 | 70 | fn try_from(helper: SerdeHelper) -> Result { 71 | helper.0.try_into() 72 | } 73 | } 74 | 75 | impl From> for SerdeHelper { 76 | fn from(sk: SigningKey) -> Self { 77 | Self(sk.into()) 78 | } 79 | } 80 | 81 | impl SigningKey { 82 | /// Randomize this public key with the given `randomizer`. 83 | pub fn randomize(&self, randomizer: &Randomizer) -> SigningKey { 84 | let sk = self.sk + randomizer; 85 | let pk = VerificationKey::from(&sk); 86 | SigningKey { sk, pk } 87 | } 88 | } 89 | 90 | impl SigningKey { 91 | /// Generate a new signing key. 92 | pub fn new(mut rng: R) -> SigningKey { 93 | let sk = { 94 | let mut bytes = [0; 64]; 95 | rng.fill_bytes(&mut bytes); 96 | T::Scalar::from_bytes_wide(&bytes) 97 | }; 98 | let pk = VerificationKey::from(&sk); 99 | SigningKey { sk, pk } 100 | } 101 | 102 | /// Create a signature of type `T` on `msg` using this `SigningKey`. 103 | // Similar to signature::Signer but without boxed errors. 104 | pub fn sign(&self, mut rng: R, msg: &[u8]) -> Signature { 105 | use crate::HStar; 106 | 107 | // Choose a byte sequence uniformly at random of length 108 | // (\ell_H + 128)/8 bytes. For RedJubjub and RedPallas this is 109 | // (512 + 128)/8 = 80. 110 | let random_bytes = { 111 | let mut bytes = [0; 80]; 112 | rng.fill_bytes(&mut bytes); 113 | bytes 114 | }; 115 | 116 | let nonce = HStar::::default() 117 | .update(&random_bytes[..]) 118 | .update(&self.pk.bytes.bytes[..]) // XXX ugly 119 | .update(msg) 120 | .finalize(); 121 | 122 | let r: T::Point = T::basepoint() * nonce; 123 | let r_bytes: [u8; 32] = r.to_bytes().as_ref().try_into().unwrap(); 124 | 125 | let c = HStar::::default() 126 | .update(&r_bytes[..]) 127 | .update(&self.pk.bytes.bytes[..]) // XXX ugly 128 | .update(msg) 129 | .finalize(); 130 | 131 | let s = nonce + (c * self.sk); 132 | let s_bytes = s.to_repr().as_ref().try_into().unwrap(); 133 | 134 | Signature { 135 | r_bytes, 136 | s_bytes, 137 | _marker: PhantomData, 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/verification_key.rs: -------------------------------------------------------------------------------- 1 | // -*- mode: rust; -*- 2 | // 3 | // This file is part of reddsa. 4 | // Copyright (c) 2019-2021 Zcash Foundation 5 | // See LICENSE for licensing information. 6 | // 7 | // Authors: 8 | // - Deirdre Connolly 9 | // - Henry de Valence 10 | 11 | use core::{ 12 | convert::{TryFrom, TryInto}, 13 | fmt, 14 | hash::Hash, 15 | marker::PhantomData, 16 | }; 17 | 18 | use group::{cofactor::CofactorGroup, ff::PrimeField, GroupEncoding}; 19 | 20 | use crate::{hex_if_possible, Error, Randomizer, SigType, Signature, SpendAuth}; 21 | 22 | /// A refinement type for `[u8; 32]` indicating that the bytes represent 23 | /// an encoding of a RedDSA verification key. 24 | /// 25 | /// This is useful for representing a compressed verification key; the 26 | /// [`VerificationKey`] type in this library holds other decompressed state 27 | /// used in signature verification. 28 | #[derive(Copy, Clone, Hash, PartialEq, Eq)] 29 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 30 | pub struct VerificationKeyBytes { 31 | pub(crate) bytes: [u8; 32], 32 | pub(crate) _marker: PhantomData, 33 | } 34 | 35 | impl fmt::Debug for VerificationKeyBytes { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | f.debug_struct("VerificationKeyBytes") 38 | .field("bytes", &hex_if_possible(&self.bytes)) 39 | .finish() 40 | } 41 | } 42 | 43 | impl From<[u8; 32]> for VerificationKeyBytes { 44 | fn from(bytes: [u8; 32]) -> VerificationKeyBytes { 45 | VerificationKeyBytes { 46 | bytes, 47 | _marker: PhantomData, 48 | } 49 | } 50 | } 51 | 52 | impl From> for [u8; 32] { 53 | fn from(refined: VerificationKeyBytes) -> [u8; 32] { 54 | refined.bytes 55 | } 56 | } 57 | 58 | /// A valid RedDSA verification key. 59 | /// 60 | /// This type holds decompressed state used in signature verification; if the 61 | /// verification key may not be used immediately, it is probably better to use 62 | /// [`VerificationKeyBytes`], which is a refinement type for `[u8; 32]`. 63 | /// 64 | /// ## Consensus properties 65 | /// 66 | /// The `TryFrom` conversion performs the following Zcash 67 | /// consensus rule checks: 68 | /// 69 | /// 1. The check that the bytes are a canonical encoding of a verification key; 70 | /// 2. The check that the verification key is not a point of small order. 71 | #[derive(PartialEq, Copy, Clone, Debug)] 72 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 73 | #[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))] 74 | #[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))] 75 | #[cfg_attr(feature = "serde", serde(bound = "T: SigType"))] 76 | pub struct VerificationKey { 77 | pub(crate) point: T::Point, 78 | pub(crate) bytes: VerificationKeyBytes, 79 | } 80 | 81 | impl From> for VerificationKeyBytes { 82 | fn from(pk: VerificationKey) -> VerificationKeyBytes { 83 | pk.bytes 84 | } 85 | } 86 | 87 | impl From> for [u8; 32] { 88 | fn from(pk: VerificationKey) -> [u8; 32] { 89 | pk.bytes.bytes 90 | } 91 | } 92 | 93 | impl TryFrom> for VerificationKey { 94 | type Error = Error; 95 | 96 | fn try_from(bytes: VerificationKeyBytes) -> Result { 97 | // XXX-jubjub: this should not use CtOption 98 | // XXX-jubjub: this takes ownership of bytes, while Fr doesn't. 99 | // This checks that the encoding is canonical... 100 | let mut repr = ::Repr::default(); 101 | repr.as_mut().copy_from_slice(&bytes.bytes); 102 | let maybe_point = T::Point::from_bytes(&repr); 103 | if maybe_point.is_some().into() { 104 | let point = maybe_point.unwrap(); 105 | // Note that small-order verification keys (including the identity) are not 106 | // rejected here. Previously they were rejected, but this was a bug as the 107 | // RedDSA specification allows them. Zcash Sapling rejects small-order points 108 | // for the RedJubjub spend authorization key rk; this now occurs separately. 109 | // Meanwhile, Zcash Orchard uses a prime-order group, so the only small-order 110 | // point would be the identity, which is allowed in Orchard. 111 | Ok(VerificationKey { point, bytes }) 112 | } else { 113 | Err(Error::MalformedVerificationKey) 114 | } 115 | } 116 | } 117 | 118 | impl TryFrom<[u8; 32]> for VerificationKey { 119 | type Error = Error; 120 | 121 | fn try_from(bytes: [u8; 32]) -> Result { 122 | VerificationKeyBytes::from(bytes).try_into() 123 | } 124 | } 125 | 126 | impl VerificationKey { 127 | /// Randomize this verification key with the given `randomizer`. 128 | /// 129 | /// Randomization is only supported for `SpendAuth` keys. 130 | pub fn randomize(&self, randomizer: &Randomizer) -> VerificationKey { 131 | let point = self.point + (T::basepoint() * randomizer); 132 | let bytes = VerificationKeyBytes { 133 | bytes: point.to_bytes().as_ref().try_into().unwrap(), 134 | _marker: PhantomData, 135 | }; 136 | VerificationKey { bytes, point } 137 | } 138 | } 139 | 140 | impl VerificationKey { 141 | pub(crate) fn from(s: &T::Scalar) -> VerificationKey { 142 | let point = T::basepoint() * s; 143 | let bytes = VerificationKeyBytes { 144 | bytes: point.to_bytes().as_ref().try_into().unwrap(), 145 | _marker: PhantomData, 146 | }; 147 | VerificationKey { bytes, point } 148 | } 149 | 150 | /// Verify a purported `signature` over `msg` made by this verification key. 151 | // This is similar to impl signature::Verifier but without boxed errors 152 | pub fn verify(&self, msg: &[u8], signature: &Signature) -> Result<(), Error> { 153 | use crate::HStar; 154 | let c = HStar::::default() 155 | .update(&signature.r_bytes[..]) 156 | .update(&self.bytes.bytes[..]) // XXX ugly 157 | .update(msg) 158 | .finalize(); 159 | self.verify_prehashed(signature, c) 160 | } 161 | 162 | /// Verify a purported `signature` with a prehashed challenge. 163 | #[allow(non_snake_case)] 164 | pub(crate) fn verify_prehashed( 165 | &self, 166 | signature: &Signature, 167 | c: T::Scalar, 168 | ) -> Result<(), Error> { 169 | let r = { 170 | // XXX-jubjub: should not use CtOption here 171 | // XXX-jubjub: inconsistent ownership in from_bytes 172 | let mut repr = ::Repr::default(); 173 | repr.as_mut().copy_from_slice(&signature.r_bytes); 174 | let maybe_point = T::Point::from_bytes(&repr); 175 | if maybe_point.is_some().into() { 176 | maybe_point.unwrap() 177 | } else { 178 | return Err(Error::InvalidSignature); 179 | } 180 | }; 181 | 182 | let s = { 183 | // XXX-jubjub: should not use CtOption here 184 | let mut repr = ::Repr::default(); 185 | repr.as_mut().copy_from_slice(&signature.s_bytes); 186 | let maybe_scalar = T::Scalar::from_repr(repr); 187 | if maybe_scalar.is_some().into() { 188 | maybe_scalar.unwrap() 189 | } else { 190 | return Err(Error::InvalidSignature); 191 | } 192 | }; 193 | 194 | // XXX rewrite as normal double scalar mul 195 | // Verify check is h * ( - s * B + R + c * A) == 0 196 | // h * ( s * B - c * A - R) == 0 197 | let sB = T::basepoint() * s; 198 | let cA = self.point * c; 199 | let check = sB - cA - r; 200 | 201 | if check.is_small_order().into() { 202 | Ok(()) 203 | } else { 204 | Err(Error::InvalidSignature) 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /tests/batch.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "alloc")] 2 | 3 | use rand::thread_rng; 4 | 5 | use reddsa::*; 6 | 7 | #[test] 8 | fn spendauth_batch_verify() { 9 | let mut rng = thread_rng(); 10 | let mut batch = batch::Verifier::<_, sapling::Binding>::new(); 11 | for _ in 0..32 { 12 | let sk = SigningKey::::new(&mut rng); 13 | let vk = VerificationKey::from(&sk); 14 | let msg = b"BatchVerifyTest"; 15 | let sig = sk.sign(&mut rng, &msg[..]); 16 | batch.queue(batch::Item::from_spendauth(vk.into(), sig, msg)); 17 | } 18 | assert!(batch.verify(rng).is_ok()); 19 | } 20 | 21 | #[test] 22 | fn binding_batch_verify() { 23 | let mut rng = thread_rng(); 24 | let mut batch = batch::Verifier::::new(); 25 | for _ in 0..32 { 26 | let sk = SigningKey::::new(&mut rng); 27 | let vk = VerificationKey::from(&sk); 28 | let msg = b"BatchVerifyTest"; 29 | let sig = sk.sign(&mut rng, &msg[..]); 30 | batch.queue(batch::Item::from_binding(vk.into(), sig, msg)); 31 | } 32 | assert!(batch.verify(rng).is_ok()); 33 | } 34 | 35 | #[test] 36 | fn alternating_batch_verify() { 37 | let mut rng = thread_rng(); 38 | let mut batch = batch::Verifier::new(); 39 | for i in 0..32 { 40 | let item = match i % 2 { 41 | 0 => { 42 | let sk = SigningKey::::new(&mut rng); 43 | let vk = VerificationKey::from(&sk); 44 | let msg = b"BatchVerifyTest"; 45 | let sig = sk.sign(&mut rng, &msg[..]); 46 | batch::Item::from_spendauth(vk.into(), sig, msg) 47 | } 48 | 1 => { 49 | let sk = SigningKey::::new(&mut rng); 50 | let vk = VerificationKey::from(&sk); 51 | let msg = b"BatchVerifyTest"; 52 | let sig = sk.sign(&mut rng, &msg[..]); 53 | batch::Item::from_binding(vk.into(), sig, msg) 54 | } 55 | _ => unreachable!(), 56 | }; 57 | batch.queue(item); 58 | } 59 | assert!(batch.verify(rng).is_ok()); 60 | } 61 | 62 | #[test] 63 | fn bad_batch_verify() { 64 | let mut rng = thread_rng(); 65 | let bad_index = 4; // must be even 66 | let mut batch = batch::Verifier::new(); 67 | let mut items = Vec::new(); 68 | for i in 0..32 { 69 | let item = match i % 2 { 70 | 0 => { 71 | let sk = SigningKey::::new(&mut rng); 72 | let vk = VerificationKey::from(&sk); 73 | let msg = b"BatchVerifyTest"; 74 | let sig = if i != bad_index { 75 | sk.sign(&mut rng, &msg[..]) 76 | } else { 77 | sk.sign(&mut rng, b"bad") 78 | }; 79 | batch::Item::from_spendauth(vk.into(), sig, msg) 80 | } 81 | 1 => { 82 | let sk = SigningKey::::new(&mut rng); 83 | let vk = VerificationKey::from(&sk); 84 | let msg = b"BatchVerifyTest"; 85 | let sig = sk.sign(&mut rng, &msg[..]); 86 | batch::Item::from_binding(vk.into(), sig, msg) 87 | } 88 | _ => unreachable!(), 89 | }; 90 | items.push(item.clone()); 91 | batch.queue(item); 92 | } 93 | assert!(batch.verify(rng).is_err()); 94 | for (i, item) in items.drain(..).enumerate() { 95 | if i != bad_index { 96 | assert!(item.verify_single().is_ok()); 97 | } else { 98 | assert!(item.verify_single().is_err()); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /tests/bincode.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | 3 | use proptest::prelude::*; 4 | use reddsa::*; 5 | use std::convert::TryFrom; 6 | 7 | proptest! { 8 | #[test] 9 | fn secretkey_serialization( 10 | bytes in prop::array::uniform32(any::()), 11 | ) { 12 | let sk_result_from = SigningKey::::try_from(bytes); 13 | let sk_result_bincode: Result, _> 14 | = bincode::deserialize(&bytes[..]); 15 | 16 | // Check 1: both decoding methods should agree 17 | match (sk_result_from, sk_result_bincode) { 18 | // Both agree on success 19 | (Ok(sk_from), Ok(sk_bincode)) => { 20 | let pk_bytes_from = VerificationKeyBytes::from(VerificationKey::from(&sk_from)); 21 | let pk_bytes_bincode = VerificationKeyBytes::from(VerificationKey::from(&sk_bincode)); 22 | assert_eq!(pk_bytes_from, pk_bytes_bincode); 23 | 24 | // Check 2: bincode encoding should match original bytes. 25 | let bytes_bincode = bincode::serialize(&sk_from).unwrap(); 26 | assert_eq!(&bytes[..], &bytes_bincode[..]); 27 | 28 | // Check 3: From encoding should match original bytes. 29 | let bytes_from: [u8; 32] = sk_bincode.into(); 30 | assert_eq!(&bytes[..], &bytes_from[..]); 31 | } 32 | // Both agree on failure 33 | (Err(_), Err(_)) => {}, 34 | _ => panic!("bincode and try_from do not agree"), 35 | } 36 | } 37 | 38 | #[test] 39 | fn publickeybytes_serialization( 40 | bytes in prop::array::uniform32(any::()), 41 | ) { 42 | let pk_bytes_from = VerificationKeyBytes::::from(bytes); 43 | let pk_bytes_bincode: VerificationKeyBytes:: 44 | = bincode::deserialize(&bytes[..]).unwrap(); 45 | 46 | // Check 1: both decoding methods should have the same result. 47 | assert_eq!(pk_bytes_from, pk_bytes_bincode); 48 | 49 | // Check 2: bincode encoding should match original bytes. 50 | let bytes_bincode = bincode::serialize(&pk_bytes_from).unwrap(); 51 | assert_eq!(&bytes[..], &bytes_bincode[..]); 52 | 53 | // Check 3: From encoding should match original bytes. 54 | let bytes_from: [u8; 32] = pk_bytes_bincode.into(); 55 | assert_eq!(&bytes[..], &bytes_from[..]); 56 | } 57 | 58 | #[test] 59 | fn publickey_serialization( 60 | bytes in prop::array::uniform32(any::()), 61 | ) { 62 | let pk_result_try_from = VerificationKey::::try_from(bytes); 63 | let pk_result_bincode: Result, _> 64 | = bincode::deserialize(&bytes[..]); 65 | 66 | // Check 1: both decoding methods should have the same result 67 | match (pk_result_try_from, pk_result_bincode) { 68 | // Both agree on success 69 | (Ok(pk_try_from), Ok(pk_bincode)) => { 70 | // Check 2: bincode encoding should match original bytes 71 | let bytes_bincode = bincode::serialize(&pk_try_from).unwrap(); 72 | assert_eq!(&bytes[..], &bytes_bincode[..]); 73 | // Check 3: From encoding should match original bytes 74 | let bytes_from: [u8; 32] = pk_bincode.into(); 75 | assert_eq!(&bytes[..], &bytes_from[..]); 76 | }, 77 | // Both agree on failure 78 | (Err(_), Err(_)) => {}, 79 | _ => panic!("bincode and try_from do not agree"), 80 | } 81 | } 82 | 83 | #[test] 84 | fn signature_serialization( 85 | lo in prop::array::uniform32(any::()), 86 | hi in prop::array::uniform32(any::()), 87 | ) { 88 | // array length hack 89 | let bytes = { 90 | let mut bytes = [0; 64]; 91 | bytes[0..32].copy_from_slice(&lo[..]); 92 | bytes[32..64].copy_from_slice(&hi[..]); 93 | bytes 94 | }; 95 | 96 | let sig_bytes_from = Signature::::from(bytes); 97 | let sig_bytes_bincode: Signature:: 98 | = bincode::deserialize(&bytes[..]).unwrap(); 99 | 100 | // Check 1: both decoding methods should have the same result. 101 | assert_eq!(sig_bytes_from, sig_bytes_bincode); 102 | 103 | // Check 2: bincode encoding should match original bytes. 104 | let bytes_bincode = bincode::serialize(&sig_bytes_from).unwrap(); 105 | assert_eq!(&bytes[..], &bytes_bincode[..]); 106 | 107 | // Check 3: From encoding should match original bytes. 108 | let bytes_from: [u8; 64] = sig_bytes_bincode.into(); 109 | assert_eq!(&bytes[..], &bytes_from[..]); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/frost_redjubjub.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "frost")] 2 | 3 | use group::GroupEncoding; 4 | use rand::thread_rng; 5 | 6 | use frost_rerandomized::frost_core::{Ciphersuite, Group, GroupError}; 7 | 8 | use reddsa::{frost::redjubjub::JubjubBlake2b512, sapling}; 9 | 10 | #[test] 11 | fn check_sign_with_dealer() { 12 | let rng = thread_rng(); 13 | 14 | frost_rerandomized::frost_core::tests::ciphersuite_generic::check_sign_with_dealer::< 15 | JubjubBlake2b512, 16 | _, 17 | >(rng); 18 | } 19 | 20 | #[test] 21 | fn check_randomized_sign_with_dealer() { 22 | let rng = thread_rng(); 23 | 24 | let (msg, group_signature, group_pubkey) = 25 | frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); 26 | 27 | // Check that the threshold signature can be verified by the `reddsa` crate 28 | // public key (interoperability test) 29 | 30 | let sig = { 31 | let bytes: [u8; 64] = group_signature.serialize().unwrap().try_into().unwrap(); 32 | reddsa::Signature::::from(bytes) 33 | }; 34 | let pk_bytes = { 35 | let bytes: [u8; 32] = group_pubkey.serialize().unwrap().try_into().unwrap(); 36 | reddsa::VerificationKeyBytes::::from(bytes) 37 | }; 38 | 39 | // Check that the verification key is a valid RedDSA verification key. 40 | let pub_key = reddsa::VerificationKey::try_from(pk_bytes) 41 | .expect("The test verification key to be well-formed."); 42 | 43 | // Check that signature validation has the expected result. 44 | assert!(pub_key.verify(&msg, &sig).is_ok()); 45 | } 46 | 47 | #[test] 48 | fn check_sign_with_dkg() { 49 | let rng = thread_rng(); 50 | 51 | frost_rerandomized::frost_core::tests::ciphersuite_generic::check_sign_with_dkg::< 52 | JubjubBlake2b512, 53 | _, 54 | >(rng); 55 | } 56 | 57 | #[test] 58 | fn check_deserialize_identity() { 59 | let r = ::Group::serialize( 60 | &::Group::identity(), 61 | ); 62 | assert_eq!(r, Err(GroupError::InvalidIdentityElement)); 63 | let raw_identity = ::Group::identity(); 64 | let r = ::Group::deserialize(&raw_identity.to_bytes()); 65 | assert_eq!(r, Err(GroupError::InvalidIdentityElement)); 66 | } 67 | 68 | #[test] 69 | fn check_deserialize_non_canonical() { 70 | let encoded_generator = ::Group::serialize( 71 | &::Group::generator(), 72 | ) 73 | .unwrap(); 74 | let r = ::Group::deserialize(&encoded_generator); 75 | assert!(r.is_ok()); 76 | 77 | // This is x = p + 3 which is non-canonical and maps to a valid point. 78 | let encoded_point = 79 | hex::decode("04000000fffffffffe5bfeff02a4bd5305d8a10908d83933487d9d2953a7ed73") 80 | .unwrap() 81 | .try_into() 82 | .unwrap(); 83 | let r = ::Group::deserialize(&encoded_point); 84 | assert_eq!(r, Err(GroupError::MalformedElement)); 85 | } 86 | 87 | #[test] 88 | fn check_deserialize_non_prime_order() { 89 | let encoded_point = 90 | hex::decode("0300000000000000000000000000000000000000000000000000000000000000") 91 | .unwrap() 92 | .try_into() 93 | .unwrap(); 94 | let r = ::Group::deserialize(&encoded_point); 95 | assert_eq!(r, Err(GroupError::InvalidNonPrimeOrderElement)); 96 | } 97 | -------------------------------------------------------------------------------- /tests/frost_redpallas.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "frost")] 2 | 3 | use std::collections::BTreeMap; 4 | 5 | use group::GroupEncoding; 6 | use rand::thread_rng; 7 | 8 | use frost_rerandomized::frost_core::{self as frost, Ciphersuite, Group, GroupError}; 9 | 10 | use reddsa::{ 11 | frost::redpallas::{keys::EvenY, PallasBlake2b512}, 12 | orchard, 13 | }; 14 | 15 | #[test] 16 | fn check_sign_with_dealer() { 17 | let rng = thread_rng(); 18 | 19 | frost::tests::ciphersuite_generic::check_sign_with_dealer::(rng); 20 | } 21 | 22 | #[test] 23 | fn check_randomized_sign_with_dealer() { 24 | let rng = thread_rng(); 25 | 26 | let (msg, group_signature, group_pubkey) = 27 | frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); 28 | 29 | // Check that the threshold signature can be verified by the `reddsa` crate 30 | // public key (interoperability test) 31 | 32 | let sig = { 33 | let bytes: [u8; 64] = group_signature.serialize().unwrap().try_into().unwrap(); 34 | reddsa::Signature::::from(bytes) 35 | }; 36 | let pk_bytes = { 37 | let bytes: [u8; 32] = group_pubkey.serialize().unwrap().try_into().unwrap(); 38 | reddsa::VerificationKeyBytes::::from(bytes) 39 | }; 40 | 41 | // Check that the verification key is a valid RedDSA verification key. 42 | let pub_key = reddsa::VerificationKey::try_from(pk_bytes) 43 | .expect("The test verification key to be well-formed."); 44 | 45 | // Check that signature validation has the expected result. 46 | assert!(pub_key.verify(&msg, &sig).is_ok()); 47 | } 48 | 49 | #[test] 50 | fn check_sign_with_dkg() { 51 | let rng = thread_rng(); 52 | 53 | frost::tests::ciphersuite_generic::check_sign_with_dkg::(rng); 54 | } 55 | 56 | #[test] 57 | fn check_deserialize_identity() { 58 | let r = ::Group::serialize( 59 | &::Group::identity(), 60 | ); 61 | assert_eq!(r, Err(GroupError::InvalidIdentityElement)); 62 | let raw_identity = ::Group::identity(); 63 | let r = ::Group::deserialize(&raw_identity.to_bytes()); 64 | assert_eq!(r, Err(GroupError::InvalidIdentityElement)); 65 | } 66 | 67 | #[test] 68 | fn check_deserialize_non_canonical() { 69 | let encoded_generator = ::Group::serialize( 70 | &::Group::generator(), 71 | ) 72 | .unwrap(); 73 | let r = ::Group::deserialize(&encoded_generator); 74 | assert!(r.is_ok()); 75 | 76 | // This is x = p + 3 which is non-canonical and maps to a valid point. 77 | let encoded_point = 78 | hex::decode("04000000ed302d991bf94c09fc98462200000000000000000000000000000040") 79 | .unwrap() 80 | .try_into() 81 | .unwrap(); 82 | let r = ::Group::deserialize(&encoded_point); 83 | assert_eq!(r, Err(GroupError::MalformedElement)); 84 | } 85 | 86 | #[test] 87 | fn check_even_y_frost_core() { 88 | let mut rng = thread_rng(); 89 | 90 | // Since there is a 50% chance of the public key having an odd Y (which 91 | // we need to actually test), loop until we get an odd Y. 92 | loop { 93 | let max_signers = 5; 94 | let min_signers = 3; 95 | // Generate keys with frost-core function, which doesn't ensure even Y 96 | let (shares, public_key_package) = 97 | frost::keys::generate_with_dealer::( 98 | max_signers, 99 | min_signers, 100 | frost::keys::IdentifierList::Default, 101 | &mut rng, 102 | ) 103 | .unwrap(); 104 | 105 | if !public_key_package.has_even_y() { 106 | // Test consistency of into_even_y() for PublicKeyPackage 107 | let even_public_key_package_is_even_none = public_key_package.clone().into_even_y(None); 108 | let even_public_key_package_is_even_false = 109 | public_key_package.clone().into_even_y(Some(false)); 110 | assert_eq!( 111 | even_public_key_package_is_even_false, 112 | even_public_key_package_is_even_none 113 | ); 114 | assert_ne!(public_key_package, even_public_key_package_is_even_false); 115 | assert_ne!(public_key_package, even_public_key_package_is_even_none); 116 | 117 | // Test consistency of into_even_y() for SecretShare (arbitrarily on 118 | // the first secret share) 119 | let secret_share = shares.first_key_value().unwrap().1.clone(); 120 | let even_secret_share_is_even_none = secret_share.clone().into_even_y(None); 121 | let even_secret_share_is_even_false = secret_share.clone().into_even_y(Some(false)); 122 | assert_eq!( 123 | even_secret_share_is_even_false, 124 | even_secret_share_is_even_none 125 | ); 126 | assert_ne!(secret_share, even_secret_share_is_even_false); 127 | assert_ne!(secret_share, even_secret_share_is_even_none); 128 | 129 | // Make secret shares even, then convert into KeyPackages 130 | let key_packages_evened_before: BTreeMap<_, _> = shares 131 | .clone() 132 | .into_iter() 133 | .map(|(identifier, share)| { 134 | Ok(( 135 | identifier, 136 | frost::keys::KeyPackage::try_from(share.into_even_y(None))?, 137 | )) 138 | }) 139 | .collect::>>() 140 | .unwrap(); 141 | // Convert into KeyPackages, then make them even 142 | let key_packages_evened_after: BTreeMap<_, _> = shares 143 | .into_iter() 144 | .map(|(identifier, share)| { 145 | Ok(( 146 | identifier, 147 | frost::keys::KeyPackage::try_from(share)?.into_even_y(None), 148 | )) 149 | }) 150 | .collect::>>() 151 | .unwrap(); 152 | // Make sure they are equal 153 | assert_eq!(key_packages_evened_after, key_packages_evened_before); 154 | 155 | // Check if signing works with evened keys 156 | frost::tests::ciphersuite_generic::check_sign( 157 | min_signers, 158 | key_packages_evened_after, 159 | &mut rng, 160 | even_public_key_package_is_even_none, 161 | ) 162 | .unwrap(); 163 | 164 | // We managed to test it; break the loop and return 165 | break; 166 | } 167 | } 168 | } 169 | 170 | #[test] 171 | fn check_even_y_reddsa() { 172 | let mut rng = thread_rng(); 173 | 174 | // Since there is a ~50% chance of having a odd Y internally, to make sure 175 | // that odd Ys are converted to even, we test multiple times to increase 176 | // the chance of an odd Y being generated internally 177 | for _ in 0..16 { 178 | let max_signers = 5; 179 | let min_signers = 3; 180 | // Generate keys with reexposed reddsa function, which ensures even Y 181 | let (shares, public_key_package) = 182 | reddsa::frost::redpallas::keys::generate_with_dealer::<_>( 183 | max_signers, 184 | min_signers, 185 | frost::keys::IdentifierList::Default, 186 | &mut rng, 187 | ) 188 | .unwrap(); 189 | 190 | assert!(public_key_package.has_even_y()); 191 | assert!(shares.values().all(|s| s.has_even_y())); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /tests/proptests.proptest-regressions: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 25716e9dc4549b01b395fca1fc076fc34300ad972ac59c5e098f7ec90a03446b # shrinks to tweaks = [ChangePubkey], rng_seed = 946433020594646748 8 | cc ddb674b23f131d33cdbe34f3959f7fc076f4815a12ad5d6f741ae38d2689d1c2 # shrinks to tweaks = [ChangePubkey, ChangePubkey, ChangePubkey, ChangePubkey], rng_seed = 8346595811973717667 9 | -------------------------------------------------------------------------------- /tests/proptests.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | 3 | use proptest::prelude::*; 4 | use rand_core::{CryptoRng, RngCore}; 5 | 6 | use reddsa::*; 7 | 8 | /// A signature test-case, containing signature data and expected validity. 9 | #[derive(Clone, Debug)] 10 | struct SignatureCase { 11 | msg: Vec, 12 | sig: Signature, 13 | pk_bytes: VerificationKeyBytes, 14 | invalid_pk_bytes: VerificationKeyBytes, 15 | is_valid: bool, 16 | } 17 | 18 | /// A modification to a test-case. 19 | #[derive(Copy, Clone, Debug)] 20 | enum Tweak { 21 | /// No-op, used to check that unchanged cases verify. 22 | None, 23 | /// Change the message the signature is defined for, invalidating the signature. 24 | ChangeMessage, 25 | /// Change the public key the signature is defined for, invalidating the signature. 26 | ChangePubkey, 27 | /* XXX implement this -- needs to regenerate a custom signature because the 28 | nonce commitment is fed into the hash, so it has to have torsion at signing 29 | time. 30 | /// Change the case to have a torsion component in the signature's `r` value. 31 | AddTorsion, 32 | */ 33 | /* XXX implement this -- needs custom handling of field arithmetic. 34 | /// Change the signature's `s` scalar to be unreduced (mod L), invalidating the signature. 35 | UnreducedScalar, 36 | */ 37 | } 38 | 39 | impl SignatureCase { 40 | fn new(mut rng: R, msg: Vec) -> Self { 41 | let sk = SigningKey::new(&mut rng); 42 | let sig = sk.sign(&mut rng, &msg); 43 | let pk_bytes = VerificationKey::from(&sk).into(); 44 | let invalid_pk_bytes = VerificationKey::from(&SigningKey::new(&mut rng)).into(); 45 | Self { 46 | msg, 47 | sig, 48 | pk_bytes, 49 | invalid_pk_bytes, 50 | is_valid: true, 51 | } 52 | } 53 | 54 | // Check that signature verification succeeds or fails, as expected. 55 | fn check(&self) -> bool { 56 | // The signature data is stored in (refined) byte types, but do a round trip 57 | // conversion to raw bytes to exercise those code paths. 58 | let sig = { 59 | let bytes: [u8; 64] = self.sig.into(); 60 | Signature::::from(bytes) 61 | }; 62 | let pk_bytes = { 63 | let bytes: [u8; 32] = self.pk_bytes.into(); 64 | VerificationKeyBytes::::from(bytes) 65 | }; 66 | 67 | // Check that the verification key is a valid RedDSA verification key. 68 | let pub_key = VerificationKey::try_from(pk_bytes) 69 | .expect("The test verification key to be well-formed."); 70 | 71 | // Check that signature validation has the expected result. 72 | self.is_valid == pub_key.verify(&self.msg, &sig).is_ok() 73 | } 74 | 75 | fn apply_tweak(&mut self, tweak: &Tweak) { 76 | match tweak { 77 | Tweak::None => {} 78 | Tweak::ChangeMessage => { 79 | // Changing the message makes the signature invalid. 80 | self.msg.push(90); 81 | self.is_valid = false; 82 | } 83 | Tweak::ChangePubkey => { 84 | // Changing the public key makes the signature invalid. 85 | self.pk_bytes = self.invalid_pk_bytes; 86 | self.is_valid = false; 87 | } 88 | } 89 | } 90 | } 91 | 92 | fn tweak_strategy() -> impl Strategy { 93 | prop_oneof![ 94 | 10 => Just(Tweak::None), 95 | 1 => Just(Tweak::ChangeMessage), 96 | 1 => Just(Tweak::ChangePubkey), 97 | ] 98 | } 99 | 100 | use rand_chacha::ChaChaRng; 101 | use rand_core::SeedableRng; 102 | 103 | proptest! { 104 | 105 | #[test] 106 | fn tweak_signature( 107 | tweaks in prop::collection::vec(tweak_strategy(), (0,5)), 108 | rng_seed in prop::array::uniform32(any::()), 109 | ) { 110 | // Use a deterministic RNG so that test failures can be reproduced. 111 | // Seeding with 64 bits of entropy is INSECURE and this code should 112 | // not be copied outside of this test! 113 | let mut rng = ChaChaRng::from_seed(rng_seed); 114 | 115 | // Create a test case for each signature type. 116 | let msg = b"test message for proptests"; 117 | let mut binding = SignatureCase::::new(&mut rng, msg.to_vec()); 118 | let mut spendauth = SignatureCase::::new(&mut rng, msg.to_vec()); 119 | 120 | // Apply tweaks to each case. 121 | for t in &tweaks { 122 | binding.apply_tweak(t); 123 | spendauth.apply_tweak(t); 124 | } 125 | 126 | assert!(binding.check()); 127 | assert!(spendauth.check()); 128 | } 129 | 130 | #[test] 131 | fn randomization_commutes_with_pubkey_homomorphism(rng_seed in prop::array::uniform32(any::())) { 132 | // Use a deterministic RNG so that test failures can be reproduced. 133 | let mut rng = ChaChaRng::from_seed(rng_seed); 134 | 135 | let r = { 136 | // XXX-jubjub: better API for this 137 | let mut bytes = [0; 64]; 138 | rng.fill_bytes(&mut bytes[..]); 139 | jubjub::Scalar::from_bytes_wide(&bytes) 140 | }; 141 | 142 | let sk = SigningKey::::new(&mut rng); 143 | let pk = VerificationKey::from(&sk); 144 | 145 | let sk_r = sk.randomize(&r); 146 | let pk_r = pk.randomize(&r); 147 | 148 | let pk_r_via_sk_rand: [u8; 32] = VerificationKeyBytes::from(VerificationKey::from(&sk_r)).into(); 149 | let pk_r_via_pk_rand: [u8; 32] = VerificationKeyBytes::from(pk_r).into(); 150 | 151 | assert_eq!(pk_r_via_pk_rand, pk_r_via_sk_rand); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /tests/smallorder.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | 3 | use jubjub::{AffinePoint, Fq}; 4 | 5 | use reddsa::*; 6 | 7 | #[test] 8 | fn identity_publickey_passes() { 9 | let identity = AffinePoint::identity(); 10 | assert!(::from(identity.is_small_order())); 11 | let bytes = identity.to_bytes(); 12 | let pk_bytes = VerificationKeyBytes::::from(bytes); 13 | assert!(VerificationKey::::try_from(pk_bytes).is_ok()); 14 | } 15 | 16 | #[test] 17 | fn smallorder_publickey_passes() { 18 | // (1,0) is a point of order 4 on any Edwards curve 19 | let order4 = AffinePoint::from_raw_unchecked(Fq::one(), Fq::zero()); 20 | assert!(::from(order4.is_small_order())); 21 | let bytes = order4.to_bytes(); 22 | let pk_bytes = VerificationKeyBytes::::from(bytes); 23 | assert!(VerificationKey::::try_from(pk_bytes).is_ok()); 24 | } 25 | -------------------------------------------------------------------------------- /zcash-frost-audit-report-20210323.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZcashFoundation/reddsa/975f9ca835c4b9196c81608e55192b0f711e951d/zcash-frost-audit-report-20210323.pdf --------------------------------------------------------------------------------