From 5f58d6a92383ad88cb0b1d81a3e18aa35d145ef8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:34:39 +0000 Subject: [PATCH 001/175] Bump reviewdog/action-actionlint from 1.41.0 to 1.42.0 (#618) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.41.0 to 1.42.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.41.0...v1.42.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e547fcd19..4579fa72a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -143,7 +143,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.1 - - uses: reviewdog/action-actionlint@v1.41.0 + - uses: reviewdog/action-actionlint@v1.42.0 with: level: warning fail_on_error: false From 6e5aff8342fa4f0fe99720931921f94255e64900 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:42:15 +0000 Subject: [PATCH 002/175] Bump codecov/codecov-action from 4.0.1 to 4.1.0 (#620) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.1 to 4.1.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4.0.1...v4.1.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 47ece33b1..e0af51e4c 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v4.0.1 + uses: codecov/codecov-action@v4.1.0 From 3a72a9dfa3601c5b32094ee8126b0d0177b6f3ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 17:20:08 +0000 Subject: [PATCH 003/175] Bump reviewdog/action-actionlint from 1.42.0 to 1.43.0 (#627) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.42.0 to 1.43.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.42.0...v1.43.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4579fa72a..5a97a4a6d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -143,7 +143,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.1 - - uses: reviewdog/action-actionlint@v1.42.0 + - uses: reviewdog/action-actionlint@v1.43.0 with: level: warning fail_on_error: false From a47177da4c9189a27aba1c7b781993a5f24e7a40 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 10 Apr 2024 12:02:49 -0300 Subject: [PATCH 004/175] docs: update demo section to match current code (#632) * update demo docs * update mdbook-admonish --- book/book.toml | 4 +- book/mdbook-admonish.css | 39 +++++++++++-------- book/src/zcash/ywallet-demo.md | 69 ++++++++++++++++++---------------- 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/book/book.toml b/book/book.toml index 5fdedd32f..e95c664fb 100644 --- a/book/book.toml +++ b/book/book.toml @@ -9,9 +9,9 @@ title = "The ZF FROST Book" [preprocessor.admonish] command = "mdbook-admonish" -assets_version = "3.0.0" # do not edit: managed by `mdbook-admonish install` +assets_version = "3.0.2" # do not edit: managed by `mdbook-admonish install` [output] [output.html] -additional-css = ["./mdbook-admonish.css"] +additional-css = ["./mdbook-admonish.css", "book/mdbook-admonish.css"] diff --git a/book/mdbook-admonish.css b/book/mdbook-admonish.css index e0a336553..45aeff051 100644 --- a/book/mdbook-admonish.css +++ b/book/mdbook-admonish.css @@ -1,20 +1,4 @@ @charset "UTF-8"; -:root { - --md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,"); - --md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,"); - --md-details-icon: url("data:image/svg+xml;charset=utf-8,"); -} - :is(.admonition) { display: flow-root; margin: 1.5625em 0; @@ -71,6 +55,8 @@ a.admonition-anchor-link::before { padding-inline: 4.4rem 1.2rem; font-weight: 700; background-color: rgba(68, 138, 255, 0.1); + print-color-adjust: exact; + -webkit-print-color-adjust: exact; display: flex; } :is(.admonition-title, summary.admonition-title) p { @@ -86,6 +72,8 @@ html :is(.admonition-title, summary.admonition-title):last-child { width: 2rem; height: 2rem; background-color: #448aff; + print-color-adjust: exact; + -webkit-print-color-adjust: exact; mask-image: url('data:image/svg+xml;charset=utf-8,'); -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); mask-repeat: no-repeat; @@ -119,6 +107,25 @@ details[open].admonition > summary.admonition-title::after { transform: rotate(90deg); } +:root { + --md-details-icon: url("data:image/svg+xml;charset=utf-8,"); +} + +:root { + --md-admonition-icon--admonish-note: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-abstract: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-info: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-tip: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-success: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-question: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-warning: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-failure: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-danger: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-bug: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-example: url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--admonish-quote: url("data:image/svg+xml;charset=utf-8,"); +} + :is(.admonition):is(.admonish-note) { border-color: #448aff; } diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index cf3491556..d5537fcbe 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -3,15 +3,6 @@ This tutorial explaing how to run the FROST demo using Ywallet that was [presented during Zcon4](https://www.youtube.com/watch?v=xvzESdDtczo). -## Information - -1. The Trusted Dealer journey -2. RedPallas -3. YWallet -4. Sprout -5. [Sapling](https://docs.rs/reddsa/0.5.1/reddsa/sapling/index.html) -6. [frost-ed25519 crate](https://crates.io/crates/frost-ed25519) - Ywallet supports [offline signing](https://ywallet.app/advanced/offline_signature/), which allows having a view-only account that can generate a transaction plan, which can be signed by @@ -58,11 +49,11 @@ cd frost-zcash-demo/ cargo run --bin trusted-dealer --features redpallas ``` -Answer the prompts with `2` (minimum number of signers), `3` -(maximum) and empty, pressing enter to submit each. - -A bunch of information will be printed. Copy and paste them somewhere to use -them later, or leave the terminal open. +This will by default generate a 2-of-3 key shares. The public key package +will be written to `public-key-package.json`, while key packages will be +written to `key-package-1.json` through `-3`. You can change the threhsold, +number of shares and file names using the command line; append `-- -h` +to the commend above for the command line help. ```admonish info If you want to use DKG instead of Trusted Dealer, instead of the command above, @@ -104,12 +95,11 @@ take a bit to compile. It will show a bunch of warnings which is normal. cargo run --release --bin sign --features dotenv -- -g ``` -When prompted for the `ak`, paste the `verifying_key` value that was printed in -the previous part, inside the Public Key Package. For example, in the following -package +When prompted for the `ak`, paste the `verifying_key` value that is listed +inside the Public Key Package in `public-key-package.json`. For example, in the +following package ``` -Public key package: {"verifying_shares": ...snip... ,"verifying_key":"d2bf40ca860fb97e9d6d15d7d25e4f17d2e8ba5dd7069188cbf30b023910a71b","ciphersuite":"FROST(Pallas, BLAKE2b-512)"} ``` @@ -177,42 +167,55 @@ In the first one, the Coordinator, run (in the same folder where key generation was run): ``` -cargo run --bin coordinator --features redpallas +cargo run --bin coordinator --features redpallas -- --cli ``` And then: -- Paste the JSON public key package generate during key generation (it's a single - line with a JSON object). +- It should read the public key package from `public-key-package.json`. - Type `2` for the number of participants. -- Paste the identifier of the first participant, you can see it in the Secret - Share printed during key generation. If you used trusted dealer key - generation, it will be +- Paste the identifier of the first participant, you can see it in + `key-package-1.json`. If you used trusted dealer key generation, it will be `0100000000000000000000000000000000000000000000000000000000000000`. -- Paste the second identifier, e.g. - `0200000000000000000000000000000000000000000000000000000000000000`. -- When prompted for the message to be signed, paste the SIGHASH printed by the - signer above (just the hex value, e.g. - ``4d065453cfa4cfb4f98dbc9cff60c4a3904ed91c523b8ef8d67d28bea7f12ea3``). + Create a new terminal, for participant 1, and run: ``` -cargo run --bin participant --features redpallas +cargo run --bin participant --features redpallas -- --cli --key-package key-package-1.json ``` And then: -- Paste the Secret Share printed during key generation (or Key Package if you - used DKG). - Copy the SigningCommitments line and paste into the Coordinator CLI. -Do the same for participant 2. +Go back to the coordinator and: + +- Paste the second identifier, e.g. + `0200000000000000000000000000000000000000000000000000000000000000`. + +Create a new terminal, for participant 2, and run: + +``` +cargo run --bin participant --features redpallas -- --cli --key-package key-package-2.json +``` + +And then: + +- Copy the SigningCommitments line and paste into the Coordinator CLI. +- When prompted for the message to be signed, paste the SIGHASH printed by the + signer above (just the hex value, e.g. + ``4d065453cfa4cfb4f98dbc9cff60c4a3904ed91c523b8ef8d67d28bea7f12ea3``). You should be at the Coordinator CLI. Paste the Randomizer generated by the signer before and copy the Signing Package line that it was printed by the Coordinator CLI before the Randomizer prompt. +```admonish warning +If you prefer to pass the randomizer as a file by using the `--randomizer` +argument, you will need to convert it to binary format. +``` + Switch to participant 1 and: - Paste the Signing Package From c205ef73e04352c2a82ec45e67d1208c4918306c Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 10 Apr 2024 12:13:31 -0300 Subject: [PATCH 005/175] Fix --no-default-features (#630) * fix --no-default-features; also make sure everything compilers with every feature combination * backport some fixes from no-std PR * update CHANGELOG --- frost-core/CHANGELOG.md | 6 ++++++ frost-core/Cargo.toml | 2 +- frost-core/src/keys.rs | 7 ++++--- frost-core/src/keys/dkg.rs | 2 ++ frost-core/src/lib.rs | 2 +- frost-core/src/round1.rs | 12 ++++++------ frost-core/src/round2.rs | 1 + frost-core/src/serialization.rs | 7 ++++++- frost-core/src/tests/ciphersuite_generic.rs | 1 + frost-ed25519/Cargo.toml | 5 +++-- frost-ed448/Cargo.toml | 5 +++-- frost-p256/Cargo.toml | 5 +++-- frost-rerandomized/Cargo.toml | 5 +++-- frost-ristretto255/Cargo.toml | 5 +++-- frost-secp256k1/Cargo.toml | 5 +++-- 15 files changed, 46 insertions(+), 24 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 4821c4752..725a6de38 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,6 +4,12 @@ Entries are listed in reverse chronological order. ## Unreleased +## 1.0.1 + +* Fixed `no-default-features`, previously it wouldn't compile. +* Fixed some feature handling that would include unneeded dependencies in some + cases. + ## Released ## 1.0.0 diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 0dcaa8b61..6876215c6 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -62,7 +62,7 @@ internals = [] serde = ["dep:serde", "dep:serdect"] serialization = ["serde", "dep:postcard"] # Exposes ciphersuite-generic tests for other crates to use -test-impl = ["proptest", "serde_json", "criterion"] +test-impl = ["dep:proptest", "dep:serde_json", "dep:criterion"] # Enable cheater detection cheater-detection = [] diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 533cb13f8..0ec6fb245 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -17,14 +17,15 @@ use rand_core::{CryptoRng, RngCore}; use zeroize::{DefaultIsZeroes, Zeroize}; use crate::{ - serialization::{Deserialize, Serialize}, - Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, SigningKey, - VerifyingKey, + Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, SigningKey, VerifyingKey, }; #[cfg(feature = "serde")] use crate::serialization::{ElementSerialization, ScalarSerialization}; +#[cfg(feature = "serialization")] +use crate::serialization::{Deserialize, Serialize}; + use super::compute_lagrange_coefficient; pub mod dkg; diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index fa3706518..e6e10ac65 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -50,6 +50,7 @@ pub mod round1 { use derive_getters::Getters; use zeroize::Zeroize; + #[cfg(feature = "serialization")] use crate::serialization::{Deserialize, Serialize}; use super::*; @@ -167,6 +168,7 @@ pub mod round2 { use derive_getters::Getters; use zeroize::Zeroize; + #[cfg(feature = "serialization")] use crate::serialization::{Deserialize, Serialize}; use super::*; diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index ba0ed59e0..bee7cbbcf 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -153,7 +153,7 @@ struct Header { serde(deserialize_with = "crate::serialization::ciphersuite_deserialize::<_, C>") )] ciphersuite: (), - #[serde(skip)] + #[cfg_attr(feature = "serde", serde(skip))] phantom: PhantomData, } diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index fd9faa96a..1baf304ba 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -12,15 +12,14 @@ use hex::FromHex; use rand_core::{CryptoRng, RngCore}; use zeroize::Zeroize; -use crate as frost; -use crate::{ - serialization::{Deserialize, Serialize}, - Ciphersuite, Element, Error, Field, Group, Header, Scalar, -}; +use crate::{Ciphersuite, Element, Error, Field, Group, Header, Scalar}; #[cfg(feature = "serde")] use crate::serialization::{ElementSerialization, ScalarSerialization}; +#[cfg(feature = "serialization")] +use crate::serialization::{Deserialize, Serialize}; + use super::{keys::SigningShare, Identifier}; /// A scalar that is a signing nonce. @@ -353,11 +352,12 @@ where /// Computes the [signature commitment share] from these round one signing commitments. /// /// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio + #[cfg(any(feature = "cheater-detection", feature = "internals"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(super) fn to_group_commitment_share( self, - binding_factor: &frost::BindingFactor, + binding_factor: &crate::BindingFactor, ) -> GroupCommitmentShare { GroupCommitmentShare::(self.hiding.0 + (self.binding.0 * binding_factor.0)) } diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 509a6e022..20412a0e3 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -81,6 +81,7 @@ where /// This is the final step of [`verify_signature_share`] from the spec. /// /// [`verify_signature_share`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio + #[cfg(any(feature = "cheater-detection", feature = "internals"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn verify( diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index d98416f76..e3127c75a 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -1,6 +1,10 @@ //! Serialization support. -use crate::{Ciphersuite, Error, Field, Group}; +#[cfg(feature = "serde")] +use crate::{Ciphersuite, Field, Group}; + +#[cfg(feature = "serialization")] +use crate::Error; #[cfg(feature = "serde")] #[cfg_attr(feature = "internals", visibility::make(pub))] @@ -89,6 +93,7 @@ where // The short 4-byte ID. Derived as the CRC-32 of the UTF-8 // encoded ID in big endian format. +#[cfg(feature = "serde")] const fn short_id() -> [u8; 4] where C: Ciphersuite, diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 4dd673176..5fdba722c 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -326,6 +326,7 @@ fn check_aggregate_errors( ); } +#[cfg(feature = "cheater-detection")] fn check_aggregate_corrupted_share( signing_package: frost::SigningPackage, mut signature_shares: BTreeMap, frost::round2::SignatureShare>, diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 4bd685af6..378a6769c 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -46,14 +46,15 @@ serde_json = "1.0" [features] nightly = [] default = ["serialization", "cheater-detection"] -serialization = ["serde", "frost-core/serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] ## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection"] +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] +## Enable a default serialization format. Enables `serde`. +serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index dcae381d2..ad8f3cd61 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -44,14 +44,15 @@ serde_json = "1.0" [features] nightly = [] default = ["serialization", "cheater-detection"] -serialization = ["serde", "frost-core/serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] ## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection"] +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] +## Enable a default serialization format. Enables `serde`. +serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index f0dd0786b..65c3e976f 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -45,14 +45,15 @@ serde_json = "1.0" [features] nightly = [] default = ["serialization", "cheater-detection"] -serialization = ["serde", "frost-core/serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] ## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection"] +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] +## Enable a default serialization format. Enables `serde`. +serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 71c37335f..0c2a9cc9f 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -30,13 +30,14 @@ rand_core = "0.6" [features] nightly = [] default = ["serialization", "cheater-detection"] -serialization = ["serde", "frost-core/serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] # Exposes ciphersuite-generic tests for other crates to use -test-impl = ["frost-core/test-impl"] +test-impl = ["frost-core/test-impl", "serialization"] ## Enable cheater detection cheater-detection = ["frost-core/cheater-detection"] +## Enable a default serialization format. Enables `serde`. +serialization = ["serde", "frost-core/serialization"] diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index a886f8e8d..990f875ad 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -42,14 +42,15 @@ serde_json = "1.0" [features] nightly = [] default = ["serialization", "cheater-detection"] -serialization = ["serde", "frost-core/serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] ## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection"] +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] +## Enable a default serialization format. Enables `serde`. +serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index 30e726a38..e19df4bfe 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -44,14 +44,15 @@ serde_json = "1.0" [features] nightly = [] default = ["serialization", "cheater-detection"] -serialization = ["serde", "frost-core/serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] ## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection"] +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] +## Enable a default serialization format. Enables `serde`. +serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] [lib] # Disables non-criterion benchmark which is not used; prevents errors From e5c5e53b86dabd2e04ec7bd54af3109161eaf6a9 Mon Sep 17 00:00:00 2001 From: natalie Date: Tue, 16 Apr 2024 22:58:54 +0100 Subject: [PATCH 006/175] Fix clippy issue (#640) --- frost-core/src/tests/vss_commitment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 562affc03..9a0c40d21 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -31,7 +31,7 @@ pub fn check_serialize_vss_commitment(mu // --- - let expected = vec![ + let expected = [ ::serialize(&input_1), ::serialize(&input_2), ::serialize(&input_3), From 9ca9f59360a72218dfe409993a02372adf6e14b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 21:48:51 +0000 Subject: [PATCH 007/175] Bump codecov/codecov-action from 4.1.0 to 4.3.0 (#638) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.1.0 to 4.3.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4.1.0...v4.3.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index e0af51e4c..a96a4a699 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v4.1.0 + uses: codecov/codecov-action@v4.3.0 From af0fc6e4903a861ac1896b5b6eb4148df8ba6ba5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 21:48:55 +0000 Subject: [PATCH 008/175] Bump reviewdog/action-actionlint from 1.43.0 to 1.44.0 (#641) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.43.0 to 1.44.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.43.0...v1.44.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5a97a4a6d..d863f8cbf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -143,7 +143,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.1 - - uses: reviewdog/action-actionlint@v1.43.0 + - uses: reviewdog/action-actionlint@v1.44.0 with: level: warning fail_on_error: false From 5c8d90501a989ee0d974aed6167fe372b2c6731c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 22:10:51 +0000 Subject: [PATCH 009/175] Bump peaceiris/actions-mdbook from 1.2.0 to 2.0.0 (#637) Bumps [peaceiris/actions-mdbook](https://github.com/peaceiris/actions-mdbook) from 1.2.0 to 2.0.0. - [Release notes](https://github.com/peaceiris/actions-mdbook/releases) - [Changelog](https://github.com/peaceiris/actions-mdbook/blob/main/CHANGELOG.md) - [Commits](https://github.com/peaceiris/actions-mdbook/compare/v1.2.0...v2.0.0) --- updated-dependencies: - dependency-name: peaceiris/actions-mdbook dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 11dbc6710..e3804d75b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -50,7 +50,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Setup mdBook - uses: peaceiris/actions-mdbook@v1.2.0 + uses: peaceiris/actions-mdbook@v2.0.0 with: mdbook-version: '0.4.18' From 7e3eb5ba1a74afb5c0261264ced2624856092703 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 20:57:16 +0000 Subject: [PATCH 010/175] Bump reviewdog/action-actionlint from 1.44.0 to 1.46.0 (#650) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.44.0 to 1.46.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.44.0...v1.46.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d863f8cbf..281e3c5e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -143,7 +143,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.1 - - uses: reviewdog/action-actionlint@v1.44.0 + - uses: reviewdog/action-actionlint@v1.46.0 with: level: warning fail_on_error: false From 63892e175e49aa61dc60a23970a5092c2833995a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 21:01:42 +0000 Subject: [PATCH 011/175] Update derive-getters requirement from 0.3.0 to 0.4.0 (#651) Updates the requirements on derive-getters to permit the latest version. --- updated-dependencies: - dependency-name: derive-getters dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frost-core/Cargo.toml | 2 +- frost-rerandomized/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 6876215c6..2042a6d1f 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -26,7 +26,7 @@ byteorder = "1.4" const-crc32 = "1.2.0" document-features = "0.2.7" debugless-unwrap = "0.0.4" -derive-getters = "0.3.0" +derive-getters = "0.4.0" hex = "0.4.3" postcard = { version = "1.0.0", features = ["use-std"], optional = true } rand_core = "0.6" diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 0c2a9cc9f..21bf223db 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -20,7 +20,7 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -derive-getters = "0.3.0" +derive-getters = "0.4.0" document-features = "0.2.7" frost-core = { path = "../frost-core", version = "1.0.0", features = ["internals"] } rand_core = "0.6" From 2b0df051e0c22462e1517b8f0e222207a70c1b8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 21:01:45 +0000 Subject: [PATCH 012/175] Bump codecov/codecov-action from 4.3.0 to 4.4.0 (#654) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.3.0 to 4.4.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4.3.0...v4.4.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index a96a4a699..c11d550a3 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v4.3.0 + uses: codecov/codecov-action@v4.4.0 From e32e16df9bb7bd0f713056f2ea65ed4421a434a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 21:05:04 +0000 Subject: [PATCH 013/175] Bump actions/checkout from 4.1.1 to 4.1.6 (#655) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.6) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index c11d550a3..9d9102361 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -23,7 +23,7 @@ jobs: RUST_BACKTRACE: full steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.6 with: persist-credentials: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e3804d75b..5dd4c6cbe 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the source code - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4.1.6 with: persist-credentials: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 281e3c5e5..ee0378a59 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.6 - uses: actions-rs/toolchain@v1.0.7 with: toolchain: beta @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.6 - uses: actions-rs/toolchain@v1.0.7 with: toolchain: beta @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.6 with: persist-credentials: false @@ -79,7 +79,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.6 with: persist-credentials: false @@ -101,7 +101,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.6 with: persist-credentials: false @@ -123,7 +123,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.6 with: persist-credentials: false @@ -142,7 +142,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.6 - uses: reviewdog/action-actionlint@v1.46.0 with: level: warning From 3ebe7a1c9d407de0875f1a68bde0aa019ddd45ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 21:05:07 +0000 Subject: [PATCH 014/175] Update itertools requirement from 0.12.0 to 0.13.0 (#656) Updates the requirements on [itertools](https://github.com/rust-itertools/itertools) to permit the latest version. - [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-itertools/itertools/compare/v0.12.0...v0.13.0) --- updated-dependencies: - dependency-name: itertools dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frost-core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 2042a6d1f..9048b37c9 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -35,7 +35,7 @@ serdect = { version = "0.2.0", optional = true } thiserror = "1.0" visibility = "0.1.0" zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } -itertools = "0.12.0" +itertools = "0.13.0" # Test dependencies used with the test-impl feature proptest = { version = "1.0", optional = true } From 96f11441c4e2eb26be5aaf592c87d8dccd36c840 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 30 May 2024 12:10:37 -0300 Subject: [PATCH 015/175] docs: update Ywallet demo section for phase 2 (#652) * docs: update Ywallet demo section for phase 2 * fix unrelated SecretPackage -> KeyPackage * Update book/src/zcash/ywallet-demo.md --- book/src/tutorial/trusted-dealer.md | 2 +- book/src/zcash/ywallet-demo.md | 61 ++++++++++++----------------- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/book/src/tutorial/trusted-dealer.md b/book/src/tutorial/trusted-dealer.md index 999485d53..8dcfe9040 100644 --- a/book/src/tutorial/trusted-dealer.md +++ b/book/src/tutorial/trusted-dealer.md @@ -59,7 +59,7 @@ to generate signatures. ``` ```admonish danger -The `SecretPackage` contents must be stored securely. For example: +The `KeyPackage` contents must be stored securely. For example: - Make sure other users in the system can't read it; - If possible, use the OS secure storage such that the package diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index d5537fcbe..bed39d5f5 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -161,71 +161,60 @@ When prompted, paste the UFVK generated previously. The program will print a SIGHASH and a Randomizer. Now you will need to simulate two participants and a Coordinator to sign the -transaction. It's probably easier to open three terminals. +transaction, and the FROST server that handles communications between them. +It's probably easier to open four terminals. -In the first one, the Coordinator, run (in the same folder where key -generation was run): +In the first one, the server, run (in the same folder where key generation was +run): ``` -cargo run --bin coordinator --features redpallas -- --cli +RUST_LOG=debug cargo run --bin server --features redpallas -- +``` + +In the second one, the Coordinator, run (in the same folder where key generation +was run): + +``` +cargo run --bin coordinator --features redpallas -- --http -r - ``` And then: - It should read the public key package from `public-key-package.json`. - Type `2` for the number of participants. -- Paste the identifier of the first participant, you can see it in - `key-package-1.json`. If you used trusted dealer key generation, it will be - `0100000000000000000000000000000000000000000000000000000000000000`. +- Copy the session ID that is printed. -Create a new terminal, for participant 1, and run: +In the third terminal, Participant 1, run the following, replacing +`` with the session ID you have just copied: ``` -cargo run --bin participant --features redpallas -- --cli --key-package key-package-1.json +cargo run --bin participant --features redpallas -- --cli --key-package key-package-1.json -s ``` -And then: - -- Copy the SigningCommitments line and paste into the Coordinator CLI. - -Go back to the coordinator and: - -- Paste the second identifier, e.g. - `0200000000000000000000000000000000000000000000000000000000000000`. - -Create a new terminal, for participant 2, and run: +In the fourth terminal, for Participant 2, run the following, again replacing +the session ID: ``` -cargo run --bin participant --features redpallas -- --cli --key-package key-package-2.json +cargo run --bin participant --features redpallas -- --cli --key-package key-package-2.json -s ``` -And then: +Go back to the Coordinator CLI: -- Copy the SigningCommitments line and paste into the Coordinator CLI. - When prompted for the message to be signed, paste the SIGHASH printed by the signer above (just the hex value, e.g. ``4d065453cfa4cfb4f98dbc9cff60c4a3904ed91c523b8ef8d67d28bea7f12ea3``). - -You should be at the Coordinator CLI. Paste the Randomizer generated by the -signer before and copy the Signing Package line that it was printed by the -Coordinator CLI before the Randomizer prompt. +- When prompted for the randomizer, paste the randomizer printed by the signer + above (again just the hex value) ```admonish warning If you prefer to pass the randomizer as a file by using the `--randomizer` argument, you will need to convert it to binary format. ``` -Switch to participant 1 and: - -- Paste the Signing Package -- Paste the Randomizer printed by the signer before. -- Copy the SignatureShare line and paste it into the Coordinator CLI. - -Do the same for participant 2. - -You should be at the Coordinator CLI. It has just printed the final -FROST-generated signature. Hurrah! Copy it (just the hex value). +The protocol should run and complete succesfully. You should still be at the +Coordinator CLI. It has just printed the final FROST-generated signature. +Hurrah! Copy it (just the hex value). Go back to the signer and paste the signature. It will write the raw signed transaction to the file you specified. From 2f07dbc439c37bebc9fdb3136414d21e1c8abacf Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 4 Jun 2024 12:57:00 -0300 Subject: [PATCH 016/175] ci: use cargo-all-features (#659) --- .github/workflows/main.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ee0378a59..edc2910d1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,6 +24,16 @@ jobs: with: command: build + build_all_features: + name: build with all features combinations + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo install cargo-all-features + - run: cargo build-all-features + test_beta: name: test on beta runs-on: ubuntu-latest From 9e19bc9d49d3fe3a5056c988bfaefa12dcdbf608 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 6 Jun 2024 10:42:09 -0300 Subject: [PATCH 017/175] randomized: add Debug to structs (#622) --- frost-rerandomized/Cargo.toml | 1 + frost-rerandomized/src/lib.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 21bf223db..fbccc56b0 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -23,6 +23,7 @@ rustdoc-args = ["--cfg", "docsrs"] derive-getters = "0.4.0" document-features = "0.2.7" frost-core = { path = "../frost-core", version = "1.0.0", features = ["internals"] } +hex = "0.4.3" rand_core = "0.6" [dev-dependencies] diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index f4ff81b91..97e87590d 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -249,6 +249,19 @@ where } } +impl core::fmt::Debug for Randomizer +where + C: Ciphersuite, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Randomizer") + .field(&hex::encode( + <::Field>::serialize(&self.0).as_ref(), + )) + .finish() + } +} + /// Randomized parameters for a signing instance of randomized FROST. #[derive(Clone, PartialEq, Eq, Getters)] pub struct RandomizedParams { @@ -303,3 +316,19 @@ where } } } + +impl core::fmt::Debug for RandomizedParams +where + C: Ciphersuite, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RandomizedParams") + .field("randomizer", &self.randomizer) + .field( + "randomizer_element", + &hex::encode(::serialize(&self.randomizer_element).as_ref()), + ) + .field("randomized_verifying_key", &self.randomized_verifying_key) + .finish() + } +} From a7ab0352d8108a3fb0b912062d7c73a39f7efc0e Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 11 Jun 2024 12:22:05 -0300 Subject: [PATCH 018/175] ci: test MSRV and minimal versions; also fix thiserror min version (#624) --- .github/workflows/main.yml | 14 ++++++++++++++ frost-core/Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index edc2910d1..2ad6fa58d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,6 +24,20 @@ jobs: with: command: build + build_msrv: + name: build with MSRV (1.66.1) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4.1.1 + # Re-resolve Cargo.lock with minimal versions + - uses: dtolnay/rust-toolchain@nightly + - run: cargo update -Z minimal-versions + # Now check that `cargo build` works with respect to the oldest possible + # deps and the stated MSRV + - uses: dtolnay/rust-toolchain@1.66.1 + - run: cargo build --all-features + build_all_features: name: build with all features combinations runs-on: ubuntu-latest diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 9048b37c9..2a029825e 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -32,7 +32,7 @@ postcard = { version = "1.0.0", features = ["use-std"], optional = true } rand_core = "0.6" serde = { version = "1.0.160", features = ["derive"], optional = true } serdect = { version = "0.2.0", optional = true } -thiserror = "1.0" +thiserror = "1.0.29" visibility = "0.1.0" zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } itertools = "0.13.0" From 36fbf541db5afe85325b31e8e4c2fdb23b7de651 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 22:26:57 +0000 Subject: [PATCH 019/175] Bump reviewdog/action-actionlint from 1.46.0 to 1.48.0 (#669) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.46.0 to 1.48.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.46.0...v1.48.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2ad6fa58d..c6d7c520f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -167,7 +167,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.6 - - uses: reviewdog/action-actionlint@v1.46.0 + - uses: reviewdog/action-actionlint@v1.48.0 with: level: warning fail_on_error: false From 7f034a49269549cc7bfabcb085bcf2e817bed8aa Mon Sep 17 00:00:00 2001 From: wangcundashang Date: Wed, 19 Jun 2024 17:39:51 +0900 Subject: [PATCH 020/175] chore: fix some comments (#682) Signed-off-by: wangcundashang --- README.md | 2 +- book/src/dev/developer-guide.md | 2 +- book/src/zcash/ywallet-demo.md | 2 +- plot.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0101e8370..1774125d0 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Besides FROST itself, this repository also provides: - Trusted dealer key generation as specified in the appendix of ['Two-Round Threshold Schnorr Signatures with FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/); - Distributed key generation as specified in the original paper [FROST20](https://eprint.iacr.org/2020/852.pdf); -- Repairable Theshold Scheme (RTS) from ['A Survey and Refinement of Repairable Threshold Schemes'](https://eprint.iacr.org/2017/1155) which allows a participant to recover a lost share with the help of a threshold of other participants; +- Repairable Threshold Scheme (RTS) from ['A Survey and Refinement of Repairable Threshold Schemes'](https://eprint.iacr.org/2017/1155) which allows a participant to recover a lost share with the help of a threshold of other participants; - Rerandomized FROST (paper under review). ## Getting Started diff --git a/book/src/dev/developer-guide.md b/book/src/dev/developer-guide.md index a1384c622..27ffb5809 100644 --- a/book/src/dev/developer-guide.md +++ b/book/src/dev/developer-guide.md @@ -8,7 +8,7 @@ # Coverage -Test coverage checks are performed in the pipeline. This is cofigured here: `.github/workflows/coverage.yaml` +Test coverage checks are performed in the pipeline. This is configured here: `.github/workflows/coverage.yaml` To run these locally: 1. Install coverage tool by running `cargo install cargo-llvm-cov` 2. Run `cargo llvm-cov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs'` (you may be asked if you want to install `llvm-tools-preview`, if so type `Y`) diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index bed39d5f5..54c0dc858 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -1,6 +1,6 @@ # Ywallet Demo Tutorial -This tutorial explaing how to run the FROST demo using Ywallet that was +This tutorial explaining how to run the FROST demo using Ywallet that was [presented during Zcon4](https://www.youtube.com/watch?v=xvzESdDtczo). Ywallet supports [offline diff --git a/plot.py b/plot.py index 9a3a0e2c0..20680b719 100644 --- a/plot.py +++ b/plot.py @@ -1,6 +1,6 @@ """ -Generate the graphs for the FROST perfomance blog post. +Generate the graphs for the FROST performance blog post. Install cargo-criterion: From 8be644fb6c0b070d8e39fb2474ed6627f767795e Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 19 Jun 2024 10:21:02 -0300 Subject: [PATCH 021/175] return error when deserializing the identity (#671) --- frost-core/CHANGELOG.md | 17 +++ frost-core/src/batch.rs | 16 ++- frost-core/src/benches.rs | 3 +- frost-core/src/keys.rs | 131 +++++++----------- frost-core/src/keys/dkg.rs | 21 +-- frost-core/src/lib.rs | 42 +++--- frost-core/src/round1.rs | 66 ++++----- frost-core/src/round2.rs | 6 +- frost-core/src/serialization.rs | 29 ++-- frost-core/src/signature.rs | 23 ++- frost-core/src/signing_key.rs | 21 ++- frost-core/src/tests/batch.rs | 6 +- .../src/tests/coefficient_commitment.rs | 12 +- frost-core/src/tests/proptests.rs | 4 +- frost-core/src/tests/vectors.rs | 13 +- frost-core/src/tests/vss_commitment.rs | 34 ++--- frost-core/src/traits.rs | 9 +- frost-core/src/verifying_key.rs | 64 +++------ frost-ed25519/src/lib.rs | 7 +- frost-ed25519/tests/helpers/mod.rs | 4 +- frost-ed25519/tests/helpers/samples.rs | 16 +-- frost-ed448/src/lib.rs | 7 +- frost-ed448/tests/helpers/samples.rs | 16 +-- frost-p256/src/lib.rs | 18 +-- frost-p256/src/tests/deserialize.rs | 3 +- frost-p256/tests/helpers/samples.rs | 16 +-- frost-rerandomized/src/lib.rs | 4 +- frost-ristretto255/src/lib.rs | 7 +- frost-ristretto255/tests/helpers/samples.rs | 16 +-- frost-secp256k1/src/lib.rs | 18 +-- frost-secp256k1/src/tests/deserialize.rs | 3 +- frost-secp256k1/tests/helpers/samples.rs | 16 +-- 32 files changed, 337 insertions(+), 331 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 725a6de38..7ddbe73e9 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,6 +4,23 @@ Entries are listed in reverse chronological order. ## Unreleased +* Changed the `deserialize()` function of Elements and structs containing + Elements to return an error if the element is the identity. This is a + requirement in the FROST specification that wasn't being followed. We are not + aware of any possible security issues that could be caused by this; in the + unlikely case that the identity was being serialized, this would be caught by + deserialization methods. However, we consider this change the right thing to + do as a defense-in-depth mechanism. This entails the following changes: + * `Group::serialize()` now returns an error. When implementing it, you must + return an error if it attempts to serialize the identity. + * `VerifyingShare::serialize()`, `CoefficientCommitment::serialize()`, + `VerifiableSecretSharingCommitment::serialize()`, + `NonceCommitment::serialize()`, `Signature::serialize()`, + `VerifyingKey::serialize()` can now all return an error. +* Removed `batch::Item::into()` which created a batch Item from a triple of + VerifyingKey, Signature and message. Use the new `batch::Item::new()` instead + (which can return an error). + ## 1.0.1 * Fixed `no-default-features`, previously it wouldn't compile. diff --git a/frost-core/src/batch.rs b/frost-core/src/batch.rs index 37d25bdfe..9aa819991 100644 --- a/frost-core/src/batch.rs +++ b/frost-core/src/batch.rs @@ -25,16 +25,20 @@ pub struct Item { c: Challenge, } -impl<'msg, C, M> From<(VerifyingKey, Signature, &'msg M)> for Item +impl Item where C: Ciphersuite, - M: AsRef<[u8]>, { - fn from((vk, sig, msg): (VerifyingKey, Signature, &'msg M)) -> Self { + /// Create a new batch [`Item`] from a [`VerifyingKey`], [`Signature`] + /// and a message to be verified. + pub fn new(vk: VerifyingKey, sig: Signature, msg: M) -> Result> + where + M: AsRef<[u8]>, + { // Compute c now to avoid dependency on the msg lifetime. - let c = crate::challenge(&sig.R, &vk, msg.as_ref()); + let c = crate::challenge(&sig.R, &vk, msg.as_ref())?; - Self { vk, sig, c } + Ok(Self { vk, sig, c }) } } @@ -129,7 +133,7 @@ where Rs.push(R); VK_coeffs.push(<::Field>::zero() + (blind * item.c.0)); - VKs.push(item.vk.element); + VKs.push(item.vk.to_element()); } let scalars = once(&P_coeff_acc) diff --git a/frost-core/src/benches.rs b/frost-core/src/benches.rs index 74931bc75..16810cc48 100644 --- a/frost-core/src/benches.rs +++ b/frost-core/src/benches.rs @@ -67,7 +67,8 @@ pub fn bench_batch_verify( let msg = b"Bench"; let Item { vk, sig } = item; - batch.queue((*vk, *sig, msg)); + let item = batch::Item::::new(*vk, *sig, msg).unwrap(); + batch.queue(item); } batch.verify(&mut rng) }) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 0ec6fb245..75ffc8fd0 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -17,11 +17,12 @@ use rand_core::{CryptoRng, RngCore}; use zeroize::{DefaultIsZeroes, Zeroize}; use crate::{ - Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, SigningKey, VerifyingKey, + serialization::SerializableElement, Ciphersuite, Element, Error, Field, Group, GroupError, + Header, Identifier, Scalar, SigningKey, VerifyingKey, }; #[cfg(feature = "serde")] -use crate::serialization::{ElementSerialization, ScalarSerialization}; +use crate::serialization::ScalarSerialization; #[cfg(feature = "serialization")] use crate::serialization::{Deserialize, Serialize}; @@ -38,7 +39,7 @@ pub(crate) fn sum_commitments( commitments: &[&VerifiableSecretSharingCommitment], ) -> Result, Error> { let mut group_commitment = vec![ - CoefficientCommitment(::identity()); + CoefficientCommitment::new(::identity()); commitments .first() .ok_or(Error::IncorrectNumberOfCommitments)? @@ -47,7 +48,7 @@ pub(crate) fn sum_commitments( ]; for commitment in commitments { for (i, c) in group_commitment.iter_mut().enumerate() { - *c = CoefficientCommitment( + *c = CoefficientCommitment::new( c.value() + commitment .0 @@ -186,9 +187,8 @@ where #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "ElementSerialization"))] -pub struct VerifyingShare(pub(super) Element) +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct VerifyingShare(pub(super) SerializableElement) where C: Ciphersuite; @@ -197,27 +197,30 @@ where C: Ciphersuite, { /// Create a new [`VerifyingShare`] from a element. - #[cfg(feature = "internals")] - pub fn new(element: Element) -> Self { - Self(element) + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn new(element: Element) -> Self { + Self(SerializableElement(element)) } /// Get the inner element. - #[cfg(feature = "internals")] - pub fn to_element(&self) -> Element { - self.0 + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + #[allow(dead_code)] + pub(crate) fn to_element(&self) -> Element { + self.0 .0 } /// Deserialize from bytes pub fn deserialize(bytes: ::Serialization) -> Result> { ::deserialize(&bytes) - .map(|element| Self(element)) + .map(|element| Self(SerializableElement(element))) .map_err(|e| e.into()) } /// Serialize to bytes - pub fn serialize(&self) -> ::Serialization { - ::serialize(&self.0) + pub fn serialize(&self) -> Result<::Serialization, Error> { + Ok(::serialize(&self.0 .0)?) } /// Computes a verifying share for a peer given the group commitment. @@ -237,7 +240,7 @@ where // Y_i = ∏_{k=0}^{t−1} (∏_{j=1}^n φ_{jk})^{i^k mod q} // i.e. we can operate on the sum of all φ_j commitments, which is // what is passed to the functions. - VerifyingShare(evaluate_vss(identifier, commitment)) + VerifyingShare::new(evaluate_vss(identifier, commitment)) } } @@ -247,7 +250,12 @@ where { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("VerifyingShare") - .field(&hex::encode(self.serialize())) + .field( + &self + .serialize() + .map(hex::encode) + .unwrap_or("".to_string()), + ) .finish() } } @@ -257,29 +265,7 @@ where C: Ciphersuite, { fn from(secret: SigningShare) -> VerifyingShare { - VerifyingShare(::generator() * secret.0 as Scalar) - } -} - -#[cfg(feature = "serde")] -impl TryFrom> for VerifyingShare -where - C: Ciphersuite, -{ - type Error = Error; - - fn try_from(value: ElementSerialization) -> Result { - Self::deserialize(value.0) - } -} - -#[cfg(feature = "serde")] -impl From> for ElementSerialization -where - C: Ciphersuite, -{ - fn from(value: VerifyingShare) -> Self { - Self(value.serialize()) + VerifyingShare::new(::generator() * secret.0 as Scalar) } } @@ -290,9 +276,7 @@ where #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "ElementSerialization"))] -pub struct CoefficientCommitment(pub(crate) Element); +pub struct CoefficientCommitment(pub(crate) SerializableElement); impl CoefficientCommitment where @@ -301,12 +285,12 @@ where /// Create a new CoefficientCommitment. #[cfg_attr(feature = "internals", visibility::make(pub))] pub(crate) fn new(value: Element) -> Self { - Self(value) + Self(SerializableElement(value)) } /// returns serialized element - pub fn serialize(&self) -> ::Serialization { - ::serialize(&self.0) + pub fn serialize(&self) -> Result<::Serialization, Error> { + Ok(::serialize(&self.0 .0)?) } /// Creates a new commitment from a coefficient input @@ -318,7 +302,7 @@ where /// Returns inner element value pub fn value(&self) -> Element { - self.0 + self.0 .0 } } @@ -328,33 +312,16 @@ where { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("CoefficientCommitment") - .field(&hex::encode(self.serialize())) + .field( + &self + .serialize() + .map(hex::encode) + .unwrap_or("".to_string()), + ) .finish() } } -#[cfg(feature = "serde")] -impl TryFrom> for CoefficientCommitment -where - C: Ciphersuite, -{ - type Error = Error; - - fn try_from(value: ElementSerialization) -> Result { - Self::deserialize(value.0) - } -} - -#[cfg(feature = "serde")] -impl From> for ElementSerialization -where - C: Ciphersuite, -{ - fn from(value: CoefficientCommitment) -> Self { - Self(value.serialize()) - } -} - /// Contains the commitments to the coefficients for our secret polynomial _f_, /// used to generate participants' key shares. /// @@ -385,11 +352,12 @@ where } /// Returns serialized coefficent commitments - pub fn serialize(&self) -> Vec<::Serialization> { - self.0 + pub fn serialize(&self) -> Result::Serialization>, Error> { + Ok(self + .0 .iter() - .map(|cc| <::Group as Group>::serialize(&cc.0)) - .collect() + .map(|cc| <::Group as Group>::serialize(&cc.value())) + .collect::>()?) } /// Returns VerifiableSecretSharingCommitment from a vector of serialized CoefficientCommitments @@ -408,7 +376,7 @@ where /// element in the vector), or an error if the vector is empty. pub(crate) fn verifying_key(&self) -> Result, Error> { Ok(VerifyingKey::new( - self.0.first().ok_or(Error::MissingCommitment)?.0, + self.0.first().ok_or(Error::MissingCommitment)?.0 .0, )) } @@ -488,7 +456,10 @@ where return Err(Error::InvalidSecretShare); } - Ok((VerifyingShare(result), self.commitment.verifying_key()?)) + Ok(( + VerifyingShare::new(result), + self.commitment.verifying_key()?, + )) } } @@ -633,7 +604,9 @@ fn evaluate_vss( let (_, result) = commitment.0.iter().fold( (<::Field>::one(), ::identity()), - |(i_to_the_k, sum_so_far), comm_k| (i * i_to_the_k, sum_so_far + comm_k.0 * i_to_the_k), + |(i_to_the_k, sum_so_far), comm_k| { + (i * i_to_the_k, sum_so_far + comm_k.value() * i_to_the_k) + }, ); result } @@ -862,7 +835,7 @@ pub(crate) fn generate_secret_polynomial( // Create the vector of commitments let commitment: Vec<_> = coefficients .iter() - .map(|c| CoefficientCommitment(::generator() * *c)) + .map(|c| CoefficientCommitment::new(::generator() * *c)) .collect(); let commitment: VerifiableSecretSharingCommitment = VerifiableSecretSharingCommitment(commitment); diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index e6e10ac65..dd09bda57 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -318,17 +318,19 @@ fn challenge( identifier: Identifier, verifying_key: &VerifyingKey, R: &Element, -) -> Option> +) -> Result, Error> where C: Ciphersuite, { let mut preimage = vec![]; preimage.extend_from_slice(identifier.serialize().as_ref()); - preimage.extend_from_slice(::serialize(&verifying_key.element).as_ref()); - preimage.extend_from_slice(::serialize(R).as_ref()); + preimage.extend_from_slice(::serialize(&verifying_key.to_element())?.as_ref()); + preimage.extend_from_slice(::serialize(R)?.as_ref()); - Some(Challenge(C::HDKG(&preimage[..])?)) + Ok(Challenge( + C::HDKG(&preimage[..]).ok_or(Error::DKGNotSupported)?, + )) } /// Compute the proof of knowledge of the secret coefficients used to generate @@ -348,8 +350,7 @@ pub(crate) fn compute_proof_of_knowledge // > a context string to prevent replay attacks. let k = <::Field>::random(&mut rng); let R_i = ::generator() * k; - let c_i = challenge::(identifier, &commitment.verifying_key()?, &R_i) - .ok_or(Error::DKGNotSupported)?; + let c_i = challenge::(identifier, &commitment.verifying_key()?, &R_i)?; let a_i0 = *coefficients .first() .expect("coefficients must have at least one element"); @@ -363,7 +364,7 @@ pub(crate) fn compute_proof_of_knowledge pub(crate) fn verify_proof_of_knowledge( identifier: Identifier, commitment: &VerifiableSecretSharingCommitment, - proof_of_knowledge: Signature, + proof_of_knowledge: &Signature, ) -> Result<(), Error> { // Round 1, Step 5 // @@ -374,8 +375,8 @@ pub(crate) fn verify_proof_of_knowledge( let R_ell = proof_of_knowledge.R; let mu_ell = proof_of_knowledge.z; let phi_ell0 = commitment.verifying_key()?; - let c_ell = challenge::(ell, &phi_ell0, &R_ell).ok_or(Error::DKGNotSupported)?; - if R_ell != ::generator() * mu_ell - phi_ell0.element * c_ell.0 { + let c_ell = challenge::(ell, &phi_ell0, &R_ell)?; + if R_ell != ::generator() * mu_ell - phi_ell0.to_element() * c_ell.0 { return Err(Error::InvalidProofOfKnowledge { culprit: ell }); } Ok(()) @@ -422,7 +423,7 @@ pub fn part2( verify_proof_of_knowledge( ell, &round1_package.commitment, - round1_package.proof_of_knowledge, + &round1_package.proof_of_knowledge, )?; // Round 2, Step 1 diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index bee7cbbcf..96c5cba69 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -107,17 +107,21 @@ where /// [RFC]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.2 #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -fn challenge(R: &Element, verifying_key: &VerifyingKey, msg: &[u8]) -> Challenge +fn challenge( + R: &Element, + verifying_key: &VerifyingKey, + msg: &[u8], +) -> Result, Error> where C: Ciphersuite, { let mut preimage = vec![]; - preimage.extend_from_slice(::serialize(R).as_ref()); - preimage.extend_from_slice(::serialize(&verifying_key.element).as_ref()); + preimage.extend_from_slice(::serialize(R)?.as_ref()); + preimage.extend_from_slice(::serialize(&verifying_key.to_element())?.as_ref()); preimage.extend_from_slice(msg); - Challenge(C::H2(&preimage[..])) + Ok(Challenge(C::H2(&preimage[..]))) } /// Generates a random nonzero scalar. @@ -233,13 +237,13 @@ pub(crate) fn compute_binding_factor_list( signing_package: &SigningPackage, verifying_key: &VerifyingKey, additional_prefix: &[u8], -) -> BindingFactorList +) -> Result, Error> where C: Ciphersuite, { - let preimages = signing_package.binding_factor_preimages(verifying_key, additional_prefix); + let preimages = signing_package.binding_factor_preimages(verifying_key, additional_prefix)?; - BindingFactorList( + Ok(BindingFactorList( preimages .iter() .map(|(identifier, preimage)| { @@ -247,7 +251,7 @@ where (*identifier, BindingFactor(binding_factor)) }) .collect(), - ) + )) } #[cfg(any(test, feature = "test-impl"))] @@ -399,28 +403,30 @@ where // We separate this out into its own method so it can be tested #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + #[allow(clippy::type_complexity)] pub fn binding_factor_preimages( &self, verifying_key: &VerifyingKey, additional_prefix: &[u8], - ) -> Vec<(Identifier, Vec)> { + ) -> Result, Vec)>, Error> { let mut binding_factor_input_prefix = vec![]; // The length of a serialized verifying key of the same cipersuite does // not change between runs of the protocol, so we don't need to hash to // get a fixed length. - binding_factor_input_prefix.extend_from_slice(verifying_key.serialize().as_ref()); + binding_factor_input_prefix.extend_from_slice(verifying_key.serialize()?.as_ref()); // The message is hashed with H4 to force the variable-length message // into a fixed-length byte string, same for hashing the variable-sized // (between runs of the protocol) set of group commitments, but with H5. binding_factor_input_prefix.extend_from_slice(C::H4(self.message.as_slice()).as_ref()); binding_factor_input_prefix.extend_from_slice( - C::H5(&round1::encode_group_commitments(self.signing_commitments())[..]).as_ref(), + C::H5(&round1::encode_group_commitments(self.signing_commitments())?[..]).as_ref(), ); binding_factor_input_prefix.extend_from_slice(additional_prefix); - self.signing_commitments() + Ok(self + .signing_commitments() .keys() .map(|identifier| { let mut binding_factor_input = vec![]; @@ -429,7 +435,7 @@ where binding_factor_input.extend_from_slice(identifier.serialize().as_ref()); (*identifier, binding_factor_input) }) - .collect() + .collect()) } } @@ -496,7 +502,7 @@ where for (commitment_identifier, commitment) in signing_package.signing_commitments() { // The following check prevents a party from accidentally revealing their share. // Note that the '&&' operator would be sufficient. - if identity == commitment.binding.0 || identity == commitment.hiding.0 { + if identity == commitment.binding.value() || identity == commitment.hiding.value() { return Err(Error::IdentityCommitment); } @@ -506,10 +512,10 @@ where // Collect the binding commitments and their binding factors for one big // multiscalar multiplication at the end. - binding_elements.push(commitment.binding.0); + binding_elements.push(commitment.binding.value()); binding_scalars.push(binding_factor.0); - group_commitment = group_commitment + commitment.hiding.0; + group_commitment = group_commitment + commitment.hiding.value(); } let accumulated_binding_commitment: Element = @@ -568,7 +574,7 @@ where // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. let binding_factor_list: BindingFactorList = - compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[]); + compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?; // Compute the group commitment from signing commitments produced in round one. let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; @@ -605,7 +611,7 @@ where &group_commitment.0, &pubkeys.verifying_key, signing_package.message().as_slice(), - ); + )?; // Verify the signature shares. for (signature_share_identifier, signature_share) in signature_shares { diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 1baf304ba..bfc11c1bf 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -12,10 +12,12 @@ use hex::FromHex; use rand_core::{CryptoRng, RngCore}; use zeroize::Zeroize; -use crate::{Ciphersuite, Element, Error, Field, Group, Header, Scalar}; +use crate::{ + serialization::SerializableElement, Ciphersuite, Element, Error, Field, Group, Header, Scalar, +}; #[cfg(feature = "serde")] -use crate::serialization::{ElementSerialization, ScalarSerialization}; +use crate::serialization::ScalarSerialization; #[cfg(feature = "serialization")] use crate::serialization::{Deserialize, Serialize}; @@ -136,46 +138,31 @@ where #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "ElementSerialization"))] -pub struct NonceCommitment(pub(super) Element); +pub struct NonceCommitment(pub(super) SerializableElement); impl NonceCommitment where C: Ciphersuite, { + /// Create a new [`NonceCommitment`] from an [`Element`] + pub(crate) fn new(value: Element) -> Self { + Self(SerializableElement(value)) + } + + pub(crate) fn value(&self) -> Element { + self.0 .0 + } + /// Deserialize [`NonceCommitment`] from bytes pub fn deserialize(bytes: ::Serialization) -> Result> { ::deserialize(&bytes) - .map(|element| Self(element)) + .map(|element| Self::new(element)) .map_err(|e| e.into()) } /// Serialize [`NonceCommitment`] to bytes - pub fn serialize(&self) -> ::Serialization { - ::serialize(&self.0) - } -} - -#[cfg(feature = "serde")] -impl TryFrom> for NonceCommitment -where - C: Ciphersuite, -{ - type Error = Error; - - fn try_from(value: ElementSerialization) -> Result { - Self::deserialize(value.0) - } -} - -#[cfg(feature = "serde")] -impl From> for ElementSerialization -where - C: Ciphersuite, -{ - fn from(value: NonceCommitment) -> Self { - Self(value.serialize()) + pub fn serialize(&self) -> Result<::Serialization, Error> { + Ok(::serialize(&self.0 .0)?) } } @@ -185,7 +172,12 @@ where { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("NonceCommitment") - .field(&hex::encode(self.serialize())) + .field( + &self + .serialize() + .map(hex::encode) + .unwrap_or("".to_string()), + ) .finish() } } @@ -204,7 +196,7 @@ where C: Ciphersuite, { fn from(nonce: &Nonce) -> Self { - Self(::generator() * nonce.0) + Self::new(::generator() * nonce.0) } } @@ -359,7 +351,7 @@ where self, binding_factor: &crate::BindingFactor, ) -> GroupCommitmentShare { - GroupCommitmentShare::(self.hiding.0 + (self.binding.0 * binding_factor.0)) + GroupCommitmentShare::(self.hiding.value() + (self.binding.value() * binding_factor.0)) } } @@ -406,16 +398,16 @@ pub struct GroupCommitmentShare(pub(super) Element); /// [`encode_group_commitment_list()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-list-operations pub(super) fn encode_group_commitments( signing_commitments: &BTreeMap, SigningCommitments>, -) -> Vec { +) -> Result, Error> { let mut bytes = vec![]; for (item_identifier, item) in signing_commitments { bytes.extend_from_slice(item_identifier.serialize().as_ref()); - bytes.extend_from_slice(::serialize(&item.hiding.0).as_ref()); - bytes.extend_from_slice(::serialize(&item.binding.0).as_ref()); + bytes.extend_from_slice(::serialize(&item.hiding.value())?.as_ref()); + bytes.extend_from_slice(::serialize(&item.binding.value())?.as_ref()); } - bytes + Ok(bytes) } /// Done once by each participant, to generate _their_ nonces and commitments diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 20412a0e3..f71bddeff 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -93,7 +93,7 @@ where challenge: &Challenge, ) -> Result<(), Error> { if (::generator() * self.share) - != (group_commitment_share.0 + (verifying_share.0 * challenge.0 * lambda_i)) + != (group_commitment_share.0 + (verifying_share.to_element() * challenge.0 * lambda_i)) { return Err(Error::InvalidSignatureShare { culprit: identifier, @@ -202,7 +202,7 @@ pub fn sign( // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. let binding_factor_list: BindingFactorList = - compute_binding_factor_list(signing_package, &key_package.verifying_key, &[]); + compute_binding_factor_list(signing_package, &key_package.verifying_key, &[])?; let binding_factor: frost::BindingFactor = binding_factor_list .get(&key_package.identifier) .ok_or(Error::UnknownIdentifier)? @@ -219,7 +219,7 @@ pub fn sign( &group_commitment.0, &key_package.verifying_key, signing_package.message.as_slice(), - ); + )?; // Compute the Schnorr signature share. let signature_share = compute_signature_share( diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index e3127c75a..eda0bdec7 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -1,11 +1,15 @@ //! Serialization support. +use crate::Ciphersuite; + #[cfg(feature = "serde")] -use crate::{Ciphersuite, Field, Group}; +use crate::{Field, Group}; #[cfg(feature = "serialization")] use crate::Error; +use crate::Element; + #[cfg(feature = "serde")] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] @@ -51,13 +55,11 @@ where } } -#[cfg(feature = "serde")] -pub(crate) struct ElementSerialization( - pub(crate) <::Group as Group>::Serialization, -); +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) struct SerializableElement(pub(crate) Element); #[cfg(feature = "serde")] -impl serde::Serialize for ElementSerialization +impl serde::Serialize for SerializableElement where C: Ciphersuite, { @@ -65,12 +67,14 @@ where where S: serde::Serializer, { - serdect::array::serialize_hex_lower_or_bin(&self.0.as_ref(), serializer) + let serialized = + ::serialize(&self.0).map_err(serde::ser::Error::custom)?; + serdect::array::serialize_hex_lower_or_bin(&serialized.as_ref(), serializer) } } #[cfg(feature = "serde")] -impl<'de, C> serde::Deserialize<'de> for ElementSerialization +impl<'de, C> serde::Deserialize<'de> for SerializableElement where C: Ciphersuite, { @@ -80,14 +84,19 @@ where { // Get size from the size of the generator let generator = ::generator(); - let len = ::serialize(&generator).as_ref().len(); + let len = ::serialize(&generator) + .expect("serializing the generator always works") + .as_ref() + .len(); let mut bytes = vec![0u8; len]; serdect::array::deserialize_hex_or_bin(&mut bytes[..], deserializer)?; let array = bytes .try_into() .map_err(|_| serde::de::Error::custom("invalid byte length"))?; - Ok(Self(array)) + ::deserialize(&array) + .map(|element| Self(element)) + .map_err(serde::de::Error::custom) } } diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index e913c7de1..a55b0db20 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -35,7 +35,7 @@ where // and get its length. Note that we can't use the identity because it can be encoded // shorter in some cases (e.g. P-256, which uses SEC1 encoding). let generator = ::generator(); - let mut R_bytes = Vec::from(::serialize(&generator).as_ref()); + let mut R_bytes = Vec::from(::serialize(&generator)?.as_ref()); let R_bytes_len = R_bytes.len(); @@ -71,13 +71,13 @@ where } /// Converts this signature to its [`Ciphersuite::SignatureSerialization`] in bytes. - pub fn serialize(&self) -> C::SignatureSerialization { + pub fn serialize(&self) -> Result> { let mut bytes = vec![]; - bytes.extend(::serialize(&self.R).as_ref()); + bytes.extend(::serialize(&self.R)?.as_ref()); bytes.extend(<::Field>::serialize(&self.z).as_ref()); - bytes.try_into().debugless_unwrap() + Ok(bytes.try_into().debugless_unwrap()) } } @@ -92,7 +92,13 @@ where where S: serde::Serializer, { - serdect::slice::serialize_hex_lower_or_bin(&self.serialize().as_ref(), serializer) + serdect::slice::serialize_hex_lower_or_bin( + &self + .serialize() + .map_err(serde::ser::Error::custom)? + .as_ref(), + serializer, + ) } } @@ -120,7 +126,12 @@ where impl std::fmt::Debug for Signature { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("Signature") - .field("R", &hex::encode(::serialize(&self.R).as_ref())) + .field( + "R", + &::serialize(&self.R) + .map(|s| hex::encode(s.as_ref())) + .unwrap_or("".to_string()), + ) .field( "z", &hex::encode(<::Field>::serialize(&self.z).as_ref()), diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index 26d812f2f..03e13156d 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -31,11 +31,7 @@ where let scalar = <::Field as Field>::deserialize(&bytes).map_err(Error::from)?; - if scalar == <::Field as Field>::zero() { - return Err(Error::MalformedSigningKey); - } - - Ok(Self { scalar }) + Self::from_scalar(scalar) } /// Serialize `SigningKey` to bytes @@ -50,18 +46,21 @@ where let R = ::generator() * k; // Generate Schnorr challenge - let c = crate::challenge::(&R, &VerifyingKey::::from(*self), msg); + let c = crate::challenge::(&R, &VerifyingKey::::from(*self), msg).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero."); let z = k + (c.0 * self.scalar); Signature { R, z } } - /// Creates a SigningKey from a scalar. + /// Creates a SigningKey from a scalar. Returns an error if the scalar is zero. pub fn from_scalar( scalar: <<::Group as Group>::Field as Field>::Scalar, - ) -> Self { - Self { scalar } + ) -> Result> { + if scalar == <::Field as Field>::zero() { + return Err(Error::MalformedSigningKey); + } + Ok(Self { scalar }) } /// Return the underlying scalar. @@ -84,9 +83,7 @@ where C: Ciphersuite, { fn from(signing_key: &SigningKey) -> Self { - VerifyingKey { - element: C::Group::generator() * signing_key.scalar, - } + VerifyingKey::new(C::Group::generator() * signing_key.scalar) } } diff --git a/frost-core/src/tests/batch.rs b/frost-core/src/tests/batch.rs index 2f40433b1..008e0cb78 100644 --- a/frost-core/src/tests/batch.rs +++ b/frost-core/src/tests/batch.rs @@ -10,7 +10,7 @@ pub fn batch_verify(mut rng: R) { let msg = b"BatchVerifyTest"; let sig = sk.sign(&mut rng, &msg[..]); assert!(vk.verify(msg, &sig).is_ok()); - batch.queue((vk, sig, msg)); + batch.queue(batch::Item::::new(vk, sig, msg).unwrap()); } assert!(batch.verify(rng).is_ok()); } @@ -31,14 +31,14 @@ pub fn bad_batch_verify(mut rng: R) { } else { sk.sign(&mut rng, b"bad") }; - (vk, sig, msg).into() + batch::Item::::new(vk, sig, msg).unwrap() } 1 => { let sk = SigningKey::new(&mut rng); let vk = VerifyingKey::::from(&sk); let msg = b"BatchVerifyTest"; let sig = sk.sign(&mut rng, &msg[..]); - (vk, sig, msg).into() + batch::Item::::new(vk, sig, msg).unwrap() } _ => unreachable!(), }; diff --git a/frost-core/src/tests/coefficient_commitment.rs b/frost-core/src/tests/coefficient_commitment.rs index 36c241c8c..6218d1671 100644 --- a/frost-core/src/tests/coefficient_commitment.rs +++ b/frost-core/src/tests/coefficient_commitment.rs @@ -16,9 +16,11 @@ pub fn check_serialization_of_coefficient_commitment(&mut rng); - let expected = ::serialize(&element); + let expected = ::serialize(&element).unwrap(); - let data = frost::keys::CoefficientCommitment::(element).serialize(); + let data = frost::keys::CoefficientCommitment::::new(element) + .serialize() + .unwrap(); assert!(expected.as_ref() == data.as_ref()); } @@ -27,9 +29,9 @@ pub fn check_serialization_of_coefficient_commitment(mut rng: R) { let element = generate_element::(&mut rng); - let expected = CoefficientCommitment::(element); + let expected = CoefficientCommitment::::new(element); - let serialized_element = ::serialize(&element); + let serialized_element = ::serialize(&element).unwrap(); let coeff_commitment = frost::keys::CoefficientCommitment::::deserialize(serialized_element).unwrap(); @@ -59,7 +61,7 @@ pub fn check_get_value_of_coefficient_commitment(&mut rng); - let coeff_commitment = frost::keys::CoefficientCommitment::(element); + let coeff_commitment = frost::keys::CoefficientCommitment::::new(element); let value = coeff_commitment.value(); assert!(value == element) diff --git a/frost-core/src/tests/proptests.rs b/frost-core/src/tests/proptests.rs index f7c70897e..06a3925f4 100644 --- a/frost-core/src/tests/proptests.rs +++ b/frost-core/src/tests/proptests.rs @@ -59,12 +59,12 @@ where // The signature data is stored in (refined) byte types, but do a round trip // conversion to raw bytes to exercise those code paths. let _sig = { - let bytes = self.sig.serialize(); + let bytes = self.sig.serialize().unwrap(); Signature::::deserialize(bytes) }; // Check that the verification key is a valid key. - let _pub_key = VerifyingKey::::deserialize(self.vk.serialize()) + let _pub_key = VerifyingKey::::deserialize(self.vk.serialize().unwrap()) .expect("The test verification key to be well-formed."); // Check that signature validation has the expected result. diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index d38f7af3c..837e8929d 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -263,13 +263,14 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { for (identifier, input) in signing_package .binding_factor_preimages(&verifying_key, &[]) + .unwrap() .iter() { assert_eq!(*input, binding_factor_inputs[identifier]); } let binding_factor_list: frost::BindingFactorList = - compute_binding_factor_list(&signing_package, &verifying_key, &[]); + compute_binding_factor_list(&signing_package, &verifying_key, &[]).unwrap(); for (identifier, binding_factor) in binding_factor_list.0.iter() { assert_eq!(*binding_factor, binding_factors[identifier]); @@ -311,7 +312,10 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { // Check that the generated signature matches the test vector signature let group_signature = group_signature_result.unwrap(); - assert_eq!(group_signature.serialize().as_ref(), signature_bytes); + assert_eq!( + group_signature.serialize().unwrap().as_ref(), + signature_bytes + ); // Aggregate the FROST signature from our signature shares let group_signature_result = @@ -322,5 +326,8 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { // Check that the generated signature matches the test vector signature let group_signature = group_signature_result.unwrap(); - assert_eq!(group_signature.serialize().as_ref(), signature_bytes); + assert_eq!( + group_signature.serialize().unwrap().as_ref(), + signature_bytes + ); } diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 9a0c40d21..31b5d4cbb 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -24,20 +24,22 @@ pub fn check_serialize_vss_commitment(mu let input_3 = generate_element::(&mut rng); let coeff_comms = vec![ - CoefficientCommitment::(input_1), - CoefficientCommitment(input_2), - CoefficientCommitment(input_3), + CoefficientCommitment::::new(input_1), + CoefficientCommitment::new(input_2), + CoefficientCommitment::new(input_3), ]; // --- let expected = [ - ::serialize(&input_1), - ::serialize(&input_2), - ::serialize(&input_3), + ::serialize(&input_1).unwrap(), + ::serialize(&input_2).unwrap(), + ::serialize(&input_3).unwrap(), ]; - let vss_commitment = VerifiableSecretSharingCommitment(coeff_comms).serialize(); + let vss_commitment = VerifiableSecretSharingCommitment(coeff_comms) + .serialize() + .unwrap(); assert!(expected.len() == vss_commitment.len()); assert!(expected @@ -56,18 +58,18 @@ pub fn check_deserialize_vss_commitment( let input_3 = generate_element::(&mut rng); let coeff_comms = vec![ - CoefficientCommitment::(input_1), - CoefficientCommitment(input_2), - CoefficientCommitment(input_3), + CoefficientCommitment::::new(input_1), + CoefficientCommitment::new(input_2), + CoefficientCommitment::new(input_3), ]; // --- let expected = VerifiableSecretSharingCommitment(coeff_comms); let data = vec![ - ::serialize(&input_1), - ::serialize(&input_2), - ::serialize(&input_3), + ::serialize(&input_1).unwrap(), + ::serialize(&input_2).unwrap(), + ::serialize(&input_3).unwrap(), ]; let vss_value = VerifiableSecretSharingCommitment::deserialize(data); @@ -98,9 +100,9 @@ pub fn check_deserialize_vss_commitment_error::serialize(&input_1), - ::serialize(&input_2), - ::serialize(&input_3), + ::serialize(&input_1).unwrap(), + ::serialize(&input_2).unwrap(), + ::serialize(&input_3).unwrap(), serialized, ]; diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 817caf60d..4221d96bf 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -113,11 +113,12 @@ pub trait Group: Copy + Clone + PartialEq { /// [`ScalarBaseMult()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.5 fn generator() -> Self::Element; - /// A member function of a group _G_ that maps an [`Element`] to a unique byte array buf of - /// fixed length Ne. + /// A member function of a group _G_ that maps an [`Element`] to a unique + /// byte array buf of fixed length Ne. This function raises an error if the + /// element is the identity element of the group. /// /// - fn serialize(element: &Self::Element) -> Self::Serialization; + fn serialize(element: &Self::Element) -> Result; /// A member function of a [`Group`] that attempts to map a byte array `buf` to an [`Element`]. /// @@ -224,7 +225,7 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug { signature: &Signature, public_key: &VerifyingKey, ) -> Result<(), Error> { - let c = crate::challenge::(&signature.R, public_key, msg); + let c = crate::challenge::(&signature.R, public_key, msg)?; public_key.verify_prehashed(c, signature) } diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index 519020b58..2effd05e3 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -3,22 +3,18 @@ use std::fmt::{self, Debug}; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; -use crate::{Challenge, Ciphersuite, Element, Error, Group, Signature}; - -#[cfg(feature = "serde")] -use crate::serialization::ElementSerialization; +use crate::{serialization::SerializableElement, Challenge, Ciphersuite, Error, Group, Signature}; /// A valid verifying key for Schnorr signatures over a FROST [`Ciphersuite::Group`]. #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(try_from = "ElementSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "ElementSerialization"))] +#[cfg_attr(feature = "serde", serde(transparent))] pub struct VerifyingKey where C: Ciphersuite, { - pub(crate) element: Element, + pub(crate) element: SerializableElement, } impl VerifyingKey @@ -29,13 +25,16 @@ where #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn new(element: ::Element) -> Self { - Self { element } + Self { + element: SerializableElement(element), + } } /// Return the underlying element. - #[cfg(feature = "internals")] - pub fn to_element(self) -> ::Element { - self.element + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn to_element(self) -> ::Element { + self.element.0 } /// Deserialize from bytes @@ -43,13 +42,13 @@ where bytes: ::Serialization, ) -> Result, Error> { ::deserialize(&bytes) - .map(|element| VerifyingKey { element }) + .map(|element| VerifyingKey::new(element)) .map_err(|e| e.into()) } /// Serialize `VerifyingKey` to bytes - pub fn serialize(&self) -> ::Serialization { - ::serialize(&self.element) + pub fn serialize(&self) -> Result<::Serialization, Error> { + Ok(::serialize(&self.element.0)?) } /// Verify a purported `signature` with a pre-hashed [`Challenge`] made by this verification @@ -64,7 +63,7 @@ where // // where h is the cofactor let zB = C::Group::generator() * signature.z; - let cA = self.element * challenge.0; + let cA = self.element.0 * challenge.0; let check = (zB - cA - signature.R) * C::Group::cofactor(); if check == C::Group::identity() { @@ -84,13 +83,13 @@ where pub(crate) fn from_commitment( commitment: &crate::keys::VerifiableSecretSharingCommitment, ) -> Result, Error> { - Ok(VerifyingKey { - element: commitment + Ok(VerifyingKey::new( + commitment .coefficients() .first() .ok_or(Error::IncorrectCommitment)? .value(), - }) + )) } } @@ -100,7 +99,12 @@ where { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("VerifyingKey") - .field(&hex::encode(self.serialize())) + .field( + &self + .serialize() + .map(hex::encode) + .unwrap_or("".to_string()), + ) .finish() } } @@ -120,25 +124,3 @@ where } } } - -#[cfg(feature = "serde")] -impl TryFrom> for VerifyingKey -where - C: Ciphersuite, -{ - type Error = Error; - - fn try_from(value: ElementSerialization) -> Result { - Self::deserialize(value.0) - } -} - -#[cfg(feature = "serde")] -impl From> for ElementSerialization -where - C: Ciphersuite, -{ - fn from(value: VerifyingKey) -> Self { - Self(value.serialize()) - } -} diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index b774c17d8..c7644edbe 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -99,8 +99,11 @@ impl Group for Ed25519Group { ED25519_BASEPOINT_POINT } - fn serialize(element: &Self::Element) -> Self::Serialization { - element.compress().to_bytes() + fn serialize(element: &Self::Element) -> Result { + if *element == Self::identity() { + return Err(GroupError::InvalidIdentityElement); + } + Ok(element.compress().to_bytes()) } fn deserialize(buf: &Self::Serialization) -> Result { diff --git a/frost-ed25519/tests/helpers/mod.rs b/frost-ed25519/tests/helpers/mod.rs index 8f0f795dc..ec68f5884 100644 --- a/frost-ed25519/tests/helpers/mod.rs +++ b/frost-ed25519/tests/helpers/mod.rs @@ -14,11 +14,11 @@ pub fn verify_signature( group_pubkey: frost_core::VerifyingKey, ) { let sig = { - let bytes: [u8; 64] = group_signature.serialize(); + let bytes: [u8; 64] = group_signature.serialize().unwrap(); ed25519_dalek::Signature::from(bytes) }; let pub_key = { - let bytes = group_pubkey.serialize(); + let bytes = group_pubkey.serialize().unwrap(); ed25519_dalek::VerifyingKey::from_bytes(&bytes).unwrap() }; // Check that signature validation has the expected result. diff --git a/frost-ed25519/tests/helpers/samples.rs b/frost-ed25519/tests/helpers/samples.rs index 3080df015..9f9cc79ff 100644 --- a/frost-ed25519/tests/helpers/samples.rs +++ b/frost-ed25519/tests/helpers/samples.rs @@ -44,8 +44,8 @@ pub fn signing_nonces() -> SigningNonces { /// Generate a sample SigningCommitments. pub fn signing_commitments() -> SigningCommitments { - let serialized_element1 = ::Group::serialize(&element1()); - let serialized_element2 = ::Group::serialize(&element2()); + let serialized_element1 = ::Group::serialize(&element1()).unwrap(); + let serialized_element2 = ::Group::serialize(&element2()).unwrap(); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); @@ -72,7 +72,7 @@ pub fn signature_share() -> SignatureShare { pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -84,10 +84,10 @@ pub fn secret_share() -> SecretShare { pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) @@ -96,9 +96,9 @@ pub fn key_package() -> KeyPackage { /// Generate a sample PublicKeyPackage. pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); @@ -108,7 +108,7 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let serialized_signature = serialized_element .as_ref() .iter() diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index a73a1de1f..74a6f10dd 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -98,8 +98,11 @@ impl Group for Ed448Group { Self::Element::generator() } - fn serialize(element: &Self::Element) -> Self::Serialization { - element.compress().0 + fn serialize(element: &Self::Element) -> Result { + if *element == Self::identity() { + return Err(GroupError::InvalidIdentityElement); + } + Ok(element.compress().0) } fn deserialize(buf: &Self::Serialization) -> Result { diff --git a/frost-ed448/tests/helpers/samples.rs b/frost-ed448/tests/helpers/samples.rs index 435196f44..10d077f2f 100644 --- a/frost-ed448/tests/helpers/samples.rs +++ b/frost-ed448/tests/helpers/samples.rs @@ -44,8 +44,8 @@ pub fn signing_nonces() -> SigningNonces { /// Generate a sample SigningCommitments. pub fn signing_commitments() -> SigningCommitments { - let serialized_element1 = ::Group::serialize(&element1()); - let serialized_element2 = ::Group::serialize(&element2()); + let serialized_element1 = ::Group::serialize(&element1()).unwrap(); + let serialized_element2 = ::Group::serialize(&element2()).unwrap(); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); @@ -72,7 +72,7 @@ pub fn signature_share() -> SignatureShare { pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -84,10 +84,10 @@ pub fn secret_share() -> SecretShare { pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) @@ -96,9 +96,9 @@ pub fn key_package() -> KeyPackage { /// Generate a sample PublicKeyPackage. pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); @@ -108,7 +108,7 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let serialized_signature = serialized_element .as_ref() .iter() diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 90f616d8e..cfbf880dd 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -112,21 +112,15 @@ impl Group for P256Group { ProjectivePoint::GENERATOR } - fn serialize(element: &Self::Element) -> Self::Serialization { + fn serialize(element: &Self::Element) -> Result { + if *element == Self::identity() { + return Err(GroupError::InvalidIdentityElement); + } let mut fixed_serialized = [0; 33]; let serialized_point = element.to_encoded_point(true); let serialized = serialized_point.as_bytes(); - // Sanity check; either it takes all bytes or a single byte (identity). - assert!(serialized.len() == fixed_serialized.len() || serialized.len() == 1); - // Copy to the left of the buffer (i.e. pad the identity with zeroes). - // Note that identity elements shouldn't be serialized in FROST, but we - // do this padding so that this function doesn't have to return an error. - // If this encodes the identity, it will fail when deserializing. - { - let (left, _right) = fixed_serialized.split_at_mut(serialized.len()); - left.copy_from_slice(serialized); - } - fixed_serialized + fixed_serialized.copy_from_slice(serialized); + Ok(fixed_serialized) } fn deserialize(buf: &Self::Serialization) -> Result { diff --git a/frost-p256/src/tests/deserialize.rs b/frost-p256/src/tests/deserialize.rs index 38aa9e65b..ab7d8bf2f 100644 --- a/frost-p256/src/tests/deserialize.rs +++ b/frost-p256/src/tests/deserialize.rs @@ -4,7 +4,8 @@ use crate::*; fn check_deserialize_non_canonical() { let mut encoded_generator = ::Group::serialize( &::Group::generator(), - ); + ) + .unwrap(); let r = ::Group::deserialize(&encoded_generator); assert!(r.is_ok()); diff --git a/frost-p256/tests/helpers/samples.rs b/frost-p256/tests/helpers/samples.rs index 0158a8bc4..8e5cc4843 100644 --- a/frost-p256/tests/helpers/samples.rs +++ b/frost-p256/tests/helpers/samples.rs @@ -44,8 +44,8 @@ pub fn signing_nonces() -> SigningNonces { /// Generate a sample SigningCommitments. pub fn signing_commitments() -> SigningCommitments { - let serialized_element1 = ::Group::serialize(&element1()); - let serialized_element2 = ::Group::serialize(&element2()); + let serialized_element1 = ::Group::serialize(&element1()).unwrap(); + let serialized_element2 = ::Group::serialize(&element2()).unwrap(); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); @@ -72,7 +72,7 @@ pub fn signature_share() -> SignatureShare { pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -84,10 +84,10 @@ pub fn secret_share() -> SecretShare { pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) @@ -96,9 +96,9 @@ pub fn key_package() -> KeyPackage { /// Generate a sample PublicKeyPackage. pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); @@ -108,7 +108,7 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let serialized_signature = serialized_element .as_ref() .iter() diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 97e87590d..72d4f7de9 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -326,7 +326,9 @@ where .field("randomizer", &self.randomizer) .field( "randomizer_element", - &hex::encode(::serialize(&self.randomizer_element).as_ref()), + &::serialize(&self.randomizer_element) + .map(hex::encode) + .unwrap_or("".to_string()), ) .field("randomized_verifying_key", &self.randomized_verifying_key) .finish() diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index eabb403da..d402c917e 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -96,8 +96,11 @@ impl Group for RistrettoGroup { RISTRETTO_BASEPOINT_POINT } - fn serialize(element: &Self::Element) -> Self::Serialization { - element.compress().to_bytes() + fn serialize(element: &Self::Element) -> Result { + if *element == Self::identity() { + return Err(GroupError::InvalidIdentityElement); + } + Ok(element.compress().to_bytes()) } fn deserialize(buf: &Self::Serialization) -> Result { diff --git a/frost-ristretto255/tests/helpers/samples.rs b/frost-ristretto255/tests/helpers/samples.rs index ee1bfbc4f..976511267 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -44,8 +44,8 @@ pub fn signing_nonces() -> SigningNonces { /// Generate a sample SigningCommitments. pub fn signing_commitments() -> SigningCommitments { - let serialized_element1 = ::Group::serialize(&element1()); - let serialized_element2 = ::Group::serialize(&element2()); + let serialized_element1 = ::Group::serialize(&element1()).unwrap(); + let serialized_element2 = ::Group::serialize(&element2()).unwrap(); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); @@ -72,7 +72,7 @@ pub fn signature_share() -> SignatureShare { pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -84,10 +84,10 @@ pub fn secret_share() -> SecretShare { pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) @@ -96,9 +96,9 @@ pub fn key_package() -> KeyPackage { /// Generate a sample PublicKeyPackage. pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); @@ -108,7 +108,7 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let serialized_signature = serialized_element .as_ref() .iter() diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 25501bf78..3ddd5ecf1 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -112,21 +112,15 @@ impl Group for Secp256K1Group { ProjectivePoint::GENERATOR } - fn serialize(element: &Self::Element) -> Self::Serialization { + fn serialize(element: &Self::Element) -> Result { + if *element == Self::identity() { + return Err(GroupError::InvalidIdentityElement); + } let mut fixed_serialized = [0; 33]; let serialized_point = element.to_affine().to_encoded_point(true); let serialized = serialized_point.as_bytes(); - // Sanity check; either it takes all bytes or a single byte (identity). - assert!(serialized.len() == fixed_serialized.len() || serialized.len() == 1); - // Copy to the left of the buffer (i.e. pad the identity with zeroes). - // Note that identity elements shouldn't be serialized in FROST, but we - // do this padding so that this function doesn't have to return an error. - // If this encodes the identity, it will fail when deserializing. - { - let (left, _right) = fixed_serialized.split_at_mut(serialized.len()); - left.copy_from_slice(serialized); - } - fixed_serialized + fixed_serialized.copy_from_slice(serialized); + Ok(fixed_serialized) } fn deserialize(buf: &Self::Serialization) -> Result { diff --git a/frost-secp256k1/src/tests/deserialize.rs b/frost-secp256k1/src/tests/deserialize.rs index bce820014..a744832be 100644 --- a/frost-secp256k1/src/tests/deserialize.rs +++ b/frost-secp256k1/src/tests/deserialize.rs @@ -4,7 +4,8 @@ use crate::*; fn check_deserialize_non_canonical() { let mut encoded_generator = ::Group::serialize( &::Group::generator(), - ); + ) + .unwrap(); let r = ::Group::deserialize(&encoded_generator); assert!(r.is_ok()); diff --git a/frost-secp256k1/tests/helpers/samples.rs b/frost-secp256k1/tests/helpers/samples.rs index e8446fe7c..ff22aec8e 100644 --- a/frost-secp256k1/tests/helpers/samples.rs +++ b/frost-secp256k1/tests/helpers/samples.rs @@ -44,8 +44,8 @@ pub fn signing_nonces() -> SigningNonces { /// Generate a sample SigningCommitments. pub fn signing_commitments() -> SigningCommitments { - let serialized_element1 = ::Group::serialize(&element1()); - let serialized_element2 = ::Group::serialize(&element2()); + let serialized_element1 = ::Group::serialize(&element1()).unwrap(); + let serialized_element2 = ::Group::serialize(&element2()).unwrap(); let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); @@ -72,7 +72,7 @@ pub fn signature_share() -> SignatureShare { pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -84,10 +84,10 @@ pub fn secret_share() -> SecretShare { pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) @@ -96,9 +96,9 @@ pub fn key_package() -> KeyPackage { /// Generate a sample PublicKeyPackage. pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); @@ -108,7 +108,7 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let serialized_element = ::Group::serialize(&element1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); let serialized_signature = serialized_element .as_ref() .iter() From 29fd1bf2136646573b22ebe17456fc91b60b0226 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:31:27 +0000 Subject: [PATCH 022/175] Bump actions/checkout from 4.1.1 to 4.1.7 (#676) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.7) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 9d9102361..40126656c 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -23,7 +23,7 @@ jobs: RUST_BACKTRACE: full steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 with: persist-credentials: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5dd4c6cbe..bed5b0428 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the source code - uses: actions/checkout@v4.1.6 + uses: actions/checkout@v4.1.7 with: persist-credentials: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c6d7c520f..ab480ab60 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - uses: actions-rs/toolchain@v1.0.7 with: toolchain: beta @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.7 # Re-resolve Cargo.lock with minimal versions - uses: dtolnay/rust-toolchain@nightly - run: cargo update -Z minimal-versions @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.4 + - uses: actions/checkout@v4.1.7 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-all-features - run: cargo build-all-features @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - uses: actions-rs/toolchain@v1.0.7 with: toolchain: beta @@ -68,7 +68,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 with: persist-credentials: false @@ -103,7 +103,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 with: persist-credentials: false @@ -125,7 +125,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 with: persist-credentials: false @@ -147,7 +147,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 with: persist-credentials: false @@ -166,7 +166,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4.1.6 + - uses: actions/checkout@v4.1.7 - uses: reviewdog/action-actionlint@v1.48.0 with: level: warning From 84b83f56f5ef8b236e85a500b140b7ed77b42bd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:31:30 +0000 Subject: [PATCH 023/175] Bump codecov/codecov-action from 4.4.0 to 4.5.0 (#678) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4.4.0...v4.5.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 40126656c..39e503019 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v4.4.0 + uses: codecov/codecov-action@v4.5.0 From 4e5e33886a6259ffe9a77815a7c430f42c8b12bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 18:48:03 +0000 Subject: [PATCH 024/175] Update curve25519-dalek requirement from =4.1.2 to =4.1.3 (#683) --- updated-dependencies: - dependency-name: curve25519-dalek dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frost-ed25519/Cargo.toml | 2 +- frost-ristretto255/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 378a6769c..41276cb75 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -23,7 +23,7 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -curve25519-dalek = { version = "=4.1.2", features = ["rand_core"] } +curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features = "0.2.7" frost-core = { path = "../frost-core", version = "1.0.0" } frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0" } diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 990f875ad..05acd6ebc 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -19,7 +19,7 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -curve25519-dalek = { version = "=4.1.2", features = ["serde", "rand_core"] } +curve25519-dalek = { version = "=4.1.3", features = ["serde", "rand_core"] } document-features = "0.2.7" frost-core = { path = "../frost-core", version = "1.0.0" } frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0" } From 6f917babafaa83fb3673d2521f7ff6f26c08c643 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 20:04:17 +0000 Subject: [PATCH 025/175] Bump reviewdog/action-actionlint from 1.48.0 to 1.50.0 (#681) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.48.0 to 1.50.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.48.0...v1.50.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ab480ab60..e6e082c07 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -167,7 +167,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.7 - - uses: reviewdog/action-actionlint@v1.48.0 + - uses: reviewdog/action-actionlint@v1.50.0 with: level: warning fail_on_error: false From 647da3515c2f076295885cc715c034da4e262fc1 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 19 Jun 2024 17:04:19 -0300 Subject: [PATCH 026/175] core: remove MulAssign impl for Scalar (#626) --- frost-core/CHANGELOG.md | 4 ++++ frost-core/src/identifier.rs | 9 --------- frost-core/src/keys.rs | 2 +- frost-core/src/lib.rs | 8 ++++---- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 7ddbe73e9..081960126 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -20,6 +20,10 @@ Entries are listed in reverse chronological order. * Removed `batch::Item::into()` which created a batch Item from a triple of VerifyingKey, Signature and message. Use the new `batch::Item::new()` instead (which can return an error). +* Removed the `MulAssign> for Scalar` implementation since it + will result in a coherence error in future Rust versions (see #625). In the + unlikely case you're using this, you can replace e.g. `scalar *= identifier` + with `scalar = identifier * scalar`. ## 1.0.1 diff --git a/frost-core/src/identifier.rs b/frost-core/src/identifier.rs index 5525197bf..edb64cff3 100644 --- a/frost-core/src/identifier.rs +++ b/frost-core/src/identifier.rs @@ -149,15 +149,6 @@ where } } -impl std::ops::MulAssign> for Scalar -where - C: Ciphersuite, -{ - fn mul_assign(&mut self, identifier: Identifier) { - *self = *self * identifier.0 - } -} - impl std::ops::Sub for Identifier where C: Ciphersuite, diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 75ffc8fd0..38adfe866 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -582,7 +582,7 @@ fn evaluate_polynomial( let ell_scalar = identifier; for coeff in coefficients.iter().skip(1).rev() { value = value + *coeff; - value *= ell_scalar; + value = ell_scalar * value; } value = value + *coefficients diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 96c5cba69..3f6655bc5 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -306,12 +306,12 @@ fn compute_lagrange_coefficient( } if let Some(x) = x { - num *= x - *x_j; - den *= x_i - *x_j; + num = (x - *x_j) * num; + den = (x_i - *x_j) * den; } else { // Both signs inverted just to avoid requiring Neg (-*xj) - num *= *x_j; - den *= *x_j - x_i; + num = *x_j * num; + den = (*x_j - x_i) * den; } } if !x_i_found { From 57c0b7b6b68c31ebc18dd9c903124511cc85ac35 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 20 Jun 2024 06:06:58 -0300 Subject: [PATCH 027/175] add no-std support (#621) * add no-std support * add no-std support to ciphersuite crates * fix ci * update changelog, use 'dep:' where appropriate * additional fixes * fix clippy issue --- .github/workflows/main.yml | 16 ++++++++++++++ frost-core/CHANGELOG.md | 10 +++++++-- frost-core/Cargo.toml | 21 ++++++++++-------- frost-core/src/batch.rs | 8 +++---- frost-core/src/benches.rs | 8 ++++--- frost-core/src/error.rs | 4 ++++ frost-core/src/identifier.rs | 10 ++++----- frost-core/src/keys.rs | 17 +++++++------- frost-core/src/keys/dkg.rs | 22 ++++++++++++------- frost-core/src/keys/repairable.rs | 4 +++- frost-core/src/lib.rs | 19 ++++++++++------ frost-core/src/round1.rs | 8 ++++--- frost-core/src/round2.rs | 2 +- frost-core/src/scalar_mul.rs | 6 +++-- frost-core/src/serialization.rs | 9 +++++--- frost-core/src/signature.rs | 8 ++++--- frost-core/src/signing_key.rs | 4 ++-- frost-core/src/tests/ciphersuite_generic.rs | 6 +++-- .../src/tests/coefficient_commitment.rs | 2 -- frost-core/src/tests/repairable.rs | 2 +- frost-core/src/tests/vectors.rs | 2 +- frost-core/src/tests/vectors_dkg.rs | 2 +- frost-core/src/tests/vss_commitment.rs | 2 -- frost-core/src/traits.rs | 3 ++- frost-core/src/verifying_key.rs | 7 +++++- frost-ed25519/Cargo.toml | 16 ++++++++------ frost-ed25519/src/keys/repairable.rs | 2 +- frost-ed25519/src/lib.rs | 11 ++++++---- frost-ed448/Cargo.toml | 16 ++++++++------ frost-ed448/src/keys/repairable.rs | 2 +- frost-ed448/src/lib.rs | 6 ++++- frost-p256/Cargo.toml | 18 ++++++++------- frost-p256/src/keys/repairable.rs | 2 +- frost-p256/src/lib.rs | 12 ++++++---- frost-rerandomized/Cargo.toml | 15 +++++++++---- frost-rerandomized/src/lib.rs | 17 ++++++++++---- frost-rerandomized/src/tests.rs | 4 +++- frost-ristretto255/Cargo.toml | 20 +++++++++-------- frost-ristretto255/src/keys/repairable.rs | 2 +- frost-ristretto255/src/lib.rs | 10 ++++++--- frost-secp256k1/Cargo.toml | 18 ++++++++------- frost-secp256k1/src/keys/repairable.rs | 2 +- frost-secp256k1/src/lib.rs | 11 +++++++--- 43 files changed, 246 insertions(+), 140 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e6e082c07..05a9ffd43 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,6 +48,22 @@ jobs: - run: cargo install cargo-all-features - run: cargo build-all-features + build_no_std: + name: build with no_std + runs-on: ubuntu-latest + # Skip ed448 which does not support it. + strategy: + matrix: + crate: [ristretto255, ed25519, p256, secp256k1, rerandomized] + steps: + - uses: actions/checkout@v4.1.1 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + targets: thumbv6m-none-eabi + - run: cargo build -p frost-${{ matrix.crate }} --no-default-features --target thumbv6m-none-eabi + - run: cargo build -p frost-${{ matrix.crate }} --no-default-features --features serialization --target thumbv6m-none-eabi + test_beta: name: test on beta runs-on: ubuntu-latest diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 081960126..97b0927c5 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -24,6 +24,14 @@ Entries are listed in reverse chronological order. will result in a coherence error in future Rust versions (see #625). In the unlikely case you're using this, you can replace e.g. `scalar *= identifier` with `scalar = identifier * scalar`. +* Add no-std support to all crates except frost-ed448. To use, do not enable the + `std` feature that is enabled by default (i.e. use `default-features = + false`); Note that it always links to an external `alloc` crate (i.e. there is + no `alloc` feature). When disabling `std`, the only impact in the API is that + `Error` will no longer implement the `std::error::Error` trait. This is a + breaking change if you are disabling default features but rely on `Error` + implementing `std::error::Error`. In that case, simply enable the `std` + feature. ## 1.0.1 @@ -31,8 +39,6 @@ Entries are listed in reverse chronological order. * Fixed some feature handling that would include unneeded dependencies in some cases. -## Released - ## 1.0.0 * Exposed the `SigningKey::from_scalar()` and `to_scalar()` methods. This diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 2a029825e..511251401 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -22,20 +22,21 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -byteorder = "1.4" -const-crc32 = "1.2.0" +byteorder = { version = "1.4", default-features = false } +const-crc32 = { version = "1.2.0", package = "const-crc32-nostd" } document-features = "0.2.7" debugless-unwrap = "0.0.4" derive-getters = "0.4.0" -hex = "0.4.3" -postcard = { version = "1.0.0", features = ["use-std"], optional = true } -rand_core = "0.6" -serde = { version = "1.0.160", features = ["derive"], optional = true } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +postcard = { version = "1.0.0", features = ["alloc"], optional = true } +rand_core = { version = "0.6", default-features = false } +serde = { version = "1.0.160", default-features = false, features = ["derive"], optional = true } serdect = { version = "0.2.0", optional = true } -thiserror = "1.0.29" +thiserror-nostd-notrait = { version = "1.0.29", default-features = false } +thiserror = { version = "1.0.29", default-features = false, optional = true } visibility = "0.1.0" zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } -itertools = "0.13.0" +itertools = { version = "0.13.0", default-features = false } # Test dependencies used with the test-impl feature proptest = { version = "1.0", optional = true } @@ -50,8 +51,10 @@ rand_chacha = "0.3" serde_json = "1.0" [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] #! ## Features +## Enable standard library support. +std = ["dep:thiserror"] ## Expose internal types, which do not have SemVer guarantees. This is an advanced ## feature which can be useful if you need to build a modified version of FROST. ## The docs won't list them, you will need to check the source code. diff --git a/frost-core/src/batch.rs b/frost-core/src/batch.rs index 9aa819991..3aa1e8065 100644 --- a/frost-core/src/batch.rs +++ b/frost-core/src/batch.rs @@ -7,8 +7,6 @@ //! of caller code (which must assemble a batch of signatures across //! work-items), and loss of the ability to easily pinpoint failing signatures. -use std::iter::once; - use rand_core::{CryptoRng, RngCore}; use crate::{scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, *}; @@ -136,7 +134,7 @@ where VKs.push(item.vk.to_element()); } - let scalars = once(&P_coeff_acc) + let scalars = core::iter::once(&P_coeff_acc) .chain(VK_coeffs.iter()) .chain(R_coeffs.iter()); @@ -159,6 +157,8 @@ where C: Ciphersuite, { fn default() -> Self { - Self { signatures: vec![] } + Self { + signatures: Vec::new(), + } } } diff --git a/frost-core/src/benches.rs b/frost-core/src/benches.rs index 16810cc48..f89ce4af0 100644 --- a/frost-core/src/benches.rs +++ b/frost-core/src/benches.rs @@ -1,11 +1,13 @@ //! Ciphersuite-generic benchmark functions. #![allow(clippy::unwrap_used)] -use std::collections::BTreeMap; +use core::iter; -use criterion::{BenchmarkId, Criterion, Throughput}; +use alloc::{collections::BTreeMap, format, vec::Vec}; use rand_core::{CryptoRng, RngCore}; +use criterion::{BenchmarkId, Criterion, Throughput}; + use crate as frost; use crate::{batch, Ciphersuite, Signature, SigningKey, VerifyingKey}; @@ -18,7 +20,7 @@ fn sigs_with_distinct_keys( rng: &mut R, ) -> impl Iterator> { let mut rng = rng.clone(); - std::iter::repeat_with(move || { + iter::repeat_with(move || { let msg = b"Bench"; let sk = SigningKey::new(&mut rng); let vk = VerifyingKey::from(&sk); diff --git a/frost-core/src/error.rs b/frost-core/src/error.rs index 3a768badf..6fdd36069 100644 --- a/frost-core/src/error.rs +++ b/frost-core/src/error.rs @@ -1,7 +1,11 @@ //! FROST Error types +#[cfg(feature = "std")] use thiserror::Error; +#[cfg(not(feature = "std"))] +use thiserror_nostd_notrait::Error; + use crate::{Ciphersuite, Identifier}; #[derive(Error, Debug, Clone, Copy, Eq, PartialEq)] diff --git a/frost-core/src/identifier.rs b/frost-core/src/identifier.rs index edb64cff3..956d1692b 100644 --- a/frost-core/src/identifier.rs +++ b/frost-core/src/identifier.rs @@ -1,6 +1,6 @@ //! FROST participant identifiers -use std::{ +use core::{ fmt::{self, Debug}, hash::{Hash, Hasher}, }; @@ -117,7 +117,7 @@ impl Ord for Identifier where C: Ciphersuite, { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { let serialized_self = <::Field>::little_endian_serialize(&self.0); let serialized_other = <::Field>::little_endian_serialize(&other.0); // The default cmp uses lexicographic order; so we need the elements in big endian @@ -133,12 +133,12 @@ impl PartialOrd for Identifier where C: Ciphersuite, { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl std::ops::Mul> for Identifier +impl core::ops::Mul> for Identifier where C: Ciphersuite, { @@ -149,7 +149,7 @@ where } } -impl std::ops::Sub for Identifier +impl core::ops::Sub for Identifier where C: Ciphersuite, { diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 38adfe866..99e017d64 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -1,12 +1,13 @@ //! FROST keys, keygen, key shares #![allow(clippy::type_complexity)] -use std::{ - collections::{BTreeMap, BTreeSet, HashSet}, - convert::TryFrom, - default::Default, +use core::iter; + +use alloc::{ + collections::{BTreeMap, BTreeSet}, fmt::{self, Debug}, - iter, + string::ToString, + vec::Vec, }; use derive_getters::Getters; @@ -128,7 +129,7 @@ impl Debug for SigningShare where C: Ciphersuite, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result { f.debug_tuple("SigningShare").field(&"").finish() } } @@ -310,7 +311,7 @@ impl Debug for CoefficientCommitment where C: Ciphersuite, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::fmt::Result { f.debug_tuple("CoefficientCommitment") .field( &self @@ -874,7 +875,7 @@ pub(crate) fn generate_secret_shares( let (coefficients, commitment) = generate_secret_polynomial(secret, max_signers, min_signers, coefficients)?; - let identifiers_set: HashSet<_> = identifiers.iter().collect(); + let identifiers_set: BTreeSet<_> = identifiers.iter().collect(); if identifiers_set.len() != identifiers.len() { return Err(Error::DuplicatedIdentifier); } diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index dd09bda57..483da98d8 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -30,7 +30,9 @@ //! [Feldman's VSS]: https://www.cs.umd.edu/~gasarch/TOPICS/secretsharing/feldmanVSS.pdf //! [secure broadcast channel]: https://frost.zfnd.org/terminology.html#broadcast-channel -use std::{collections::BTreeMap, iter}; +use core::iter; + +use alloc::collections::BTreeMap; use rand_core::{CryptoRng, RngCore}; @@ -39,6 +41,9 @@ use crate::{ SigningKey, VerifyingKey, }; +#[cfg(feature = "serialization")] +use crate::serialization::{Deserialize, Serialize}; + use super::{ evaluate_polynomial, generate_coefficients, generate_secret_polynomial, validate_num_of_signers, KeyPackage, PublicKeyPackage, SecretShare, SigningShare, @@ -47,14 +52,15 @@ use super::{ /// DKG Round 1 structures. pub mod round1 { + use alloc::vec::Vec; use derive_getters::Getters; use zeroize::Zeroize; + use super::*; + #[cfg(feature = "serialization")] use crate::serialization::{Deserialize, Serialize}; - use super::*; - /// The package that must be broadcast by each participant to all other participants /// between the first and second parts of the DKG protocol (round 1). #[derive(Clone, Debug, PartialEq, Eq, Getters)] @@ -136,11 +142,11 @@ pub mod round1 { } } - impl std::fmt::Debug for SecretPackage + impl core::fmt::Debug for SecretPackage where C: Ciphersuite, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("SecretPackage") .field("identifier", &self.identifier) .field("coefficients", &"") @@ -169,7 +175,7 @@ pub mod round2 { use zeroize::Zeroize; #[cfg(feature = "serialization")] - use crate::serialization::{Deserialize, Serialize}; + use alloc::vec::Vec; use super::*; @@ -241,11 +247,11 @@ pub mod round2 { pub(crate) max_signers: u16, } - impl std::fmt::Debug for SecretPackage + impl core::fmt::Debug for SecretPackage where C: Ciphersuite, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("SecretPackage") .field("identifier", &self.identifier) .field("commitment", &self.commitment) diff --git a/frost-core/src/keys/repairable.rs b/frost-core/src/keys/repairable.rs index 169e0ab83..5297d8b74 100644 --- a/frost-core/src/keys/repairable.rs +++ b/frost-core/src/keys/repairable.rs @@ -4,7 +4,9 @@ //! The RTS is used to help a signer (participant) repair their lost share. This is achieved //! using a subset of the other signers know here as `helpers`. -use std::collections::{BTreeMap, BTreeSet}; +use alloc::collections::{BTreeMap, BTreeSet}; + +use alloc::vec::Vec; use crate::{ compute_lagrange_coefficient, Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier, diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 3f6655bc5..63d06ad33 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] // It's emitting false positives; see https://github.com/rust-lang/rust-clippy/issues/9413 #![allow(clippy::derive_partial_eq_without_eq)] @@ -10,11 +11,15 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] -use std::{ +#[macro_use] +extern crate alloc; + +use core::marker::PhantomData; + +use alloc::{ collections::{BTreeMap, BTreeSet}, - default::Default, fmt::{self, Debug}, - marker::PhantomData, + vec::Vec, }; use derive_getters::Getters; @@ -87,7 +92,7 @@ impl Debug for Challenge where C: Ciphersuite, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_tuple("Secret") .field(&hex::encode(<::Field>::serialize( &self.0, @@ -115,7 +120,7 @@ fn challenge( where C: Ciphersuite, { - let mut preimage = vec![]; + let mut preimage = Vec::new(); preimage.extend_from_slice(::serialize(R)?.as_ref()); preimage.extend_from_slice(::serialize(&verifying_key.to_element())?.as_ref()); @@ -409,7 +414,7 @@ where verifying_key: &VerifyingKey, additional_prefix: &[u8], ) -> Result, Vec)>, Error> { - let mut binding_factor_input_prefix = vec![]; + let mut binding_factor_input_prefix = Vec::new(); // The length of a serialized verifying key of the same cipersuite does // not change between runs of the protocol, so we don't need to hash to @@ -429,7 +434,7 @@ where .signing_commitments() .keys() .map(|identifier| { - let mut binding_factor_input = vec![]; + let mut binding_factor_input = Vec::new(); binding_factor_input.extend_from_slice(&binding_factor_input_prefix); binding_factor_input.extend_from_slice(identifier.serialize().as_ref()); diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index bfc11c1bf..08414133e 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -1,8 +1,10 @@ //! FROST Round 1 functionality and types -use std::{ +use alloc::{ collections::BTreeMap, fmt::{self, Debug}, + string::ToString, + vec::Vec, }; use derive_getters::Getters; @@ -286,7 +288,7 @@ impl Debug for SigningNonces where C: Ciphersuite, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SigningNonces") .field("hiding", &"") .field("binding", &"") @@ -344,7 +346,7 @@ where /// Computes the [signature commitment share] from these round one signing commitments. /// /// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio - #[cfg(any(feature = "cheater-detection", feature = "internals"))] + #[cfg(any(feature = "internals", feature = "cheater-detection"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(super) fn to_group_commitment_share( diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index f71bddeff..25e03a85c 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -1,6 +1,6 @@ //! FROST Round 2 functionality and types, for signature share generation -use std::fmt::{self, Debug}; +use core::fmt::{self, Debug}; use crate as frost; use crate::{ diff --git a/frost-core/src/scalar_mul.rs b/frost-core/src/scalar_mul.rs index ea441ab1f..7e6fb3fa3 100644 --- a/frost-core/src/scalar_mul.rs +++ b/frost-core/src/scalar_mul.rs @@ -4,12 +4,14 @@ // constraints. #![allow(clippy::indexing_slicing)] -use std::{ +use core::{ borrow::Borrow, fmt::{Debug, Result}, marker::PhantomData, }; +use alloc::vec::Vec; + use crate::{Ciphersuite, Element, Field, Group, Scalar}; /// Calculates the quotient of `self` and `rhs`, rounding the result towards positive infinity. @@ -243,7 +245,7 @@ impl LookupTable5 { } impl Debug for LookupTable5 { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result { + fn fmt(&self, f: &mut core::fmt::Formatter) -> Result { write!(f, "LookupTable5({:?})", self.bytes) } } diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index eda0bdec7..59ae6f6a6 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -1,5 +1,8 @@ //! Serialization support. +#[cfg(feature = "serialization")] +use alloc::vec::Vec; + use crate::Ciphersuite; #[cfg(feature = "serde")] @@ -134,7 +137,7 @@ where C: Ciphersuite, { if deserializer.is_human_readable() { - let s: String = serde::de::Deserialize::deserialize(deserializer)?; + let s: alloc::string::String = serde::de::Deserialize::deserialize(deserializer)?; if s != C::ID { Err(serde::de::Error::custom("wrong ciphersuite")) } else { @@ -186,13 +189,13 @@ pub(crate) trait Deserialize { /// Deserialize the struct from a slice of bytes. fn deserialize(bytes: &[u8]) -> Result> where - Self: std::marker::Sized; + Self: core::marker::Sized; } #[cfg(feature = "serialization")] impl Serialize for T { fn serialize(&self) -> Result, Error> { - postcard::to_stdvec(self).map_err(|_| Error::SerializationError) + postcard::to_allocvec(self).map_err(|_| Error::SerializationError) } } diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index a55b0db20..3da178ce6 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -1,5 +1,7 @@ //! Schnorr signatures over prime order groups (or subgroups) +use alloc::{string::ToString, vec::Vec}; + use debugless_unwrap::DebuglessUnwrap; use crate::{Ciphersuite, Element, Error, Field, Group, Scalar}; @@ -72,7 +74,7 @@ where /// Converts this signature to its [`Ciphersuite::SignatureSerialization`] in bytes. pub fn serialize(&self) -> Result> { - let mut bytes = vec![]; + let mut bytes = Vec::::new(); bytes.extend(::serialize(&self.R)?.as_ref()); bytes.extend(<::Field>::serialize(&self.z).as_ref()); @@ -123,8 +125,8 @@ where } } -impl std::fmt::Debug for Signature { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl core::fmt::Debug for Signature { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { f.debug_struct("Signature") .field( "R", diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index 03e13156d..a92e7fcf3 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -69,11 +69,11 @@ where } } -impl std::fmt::Debug for SigningKey +impl core::fmt::Debug for SigningKey where C: Ciphersuite, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_tuple("SigningKey").field(&"").finish() } } diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 5fdba722c..76ecf240f 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -1,12 +1,14 @@ //! Ciphersuite-generic test functions. #![allow(clippy::type_complexity)] -use std::{collections::BTreeMap, convert::TryFrom}; +use alloc::collections::BTreeMap; use crate as frost; use crate::{ keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, VerifyingKey, }; +use alloc::borrow::ToOwned; +use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; use crate::Ciphersuite; @@ -366,7 +368,7 @@ pub fn check_sign_with_dkg( mut rng: R, ) -> (Vec, Signature, VerifyingKey) where - C::Group: std::cmp::PartialEq, + C::Group: core::cmp::PartialEq, { //////////////////////////////////////////////////////////////////////////// // Key generation, Round 1 diff --git a/frost-core/src/tests/coefficient_commitment.rs b/frost-core/src/tests/coefficient_commitment.rs index 6218d1671..094b47a71 100644 --- a/frost-core/src/tests/coefficient_commitment.rs +++ b/frost-core/src/tests/coefficient_commitment.rs @@ -1,7 +1,5 @@ //! CoefficientCommitment functions -use std::convert::TryFrom; - use crate as frost; use crate::{keys::CoefficientCommitment, tests::helpers::generate_element, Group}; use debugless_unwrap::DebuglessUnwrap; diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index 8c187a859..ed4f28755 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -1,6 +1,6 @@ //! Test for Repairable Threshold Scheme -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; use debugless_unwrap::DebuglessUnwrap; use rand_core::{CryptoRng, RngCore}; diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index 837e8929d..144827a44 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -1,5 +1,5 @@ //! Helper function for testing with test vectors. -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; use debugless_unwrap::DebuglessUnwrap; use hex::{self, FromHex}; diff --git a/frost-core/src/tests/vectors_dkg.rs b/frost-core/src/tests/vectors_dkg.rs index 1b06a21a1..6619c8b56 100644 --- a/frost-core/src/tests/vectors_dkg.rs +++ b/frost-core/src/tests/vectors_dkg.rs @@ -1,5 +1,5 @@ //! Helper function for testing with test vectors. -use std::collections::BTreeMap; +use alloc::{collections::BTreeMap, string::ToString}; use debugless_unwrap::DebuglessUnwrap; use hex::{self}; diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 31b5d4cbb..91a341f0e 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -1,7 +1,5 @@ //! VerifiableSecretSharingCommitment functions -use std::convert::TryFrom; - use crate::{ keys::{CoefficientCommitment, VerifiableSecretSharingCommitment}, tests::helpers::generate_element, diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 4221d96bf..b96f27b0b 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -1,10 +1,11 @@ //! Traits used to abstract Ciphersuites. -use std::{ +use core::{ fmt::Debug, ops::{Add, Mul, Sub}, }; +use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; use crate::{Error, FieldError, GroupError, Signature, VerifyingKey}; diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index 2effd05e3..8ea63025a 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -1,4 +1,9 @@ -use std::fmt::{self, Debug}; +use core::fmt::{self, Debug}; + +use alloc::string::ToString; + +#[cfg(any(test, feature = "test-impl"))] +use alloc::vec::Vec; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 41276cb75..c90007bb7 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -25,10 +25,10 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "1.0.0" } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0" } +frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } rand_core = "0.6" -sha2 = "0.10.2" +sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" @@ -36,7 +36,7 @@ frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } ed25519-dalek = "2.0.0" insta = { version = "1.31.0", features = ["yaml"] } -hex = "0.4.3" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" proptest = "1.0" rand = "0.8" @@ -45,16 +45,18 @@ serde_json = "1.0" [features] nightly = [] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] #! ## Features +## Enable standard library support. +std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] +## Enable cheater detection +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-ed25519/src/keys/repairable.rs b/frost-ed25519/src/keys/repairable.rs index deb5a8334..5e875a760 100644 --- a/frost-ed25519/src/keys/repairable.rs +++ b/frost-ed25519/src/keys/repairable.rs @@ -4,7 +4,7 @@ //! The RTS is used to help a signer (participant) repair their lost share. This is achieved //! using a subset of the other signers know here as `helpers`. -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index c7644edbe..355e37a0c 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -5,7 +6,9 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] -use std::collections::BTreeMap; +extern crate alloc; + +use alloc::collections::BTreeMap; use curve25519_dalek::{ constants::ED25519_BASEPOINT_POINT, @@ -23,7 +26,9 @@ use frost_core as frost; mod tests; // Re-exports in our public API -pub use frost_core::{serde, Ciphersuite, Field, FieldError, Group, GroupError}; +#[cfg(feature = "serde")] +pub use frost_core::serde; +pub use frost_core::{Ciphersuite, Field, FieldError, Group, GroupError}; pub use rand_core; /// An error. @@ -230,8 +235,6 @@ pub type Identifier = frost::Identifier; /// FROST(Ed25519, SHA-512) keys, key generation, key shares. pub mod keys { - use std::collections::BTreeMap; - use super::*; /// The identifier list to use when generating key shares. diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index ad8f3cd61..1ee169b36 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -24,10 +24,10 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" ed448-goldilocks = { version = "0.9.0" } -frost-core = { path = "../frost-core", version = "1.0.0" } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0" } +frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } rand_core = "0.6" -sha3 = "0.10.6" +sha3 = { version = "0.10.6", default-features = false } [dev-dependencies] criterion = "0.5" @@ -35,7 +35,7 @@ frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } lazy_static = "1.4" insta = { version = "1.31.0", features = ["yaml"] } -hex = "0.4.3" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } proptest = "1.0" rand = "0.8" rand_chacha = "0.3" @@ -43,16 +43,18 @@ serde_json = "1.0" [features] nightly = [] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] #! ## Features +## Enable standard library support. +std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] +## Enable cheater detection +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-ed448/src/keys/repairable.rs b/frost-ed448/src/keys/repairable.rs index b44709fcb..97771f3c5 100644 --- a/frost-ed448/src/keys/repairable.rs +++ b/frost-ed448/src/keys/repairable.rs @@ -4,7 +4,7 @@ //! The RTS is used to help a signer (participant) repair their lost share. This is achieved //! using a subset of the other signers know here as `helpers`. -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 74a6f10dd..f867c4949 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -5,6 +5,8 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] +extern crate alloc; + use std::collections::BTreeMap; use ed448_goldilocks::{ @@ -24,7 +26,9 @@ use frost_core as frost; mod tests; // Re-exports in our public API -pub use frost_core::{serde, Ciphersuite, Field, FieldError, Group, GroupError}; +#[cfg(feature = "serde")] +pub use frost_core::serde; +pub use frost_core::{Ciphersuite, Field, FieldError, Group, GroupError}; pub use rand_core; /// An error. diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index 65c3e976f..4124d5cdf 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -24,18 +24,18 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" -p256 = { version = "0.13.0", features = ["hash2curve"] } -frost-core = { path = "../frost-core", version = "1.0.0" } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0" } +p256 = { version = "0.13.0", features = ["hash2curve"], default-features = false } +frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } rand_core = "0.6" -sha2 = "0.10.2" +sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] } frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } -hex = "0.4.3" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" proptest = "1.0" rand = "0.8" @@ -44,16 +44,18 @@ serde_json = "1.0" [features] nightly = [] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] #! ## Features +## Enable standard library support. +std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] +## Enable cheater detection +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-p256/src/keys/repairable.rs b/frost-p256/src/keys/repairable.rs index 310a26f75..233757456 100644 --- a/frost-p256/src/keys/repairable.rs +++ b/frost-p256/src/keys/repairable.rs @@ -4,7 +4,7 @@ //! The RTS is used to help a signer (participant) repair their lost share. This is achieved //! using a subset of the other signers know here as `helpers`. -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index cfbf880dd..2c54b10f4 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -5,7 +6,10 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] -use std::collections::BTreeMap; +extern crate alloc; + +use alloc::borrow::ToOwned; +use alloc::collections::BTreeMap; use frost_rerandomized::RandomizedCiphersuite; use p256::{ @@ -25,7 +29,9 @@ use frost_core as frost; mod tests; // Re-exports in our public API -pub use frost_core::{serde, Ciphersuite, Field, FieldError, Group, GroupError}; +#[cfg(feature = "serde")] +pub use frost_core::serde; +pub use frost_core::{Ciphersuite, Field, FieldError, Group, GroupError}; pub use rand_core; /// An error. @@ -246,8 +252,6 @@ type P = P256Sha256; pub type Identifier = frost::Identifier

; /// FROST(P-256, SHA-256) keys, key generation, key shares. pub mod keys { - use std::collections::BTreeMap; - use super::*; /// The identifier list to use when generating key shares. diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index fbccc56b0..9e9109038 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -6,8 +6,11 @@ edition = "2021" # - Update CHANGELOG.md # - Create git tag. version = "1.0.0" -authors = ["Deirdre Connolly ", "Chelsea Komlo ", - "Conrado Gouvea "] +authors = [ + "Deirdre Connolly ", + "Chelsea Komlo ", + "Conrado Gouvea ", +] readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/ZcashFoundation/frost" @@ -22,8 +25,10 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] derive-getters = "0.4.0" document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "1.0.0", features = ["internals"] } -hex = "0.4.3" +frost-core = { path = "../frost-core", version = "1.0.0", features = [ + "internals", +], default-features = false } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } rand_core = "0.6" [dev-dependencies] @@ -32,6 +37,8 @@ rand_core = "0.6" nightly = [] default = ["serialization", "cheater-detection"] #! ## Features +## Enable standard library support. +std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 72d4f7de9..174cf0bdf 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -9,20 +9,25 @@ //! - Each participant should call [`sign`] and send the resulting //! [`frost::round2::SignatureShare`] back to the Coordinator; //! - The Coordinator should then call [`aggregate`]. +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] +extern crate alloc; + #[cfg(any(test, feature = "test-impl"))] pub mod tests; -use std::collections::BTreeMap; +use alloc::{collections::BTreeMap, string::ToString}; use derive_getters::Getters; pub use frost_core; +#[cfg(feature = "serialization")] +use frost_core::SigningPackage; use frost_core::{ self as frost, keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare}, - Ciphersuite, Error, Field, Group, Scalar, SigningPackage, VerifyingKey, + Ciphersuite, Error, Field, Group, Scalar, VerifyingKey, }; #[cfg(feature = "serde")] @@ -32,6 +37,7 @@ use frost_core::serialization::ScalarSerialization; // When pulled into `reddsa`, that has its own sibling `rand_core` import. // For the time being, we do not re-export this `rand_core`. +#[cfg(feature = "serialization")] use rand_core::{CryptoRng, RngCore}; /// Randomize the given key type for usage in a FROST signing with re-randomized keys, @@ -169,6 +175,7 @@ where /// The [`SigningPackage`] must be the signing package being used in the /// current FROST signing run. It is hashed into the randomizer calculation, /// which binds it to that specific package. + #[cfg(feature = "serialization")] pub fn new( mut rng: R, signing_package: &SigningPackage, @@ -179,6 +186,7 @@ where /// Create a final Randomizer from a random Randomizer and a SigningPackage. /// Function refactored out for testing, should always be private. + #[cfg(feature = "serialization")] fn from_randomizer_and_signing_package( rng_randomizer: <<::Group as Group>::Field as Field>::Scalar, signing_package: &SigningPackage, @@ -253,7 +261,7 @@ impl core::fmt::Debug for Randomizer where C: Ciphersuite, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_tuple("Randomizer") .field(&hex::encode( <::Field>::serialize(&self.0).as_ref(), @@ -279,6 +287,7 @@ where { /// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and /// the given `participants`. + #[cfg(feature = "serialization")] pub fn new( group_verifying_key: &VerifyingKey, signing_package: &SigningPackage, @@ -321,7 +330,7 @@ impl core::fmt::Debug for RandomizedParams where C: Ciphersuite, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("RandomizedParams") .field("randomizer", &self.randomizer) .field( diff --git a/frost-rerandomized/src/tests.rs b/frost-rerandomized/src/tests.rs index 15fde19bb..dc923e5c3 100644 --- a/frost-rerandomized/src/tests.rs +++ b/frost-rerandomized/src/tests.rs @@ -1,6 +1,8 @@ //! Ciphersuite-generic test functions for re-randomized FROST. -use std::collections::BTreeMap; +use alloc::borrow::ToOwned; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; use crate::{frost_core as frost, RandomizedCiphersuite, RandomizedParams, Randomizer}; use frost_core::{Field, Group, Signature, SigningPackage, VerifyingKey}; diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 05acd6ebc..95f28af92 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -19,19 +19,19 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -curve25519-dalek = { version = "=4.1.3", features = ["serde", "rand_core"] } +curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "1.0.0" } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0" } +frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } rand_core = "0.6" -sha2 = "0.10.2" +sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] } frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } -hex = "0.4.3" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" postcard = { version = "1.0.0", features = ["use-std"] } proptest = "1.0" @@ -41,16 +41,18 @@ serde_json = "1.0" [features] nightly = [] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] #! ## Features +## Enable standard library support. +std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). -serde = ["frost-core/serde"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] +serde = ["frost-core/serde", "curve25519-dalek/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] +## Enable cheater detection +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-ristretto255/src/keys/repairable.rs b/frost-ristretto255/src/keys/repairable.rs index a303828d0..a935eb8a7 100644 --- a/frost-ristretto255/src/keys/repairable.rs +++ b/frost-ristretto255/src/keys/repairable.rs @@ -4,7 +4,7 @@ //! The RTS is used to help a signer (participant) repair their lost share. This is achieved //! using a subset of the other signers know here as `helpers`. -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index d402c917e..f6a608c43 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -1,8 +1,11 @@ +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] #![deny(missing_docs)] #![doc = include_str!("../README.md")] -use std::collections::BTreeMap; +extern crate alloc; + +use alloc::collections::BTreeMap; use curve25519_dalek::{ constants::RISTRETTO_BASEPOINT_POINT, @@ -20,7 +23,9 @@ use frost_core as frost; mod tests; // Re-exports in our public API -pub use frost_core::{serde, Ciphersuite, Field, FieldError, Group, GroupError}; +#[cfg(feature = "serde")] +pub use frost_core::serde; +pub use frost_core::{Ciphersuite, Field, FieldError, Group, GroupError}; pub use rand_core; /// An error. @@ -217,7 +222,6 @@ pub type Identifier = frost::Identifier; /// FROST(ristretto255, SHA-512) keys, key generation, key shares. pub mod keys { use super::*; - use std::collections::BTreeMap; /// The identifier list to use when generating key shares. pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, R>; diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index e19df4bfe..a7bf2ebf7 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -23,18 +23,18 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "1.0.0" } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0" } -k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"] } +frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } +k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } rand_core = "0.6" -sha2 = "0.10.2" +sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] } frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } -hex = "0.4.3" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" proptest = "1.0" rand = "0.8" @@ -43,16 +43,18 @@ serde_json = "1.0" [features] nightly = [] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] #! ## Features +## Enable standard library support. +std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). serde = ["frost-core/serde"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] +## Enable cheater detection +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-secp256k1/src/keys/repairable.rs b/frost-secp256k1/src/keys/repairable.rs index 01bb964d2..88bce01dc 100644 --- a/frost-secp256k1/src/keys/repairable.rs +++ b/frost-secp256k1/src/keys/repairable.rs @@ -4,7 +4,7 @@ //! The RTS is used to help a signer (participant) repair their lost share. This is achieved //! using a subset of the other signers know here as `helpers`. -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 3ddd5ecf1..1ebeaedb2 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -5,7 +6,10 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] -use std::collections::BTreeMap; +extern crate alloc; + +use alloc::borrow::ToOwned; +use alloc::collections::BTreeMap; use frost_rerandomized::RandomizedCiphersuite; use k256::{ @@ -26,7 +30,9 @@ use frost_core as frost; mod tests; // Re-exports in our public API -pub use frost_core::{serde, Ciphersuite, Field, FieldError, Group, GroupError}; +#[cfg(feature = "serde")] +pub use frost_core::serde; +pub use frost_core::{Ciphersuite, Field, FieldError, Group, GroupError}; pub use rand_core; /// An error. @@ -247,7 +253,6 @@ pub type Identifier = frost::Identifier; /// FROST(secp256k1, SHA-256) keys, key generation, key shares. pub mod keys { use super::*; - use std::collections::BTreeMap; /// The identifier list to use when generating key shares. pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, S>; From 8ce152222bd69267eab0460d857408ec7abfb829 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 20 Jun 2024 10:34:35 -0300 Subject: [PATCH 028/175] build and test each crate separately (#689) * build and test each crate separately * frost-core: fix benchmark compilation --- .github/workflows/main.yml | 11 ++++++++--- frost-core/Cargo.toml | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05a9ffd43..9d14a1cab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,15 +38,20 @@ jobs: - uses: dtolnay/rust-toolchain@1.66.1 - run: cargo build --all-features - build_all_features: - name: build with all features combinations + test_all_features: + name: test all features combinations runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.1.7 - uses: dtolnay/rust-toolchain@stable - run: cargo install cargo-all-features - - run: cargo build-all-features + # We check and then test because some test dependencies could help + # a bugged build work, while a regular build would fail. + - run: cargo check-all-features --release + # Note that this also tests each crate separately, which also helps + # catching some issues. + - run: cargo test-all-features --release build_no_std: name: build with no_std diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 511251401..0f45e3880 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -44,6 +44,7 @@ serde_json = { version = "1.0", optional = true } criterion = { version = "0.5", optional = true } [dev-dependencies] +criterion = { version = "0.5" } lazy_static = "1.4" proptest = "1.0" rand = "0.8" From e7b631aaf2ca45170beb15b39fa826433bb55fd4 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 20 Jun 2024 12:57:37 -0300 Subject: [PATCH 029/175] Make serialization methods consistent (#674) * make serialization consistent * fix SignatureShare and Identifier serialization * Update frost-core/CHANGELOG.md --- frost-core/CHANGELOG.md | 12 ++ frost-core/src/identifier.rs | 89 +++++------- frost-core/src/keys.rs | 130 +++++++----------- frost-core/src/keys/dkg.rs | 4 +- frost-core/src/keys/repairable.rs | 4 +- frost-core/src/lib.rs | 30 ++-- frost-core/src/round1.rs | 87 ++++-------- frost-core/src/round2.rs | 119 +++++----------- frost-core/src/serialization.rs | 71 +++++++--- frost-core/src/signature.rs | 42 ++---- frost-core/src/signing_key.rs | 20 +-- frost-core/src/tests/ciphersuite_generic.rs | 12 +- .../src/tests/coefficient_commitment.rs | 7 +- frost-core/src/tests/proptests.rs | 4 +- frost-core/src/tests/repairable.rs | 4 +- frost-core/src/tests/vectors.rs | 10 +- frost-core/src/tests/vectors_dkg.rs | 38 ++--- frost-core/src/tests/vss_commitment.rs | 2 +- frost-core/src/verifying_key.rs | 22 +-- frost-ed25519/tests/helpers/mod.rs | 4 +- frost-ed25519/tests/helpers/samples.rs | 32 ++--- frost-ed25519/tests/recreation_tests.rs | 2 +- frost-ed25519/tests/serialization_tests.rs | 7 +- frost-ed448/tests/helpers/samples.rs | 32 ++--- frost-ed448/tests/recreation_tests.rs | 2 +- frost-ed448/tests/serialization_tests.rs | 7 +- frost-p256/tests/helpers/samples.rs | 32 ++--- frost-p256/tests/recreation_tests.rs | 2 +- frost-p256/tests/serialization_tests.rs | 7 +- frost-rerandomized/Cargo.toml | 2 +- frost-rerandomized/src/lib.rs | 64 +++------ frost-rerandomized/src/tests.rs | 1 + frost-ristretto255/tests/helpers/samples.rs | 32 ++--- frost-ristretto255/tests/recreation_tests.rs | 2 +- .../tests/serialization_tests.rs | 7 +- frost-secp256k1/tests/helpers/samples.rs | 32 ++--- frost-secp256k1/tests/recreation_tests.rs | 2 +- frost-secp256k1/tests/serialization_tests.rs | 7 +- 38 files changed, 440 insertions(+), 543 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 97b0927c5..354325c30 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -17,6 +17,18 @@ Entries are listed in reverse chronological order. `VerifiableSecretSharingCommitment::serialize()`, `NonceCommitment::serialize()`, `Signature::serialize()`, `VerifyingKey::serialize()` can now all return an error. +* Changed the `serialize()` and `deserialize()` methods of all Scalar- and + Element-wrapping structs; instead of taking or returning a + `Field::Serialization` or `Element::Serialization` trait (which are usually + defined by ciphersuites as arrays of specific sizes), they simply respectively + take `&[u8]` and return `Vec`, exactly as the other structs, which should + greatly simplify non-serde serialization code. You can port existing code with + e.g. `x.serialize().as_ref()` -> `x.serialize()` and + `X::deserialize(bytes.try_into().unwrap())` -> `X::deserialize(&bytes)`. +* Removed the `ops::{Mul, MulAssign, Sub}` implementation for `Identifier`. + These were being used internally, but library users shouldn't need to use them. + If you have low-level code that relied on it, use `Identifier::{new, + to_scalar}` to handle the underlying scalar. * Removed `batch::Item::into()` which created a batch Item from a triple of VerifyingKey, Signature and message. Use the new `batch::Item::new()` instead (which can return an error). diff --git a/frost-core/src/identifier.rs b/frost-core/src/identifier.rs index 956d1692b..0ca710c38 100644 --- a/frost-core/src/identifier.rs +++ b/frost-core/src/identifier.rs @@ -5,10 +5,11 @@ use core::{ hash::{Hash, Hasher}, }; -use crate::{Ciphersuite, Error, Field, FieldError, Group, Scalar}; +use alloc::vec::Vec; -#[cfg(feature = "serde")] -use crate::serialization::ScalarSerialization; +use crate::{ + serialization::SerializableScalar, Ciphersuite, Error, Field, FieldError, Group, Scalar, +}; /// A FROST participant identifier. /// @@ -18,23 +19,34 @@ use crate::serialization::ScalarSerialization; #[derive(Copy, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "ScalarSerialization"))] -pub struct Identifier(Scalar); +// We use these to add a validation step since zero scalars should cause an +// error when deserializing. +#[cfg_attr(feature = "serde", serde(try_from = "SerializableScalar"))] +#[cfg_attr(feature = "serde", serde(into = "SerializableScalar"))] +pub struct Identifier(SerializableScalar); impl Identifier where C: Ciphersuite, { /// Create a new Identifier from a scalar. For internal use only. - fn new(scalar: Scalar) -> Result> { + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn new(scalar: Scalar) -> Result> { if scalar == <::Field>::zero() { Err(FieldError::InvalidZeroScalar.into()) } else { - Ok(Self(scalar)) + Ok(Self(SerializableScalar(scalar))) } } + /// Get the inner scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn to_scalar(&self) -> Scalar { + self.0 .0 + } + /// Derive an Identifier from an arbitrary byte string. /// /// This feature is not part of the specification and is just a convenient @@ -50,39 +62,36 @@ where } /// Serialize the identifier using the ciphersuite encoding. - pub fn serialize(&self) -> <::Field as Field>::Serialization { - <::Field>::serialize(&self.0) + pub fn serialize(&self) -> Vec { + self.0.serialize() } /// Deserialize an Identifier from a serialized buffer. /// Returns an error if it attempts to deserialize zero. - pub fn deserialize( - buf: &<::Field as Field>::Serialization, - ) -> Result> { - let scalar = <::Field>::deserialize(buf)?; - Self::new(scalar) + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self(SerializableScalar::deserialize(bytes)?)) } } #[cfg(feature = "serde")] -impl TryFrom> for Identifier +impl TryFrom> for Identifier where C: Ciphersuite, { type Error = Error; - fn try_from(value: ScalarSerialization) -> Result { - Self::deserialize(&value.0) + fn try_from(s: SerializableScalar) -> Result { + Self::new(s.0) } } #[cfg(feature = "serde")] -impl From> for ScalarSerialization +impl From> for SerializableScalar where C: Ciphersuite, { - fn from(value: Identifier) -> Self { - Self(value.serialize()) + fn from(i: Identifier) -> Self { + i.0 } } @@ -94,9 +103,7 @@ where { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Identifier") - .field(&hex::encode( - <::Field>::serialize(&self.0).as_ref(), - )) + .field(&hex::encode(self.serialize())) .finish() } } @@ -107,9 +114,7 @@ where C: Ciphersuite, { fn hash(&self, state: &mut H) { - <::Field>::serialize(&self.0) - .as_ref() - .hash(state) + self.serialize().hash(state) } } @@ -118,8 +123,10 @@ where C: Ciphersuite, { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - let serialized_self = <::Field>::little_endian_serialize(&self.0); - let serialized_other = <::Field>::little_endian_serialize(&other.0); + let serialized_self = + <::Field>::little_endian_serialize(&self.to_scalar()); + let serialized_other = + <::Field>::little_endian_serialize(&other.to_scalar()); // The default cmp uses lexicographic order; so we need the elements in big endian serialized_self .as_ref() @@ -138,28 +145,6 @@ where } } -impl core::ops::Mul> for Identifier -where - C: Ciphersuite, -{ - type Output = Scalar; - - fn mul(self, scalar: Scalar) -> Scalar { - self.0 * scalar - } -} - -impl core::ops::Sub for Identifier -where - C: Ciphersuite, -{ - type Output = Self; - - fn sub(self, rhs: Identifier) -> Self::Output { - Self(self.0 - rhs.0) - } -} - impl TryFrom for Identifier where C: Ciphersuite, @@ -182,7 +167,7 @@ where sum = sum + one; } } - Ok(Self(sum)) + Self::new(sum) } } } diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 99e017d64..8b7b9c68b 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -18,13 +18,11 @@ use rand_core::{CryptoRng, RngCore}; use zeroize::{DefaultIsZeroes, Zeroize}; use crate::{ - serialization::SerializableElement, Ciphersuite, Element, Error, Field, Group, GroupError, - Header, Identifier, Scalar, SigningKey, VerifyingKey, + serialization::{SerializableElement, SerializableScalar}, + Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, SigningKey, + VerifyingKey, }; -#[cfg(feature = "serde")] -use crate::serialization::ScalarSerialization; - #[cfg(feature = "serialization")] use crate::serialization::{Deserialize, Serialize}; @@ -84,44 +82,41 @@ pub(crate) fn default_identifiers(max_signers: u16) -> Vec(pub(crate) Scalar); +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct SigningShare(pub(crate) SerializableScalar); impl SigningShare where C: Ciphersuite, { /// Create a new [`SigningShare`] from a scalar. - #[cfg(feature = "internals")] - pub fn new(scalar: Scalar) -> Self { - Self(scalar) + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn new(scalar: Scalar) -> Self { + Self(SerializableScalar(scalar)) } /// Get the inner scalar. - #[cfg(feature = "internals")] - pub fn to_scalar(&self) -> Scalar { - self.0 + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn to_scalar(&self) -> Scalar { + self.0 .0 } /// Deserialize from bytes - pub fn deserialize( - bytes: <::Field as Field>::Serialization, - ) -> Result> { - <::Field>::deserialize(&bytes) - .map(|scalar| Self(scalar)) - .map_err(|e| e.into()) + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self(SerializableScalar::deserialize(bytes)?)) } /// Serialize to bytes - pub fn serialize(&self) -> <::Field as Field>::Serialization { - <::Field>::serialize(&self.0) + pub fn serialize(&self) -> Vec { + self.0.serialize() } /// Computes the signing share from a list of coefficients. #[cfg_attr(feature = "internals", visibility::make(pub))] pub(crate) fn from_coefficients(coefficients: &[Scalar], peer: Identifier) -> Self { - Self(evaluate_polynomial(peer, coefficients)) + Self::new(evaluate_polynomial(peer, coefficients)) } } @@ -139,7 +134,7 @@ where C: Ciphersuite, { fn default() -> Self { - Self(<::Field>::zero()) + Self::new(<::Field>::zero()) } } @@ -155,32 +150,7 @@ where fn from_hex>(hex: T) -> Result { let v: Vec = FromHex::from_hex(hex).map_err(|_| "invalid hex")?; - match v.try_into() { - Ok(bytes) => Self::deserialize(bytes).map_err(|_| "malformed secret encoding"), - Err(_) => Err("malformed secret encoding"), - } - } -} - -#[cfg(feature = "serde")] -impl TryFrom> for SigningShare -where - C: Ciphersuite, -{ - type Error = Error; - - fn try_from(value: ScalarSerialization) -> Result { - Self::deserialize(value.0) - } -} - -#[cfg(feature = "serde")] -impl From> for ScalarSerialization -where - C: Ciphersuite, -{ - fn from(value: SigningShare) -> Self { - Self(value.serialize()) + Self::deserialize(&v).map_err(|_| "malformed scalar") } } @@ -213,15 +183,13 @@ where } /// Deserialize from bytes - pub fn deserialize(bytes: ::Serialization) -> Result> { - ::deserialize(&bytes) - .map(|element| Self(SerializableElement(element))) - .map_err(|e| e.into()) + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self(SerializableElement::deserialize(bytes)?)) } /// Serialize to bytes - pub fn serialize(&self) -> Result<::Serialization, Error> { - Ok(::serialize(&self.0 .0)?) + pub fn serialize(&self) -> Result, Error> { + self.0.serialize() } /// Computes a verifying share for a peer given the group commitment. @@ -266,7 +234,7 @@ where C: Ciphersuite, { fn from(secret: SigningShare) -> VerifyingShare { - VerifyingShare::new(::generator() * secret.0 as Scalar) + VerifyingShare::new(::generator() * secret.to_scalar()) } } @@ -289,16 +257,14 @@ where Self(SerializableElement(value)) } - /// returns serialized element - pub fn serialize(&self) -> Result<::Serialization, Error> { - Ok(::serialize(&self.0 .0)?) + /// Deserialize from bytes + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self(SerializableElement::deserialize(bytes)?)) } - /// Creates a new commitment from a coefficient input - pub fn deserialize( - coefficient: ::Serialization, - ) -> Result, Error> { - Ok(Self::new(::deserialize(&coefficient)?)) + /// Serialize to bytes + pub fn serialize(&self) -> Result, Error> { + self.0.serialize() } /// Returns inner element value @@ -353,21 +319,23 @@ where } /// Returns serialized coefficent commitments - pub fn serialize(&self) -> Result::Serialization>, Error> { - Ok(self - .0 + pub fn serialize(&self) -> Result>, Error> { + self.0 .iter() - .map(|cc| <::Group as Group>::serialize(&cc.value())) - .collect::>()?) + .map(|cc| cc.serialize()) + .collect::>>() } - /// Returns VerifiableSecretSharingCommitment from a vector of serialized CoefficientCommitments - pub fn deserialize( - serialized_coefficient_commitments: Vec<::Serialization>, - ) -> Result> { + /// Returns VerifiableSecretSharingCommitment from a iterator of serialized + /// CoefficientCommitments (e.g. a Vec>). + pub fn deserialize(serialized_coefficient_commitments: I) -> Result> + where + I: IntoIterator, + V: AsRef<[u8]>, + { let mut coefficient_commitments = Vec::new(); - for cc in serialized_coefficient_commitments { - coefficient_commitments.push(CoefficientCommitment::::deserialize(cc)?); + for cc in serialized_coefficient_commitments.into_iter() { + coefficient_commitments.push(CoefficientCommitment::::deserialize(cc.as_ref())?); } Ok(Self::new(coefficient_commitments)) @@ -450,7 +418,7 @@ where /// /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#appendix-C.2-4 pub fn verify(&self) -> Result<(VerifyingShare, VerifyingKey), Error> { - let f_result = ::generator() * self.signing_share.0; + let f_result = ::generator() * self.signing_share.to_scalar(); let result = evaluate_vss(self.identifier, &self.commitment); if !(f_result == result) { @@ -580,10 +548,10 @@ fn evaluate_polynomial( ) -> Scalar { let mut value = <::Field>::zero(); - let ell_scalar = identifier; + let ell = identifier; for coeff in coefficients.iter().skip(1).rev() { value = value + *coeff; - value = ell_scalar * value; + value = value * ell.to_scalar(); } value = value + *coefficients @@ -601,7 +569,7 @@ fn evaluate_vss( identifier: Identifier, commitment: &VerifiableSecretSharingCommitment, ) -> Element { - let i = identifier; + let i = identifier.to_scalar(); let (_, result) = commitment.0.iter().fold( (<::Field>::one(), ::identity()), @@ -943,7 +911,7 @@ pub fn reconstruct( compute_lagrange_coefficient(&identifiers, None, key_package.identifier)?; // Compute y = f(0) via polynomial interpolation of these t-of-n solutions ('points) of f - secret = secret + (lagrange_coefficient * key_package.signing_share().0); + secret = secret + (lagrange_coefficient * key_package.signing_share().to_scalar()); } Ok(SigningKey { scalar: secret }) diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 483da98d8..3850662f2 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -528,11 +528,11 @@ pub fn part3( // // > Each P_i calculates their long-lived private signing share by computing // > s_i = ∑^n_{ℓ=1} f_ℓ(i), stores s_i securely, and deletes each f_ℓ(i). - signing_share = signing_share + f_ell_i.0; + signing_share = signing_share + f_ell_i.to_scalar(); } signing_share = signing_share + round2_secret_package.secret_share; - let signing_share = SigningShare(signing_share); + let signing_share = SigningShare::new(signing_share); // Round 2, Step 4 // diff --git a/frost-core/src/keys/repairable.rs b/frost-core/src/keys/repairable.rs index 5297d8b74..ea2211fad 100644 --- a/frost-core/src/keys/repairable.rs +++ b/frost-core/src/keys/repairable.rs @@ -58,7 +58,7 @@ fn compute_last_random_value( // Calculate Lagrange Coefficient for helper_i let zeta_i = compute_lagrange_coefficient(helpers, Some(participant), share_i.identifier)?; - let lhs = zeta_i * share_i.signing_share.0; + let lhs = zeta_i * share_i.signing_share.to_scalar(); let mut out: BTreeMap, Scalar> = helpers .iter() @@ -125,7 +125,7 @@ pub fn repair_share_step_3( SecretShare { header: Header::default(), identifier, - signing_share: SigningShare(share), + signing_share: SigningShare::new(share), commitment: commitment.clone(), } } diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 63d06ad33..b54f3cc3c 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -26,6 +26,7 @@ use derive_getters::Getters; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; use rand_core::{CryptoRng, RngCore}; +use serialization::SerializableScalar; use zeroize::Zeroize; pub mod batch; @@ -62,7 +63,7 @@ pub use verifying_key::VerifyingKey; /// A type refinement for the scalar field element representing the per-message _[challenge]_. /// /// [challenge]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-challenge-computa -#[derive(Clone)] +#[derive(Copy, Clone)] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) struct Challenge( @@ -74,16 +75,21 @@ where C: Ciphersuite, { /// Creates a challenge from a scalar. - #[cfg(feature = "internals")] - pub fn from_scalar( + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + #[allow(dead_code)] + pub(crate) fn from_scalar( scalar: <<::Group as Group>::Field as Field>::Scalar, ) -> Self { Self(scalar) } /// Return the underlying scalar. - #[cfg(feature = "internals")] - pub fn to_scalar(self) -> <<::Group as Group>::Field as Field>::Scalar { + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn to_scalar( + self, + ) -> <<::Group as Group>::Field as Field>::Scalar { self.0 } } @@ -195,8 +201,8 @@ where C: Ciphersuite, { /// Serializes [`BindingFactor`] to bytes. - pub fn serialize(&self) -> <::Field as Field>::Serialization { - <::Field>::serialize(&self.0) + pub fn serialize(&self) -> Vec { + SerializableScalar::(self.0).serialize() } } @@ -311,12 +317,12 @@ fn compute_lagrange_coefficient( } if let Some(x) = x { - num = (x - *x_j) * num; - den = (x_i - *x_j) * den; + num = num * (x.to_scalar() - x_j.to_scalar()); + den = den * (x_i.to_scalar() - x_j.to_scalar()); } else { // Both signs inverted just to avoid requiring Neg (-*xj) - num = *x_j * num; - den = (*x_j - x_i) * den; + num = num * x_j.to_scalar(); + den = den * (x_j.to_scalar() - x_i.to_scalar()); } } if !x_i_found { @@ -593,7 +599,7 @@ where let mut z = <::Field>::zero(); for signature_share in signature_shares.values() { - z = z + signature_share.share; + z = z + signature_share.to_scalar(); } let signature = Signature { diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 08414133e..dfe1eb0f6 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -15,12 +15,10 @@ use rand_core::{CryptoRng, RngCore}; use zeroize::Zeroize; use crate::{ - serialization::SerializableElement, Ciphersuite, Element, Error, Field, Group, Header, Scalar, + serialization::{SerializableElement, SerializableScalar}, + Ciphersuite, Element, Error, Field, Group, Header, }; -#[cfg(feature = "serde")] -use crate::serialization::ScalarSerialization; - #[cfg(feature = "serialization")] use crate::serialization::{Deserialize, Serialize}; @@ -30,9 +28,8 @@ use super::{keys::SigningShare, Identifier}; #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "ScalarSerialization"))] -pub struct Nonce(pub(super) Scalar); +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct Nonce(pub(super) SerializableScalar); impl Nonce where @@ -57,35 +54,41 @@ where Self::nonce_generate_from_random_bytes(secret, random_bytes) } + fn from_scalar(scalar: <<::Group as Group>::Field as Field>::Scalar) -> Self { + Self(SerializableScalar(scalar)) + } + + pub(crate) fn to_scalar( + self, + ) -> <<::Group as Group>::Field as Field>::Scalar { + self.0 .0 + } + /// Generates a nonce from the given random bytes. /// This function allows testing and MUST NOT be made public. pub(crate) fn nonce_generate_from_random_bytes( secret: &SigningShare, random_bytes: [u8; 32], ) -> Self { - let secret_enc = <::Field>::serialize(&secret.0); + let secret_enc = secret.0.serialize(); let input: Vec = random_bytes .iter() - .chain(secret_enc.as_ref().iter()) + .chain(secret_enc.iter()) .cloned() .collect(); - Self(C::H3(input.as_slice())) + Self::from_scalar(C::H3(input.as_slice())) } /// Deserialize [`Nonce`] from bytes - pub fn deserialize( - bytes: <::Field as Field>::Serialization, - ) -> Result> { - <::Field>::deserialize(&bytes) - .map(|scalar| Self(scalar)) - .map_err(|e| e.into()) + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self(SerializableScalar::deserialize(bytes)?)) } /// Serialize [`Nonce`] to bytes - pub fn serialize(&self) -> <::Field as Field>::Serialization { - <::Field>::serialize(&self.0) + pub fn serialize(&self) -> Vec { + self.0.serialize() } } @@ -94,7 +97,7 @@ where C: Ciphersuite, { fn zeroize(&mut self) { - *self = Nonce(<::Field>::zero()); + *self = Nonce::from_scalar(<::Field>::zero()); } } @@ -107,32 +110,7 @@ where fn from_hex>(hex: T) -> Result { let v: Vec = FromHex::from_hex(hex).map_err(|_| "invalid hex")?; - match v.try_into() { - Ok(bytes) => Self::deserialize(bytes).map_err(|_| "malformed nonce encoding"), - Err(_) => Err("malformed nonce encoding"), - } - } -} - -#[cfg(feature = "serde")] -impl TryFrom> for Nonce -where - C: Ciphersuite, -{ - type Error = Error; - - fn try_from(value: ScalarSerialization) -> Result { - Self::deserialize(value.0) - } -} - -#[cfg(feature = "serde")] -impl From> for ScalarSerialization -where - C: Ciphersuite, -{ - fn from(value: Nonce) -> Self { - Self(value.serialize()) + Self::deserialize(&v).map_err(|_| "malformed nonce encoding") } } @@ -156,15 +134,13 @@ where } /// Deserialize [`NonceCommitment`] from bytes - pub fn deserialize(bytes: ::Serialization) -> Result> { - ::deserialize(&bytes) - .map(|element| Self::new(element)) - .map_err(|e| e.into()) + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self(SerializableElement::deserialize(bytes)?)) } /// Serialize [`NonceCommitment`] to bytes - pub fn serialize(&self) -> Result<::Serialization, Error> { - Ok(::serialize(&self.0 .0)?) + pub fn serialize(&self) -> Result, Error> { + self.0.serialize() } } @@ -198,7 +174,7 @@ where C: Ciphersuite, { fn from(nonce: &Nonce) -> Self { - Self::new(::generator() * nonce.0) + Self::new(::generator() * nonce.to_scalar()) } } @@ -211,12 +187,7 @@ where fn from_hex>(hex: T) -> Result { let v: Vec = FromHex::from_hex(hex).map_err(|_| "invalid hex")?; - match v.try_into() { - Ok(bytes) => { - Self::deserialize(bytes).map_err(|_| "malformed nonce commitment encoding") - } - Err(_) => Err("malformed nonce commitment encoding"), - } + Self::deserialize(&v).map_err(|_| "malformed nonce commitment encoding") } } diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 25e03a85c..a6a0c18d8 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -7,72 +7,50 @@ use crate::{ challenge, Challenge, Ciphersuite, Error, Field, Group, {round1, *}, }; -#[cfg(feature = "serde")] -use crate::serialization::ScalarSerialization; - -// Used to help encoding a SignatureShare. Since it has a Scalar it can't -// be directly encoded with serde, so we use this struct to wrap the scalar. -#[cfg(feature = "serde")] -#[derive(Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "ScalarSerialization"))] -struct SignatureShareHelper(Scalar); - -#[cfg(feature = "serde")] -impl TryFrom> for SignatureShareHelper -where - C: Ciphersuite, -{ - type Error = Error; - - fn try_from(value: ScalarSerialization) -> Result { - <::Field>::deserialize(&value.0) - .map(|scalar| Self(scalar)) - .map_err(|e| e.into()) - } -} - -#[cfg(feature = "serde")] -impl From> for ScalarSerialization -where - C: Ciphersuite, -{ - fn from(value: SignatureShareHelper) -> Self { - Self(<::Field>::serialize(&value.0)) - } -} - /// A participant's signature share, which the coordinator will aggregate with all other signer's /// shares into the joint signature. #[derive(Clone, Copy, Eq, PartialEq, Getters)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(try_from = "SignatureShareSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "SignatureShareSerialization"))] pub struct SignatureShare { + /// Serialization header + #[getter(skip)] + pub(crate) header: Header, /// This participant's signature over the message. - pub(crate) share: Scalar, + pub(crate) share: SerializableScalar, } impl SignatureShare where C: Ciphersuite, { + pub(crate) fn new( + scalar: <<::Group as Group>::Field as Field>::Scalar, + ) -> Self { + Self { + header: Header::default(), + share: SerializableScalar(scalar), + } + } + + pub(crate) fn to_scalar( + self, + ) -> <<::Group as Group>::Field as Field>::Scalar { + self.share.0 + } + /// Deserialize [`SignatureShare`] from bytes - pub fn deserialize( - bytes: <::Field as Field>::Serialization, - ) -> Result> { - <::Field>::deserialize(&bytes) - .map(|scalar| Self { share: scalar }) - .map_err(|e| e.into()) + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self { + header: Header::default(), + share: SerializableScalar::deserialize(bytes)?, + }) } /// Serialize [`SignatureShare`] to bytes - pub fn serialize(&self) -> <::Field as Field>::Serialization { - <::Field>::serialize(&self.share) + pub fn serialize(&self) -> Vec { + self.share.serialize() } /// Tests if a signature share issued by a participant is valid before @@ -92,7 +70,7 @@ where lambda_i: Scalar, challenge: &Challenge, ) -> Result<(), Error> { - if (::generator() * self.share) + if (::generator() * self.to_scalar()) != (group_commitment_share.0 + (verifying_share.to_element() * challenge.0 * lambda_i)) { return Err(Error::InvalidSignatureShare { @@ -104,41 +82,6 @@ where } } -#[cfg(feature = "serde")] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -struct SignatureShareSerialization { - /// Serialization header - pub(crate) header: Header, - share: SignatureShareHelper, -} - -#[cfg(feature = "serde")] -impl From> for SignatureShare -where - C: Ciphersuite, -{ - fn from(value: SignatureShareSerialization) -> Self { - Self { - share: value.share.0, - } - } -} - -#[cfg(feature = "serde")] -impl From> for SignatureShareSerialization -where - C: Ciphersuite, -{ - fn from(value: SignatureShare) -> Self { - Self { - header: Header::default(), - share: SignatureShareHelper(value.share), - } - } -} - impl Debug for SignatureShare where C: Ciphersuite, @@ -160,11 +103,11 @@ fn compute_signature_share( key_package: &keys::KeyPackage, challenge: Challenge, ) -> SignatureShare { - let z_share: <::Field as Field>::Scalar = signer_nonces.hiding.0 - + (signer_nonces.binding.0 * binding_factor.0) - + (lambda_i * key_package.signing_share.0 * challenge.0); + let z_share: <::Field as Field>::Scalar = signer_nonces.hiding.to_scalar() + + (signer_nonces.binding.to_scalar() * binding_factor.0) + + (lambda_i * key_package.signing_share.to_scalar() * challenge.to_scalar()); - SignatureShare:: { share: z_share } + SignatureShare::::new(z_share) } /// Performed once by each participant selected for the signing operation. diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index 59ae6f6a6..a442e58b5 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -1,28 +1,43 @@ //! Serialization support. -#[cfg(feature = "serialization")] use alloc::vec::Vec; -use crate::Ciphersuite; - -#[cfg(feature = "serde")] -use crate::{Field, Group}; +use crate::{Ciphersuite, FieldError}; -#[cfg(feature = "serialization")] -use crate::Error; +use crate::{Element, Error, Field, Group}; -use crate::Element; - -#[cfg(feature = "serde")] +#[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] /// Helper struct to serialize a Scalar. -pub(crate) struct ScalarSerialization( - pub <<::Group as Group>::Field as Field>::Serialization, +pub(crate) struct SerializableScalar( + pub <<::Group as Group>::Field as Field>::Scalar, ); +impl SerializableScalar +where + C: Ciphersuite, +{ + /// Serialize a Scalar. + pub fn serialize(&self) -> Vec { + <::Field>::serialize(&self.0) + .as_ref() + .to_vec() + } + + /// Deserialize a Scalar from a serialized buffer. + pub fn deserialize(bytes: &[u8]) -> Result> { + let serialized: <::Field as Field>::Serialization = bytes + .to_vec() + .try_into() + .map_err(|_| FieldError::MalformedScalar)?; + let scalar = <::Field>::deserialize(&serialized)?; + Ok(Self(scalar)) + } +} + #[cfg(feature = "serde")] -impl serde::Serialize for ScalarSerialization +impl serde::Serialize for SerializableScalar where C: Ciphersuite, { @@ -30,12 +45,13 @@ where where S: serde::Serializer, { - serdect::array::serialize_hex_lower_or_bin(&self.0.as_ref(), serializer) + let serialized = <::Group as Group>::Field::serialize(&self.0); + serdect::array::serialize_hex_lower_or_bin(&serialized.as_ref(), serializer) } } #[cfg(feature = "serde")] -impl<'de, C> serde::Deserialize<'de> for ScalarSerialization +impl<'de, C> serde::Deserialize<'de> for SerializableScalar where C: Ciphersuite, { @@ -54,13 +70,36 @@ where let array = bytes .try_into() .map_err(|_| serde::de::Error::custom("invalid byte length"))?; - Ok(Self(array)) + <::Group as Group>::Field::deserialize(&array) + .map(|scalar| Self(scalar)) + .map_err(serde::de::Error::custom) } } #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) struct SerializableElement(pub(crate) Element); +impl SerializableElement +where + C: Ciphersuite, +{ + /// Serialize an Element. Returns and error if it's the identity. + pub fn serialize(&self) -> Result, Error> { + Ok(::serialize(&self.0)?.as_ref().to_vec()) + } + + /// Deserialized an Element. Returns an error if it's malformed or is the + /// identity. + pub fn deserialize(bytes: &[u8]) -> Result> { + let serialized: ::Serialization = bytes + .to_vec() + .try_into() + .map_err(|_| FieldError::MalformedScalar)?; + let scalar = ::deserialize(&serialized)?; + Ok(Self(scalar)) + } +} + #[cfg(feature = "serde")] impl serde::Serialize for SerializableElement where diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index 3da178ce6..7150b0852 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -2,8 +2,6 @@ use alloc::{string::ToString, vec::Vec}; -use debugless_unwrap::DebuglessUnwrap; - use crate::{Ciphersuite, Element, Error, Field, Group, Scalar}; /// A Schnorr signature over some prime order group (or subgroup). @@ -32,34 +30,30 @@ where } /// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature`. - pub fn deserialize(bytes: C::SignatureSerialization) -> Result> { + pub fn deserialize(bytes: &[u8]) -> Result> { // To compute the expected length of the encoded point, encode the generator // and get its length. Note that we can't use the identity because it can be encoded // shorter in some cases (e.g. P-256, which uses SEC1 encoding). let generator = ::generator(); let mut R_bytes = Vec::from(::serialize(&generator)?.as_ref()); - let R_bytes_len = R_bytes.len(); - R_bytes[..].copy_from_slice( - bytes - .as_ref() - .get(0..R_bytes_len) - .ok_or(Error::MalformedSignature)?, - ); - - let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; - let one = <::Field as Field>::zero(); let mut z_bytes = Vec::from(<::Field as Field>::serialize(&one).as_ref()); - let z_bytes_len = z_bytes.len(); + if bytes.len() != R_bytes_len + z_bytes_len { + return Err(Error::MalformedSignature); + } + + R_bytes[..].copy_from_slice(bytes.get(0..R_bytes_len).ok_or(Error::MalformedSignature)?); + + let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; + // We extract the exact length of bytes we expect, not just the remaining bytes with `bytes[R_bytes_len..]` z_bytes[..].copy_from_slice( bytes - .as_ref() .get(R_bytes_len..R_bytes_len + z_bytes_len) .ok_or(Error::MalformedSignature)?, ); @@ -72,14 +66,14 @@ where }) } - /// Converts this signature to its [`Ciphersuite::SignatureSerialization`] in bytes. - pub fn serialize(&self) -> Result> { + /// Converts this signature to its byte serialization. + pub fn serialize(&self) -> Result, Error> { let mut bytes = Vec::::new(); bytes.extend(::serialize(&self.R)?.as_ref()); bytes.extend(<::Field>::serialize(&self.z).as_ref()); - Ok(bytes.try_into().debugless_unwrap()) + Ok(bytes) } } @@ -95,10 +89,7 @@ where S: serde::Serializer, { serdect::slice::serialize_hex_lower_or_bin( - &self - .serialize() - .map_err(serde::ser::Error::custom)? - .as_ref(), + &self.serialize().map_err(serde::ser::Error::custom)?, serializer, ) } @@ -116,12 +107,9 @@ where D: serde::Deserializer<'de>, { let bytes = serdect::slice::deserialize_hex_or_bin_vec(deserializer)?; - let array = bytes - .try_into() - .map_err(|_| serde::de::Error::custom("invalid byte length"))?; - let identifier = Signature::deserialize(array) + let signature = Signature::deserialize(&bytes) .map_err(|err| serde::de::Error::custom(format!("{err}")))?; - Ok(identifier) + Ok(signature) } } diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index a92e7fcf3..4ba306ed2 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -1,8 +1,13 @@ //! Schnorr signature signing keys +use alloc::vec::Vec; + use rand_core::{CryptoRng, RngCore}; -use crate::{random_nonzero, Ciphersuite, Error, Field, Group, Scalar, Signature, VerifyingKey}; +use crate::{ + random_nonzero, serialization::SerializableScalar, Ciphersuite, Error, Field, Group, Scalar, + Signature, VerifyingKey, +}; /// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`]. #[derive(Copy, Clone, PartialEq, Eq)] @@ -25,18 +30,13 @@ where } /// Deserialize from bytes - pub fn deserialize( - bytes: <::Field as Field>::Serialization, - ) -> Result, Error> { - let scalar = - <::Field as Field>::deserialize(&bytes).map_err(Error::from)?; - - Self::from_scalar(scalar) + pub fn deserialize(bytes: &[u8]) -> Result, Error> { + Self::from_scalar(SerializableScalar::deserialize(bytes)?.0) } /// Serialize `SigningKey` to bytes - pub fn serialize(&self) -> <::Field as Field>::Serialization { - <::Field as Field>::serialize(&self.scalar) + pub fn serialize(&self) -> Vec { + SerializableScalar::(self.scalar).serialize() } /// Create a signature `msg` using this `SigningKey`. diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 76ecf240f..a62193402 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -17,7 +17,7 @@ use crate::Ciphersuite; pub fn check_zero_key_fails() { let zero = <<::Group as Group>::Field>::zero(); let encoded_zero = <<::Group as Group>::Field>::serialize(&zero); - let r = SigningKey::::deserialize(encoded_zero); + let r = SigningKey::::deserialize(encoded_zero.as_ref()); assert_eq!(r, Err(Error::MalformedSigningKey)); } @@ -49,9 +49,8 @@ pub fn check_share_generation(mut rng: R assert_eq!( frost::keys::reconstruct::(&key_packages) .unwrap() - .serialize() - .as_ref(), - secret.serialize().as_ref() + .serialize(), + secret.serialize() ); // Test error cases @@ -334,10 +333,13 @@ fn check_aggregate_corrupted_share( mut signature_shares: BTreeMap, frost::round2::SignatureShare>, pubkey_package: frost::keys::PublicKeyPackage, ) { + use crate::round2::SignatureShare; + let one = <::Group as Group>::Field::one(); // Corrupt a share let id = *signature_shares.keys().next().unwrap(); - signature_shares.get_mut(&id).unwrap().share = signature_shares[&id].share + one; + *signature_shares.get_mut(&id).unwrap() = + SignatureShare::new(signature_shares[&id].to_scalar() + one); let e = frost::aggregate(&signing_package, &signature_shares, &pubkey_package).unwrap_err(); assert_eq!(e.culprit(), Some(id)); assert_eq!(e, Error::InvalidSignatureShare { culprit: id }); diff --git a/frost-core/src/tests/coefficient_commitment.rs b/frost-core/src/tests/coefficient_commitment.rs index 094b47a71..b3392ca42 100644 --- a/frost-core/src/tests/coefficient_commitment.rs +++ b/frost-core/src/tests/coefficient_commitment.rs @@ -20,7 +20,7 @@ pub fn check_serialization_of_coefficient_commitment::serialize(&element).unwrap(); let coeff_commitment = - frost::keys::CoefficientCommitment::::deserialize(serialized_element).unwrap(); + frost::keys::CoefficientCommitment::::deserialize(serialized_element.as_ref()).unwrap(); assert!(expected == coeff_commitment); } @@ -48,7 +48,8 @@ pub fn check_create_coefficient_commitment_error( ) .debugless_unwrap(); - let coeff_commitment = frost::keys::CoefficientCommitment::::deserialize(serialized); + let coeff_commitment = + frost::keys::CoefficientCommitment::::deserialize(serialized.as_ref()); assert!(coeff_commitment.is_err()); } diff --git a/frost-core/src/tests/proptests.rs b/frost-core/src/tests/proptests.rs index 06a3925f4..9b0fbcb9f 100644 --- a/frost-core/src/tests/proptests.rs +++ b/frost-core/src/tests/proptests.rs @@ -60,11 +60,11 @@ where // conversion to raw bytes to exercise those code paths. let _sig = { let bytes = self.sig.serialize().unwrap(); - Signature::::deserialize(bytes) + Signature::::deserialize(&bytes) }; // Check that the verification key is a valid key. - let _pub_key = VerifyingKey::::deserialize(self.vk.serialize().unwrap()) + let _pub_key = VerifyingKey::::deserialize(&self.vk.serialize().unwrap()) .expect("The test verification key to be well-formed."); // Check that signature validation has the expected result. diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index ed4f28755..532e1e061 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -142,7 +142,7 @@ pub fn check_repair_share_step_1(mut rng rhs = rhs + v; } - let lhs = lagrange_coefficient * helper_4.signing_share.0; + let lhs = lagrange_coefficient * helper_4.signing_share.to_scalar(); assert!(lhs == rhs) } @@ -202,7 +202,7 @@ pub fn check_repair_share_step_3( generate_scalar_from_byte_string::(sigmas["sigma_sum"].as_str().unwrap()); let actual: SecretShare = SecretShare::new( Identifier::try_from(2).unwrap(), - SigningShare(actual_sigma), + SigningShare::new(actual_sigma), commitment, ); diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index 144827a44..c11b14414 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -34,8 +34,7 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors(json_vectors: &Value) -> TestVectors::Field as Field>::Serialization::try_from( - hex::decode(signer["sig_share"].as_str().unwrap()).unwrap(), - ) - .debugless_unwrap(); + let sig_share = hex::decode(signer["sig_share"].as_str().unwrap()).unwrap(); - let signature_share = SignatureShare::::deserialize(sig_share).unwrap(); + let signature_share = SignatureShare::::deserialize(&sig_share).unwrap(); signature_shares.insert(i.try_into().unwrap(), signature_share); } diff --git a/frost-core/src/tests/vectors_dkg.rs b/frost-core/src/tests/vectors_dkg.rs index 6619c8b56..fe775bda8 100644 --- a/frost-core/src/tests/vectors_dkg.rs +++ b/frost-core/src/tests/vectors_dkg.rs @@ -1,5 +1,5 @@ //! Helper function for testing with test vectors. -use alloc::{collections::BTreeMap, string::ToString}; +use alloc::{collections::BTreeMap, string::ToString, vec::Vec}; use debugless_unwrap::DebuglessUnwrap; use hex::{self}; @@ -66,7 +66,8 @@ pub fn parse_test_vectors_dkg(json_vectors: &Value) -> DKGTestVe round2_packages.insert(participant_2_id, build_round_2_package(json_vectors, 2)); round2_packages.insert(participant_3_id, build_round_2_package(json_vectors, 3)); - let secret = SigningKey::deserialize(json_to_scalar::(&participant["signing_key"])).unwrap(); + let secret = + SigningKey::deserialize(json_to_scalar::(&participant["signing_key"]).as_ref()).unwrap(); let coefficient = <::Field as Field>::deserialize(&json_to_scalar::( &participant["coefficient"], @@ -76,13 +77,15 @@ pub fn parse_test_vectors_dkg(json_vectors: &Value) -> DKGTestVe let public_key_package = build_public_key_package(json_vectors); let verifying_share = - VerifyingShare::deserialize(json_to_element::(&participant["verifying_share"])).unwrap(); + VerifyingShare::deserialize(json_to_element::(&participant["verifying_share"]).as_ref()) + .unwrap(); let verifying_key = - VerifyingKey::deserialize(json_to_element::(&inputs["verifying_key"])).unwrap(); + VerifyingKey::deserialize(json_to_element::(&inputs["verifying_key"]).as_ref()).unwrap(); let signing_share = - SigningShare::deserialize(json_to_scalar::(&participant["signing_share"])).unwrap(); + SigningShare::deserialize(json_to_scalar::(&participant["signing_share"]).as_ref()) + .unwrap(); let key_package = KeyPackage { header: Header::default(), @@ -114,18 +117,15 @@ fn build_round_1_package( .as_array() .unwrap() .iter() - .map(|v| json_to_element::(v)) - .collect(); + .map(|v| json_to_element::(v).as_ref().to_vec()) + .collect::>>(); let commitment = VerifiableSecretSharingCommitment::deserialize(vss_commitment).unwrap(); let proof_of_knowledge = Signature::deserialize( - C::SignatureSerialization::try_from( - hex::decode(participant["proof_of_knowledge"].as_str().unwrap()).unwrap(), - ) - .debugless_unwrap(), + &hex::decode(participant["proof_of_knowledge"].as_str().unwrap()).unwrap(), ) - .unwrap(); + .debugless_unwrap(); Round1Package { header: Header::default(), @@ -140,9 +140,9 @@ fn build_round_2_package( ) -> Round2Package { let inputs = &json_vectors["inputs"]; - let signing_share = SigningShare::deserialize(json_to_scalar::( - &inputs["1"]["signing_shares"][sender_num.to_string()], - )) + let signing_share = SigningShare::deserialize( + json_to_scalar::(&inputs["1"]["signing_shares"][sender_num.to_string()]).as_ref(), + ) .unwrap(); Round2Package { @@ -163,15 +163,15 @@ fn build_public_key_package(json_vectors: &Value) -> PublicKeyPa as u16) .try_into() .unwrap(); - let verifying_share = VerifyingShare::deserialize(json_to_element::( - &inputs[i.to_string()]["verifying_share"], - )) + let verifying_share = VerifyingShare::deserialize( + json_to_element::(&inputs[i.to_string()]["verifying_share"]).as_ref(), + ) .unwrap(); verifying_shares.insert(participant_id, verifying_share); } let verifying_key = - VerifyingKey::deserialize(json_to_element::(&inputs["verifying_key"])).unwrap(); + VerifyingKey::deserialize(json_to_element::(&inputs["verifying_key"]).as_ref()).unwrap(); PublicKeyPackage { header: Header::default(), diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 91a341f0e..10465227f 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -43,7 +43,7 @@ pub fn check_serialize_vss_commitment(mu assert!(expected .iter() .zip(vss_commitment.iter()) - .all(|(e, c)| e.as_ref() == c.as_ref())); + .all(|(e, c)| e.as_ref() == c)); } /// Test deserialize VerifiableSecretSharingCommitment diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index 8ea63025a..c3a1cfc53 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -1,9 +1,6 @@ use core::fmt::{self, Debug}; -use alloc::string::ToString; - -#[cfg(any(test, feature = "test-impl"))] -use alloc::vec::Vec; +use alloc::{string::ToString, vec::Vec}; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; @@ -43,17 +40,13 @@ where } /// Deserialize from bytes - pub fn deserialize( - bytes: ::Serialization, - ) -> Result, Error> { - ::deserialize(&bytes) - .map(|element| VerifyingKey::new(element)) - .map_err(|e| e.into()) + pub fn deserialize(bytes: &[u8]) -> Result, Error> { + Ok(Self::new(SerializableElement::deserialize(bytes)?.0)) } /// Serialize `VerifyingKey` to bytes - pub fn serialize(&self) -> Result<::Serialization, Error> { - Ok(::serialize(&self.element.0)?) + pub fn serialize(&self) -> Result, Error> { + self.element.serialize() } /// Verify a purported `signature` with a pre-hashed [`Challenge`] made by this verification @@ -123,9 +116,6 @@ where fn from_hex>(hex: T) -> Result { let v: Vec = FromHex::from_hex(hex).map_err(|_| "invalid hex")?; - match v.try_into() { - Ok(bytes) => Self::deserialize(bytes).map_err(|_| "malformed verifying key encoding"), - Err(_) => Err("malformed verifying key encoding"), - } + Self::deserialize(&v).map_err(|_| "malformed verifying key encoding") } } diff --git a/frost-ed25519/tests/helpers/mod.rs b/frost-ed25519/tests/helpers/mod.rs index ec68f5884..2a936ac3e 100644 --- a/frost-ed25519/tests/helpers/mod.rs +++ b/frost-ed25519/tests/helpers/mod.rs @@ -14,11 +14,11 @@ pub fn verify_signature( group_pubkey: frost_core::VerifyingKey, ) { let sig = { - let bytes: [u8; 64] = group_signature.serialize().unwrap(); + let bytes: [u8; 64] = group_signature.serialize().unwrap().try_into().unwrap(); ed25519_dalek::Signature::from(bytes) }; let pub_key = { - let bytes = group_pubkey.serialize().unwrap(); + let bytes = group_pubkey.serialize().unwrap().try_into().unwrap(); ed25519_dalek::VerifyingKey::from_bytes(&bytes).unwrap() }; // Check that signature validation has the expected result. diff --git a/frost-ed25519/tests/helpers/samples.rs b/frost-ed25519/tests/helpers/samples.rs index 9f9cc79ff..6f22aed3d 100644 --- a/frost-ed25519/tests/helpers/samples.rs +++ b/frost-ed25519/tests/helpers/samples.rs @@ -36,8 +36,8 @@ fn scalar1() -> Scalar { pub fn signing_nonces() -> SigningNonces { let serialized_scalar1 = <::Group as Group>::Field::serialize(&scalar1()); let serialized_scalar2 = <::Group as Group>::Field::serialize(&scalar1()); - let hiding_nonce = Nonce::deserialize(serialized_scalar1).unwrap(); - let binding_nonce = Nonce::deserialize(serialized_scalar2).unwrap(); + let hiding_nonce = Nonce::deserialize(serialized_scalar1.as_ref()).unwrap(); + let binding_nonce = Nonce::deserialize(serialized_scalar2.as_ref()).unwrap(); SigningNonces::from_nonces(hiding_nonce, binding_nonce) } @@ -46,8 +46,10 @@ pub fn signing_nonces() -> SigningNonces { pub fn signing_commitments() -> SigningCommitments { let serialized_element1 = ::Group::serialize(&element1()).unwrap(); let serialized_element2 = ::Group::serialize(&element2()).unwrap(); - let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); - let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); + let hiding_nonce_commitment = + NonceCommitment::deserialize(serialized_element1.as_ref()).unwrap(); + let binding_nonce_commitment = + NonceCommitment::deserialize(serialized_element2.as_ref()).unwrap(); SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } @@ -65,7 +67,7 @@ pub fn signing_package() -> SigningPackage { pub fn signature_share() -> SignatureShare { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - SignatureShare::deserialize(serialized_scalar).unwrap() + SignatureShare::deserialize(serialized_scalar.as_ref()).unwrap() } /// Generate a sample SecretShare. @@ -73,7 +75,7 @@ pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -85,10 +87,10 @@ pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) } @@ -97,9 +99,9 @@ pub fn key_package() -> KeyPackage { pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); PublicKeyPackage::new(verifying_shares, verifying_key) @@ -114,12 +116,10 @@ pub fn round1_package() -> round1::Package { .iter() .chain(serialized_scalar.as_ref().iter()) .cloned() - .collect::>() - .try_into() - .unwrap(); + .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(serialized_signature).unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } @@ -127,7 +127,7 @@ pub fn round1_package() -> round1::Package { /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); round2::Package::new(signing_share) } diff --git a/frost-ed25519/tests/recreation_tests.rs b/frost-ed25519/tests/recreation_tests.rs index 7fa11b792..0b1d44f13 100644 --- a/frost-ed25519/tests/recreation_tests.rs +++ b/frost-ed25519/tests/recreation_tests.rs @@ -54,7 +54,7 @@ fn check_signature_share_recreation() { let encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); + let new_signature_share = SignatureShare::deserialize(&encoded).unwrap(); assert!(signature_share == new_signature_share); } diff --git a/frost-ed25519/tests/serialization_tests.rs b/frost-ed25519/tests/serialization_tests.rs index fbe26a41f..9acd53be8 100644 --- a/frost-ed25519/tests/serialization_tests.rs +++ b/frost-ed25519/tests/serialization_tests.rs @@ -49,8 +49,11 @@ fn check_signing_package_postcard_serialization() { fn check_signature_share_postcard_serialization() { let signature_share = samples::signature_share(); let bytes = signature_share.serialize(); - assert_snapshot!(hex::encode(bytes)); - assert_eq!(signature_share, SignatureShare::deserialize(bytes).unwrap()); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signature_share, + SignatureShare::deserialize(&bytes).unwrap() + ); } #[test] fn check_secret_share_postcard_serialization() { diff --git a/frost-ed448/tests/helpers/samples.rs b/frost-ed448/tests/helpers/samples.rs index 10d077f2f..ec5567369 100644 --- a/frost-ed448/tests/helpers/samples.rs +++ b/frost-ed448/tests/helpers/samples.rs @@ -36,8 +36,8 @@ fn scalar1() -> Scalar { pub fn signing_nonces() -> SigningNonces { let serialized_scalar1 = <::Group as Group>::Field::serialize(&scalar1()); let serialized_scalar2 = <::Group as Group>::Field::serialize(&scalar1()); - let hiding_nonce = Nonce::deserialize(serialized_scalar1).unwrap(); - let binding_nonce = Nonce::deserialize(serialized_scalar2).unwrap(); + let hiding_nonce = Nonce::deserialize(serialized_scalar1.as_ref()).unwrap(); + let binding_nonce = Nonce::deserialize(serialized_scalar2.as_ref()).unwrap(); SigningNonces::from_nonces(hiding_nonce, binding_nonce) } @@ -46,8 +46,10 @@ pub fn signing_nonces() -> SigningNonces { pub fn signing_commitments() -> SigningCommitments { let serialized_element1 = ::Group::serialize(&element1()).unwrap(); let serialized_element2 = ::Group::serialize(&element2()).unwrap(); - let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); - let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); + let hiding_nonce_commitment = + NonceCommitment::deserialize(serialized_element1.as_ref()).unwrap(); + let binding_nonce_commitment = + NonceCommitment::deserialize(serialized_element2.as_ref()).unwrap(); SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } @@ -65,7 +67,7 @@ pub fn signing_package() -> SigningPackage { pub fn signature_share() -> SignatureShare { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - SignatureShare::deserialize(serialized_scalar).unwrap() + SignatureShare::deserialize(serialized_scalar.as_ref()).unwrap() } /// Generate a sample SecretShare. @@ -73,7 +75,7 @@ pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -85,10 +87,10 @@ pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) } @@ -97,9 +99,9 @@ pub fn key_package() -> KeyPackage { pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); PublicKeyPackage::new(verifying_shares, verifying_key) @@ -114,12 +116,10 @@ pub fn round1_package() -> round1::Package { .iter() .chain(serialized_scalar.as_ref().iter()) .cloned() - .collect::>() - .try_into() - .unwrap(); + .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(serialized_signature).unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } @@ -127,7 +127,7 @@ pub fn round1_package() -> round1::Package { /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); round2::Package::new(signing_share) } diff --git a/frost-ed448/tests/recreation_tests.rs b/frost-ed448/tests/recreation_tests.rs index 1caababa3..e51f8b6ed 100644 --- a/frost-ed448/tests/recreation_tests.rs +++ b/frost-ed448/tests/recreation_tests.rs @@ -54,7 +54,7 @@ fn check_signature_share_recreation() { let encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); + let new_signature_share = SignatureShare::deserialize(&encoded).unwrap(); assert!(signature_share == new_signature_share); } diff --git a/frost-ed448/tests/serialization_tests.rs b/frost-ed448/tests/serialization_tests.rs index 761e56b78..60a56b9a0 100644 --- a/frost-ed448/tests/serialization_tests.rs +++ b/frost-ed448/tests/serialization_tests.rs @@ -49,8 +49,11 @@ fn check_signing_package_postcard_serialization() { fn check_signature_share_postcard_serialization() { let signature_share = samples::signature_share(); let bytes = signature_share.serialize(); - assert_snapshot!(hex::encode(bytes)); - assert_eq!(signature_share, SignatureShare::deserialize(bytes).unwrap()); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signature_share, + SignatureShare::deserialize(&bytes).unwrap() + ); } #[test] fn check_secret_share_postcard_serialization() { diff --git a/frost-p256/tests/helpers/samples.rs b/frost-p256/tests/helpers/samples.rs index 8e5cc4843..340bc7635 100644 --- a/frost-p256/tests/helpers/samples.rs +++ b/frost-p256/tests/helpers/samples.rs @@ -36,8 +36,8 @@ fn scalar1() -> Scalar { pub fn signing_nonces() -> SigningNonces { let serialized_scalar1 = <::Group as Group>::Field::serialize(&scalar1()); let serialized_scalar2 = <::Group as Group>::Field::serialize(&scalar1()); - let hiding_nonce = Nonce::deserialize(serialized_scalar1).unwrap(); - let binding_nonce = Nonce::deserialize(serialized_scalar2).unwrap(); + let hiding_nonce = Nonce::deserialize(serialized_scalar1.as_ref()).unwrap(); + let binding_nonce = Nonce::deserialize(serialized_scalar2.as_ref()).unwrap(); SigningNonces::from_nonces(hiding_nonce, binding_nonce) } @@ -46,8 +46,10 @@ pub fn signing_nonces() -> SigningNonces { pub fn signing_commitments() -> SigningCommitments { let serialized_element1 = ::Group::serialize(&element1()).unwrap(); let serialized_element2 = ::Group::serialize(&element2()).unwrap(); - let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); - let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); + let hiding_nonce_commitment = + NonceCommitment::deserialize(serialized_element1.as_ref()).unwrap(); + let binding_nonce_commitment = + NonceCommitment::deserialize(serialized_element2.as_ref()).unwrap(); SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } @@ -65,7 +67,7 @@ pub fn signing_package() -> SigningPackage { pub fn signature_share() -> SignatureShare { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - SignatureShare::deserialize(serialized_scalar).unwrap() + SignatureShare::deserialize(serialized_scalar.as_ref()).unwrap() } /// Generate a sample SecretShare. @@ -73,7 +75,7 @@ pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -85,10 +87,10 @@ pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) } @@ -97,9 +99,9 @@ pub fn key_package() -> KeyPackage { pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); PublicKeyPackage::new(verifying_shares, verifying_key) @@ -114,12 +116,10 @@ pub fn round1_package() -> round1::Package { .iter() .chain(serialized_scalar.as_ref().iter()) .cloned() - .collect::>() - .try_into() - .unwrap(); + .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(serialized_signature).unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } @@ -127,7 +127,7 @@ pub fn round1_package() -> round1::Package { /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); round2::Package::new(signing_share) } diff --git a/frost-p256/tests/recreation_tests.rs b/frost-p256/tests/recreation_tests.rs index 41ea2802f..a24e57f5c 100644 --- a/frost-p256/tests/recreation_tests.rs +++ b/frost-p256/tests/recreation_tests.rs @@ -54,7 +54,7 @@ fn check_signature_share_recreation() { let encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); + let new_signature_share = SignatureShare::deserialize(&encoded).unwrap(); assert!(signature_share == new_signature_share); } diff --git a/frost-p256/tests/serialization_tests.rs b/frost-p256/tests/serialization_tests.rs index a920b6c2e..187e7fc9f 100644 --- a/frost-p256/tests/serialization_tests.rs +++ b/frost-p256/tests/serialization_tests.rs @@ -49,8 +49,11 @@ fn check_signing_package_postcard_serialization() { fn check_signature_share_postcard_serialization() { let signature_share = samples::signature_share(); let bytes = signature_share.serialize(); - assert_snapshot!(hex::encode(bytes)); - assert_eq!(signature_share, SignatureShare::deserialize(bytes).unwrap()); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signature_share, + SignatureShare::deserialize(&bytes).unwrap() + ); } #[test] fn check_secret_share_postcard_serialization() { diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 9e9109038..0d8a83e23 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -26,7 +26,7 @@ rustdoc-args = ["--cfg", "docsrs"] derive-getters = "0.4.0" document-features = "0.2.7" frost-core = { path = "../frost-core", version = "1.0.0", features = [ - "internals", + "internals" ], default-features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } rand_core = "0.6" diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 174cf0bdf..a309d6f1a 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -17,7 +17,7 @@ extern crate alloc; #[cfg(any(test, feature = "test-impl"))] pub mod tests; -use alloc::{collections::BTreeMap, string::ToString}; +use alloc::{collections::BTreeMap, string::ToString, vec::Vec}; use derive_getters::Getters; pub use frost_core; @@ -27,13 +27,12 @@ use frost_core::SigningPackage; use frost_core::{ self as frost, keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare}, + serialization::SerializableScalar, Ciphersuite, Error, Field, Group, Scalar, VerifyingKey, }; #[cfg(feature = "serde")] use frost_core::serde; -#[cfg(feature = "serde")] -use frost_core::serialization::ScalarSerialization; // When pulled into `reddsa`, that has its own sibling `rand_core` import. // For the time being, we do not re-export this `rand_core`. @@ -75,7 +74,7 @@ impl Randomize for KeyPackage { let signing_share = self.signing_share(); let randomized_signing_share = - SigningShare::new(signing_share.to_scalar() + randomized_params.randomizer.0); + SigningShare::new(signing_share.to_scalar() + randomized_params.randomizer.to_scalar()); let randomized_key_package = KeyPackage::new( *self.identifier(), @@ -161,10 +160,18 @@ where #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] -#[cfg_attr(feature = "serde", serde(try_from = "ScalarSerialization"))] -#[cfg_attr(feature = "serde", serde(into = "ScalarSerialization"))] +#[cfg_attr(feature = "serde", serde(transparent))] #[cfg_attr(feature = "serde", serde(crate = "self::serde"))] -pub struct Randomizer(Scalar); +pub struct Randomizer(SerializableScalar); + +impl Randomizer +where + C: Ciphersuite, +{ + pub(crate) fn to_scalar(self) -> Scalar { + self.0 .0 + } +} impl Randomizer where @@ -202,7 +209,7 @@ where .concat(), ) .ok_or(Error::SerializationError)?; - Ok(Self(randomizer)) + Ok(Self(SerializableScalar(randomizer))) } } @@ -217,43 +224,18 @@ where /// reasons with specifications on how the randomizer must be generated. Use /// [`Randomizer::new()`] instead. pub fn from_scalar(scalar: Scalar) -> Self { - Self(scalar) + Self(SerializableScalar(scalar)) } /// Serialize the identifier using the ciphersuite encoding. - pub fn serialize(&self) -> <::Field as Field>::Serialization { - <::Field>::serialize(&self.0) + pub fn serialize(&self) -> Vec { + self.0.serialize() } /// Deserialize an Identifier from a serialized buffer. /// Returns an error if it attempts to deserialize zero. - pub fn deserialize( - buf: &<::Field as Field>::Serialization, - ) -> Result> { - let scalar = <::Field>::deserialize(buf)?; - Ok(Self(scalar)) - } -} - -#[cfg(feature = "serde")] -impl TryFrom> for Randomizer -where - C: Ciphersuite, -{ - type Error = Error; - - fn try_from(value: ScalarSerialization) -> Result { - Self::deserialize(&value.0) - } -} - -#[cfg(feature = "serde")] -impl From> for ScalarSerialization -where - C: Ciphersuite, -{ - fn from(value: Randomizer) -> Self { - Self(value.serialize()) + pub fn deserialize(buf: &[u8]) -> Result> { + Ok(Self(SerializableScalar::deserialize(buf)?)) } } @@ -263,9 +245,7 @@ where { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_tuple("Randomizer") - .field(&hex::encode( - <::Field>::serialize(&self.0).as_ref(), - )) + .field(&hex::encode(self.0.serialize())) .finish() } } @@ -313,7 +293,7 @@ where group_verifying_key: &VerifyingKey, randomizer: Randomizer, ) -> Self { - let randomizer_element = ::generator() * randomizer.0; + let randomizer_element = ::generator() * randomizer.to_scalar(); let verifying_key_element = group_verifying_key.to_element(); let randomized_verifying_key_element = verifying_key_element + randomizer_element; let randomized_verifying_key = VerifyingKey::::new(randomized_verifying_key_element); diff --git a/frost-rerandomized/src/tests.rs b/frost-rerandomized/src/tests.rs index dc923e5c3..f29c91c96 100644 --- a/frost-rerandomized/src/tests.rs +++ b/frost-rerandomized/src/tests.rs @@ -1,4 +1,5 @@ //! Ciphersuite-generic test functions for re-randomized FROST. +#![cfg(feature = "serialization")] use alloc::borrow::ToOwned; use alloc::collections::BTreeMap; diff --git a/frost-ristretto255/tests/helpers/samples.rs b/frost-ristretto255/tests/helpers/samples.rs index 976511267..f598c53f4 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -36,8 +36,8 @@ fn scalar1() -> Scalar { pub fn signing_nonces() -> SigningNonces { let serialized_scalar1 = <::Group as Group>::Field::serialize(&scalar1()); let serialized_scalar2 = <::Group as Group>::Field::serialize(&scalar1()); - let hiding_nonce = Nonce::deserialize(serialized_scalar1).unwrap(); - let binding_nonce = Nonce::deserialize(serialized_scalar2).unwrap(); + let hiding_nonce = Nonce::deserialize(serialized_scalar1.as_ref()).unwrap(); + let binding_nonce = Nonce::deserialize(serialized_scalar2.as_ref()).unwrap(); SigningNonces::from_nonces(hiding_nonce, binding_nonce) } @@ -46,8 +46,10 @@ pub fn signing_nonces() -> SigningNonces { pub fn signing_commitments() -> SigningCommitments { let serialized_element1 = ::Group::serialize(&element1()).unwrap(); let serialized_element2 = ::Group::serialize(&element2()).unwrap(); - let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); - let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); + let hiding_nonce_commitment = + NonceCommitment::deserialize(serialized_element1.as_ref()).unwrap(); + let binding_nonce_commitment = + NonceCommitment::deserialize(serialized_element2.as_ref()).unwrap(); SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } @@ -65,7 +67,7 @@ pub fn signing_package() -> SigningPackage { pub fn signature_share() -> SignatureShare { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - SignatureShare::deserialize(serialized_scalar).unwrap() + SignatureShare::deserialize(serialized_scalar.as_ref()).unwrap() } /// Generate a sample SecretShare. @@ -73,7 +75,7 @@ pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -85,10 +87,10 @@ pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) } @@ -97,9 +99,9 @@ pub fn key_package() -> KeyPackage { pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); PublicKeyPackage::new(verifying_shares, verifying_key) @@ -114,12 +116,10 @@ pub fn round1_package() -> round1::Package { .iter() .chain(serialized_scalar.as_ref().iter()) .cloned() - .collect::>() - .try_into() - .unwrap(); + .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(serialized_signature).unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } @@ -127,7 +127,7 @@ pub fn round1_package() -> round1::Package { /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); round2::Package::new(signing_share) } diff --git a/frost-ristretto255/tests/recreation_tests.rs b/frost-ristretto255/tests/recreation_tests.rs index c12cad7e2..a8ed937c0 100644 --- a/frost-ristretto255/tests/recreation_tests.rs +++ b/frost-ristretto255/tests/recreation_tests.rs @@ -54,7 +54,7 @@ fn check_signature_share_recreation() { let encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); + let new_signature_share = SignatureShare::deserialize(&encoded).unwrap(); assert!(signature_share == new_signature_share); } diff --git a/frost-ristretto255/tests/serialization_tests.rs b/frost-ristretto255/tests/serialization_tests.rs index 0578a64a6..ffc5dd8bc 100644 --- a/frost-ristretto255/tests/serialization_tests.rs +++ b/frost-ristretto255/tests/serialization_tests.rs @@ -49,8 +49,11 @@ fn check_signing_package_postcard_serialization() { fn check_signature_share_postcard_serialization() { let signature_share = samples::signature_share(); let bytes = signature_share.serialize(); - assert_snapshot!(hex::encode(bytes)); - assert_eq!(signature_share, SignatureShare::deserialize(bytes).unwrap()); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signature_share, + SignatureShare::deserialize(&bytes).unwrap() + ); } #[test] fn check_secret_share_postcard_serialization() { diff --git a/frost-secp256k1/tests/helpers/samples.rs b/frost-secp256k1/tests/helpers/samples.rs index ff22aec8e..3afe78f9c 100644 --- a/frost-secp256k1/tests/helpers/samples.rs +++ b/frost-secp256k1/tests/helpers/samples.rs @@ -36,8 +36,8 @@ fn scalar1() -> Scalar { pub fn signing_nonces() -> SigningNonces { let serialized_scalar1 = <::Group as Group>::Field::serialize(&scalar1()); let serialized_scalar2 = <::Group as Group>::Field::serialize(&scalar1()); - let hiding_nonce = Nonce::deserialize(serialized_scalar1).unwrap(); - let binding_nonce = Nonce::deserialize(serialized_scalar2).unwrap(); + let hiding_nonce = Nonce::deserialize(serialized_scalar1.as_ref()).unwrap(); + let binding_nonce = Nonce::deserialize(serialized_scalar2.as_ref()).unwrap(); SigningNonces::from_nonces(hiding_nonce, binding_nonce) } @@ -46,8 +46,10 @@ pub fn signing_nonces() -> SigningNonces { pub fn signing_commitments() -> SigningCommitments { let serialized_element1 = ::Group::serialize(&element1()).unwrap(); let serialized_element2 = ::Group::serialize(&element2()).unwrap(); - let hiding_nonce_commitment = NonceCommitment::deserialize(serialized_element1).unwrap(); - let binding_nonce_commitment = NonceCommitment::deserialize(serialized_element2).unwrap(); + let hiding_nonce_commitment = + NonceCommitment::deserialize(serialized_element1.as_ref()).unwrap(); + let binding_nonce_commitment = + NonceCommitment::deserialize(serialized_element2.as_ref()).unwrap(); SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) } @@ -65,7 +67,7 @@ pub fn signing_package() -> SigningPackage { pub fn signature_share() -> SignatureShare { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - SignatureShare::deserialize(serialized_scalar).unwrap() + SignatureShare::deserialize(serialized_scalar.as_ref()).unwrap() } /// Generate a sample SecretShare. @@ -73,7 +75,7 @@ pub fn secret_share() -> SecretShare { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); @@ -85,10 +87,10 @@ pub fn key_package() -> KeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) } @@ -97,9 +99,9 @@ pub fn key_package() -> KeyPackage { pub fn public_key_package() -> PublicKeyPackage { let identifier = 42u16.try_into().unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_share = VerifyingShare::deserialize(serialized_element).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); let serialized_element = ::Group::serialize(&element1()).unwrap(); - let verifying_key = VerifyingKey::deserialize(serialized_element).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); PublicKeyPackage::new(verifying_shares, verifying_key) @@ -114,12 +116,10 @@ pub fn round1_package() -> round1::Package { .iter() .chain(serialized_scalar.as_ref().iter()) .cloned() - .collect::>() - .try_into() - .unwrap(); + .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(serialized_signature).unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } @@ -127,7 +127,7 @@ pub fn round1_package() -> round1::Package { /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); - let signing_share = SigningShare::deserialize(serialized_scalar).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); round2::Package::new(signing_share) } diff --git a/frost-secp256k1/tests/recreation_tests.rs b/frost-secp256k1/tests/recreation_tests.rs index 0806e55f3..bb2f83158 100644 --- a/frost-secp256k1/tests/recreation_tests.rs +++ b/frost-secp256k1/tests/recreation_tests.rs @@ -54,7 +54,7 @@ fn check_signature_share_recreation() { let encoded = signature_share.serialize(); - let new_signature_share = SignatureShare::deserialize(encoded).unwrap(); + let new_signature_share = SignatureShare::deserialize(&encoded).unwrap(); assert!(signature_share == new_signature_share); } diff --git a/frost-secp256k1/tests/serialization_tests.rs b/frost-secp256k1/tests/serialization_tests.rs index 0a304a491..d02c45beb 100644 --- a/frost-secp256k1/tests/serialization_tests.rs +++ b/frost-secp256k1/tests/serialization_tests.rs @@ -49,8 +49,11 @@ fn check_signing_package_postcard_serialization() { fn check_signature_share_postcard_serialization() { let signature_share = samples::signature_share(); let bytes = signature_share.serialize(); - assert_snapshot!(hex::encode(bytes)); - assert_eq!(signature_share, SignatureShare::deserialize(bytes).unwrap()); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signature_share, + SignatureShare::deserialize(&bytes).unwrap() + ); } #[test] fn check_secret_share_postcard_serialization() { From f95aeb22d1e7ede968f7916340c66ebb9683ae50 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 20 Jun 2024 13:41:39 -0300 Subject: [PATCH 030/175] Bump versions to 2.0.0-rc.0 (#691) * bump versions to 2.0.0-rc.0 * simplify all features test * remove build all features test for now --- .github/workflows/main.yml | 33 +++++++++++++++++++-------------- book/src/tutorial/importing.md | 4 ++-- frost-core/CHANGELOG.md | 9 ++------- frost-core/Cargo.toml | 2 +- frost-ed25519/Cargo.toml | 10 +++++----- frost-ed448/Cargo.toml | 10 +++++----- frost-p256/Cargo.toml | 10 +++++----- frost-rerandomized/Cargo.toml | 4 ++-- frost-ristretto255/Cargo.toml | 10 +++++----- frost-secp256k1/Cargo.toml | 10 +++++----- 10 files changed, 51 insertions(+), 51 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9d14a1cab..4a75ffb7d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,20 +38,25 @@ jobs: - uses: dtolnay/rust-toolchain@1.66.1 - run: cargo build --all-features - test_all_features: - name: test all features combinations - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4.1.7 - - uses: dtolnay/rust-toolchain@stable - - run: cargo install cargo-all-features - # We check and then test because some test dependencies could help - # a bugged build work, while a regular build would fail. - - run: cargo check-all-features --release - # Note that this also tests each crate separately, which also helps - # catching some issues. - - run: cargo test-all-features --release + # TODO: this is filling up the disk space in CI. See if there is a way to + # workaround it. + + # build_all_features: + # name: build all features combinations + # runs-on: ubuntu-latest + + # steps: + # - uses: actions/checkout@v4.1.7 + # - uses: dtolnay/rust-toolchain@stable + # - run: cargo install cargo-all-features + # # We check and then test because some test dependencies could help + # # a bugged build work, while a regular build would fail. + # # Note that this also builds each crate separately, which also helps + # # catching some issues. + # - run: cargo check-all-features + # # Build all tests. We don't run them to save time, since it's unlikely + # # that tests would fail due to feature combinations. + # - run: cargo test-all-features --no-run build_no_std: name: build with no_std diff --git a/book/src/tutorial/importing.md b/book/src/tutorial/importing.md index 7fd3c5631..56d01746f 100644 --- a/book/src/tutorial/importing.md +++ b/book/src/tutorial/importing.md @@ -6,7 +6,7 @@ Add to your `Cargo.toml` file: ``` [dependencies] -frost-ristretto255 = "1.0.0" +frost-ristretto255 = "2.0.0-rc.0" ``` ## Handling errors @@ -38,7 +38,7 @@ needs to be transmitted. The importing would look like: ``` [dependencies] -frost-ristretto255 = { version = "0.7.0", features = ["serde"] } +frost-ristretto255 = { version = "2.0.0-rc.0", features = ["serde"] } ``` Note that serde usage is optional. Applications can use different encodings, and diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 354325c30..abd9af084 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,6 +4,8 @@ Entries are listed in reverse chronological order. ## Unreleased +## 2.0.0-rc.0 + * Changed the `deserialize()` function of Elements and structs containing Elements to return an error if the element is the identity. This is a requirement in the FROST specification that wasn't being followed. We are not @@ -32,10 +34,6 @@ Entries are listed in reverse chronological order. * Removed `batch::Item::into()` which created a batch Item from a triple of VerifyingKey, Signature and message. Use the new `batch::Item::new()` instead (which can return an error). -* Removed the `MulAssign> for Scalar` implementation since it - will result in a coherence error in future Rust versions (see #625). In the - unlikely case you're using this, you can replace e.g. `scalar *= identifier` - with `scalar = identifier * scalar`. * Add no-std support to all crates except frost-ed448. To use, do not enable the `std` feature that is enabled by default (i.e. use `default-features = false`); Note that it always links to an external `alloc` crate (i.e. there is @@ -44,9 +42,6 @@ Entries are listed in reverse chronological order. breaking change if you are disabling default features but rely on `Error` implementing `std::error::Error`. In that case, simply enable the `std` feature. - -## 1.0.1 - * Fixed `no-default-features`, previously it wouldn't compile. * Fixed some feature handling that would include unneeded dependencies in some cases. diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 0f45e3880..be3531415 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" # When releasing to crates.io: # - Update CHANGELOG.md # - Create git tag. -version = "1.0.0" +version = "2.0.0-rc.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index c90007bb7..5c1833bbb 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # - Update html_root_url # - Update CHANGELOG.md # - Create git tag. -version = "1.0.0" +version = "2.0.0-rc.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -25,15 +25,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } ed25519-dalek = "2.0.0" insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 1ee169b36..ead6ef371 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" # When releasing to crates.io: # - Update CHANGELOG.md # - Create git tag. -version = "1.0.0" +version = "2.0.0-rc.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -24,15 +24,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" ed448-goldilocks = { version = "0.9.0" } -frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } rand_core = "0.6" sha3 = { version = "0.10.6", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } lazy_static = "1.4" insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index 4124d5cdf..b55969e9b 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # - Update html_root_url # - Update CHANGELOG.md # - Create git tag. -version = "1.0.0" +version = "2.0.0-rc.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -25,15 +25,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" p256 = { version = "0.13.0", features = ["hash2curve"], default-features = false } -frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 0d8a83e23..b4394f560 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # - Update html_root_url # - Update CHANGELOG.md # - Create git tag. -version = "1.0.0" +version = "2.0.0-rc.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -25,7 +25,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] derive-getters = "0.4.0" document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "1.0.0", features = [ +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = [ "internals" ], default-features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 95f28af92..677ecc926 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # - Update html_root_url # - Update CHANGELOG.md # - Create git tag. -version = "1.0.0" +version = "2.0.0-rc.0" authors = ["Deirdre Connolly ", "Chelsea Komlo ", "Conrado Gouvea "] readme = "README.md" license = "MIT OR Apache-2.0" @@ -21,15 +21,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } -frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index a7bf2ebf7..cf3afc6c0 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" # When releasing to crates.io: # - Update CHANGELOG.md # - Create git tag. -version = "1.0.0" +version = "2.0.0-rc.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -23,16 +23,16 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "1.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "1.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "1.0.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" From 60c62b1052fcfbdc1579b98b590c9ea9b47d7e58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:45:03 +0000 Subject: [PATCH 031/175] Bump actions/checkout from 4.1.1 to 4.1.7 (#692) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.1...v4.1.7) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a75ffb7d..96e07fb0e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -66,7 +66,7 @@ jobs: matrix: crate: [ristretto255, ed25519, p256, secp256k1, rerandomized] steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.7 - uses: dtolnay/rust-toolchain@master with: toolchain: stable From 5fa454df9cf39700dda146a118074fe91961c0fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 14:45:06 +0000 Subject: [PATCH 032/175] Bump reviewdog/action-actionlint from 1.50.0 to 1.51.0 (#694) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.50.0 to 1.51.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.50.0...v1.51.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96e07fb0e..dd1b60422 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -193,7 +193,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.7 - - uses: reviewdog/action-actionlint@v1.50.0 + - uses: reviewdog/action-actionlint@v1.51.0 with: level: warning fail_on_error: false From a0d900bce6ff67786b7352213f9e29a664be3707 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 10 Jul 2024 08:19:20 -0300 Subject: [PATCH 033/175] docs: update spec links to the final URL (#700) --- frost-core/src/keys.rs | 8 ++++---- frost-core/src/lib.rs | 14 +++++++------- frost-core/src/round1.rs | 10 +++++----- frost-core/src/round2.rs | 4 ++-- frost-core/src/traits.rs | 24 ++++++++++++------------ frost-ed25519/src/lib.rs | 12 ++++++------ frost-ed448/src/lib.rs | 12 ++++++------ frost-p256/src/lib.rs | 12 ++++++------ frost-ristretto255/src/lib.rs | 12 ++++++------ frost-secp256k1/src/lib.rs | 12 ++++++------ 10 files changed, 60 insertions(+), 60 deletions(-) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 8b7b9c68b..ba24b13b3 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -416,7 +416,7 @@ where /// This also implements `derive_group_info()` from the [spec] (which is very similar), /// but only for this participant. /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#appendix-C.2-4 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#appendix-C.2-3 pub fn verify(&self) -> Result<(VerifyingShare, VerifyingKey), Error> { let f_result = ::generator() * self.signing_share.to_scalar(); let result = evaluate_vss(self.identifier, &self.commitment); @@ -466,7 +466,7 @@ pub enum IdentifierList<'a, C: Ciphersuite> { /// /// Implements [`trusted_dealer_keygen`] from the spec. /// -/// [`trusted_dealer_keygen`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#appendix-C +/// [`trusted_dealer_keygen`]: https://datatracker.ietf.org/doc/html/rfc9591#appendix-C pub fn generate_with_dealer( max_signers: u16, min_signers: u16, @@ -541,7 +541,7 @@ pub fn split( /// /// Implements [`polynomial_evaluate`] from the spec. /// -/// [`polynomial_evaluate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-evaluation-of-a-polynomial +/// [`polynomial_evaluate`]: https://datatracker.ietf.org/doc/html/rfc9591#name-additional-polynomial-opera fn evaluate_polynomial( identifier: Identifier, coefficients: &[Scalar], @@ -830,7 +830,7 @@ pub(crate) fn generate_secret_polynomial( /// /// Implements [`secret_share_shard`] from the spec. /// -/// [`secret_share_shard`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#appendix-C.1 +/// [`secret_share_shard`]: https://datatracker.ietf.org/doc/html/rfc9591#name-shamir-secret-sharing pub(crate) fn generate_secret_shares( secret: &SigningKey, max_signers: u16, diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index b54f3cc3c..b93195ecf 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -62,7 +62,7 @@ pub use verifying_key::VerifyingKey; /// A type refinement for the scalar field element representing the per-message _[challenge]_. /// -/// [challenge]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-challenge-computa +/// [challenge]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-challenge-computa #[derive(Copy, Clone)] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] @@ -114,8 +114,8 @@ where /// /// This is the only invocation of the H2 hash function from the [RFC]. /// -/// [FROST]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-challenge-computa -/// [RFC]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.2 +/// [FROST]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-challenge-computa +/// [RFC]: https://datatracker.ietf.org/doc/html/rfc9591#name-cryptographic-hash-function #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] fn challenge( @@ -241,7 +241,7 @@ where /// [`compute_binding_factors`] in the spec /// -/// [`compute_binding_factors`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-4.4 +/// [`compute_binding_factors`]: https://datatracker.ietf.org/doc/html/rfc9591#name-binding-factors-computation #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn compute_binding_factor_list( @@ -339,7 +339,7 @@ fn compute_lagrange_coefficient( /// /// Implements [`derive_interpolating_value()`] from the spec. /// -/// [`derive_interpolating_value()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-polynomials +/// [`derive_interpolating_value()`]: https://datatracker.ietf.org/doc/html/rfc9591#name-polynomials #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] fn derive_interpolating_value( @@ -489,7 +489,7 @@ where /// /// Implements [`compute_group_commitment`] from the spec. /// -/// [`compute_group_commitment`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-4.5 +/// [`compute_group_commitment`]: https://datatracker.ietf.org/doc/html/rfc9591#name-group-commitment-computatio #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] fn compute_group_commitment( @@ -595,7 +595,7 @@ where // // Implements [`aggregate`] from the spec. // - // [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-5.3 + // [`aggregate`]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-share-aggregation let mut z = <::Field>::zero(); for signature_share in signature_shares.values() { diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index dfe1eb0f6..27e1c229e 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -43,7 +43,7 @@ where /// /// An implementation of `nonce_generate(secret)` from the [spec]. /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-nonce-generation + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#name-nonce-generation pub fn new(secret: &SigningShare, rng: &mut R) -> Self where R: CryptoRng + RngCore, @@ -314,9 +314,9 @@ where } } - /// Computes the [signature commitment share] from these round one signing commitments. + /// Computes the [commitment share] from these round one signing commitments. /// - /// [signature commitment share]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio + /// [commitment share]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-share-aggregation #[cfg(any(feature = "internals", feature = "cheater-detection"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] @@ -368,7 +368,7 @@ pub struct GroupCommitmentShare(pub(super) Element); /// Returns a byte string containing the serialized representation of the /// commitment list. /// -/// [`encode_group_commitment_list()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-list-operations +/// [`encode_group_commitment_list()`]: https://datatracker.ietf.org/doc/html/rfc9591#name-list-operations pub(super) fn encode_group_commitments( signing_commitments: &BTreeMap, SigningCommitments>, ) -> Result, Error> { @@ -422,7 +422,7 @@ where /// Generates the signing nonces and commitments to be used in the signing /// operation. /// -/// [`commit`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-one-commitment +/// [`commit`]: https://datatracker.ietf.org/doc/html/rfc9591#name-round-one-commitment pub fn commit( secret: &SigningShare, rng: &mut R, diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index a6a0c18d8..7b0fd4502 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -58,7 +58,7 @@ where /// /// This is the final step of [`verify_signature_share`] from the spec. /// - /// [`verify_signature_share`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-signature-share-verificatio + /// [`verify_signature_share`]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-share-aggregation #[cfg(any(feature = "cheater-detection", feature = "internals"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] @@ -121,7 +121,7 @@ fn compute_signature_share( /// Assumes the participant has already determined which nonce corresponds with /// the commitment that was assigned by the coordinator in the SigningPackage. /// -/// [`sign`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-round-two-signature-share-g +/// [`sign`]: https://datatracker.ietf.org/doc/html/rfc9591#name-round-two-signature-share-g pub fn sign( signing_package: &SigningPackage, signer_nonces: &round1::SigningNonces, diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index b96f27b0b..a1ff62fe1 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -43,13 +43,13 @@ pub trait Field: Copy + Clone { /// Generate a random scalar from the entire space [0, l-1] /// - /// + /// fn random(rng: &mut R) -> Self::Scalar; /// A member function of a [`Field`] that maps a [`Scalar`] to a unique byte array buf of /// fixed length Ne. /// - /// + /// fn serialize(scalar: &Self::Scalar) -> Self::Serialization; /// A member function of a [`Field`] that maps a [`Scalar`] to a unique byte array buf of @@ -63,7 +63,7 @@ pub trait Field: Copy + Clone { /// Fails if the input is not a valid byte representation of an [`Scalar`] of the /// [`Field`]. This function can raise an [`Error`] if deserialization fails. /// - /// + /// fn deserialize(buf: &Self::Serialization) -> Result; } @@ -104,21 +104,21 @@ pub trait Group: Copy + Clone + PartialEq { /// Additive [identity] of the prime order group. /// - /// [identity]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.2 + /// [identity]: https://datatracker.ietf.org/doc/html/rfc9591#section-3.1-4.4 fn identity() -> Self::Element; /// The fixed generator element of the prime order group. /// /// The 'base' of ['ScalarBaseMult()'] from the spec. /// - /// [`ScalarBaseMult()`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-3.1-3.5 + /// [`ScalarBaseMult()`]: https://datatracker.ietf.org/doc/html/rfc9591#section-3.1-4.10 fn generator() -> Self::Element; /// A member function of a group _G_ that maps an [`Element`] to a unique /// byte array buf of fixed length Ne. This function raises an error if the /// element is the identity element of the group. /// - /// + /// fn serialize(element: &Self::Element) -> Result; /// A member function of a [`Group`] that attempts to map a byte array `buf` to an [`Element`]. @@ -127,7 +127,7 @@ pub trait Group: Copy + Clone + PartialEq { /// [`Group`]. This function can raise an [`Error`] if deserialization fails or if the /// resulting [`Element`] is the identity element of the group /// - /// + /// fn deserialize(buf: &Self::Serialization) -> Result; } @@ -137,7 +137,7 @@ pub type Element = <::Group as Group>::Element; /// A [FROST ciphersuite] specifies the underlying prime-order group details and cryptographic hash /// function. /// -/// [FROST ciphersuite]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-ciphersuites +/// [FROST ciphersuite]: https://datatracker.ietf.org/doc/html/rfc9591#name-ciphersuites pub trait Ciphersuite: Copy + Clone + PartialEq + Debug { /// The ciphersuite ID string. It should be equal to the contextString in /// the spec. For new ciphersuites, this should be a string that identifies @@ -159,28 +159,28 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug { /// /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. /// - /// [H1]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function + /// [H1]: https://datatracker.ietf.org/doc/html/rfc9591#name-cryptographic-hash-function fn H1(m: &[u8]) -> <::Field as Field>::Scalar; /// [H2] for a FROST ciphersuite. /// /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. /// - /// [H2]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function + /// [H2]: https://datatracker.ietf.org/doc/html/rfc9591#name-cryptographic-hash-function fn H2(m: &[u8]) -> <::Field as Field>::Scalar; /// [H3] for a FROST ciphersuite. /// /// Maps arbitrary inputs to `Self::Scalar` elements of the prime-order group scalar field. /// - /// [H3]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function + /// [H3]: https://datatracker.ietf.org/doc/html/rfc9591#name-cryptographic-hash-function fn H3(m: &[u8]) -> <::Field as Field>::Scalar; /// [H4] for a FROST ciphersuite. /// /// Usually an an alias for the ciphersuite hash function _H_ with domain separation applied. /// - /// [H4]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#name-cryptographic-hash-function + /// [H4]: https://datatracker.ietf.org/doc/html/rfc9591#name-cryptographic-hash-function fn H4(m: &[u8]) -> Self::HashOutput; /// [H5] for a FROST ciphersuite. diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 355e37a0c..1e33b2d9c 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -156,7 +156,7 @@ fn hash_to_scalar(inputs: &[&[u8]]) -> Scalar { /// Context string from the ciphersuite in the [spec] /// -/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-1 +/// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.1-1 const CONTEXT_STRING: &str = "FROST-ED25519-SHA512-v1"; /// An implementation of the FROST(Ed25519, SHA-512) ciphersuite. @@ -174,35 +174,35 @@ impl Ciphersuite for Ed25519Sha512 { /// H1 for FROST(Ed25519, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.1 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.1-2.4.2.2 fn H1(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"rho", m]) } /// H2 for FROST(Ed25519, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.2 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.1-2.4.2.4 fn H2(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[m]) } /// H3 for FROST(Ed25519, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.3 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.1-2.4.2.6 fn H3(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"nonce", m]) } /// H4 for FROST(Ed25519, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.4 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.1-2.4.2.8 fn H4(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m]) } /// H5 for FROST(Ed25519, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.1-2.2.2.5 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.1-2.4.2.10 fn H5(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m]) } diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index f867c4949..4ceb707e8 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -150,7 +150,7 @@ fn hash_to_scalar(inputs: &[&[u8]]) -> Scalar { /// Context string from the ciphersuite in the [spec] /// -/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.3-1 +/// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.3-1 const CONTEXT_STRING: &str = "FROST-ED448-SHAKE256-v1"; /// An implementation of the FROST(Ed448, SHAKE256) ciphersuite. @@ -168,35 +168,35 @@ impl Ciphersuite for Ed448Shake256 { /// H1 for FROST(Ed448, SHAKE256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.3-2.2.2.1 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.3-2.4.2.2 fn H1(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"rho", m]) } /// H2 for FROST(Ed448, SHAKE256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.3-2.2.2.2 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.3-2.4.2.4 fn H2(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[b"SigEd448\0\0", m]) } /// H3 for FROST(Ed448, SHAKE256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.3-2.2.2.3 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.3-2.4.2.6 fn H3(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"nonce", m]) } /// H4 for FROST(Ed448, SHAKE256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.3-2.2.2.4 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.3-2.4.2.8 fn H4(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m]) } /// H5 for FROST(Ed448, SHAKE256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.3-2.2.2.5 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.3-2.4.2.10 fn H5(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m]) } diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 2c54b10f4..7491d3d6f 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -168,7 +168,7 @@ fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { /// Context string from the ciphersuite in the [spec] /// -/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.4-1 +/// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-1 const CONTEXT_STRING: &str = "FROST-P256-SHA256-v1"; /// An implementation of the FROST(P-256, SHA-256) ciphersuite. @@ -186,35 +186,35 @@ impl Ciphersuite for P256Sha256 { /// H1 for FROST(P-256, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.4-2.2.2.1 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.2 fn H1(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar((CONTEXT_STRING.to_owned() + "rho").as_bytes(), m) } /// H2 for FROST(P-256, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.4-2.2.2.2 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.4 fn H2(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar((CONTEXT_STRING.to_owned() + "chal").as_bytes(), m) } /// H3 for FROST(P-256, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.4-2.2.2.3 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.6 fn H3(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar((CONTEXT_STRING.to_owned() + "nonce").as_bytes(), m) } /// H4 for FROST(P-256, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.4-2.2.2.4 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.8 fn H4(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m]) } /// H5 for FROST(P-256, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.4-2.2.2.5 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.10 fn H5(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m]) } diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index f6a608c43..d82d54052 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -142,7 +142,7 @@ fn hash_to_scalar(inputs: &[&[u8]]) -> Scalar { /// Context string from the ciphersuite in the [spec]. /// -/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.2-1 +/// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.2-1 const CONTEXT_STRING: &str = "FROST-RISTRETTO255-SHA512-v1"; /// An implementation of the FROST(ristretto255, SHA-512) ciphersuite. @@ -160,35 +160,35 @@ impl Ciphersuite for Ristretto255Sha512 { /// H1 for FROST(ristretto255, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.2-2.2.2.1 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.2-2.4.2.2 fn H1(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"rho", m]) } /// H2 for FROST(ristretto255, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.2-2.2.2.2 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.2-2.4.2.4 fn H2(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"chal", m]) } /// H3 for FROST(ristretto255, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.2-2.2.2.3 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.2-2.4.2.6 fn H3(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"nonce", m]) } /// H4 for FROST(ristretto255, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.2-2.2.2.4 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.2-2.4.2.8 fn H4(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m]) } /// H5 for FROST(ristretto255, SHA-512) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.2-2.2.2.5 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.2-2.4.2.10 fn H5(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m]) } diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 1ebeaedb2..4d25266ba 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -168,7 +168,7 @@ fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { /// Context string from the ciphersuite in the [spec]. /// -/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-1 +/// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-1 const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-v1"; /// An implementation of the FROST(secp256k1, SHA-256) ciphersuite. @@ -186,35 +186,35 @@ impl Ciphersuite for Secp256K1Sha256 { /// H1 for FROST(secp256k1, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.1 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.2 fn H1(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar((CONTEXT_STRING.to_owned() + "rho").as_bytes(), m) } /// H2 for FROST(secp256k1, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.2 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.4 fn H2(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar((CONTEXT_STRING.to_owned() + "chal").as_bytes(), m) } /// H3 for FROST(secp256k1, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.3 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.6 fn H3(m: &[u8]) -> <::Field as Field>::Scalar { hash_to_scalar((CONTEXT_STRING.to_owned() + "nonce").as_bytes(), m) } /// H4 for FROST(secp256k1, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.4 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.8 fn H4(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m]) } /// H5 for FROST(secp256k1, SHA-256) /// - /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.5 + /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.10 fn H5(m: &[u8]) -> Self::HashOutput { hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m]) } From 60cc5b9124f542e0c23bc9201cb7323e404bc057 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 10 Jul 2024 08:33:54 -0300 Subject: [PATCH 034/175] docs: update demo section per new ciphersuite selection (#699) --- book/src/zcash/ywallet-demo.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index 54c0dc858..cccc95b4f 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -46,7 +46,7 @@ Run the following (it will take a bit to compile): ``` cd frost-zcash-demo/ -cargo run --bin trusted-dealer --features redpallas +cargo run --bin trusted-dealer -- -C redpallas ``` This will by default generate a 2-of-3 key shares. The public key package @@ -59,7 +59,7 @@ to the commend above for the command line help. If you want to use DKG instead of Trusted Dealer, instead of the command above, run this for each participant, in separate terminals for each: -`cargo run --bin dkg --features redpallas` +`cargo run --bin dkg -- -C redpallas` and follow the instructions. (There will be a considerable amount of copy&pasting!) @@ -168,14 +168,14 @@ In the first one, the server, run (in the same folder where key generation was run): ``` -RUST_LOG=debug cargo run --bin server --features redpallas -- +RUST_LOG=debug cargo run --bin server ``` In the second one, the Coordinator, run (in the same folder where key generation was run): ``` -cargo run --bin coordinator --features redpallas -- --http -r - +cargo run --bin coordinator -- -C redpallas --http -r - ``` And then: @@ -189,14 +189,14 @@ In the third terminal, Participant 1, run the following, replacing `` with the session ID you have just copied: ``` -cargo run --bin participant --features redpallas -- --cli --key-package key-package-1.json -s +cargo run --bin participant -- -C redpallas --http --key-package key-package-1.json -s ``` In the fourth terminal, for Participant 2, run the following, again replacing the session ID: ``` -cargo run --bin participant --features redpallas -- --cli --key-package key-package-2.json -s +cargo run --bin participant -- -C redpallas --http --key-package key-package-2.json -s ``` Go back to the Coordinator CLI: From 835a3f0cdc4b55b003b2f7763d3148dee8f3219b Mon Sep 17 00:00:00 2001 From: natalie Date: Thu, 18 Jul 2024 23:33:10 +0100 Subject: [PATCH 035/175] Add refresh shares with dealer functionality (#665) * Add refresh shares with dealer functionality (#245) * Change refresh share API (#245) Split refresh_shares_with_dealer into calculate_zero_key and refresh_share * Fix serialisation error with refresh share (#245) Add serialisation test * Fix serialisation errors after updates (#245) Fixed some typos * Update refresh_share to accept and return a KeyPackage instead of SecretShare (#245) * Tidy up refresh share functionality (#245) * Add refresh share functionality to Book (#245) Diagram is still to be added * Update book for rereshing shares with trusted dealer (#245) * Add new verifying shares calculation for refresh shares (#245) Add tests for invalid identifiers when refreshing shares * Rename calculate_zero_key to compute_refreshing_shares (#245) * Import Vec from the alloc crate (#245) This is to be compatible with the no_std attribute * Use alloc crate instead of std for refresh shares (#245) * Fix fmt error (#245) * Refactoring refresh shares functionality (#245) * cleanups during review * Update book/src/tutorial/refreshing-shares.md * update docs * always return error in detect_cheater * add changelog entry --------- Co-authored-by: Conrado Gouvea Co-authored-by: Conrado Gouvea --- README.md | 1 + book/src/SUMMARY.md | 1 + book/src/frost.md | 33 +++ book/src/tutorial/refreshing-shares.md | 34 +++ book/src/tutorial/refreshing.png | 0 frost-core/CHANGELOG.md | 3 + frost-core/src/keys.rs | 1 + frost-core/src/keys/refresh.rs | 116 +++++++++ frost-core/src/lib.rs | 102 +++++--- frost-core/src/serialization.rs | 4 +- frost-core/src/tests.rs | 1 + frost-core/src/tests/ciphersuite_generic.rs | 52 +++- frost-core/src/tests/refresh.rs | 241 ++++++++++++++++++ frost-ed25519/src/keys/refresh.rs | 35 +++ frost-ed25519/tests/integration_tests.rs | 116 +++++++++ frost-ed448/src/keys/refresh.rs | 35 +++ frost-ed448/tests/integration_tests.rs | 116 +++++++++ frost-p256/src/keys/refresh.rs | 35 +++ frost-p256/src/lib.rs | 1 + frost-p256/tests/integration_tests.rs | 116 +++++++++ frost-ristretto255/src/keys/refresh.rs | 35 +++ frost-ristretto255/src/lib.rs | 1 + frost-ristretto255/tests/integration_tests.rs | 117 +++++++++ frost-secp256k1/src/keys/refresh.rs | 35 +++ frost-secp256k1/tests/integration_tests.rs | 116 +++++++++ gencode/src/main.rs | 1 + 26 files changed, 1293 insertions(+), 55 deletions(-) create mode 100644 book/src/tutorial/refreshing-shares.md create mode 100644 book/src/tutorial/refreshing.png create mode 100644 frost-core/src/keys/refresh.rs create mode 100644 frost-core/src/tests/refresh.rs create mode 100644 frost-ed25519/src/keys/refresh.rs create mode 100644 frost-ed448/src/keys/refresh.rs create mode 100644 frost-p256/src/keys/refresh.rs create mode 100644 frost-ristretto255/src/keys/refresh.rs create mode 100644 frost-secp256k1/src/keys/refresh.rs diff --git a/README.md b/README.md index 1774125d0..8f22461a4 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Besides FROST itself, this repository also provides: - Distributed key generation as specified in the original paper [FROST20](https://eprint.iacr.org/2020/852.pdf); - Repairable Threshold Scheme (RTS) from ['A Survey and Refinement of Repairable Threshold Schemes'](https://eprint.iacr.org/2017/1155) which allows a participant to recover a lost share with the help of a threshold of other participants; - Rerandomized FROST (paper under review). +- Refresh Share functionality using a Trusted Dealer. This can be used to refresh the shares of the participants or to remove a participant. ## Getting Started diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 4cf983c78..3970f8e06 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -7,6 +7,7 @@ - [Trusted Dealer Key Generation](tutorial/trusted-dealer.md) - [Signing](tutorial/signing.md) - [Distributed Key Generation](tutorial/dkg.md) + - [Refreshing Shares](tutorial/refreshing-shares.md) - [User Documentation](user.md) - [Serialization Format](user/serialization.md) - [FROST with Zcash](zcash.md) diff --git a/book/src/frost.md b/book/src/frost.md index dd7591ee0..1daa881f9 100644 --- a/book/src/frost.md +++ b/book/src/frost.md @@ -83,6 +83,39 @@ is still free to start the process with only 2 participants if they wish. Signature verification is carried out as normal with single-party signatures, along with the signed message and the group verifying key as inputs. +## Repairing + +Repairing shares allow participants to help another participant recover their +share if they have lost it, or also issue a new share to a new participant +(while keeping the same threshold). + +The repair share functionality requires a threshold of participants to work. +For example, in a 2-of-3 scenario, two participants can help the third recover +their share, or they could issue a new share to move to a 2-of-4 group. + +The functionality works in such a way that each participant running the repair +share function is not able to obtain the share that is being recovered or +issued. + +## Refreshing + +Refreshing shares allow participants (or a subset of them) to update their +shares in a way that maintains the same group public key. Some applications are: + +- Make it harder for attackers to compromise the shares. For example, in a + 2-of-3 threshold scenario, if an attacker steals one participant's device and + all participants refresh their shares, the attacker will need to start over + and steal two shares instead of just one more. +- Remove a participant from the group. For example, in a 2-of-3 threshold + scenario, if two participants decide to remove the third they can both refresh + their shares and the third participant would no longer be able to participate + in signing sessions with the others. (They can also then use the repair share + functionality to issue a new share and move from 2-of-2 back to 2-of-3.) + +```admonish note +This is also possible via Distributed Key Generation but this has not yet been +implemented. +``` ## Ciphersuites diff --git a/book/src/tutorial/refreshing-shares.md b/book/src/tutorial/refreshing-shares.md new file mode 100644 index 000000000..cf75b3daa --- /dev/null +++ b/book/src/tutorial/refreshing-shares.md @@ -0,0 +1,34 @@ +# Refreshing Shares using a Trusted Dealer + +The diagram below shows the refresh share process. Dashed lines +represent data being sent through an [authenticated and confidential communication +channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). + + + +The Trusted Dealer needs to first run `compute_refreshing_shares()` which +returns SecretShares (the "refreshing shares") and a PublicKeyPackage. Each +`SecretShare` must then be sent along with the `PublicKeyPackage` via an +[**authenticated** and **confidential** channel +](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) for each +participant. + +Each Participant then runs `refresh_share()` to generate a new `KeyPackage` +which will replace their old `KeyPackage`; they must also replace their old +`PublicKeyPackage` with the one sent by the Trusted Dealer. + +```admonish danger +The refreshed `KeyPackage` contents must be stored securely and the original +`KeyPackage` should be deleted. For example: + +- Make sure other users in the system can't read it; +- If possible, use the OS secure storage such that the package + contents can only be opened with the user's password or biometrics. +``` + +```admonish danger +Applications should first ensure that all participants who refreshed their +`KeyPackages` were actually able to do so successfully, before deleting their old +`KeyPackages`. How this is done is up to the application; it might require +sucessfully generating a signature with all of those participants. +``` diff --git a/book/src/tutorial/refreshing.png b/book/src/tutorial/refreshing.png new file mode 100644 index 000000000..e69de29bb diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index abd9af084..515941b56 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,6 +4,9 @@ Entries are listed in reverse chronological order. ## Unreleased +* Added refresh share functionality for trusted dealer: + `frost_core::keys::refresh::{compute_refreshing_shares, refresh_share}` + ## 2.0.0-rc.0 * Changed the `deserialize()` function of Elements and structs containing diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index ba24b13b3..5a884e425 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -29,6 +29,7 @@ use crate::serialization::{Deserialize, Serialize}; use super::compute_lagrange_coefficient; pub mod dkg; +pub mod refresh; pub mod repairable; /// Sum the commitments from all participants in a distributed key generation diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs new file mode 100644 index 000000000..53a3cdd91 --- /dev/null +++ b/frost-core/src/keys/refresh.rs @@ -0,0 +1,116 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +use crate::{ + keys::{ + generate_coefficients, generate_secret_shares, validate_num_of_signers, + CoefficientCommitment, PublicKeyPackage, SigningKey, SigningShare, VerifyingShare, + }, + Ciphersuite, CryptoRng, Error, Field, Group, Identifier, RngCore, +}; + +use super::{KeyPackage, SecretShare, VerifiableSecretSharingCommitment}; + +/// Generates new zero key shares and a public key package using a trusted +/// dealer Building a new public key package is done by taking the verifying +/// shares from the new public key package and adding them to the original +/// verifying shares +pub fn compute_refreshing_shares( + pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + rng: &mut R, +) -> Result<(Vec>, PublicKeyPackage), Error> { + // Validate inputs + if identifiers.len() != max_signers as usize { + return Err(Error::IncorrectNumberOfIdentifiers); + } + validate_num_of_signers(min_signers, max_signers)?; + + // Build refreshing shares + let refreshing_key = SigningKey { + scalar: <::Field>::zero(), + }; + + let coefficients = generate_coefficients::(min_signers as usize - 1, rng); + let refreshing_shares = generate_secret_shares( + &refreshing_key, + max_signers, + min_signers, + coefficients, + identifiers, + )?; + + let mut refreshed_verifying_shares: BTreeMap, VerifyingShare> = + BTreeMap::new(); + let mut refreshing_shares_minus_identity: Vec> = Vec::new(); + + for mut share in refreshing_shares { + let refreshing_verifying_share: VerifyingShare = SigningShare::into(share.signing_share); + + let verifying_share = pub_key_package.verifying_shares.get(&share.identifier); + + match verifying_share { + Some(verifying_share) => { + let refreshed_verifying_share = + refreshing_verifying_share.to_element() + verifying_share.to_element(); + refreshed_verifying_shares.insert( + share.identifier, + VerifyingShare::new(refreshed_verifying_share), + ); + } + None => return Err(Error::UnknownIdentifier), + }; + + share.commitment.0.remove(0); + refreshing_shares_minus_identity.push(share); + } + + let refreshed_pub_key_package = PublicKeyPackage:: { + header: pub_key_package.header, + verifying_shares: refreshed_verifying_shares, + verifying_key: pub_key_package.verifying_key, + }; + + Ok((refreshing_shares_minus_identity, refreshed_pub_key_package)) +} + +/// Each participant refreshes their shares This is done by taking the +/// `refreshing_share` received from the trusted dealer and adding it to the +/// original share +pub fn refresh_share( + mut refreshing_share: SecretShare, + current_key_package: &KeyPackage, +) -> Result, Error> { + // The identity commitment needs to be added to the VSS commitment + let identity_commitment: Vec> = + vec![CoefficientCommitment::new(C::Group::identity())]; + + let refreshing_share_commitments: Vec> = identity_commitment + .into_iter() + .chain(refreshing_share.commitment.0.clone()) + .collect(); + + refreshing_share.commitment = + VerifiableSecretSharingCommitment::::new(refreshing_share_commitments); + + // Verify refreshing_share secret share + let refreshed_share_package = KeyPackage::::try_from(refreshing_share)?; + + let signing_share: SigningShare = SigningShare::new( + refreshed_share_package.signing_share.to_scalar() + + current_key_package.signing_share.to_scalar(), + ); + + let mut new_key_package = current_key_package.clone(); + new_key_package.signing_share = signing_share; + + Ok(new_key_package) +} diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index b93195ecf..fbecfe07b 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -573,6 +573,7 @@ where if signing_package.signing_commitments().len() != signature_shares.len() { return Err(Error::UnknownIdentifier); } + if !signing_package.signing_commitments().keys().all(|id| { #[cfg(feature = "cheater-detection")] return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id); @@ -586,7 +587,6 @@ where // binding factor. let binding_factor_list: BindingFactorList = compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?; - // Compute the group commitment from signing commitments produced in round one. let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; @@ -616,48 +616,14 @@ where // This approach is more efficient since we don't need to verify all shares // if the aggregate signature is valid (which should be the common case). #[cfg(feature = "cheater-detection")] - if let Err(err) = verification_result { - // Compute the per-message challenge. - let challenge = crate::challenge::( - &group_commitment.0, - &pubkeys.verifying_key, - signing_package.message().as_slice(), + if verification_result.is_err() { + detect_cheater( + group_commitment, + pubkeys, + signing_package, + signature_shares, + &binding_factor_list, )?; - - // Verify the signature shares. - for (signature_share_identifier, signature_share) in signature_shares { - // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, - // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. - let signer_pubkey = pubkeys - .verifying_shares - .get(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)?; - - // Compute Lagrange coefficient. - let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; - - let binding_factor = binding_factor_list - .get(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)?; - - // Compute the commitment share. - let R_share = signing_package - .signing_commitment(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)? - .to_group_commitment_share(binding_factor); - - // Compute relation values to verify this signature share. - signature_share.verify( - *signature_share_identifier, - &R_share, - signer_pubkey, - lambda_i, - &challenge, - )?; - } - - // We should never reach here; but we return the verification error to be safe. - return Err(err); } #[cfg(not(feature = "cheater-detection"))] @@ -665,3 +631,55 @@ where Ok(signature) } + +/// Optional cheater detection feature +/// Each share is verified to find the cheater +fn detect_cheater( + group_commitment: GroupCommitment, + pubkeys: &keys::PublicKeyPackage, + signing_package: &SigningPackage, + signature_shares: &BTreeMap, round2::SignatureShare>, + binding_factor_list: &BindingFactorList, +) -> Result<(), Error> { + // Compute the per-message challenge. + let challenge = crate::challenge::( + &group_commitment.0, + &pubkeys.verifying_key, + signing_package.message().as_slice(), + )?; + + // Verify the signature shares. + for (signature_share_identifier, signature_share) in signature_shares { + // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, + // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. + let signer_pubkey = pubkeys + .verifying_shares + .get(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)?; + + // Compute Lagrange coefficient. + let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; + + let binding_factor = binding_factor_list + .get(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)?; + + // Compute the commitment share. + let R_share = signing_package + .signing_commitment(signature_share_identifier) + .ok_or(Error::UnknownIdentifier)? + .to_group_commitment_share(binding_factor); + + // Compute relation values to verify this signature share. + signature_share.verify( + *signature_share_identifier, + &R_share, + signer_pubkey, + lambda_i, + &challenge, + )?; + } + + // We should never reach here; but we return an error to be safe. + Err(Error::InvalidSignature) +} diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index a442e58b5..fe1df7b86 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -83,12 +83,12 @@ impl SerializableElement where C: Ciphersuite, { - /// Serialize an Element. Returns and error if it's the identity. + /// Serialize an Element. Returns an error if it's the identity. pub fn serialize(&self) -> Result, Error> { Ok(::serialize(&self.0)?.as_ref().to_vec()) } - /// Deserialized an Element. Returns an error if it's malformed or is the + /// Deserialize an Element. Returns an error if it's malformed or is the /// identity. pub fn deserialize(bytes: &[u8]) -> Result> { let serialized: ::Serialization = bytes diff --git a/frost-core/src/tests.rs b/frost-core/src/tests.rs index d2b1b7123..e4855c905 100644 --- a/frost-core/src/tests.rs +++ b/frost-core/src/tests.rs @@ -7,6 +7,7 @@ pub mod ciphersuite_generic; pub mod coefficient_commitment; pub mod helpers; pub mod proptests; +pub mod refresh; pub mod repairable; pub mod vectors; pub mod vectors_dkg; diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index a62193402..4528e9b74 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -4,8 +4,10 @@ use alloc::collections::BTreeMap; use crate as frost; +use crate::round2::SignatureShare; use crate::{ - keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, VerifyingKey, + keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningPackage, + VerifyingKey, }; use alloc::borrow::ToOwned; use alloc::vec::Vec; @@ -222,7 +224,7 @@ pub fn check_sign( // - take one (unused) commitment per signing participant let mut signature_shares = BTreeMap::new(); let message = "message to sign".as_bytes(); - let signing_package = frost::SigningPackage::new(commitments_map, message); + let signing_package = SigningPackage::new(commitments_map, message); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -249,19 +251,18 @@ pub fn check_sign( // generates the final signature. //////////////////////////////////////////////////////////////////////////// - #[cfg(not(feature = "cheater-detection"))] - let pubkey_package = PublicKeyPackage { - header: pubkey_package.header, - verifying_shares: BTreeMap::new(), - verifying_key: pubkey_package.verifying_key, - }; - check_aggregate_errors( signing_package.clone(), signature_shares.clone(), pubkey_package.clone(), ); + check_verifying_shares( + pubkey_package.clone(), + signing_package.clone(), + signature_shares.clone(), + ); + // Aggregate (also verifies the signature shares) let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; @@ -313,6 +314,13 @@ fn check_aggregate_errors( signature_shares: BTreeMap, frost::round2::SignatureShare>, pubkey_package: frost::keys::PublicKeyPackage, ) { + #[cfg(not(feature = "cheater-detection"))] + let pubkey_package = PublicKeyPackage { + header: pubkey_package.header, + verifying_shares: BTreeMap::new(), + verifying_key: pubkey_package.verifying_key, + }; + #[cfg(feature = "cheater-detection")] check_aggregate_corrupted_share( signing_package.clone(), @@ -745,7 +753,7 @@ pub fn check_sign_with_missing_identifier( + pubkeys: PublicKeyPackage, + signing_package: SigningPackage, + mut signature_shares: BTreeMap, SignatureShare>, +) { + let one = <::Group as Group>::Field::one(); + + // Corrupt last share + let id = *signature_shares.keys().last().unwrap(); + *signature_shares.get_mut(&id).unwrap() = + SignatureShare::new(signature_shares[&id].to_scalar() + one); + + let e = frost::aggregate(&signing_package, &signature_shares, &pubkeys).unwrap_err(); + assert_eq!(e.culprit(), Some(id)); + assert_eq!(e, Error::InvalidSignatureShare { culprit: id }); +} diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs new file mode 100644 index 000000000..29330b90b --- /dev/null +++ b/frost-core/src/tests/refresh.rs @@ -0,0 +1,241 @@ +//! Test for Refreshing shares + +use std::collections::BTreeMap; + +use rand_core::{CryptoRng, RngCore}; + +use crate::keys::generate_with_dealer; +use crate::keys::refresh::{compute_refreshing_shares, refresh_share}; +use crate::{self as frost}; +use crate::{ + keys::{KeyPackage, PublicKeyPackage, SecretShare}, + Ciphersuite, Error, Identifier, +}; + +use super::ciphersuite_generic::check_sign; + +/// We want to test that recover share matches the original share +pub fn check_refresh_shares_with_dealer(mut rng: R) { + // Compute shares + + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + const MAX_SIGNERS: u16 = 5; + const MIN_SIGNERS: u16 = 3; + let (old_shares, pub_key_package) = generate_with_dealer( + MAX_SIGNERS, + MIN_SIGNERS, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + let mut old_key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); + + for (k, v) in old_shares { + let key_package = KeyPackage::try_from(v).unwrap(); + old_key_packages.insert(k, key_package); + } + + //////////////////////////////////////////////////////////////////////////// + // New Key generation + //////////////////////////////////////////////////////////////////////////// + + // Signer 2 will be removed and Signers 1, 3, 4 & 5 will remain + + let remaining_ids = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + + const NEW_MAX_SIGNERS: u16 = 4; + + // Trusted Dealer generates zero keys and new public key package + + let (zero_shares, new_pub_key_package) = compute_refreshing_shares( + pub_key_package, + NEW_MAX_SIGNERS, + MIN_SIGNERS, + &remaining_ids, + &mut rng, + ) + .unwrap(); + + // Each participant refreshes their share + + let mut new_shares = BTreeMap::new(); + + for i in 0..remaining_ids.len() { + let identifier = remaining_ids[i]; + let current_share = &old_key_packages[&identifier]; + let new_share = refresh_share(zero_shares[i].clone(), current_share); + new_shares.insert(identifier, new_share); + } + + let mut key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); + + for (k, v) in new_shares { + key_packages.insert(k, v.unwrap()); + } + check_sign(MIN_SIGNERS, key_packages, rng, new_pub_key_package).unwrap(); +} + +/// We want to check that shares are refreshed with valid signers +pub fn check_refresh_shares_with_dealer_fails_with_invalid_signers< + C: Ciphersuite, + R: RngCore + CryptoRng, +>( + new_max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + error: Error, + mut rng: R, +) { + let (_old_shares, pub_key_package) = + generate_with_dealer::(5, 2, frost::keys::IdentifierList::Default, &mut rng).unwrap(); + let out = compute_refreshing_shares( + pub_key_package, + new_max_signers, + min_signers, + identifiers, + &mut rng, + ); + + assert!(out.is_err()); + assert!(out == Err(error)) +} + +/// We want to test that refresh share fails if the identifiers don't match the +/// identifiers in the public key package +pub fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package< + C: Ciphersuite, + R: RngCore + CryptoRng, +>( + mut rng: R, +) { + // Compute shares + + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + const MAX_SIGNERS: u16 = 3; + const MIN_SIGNERS: u16 = 2; + let (old_shares, incorrect_pub_key_package) = generate_with_dealer( + MAX_SIGNERS, + MIN_SIGNERS, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + let mut old_key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); + + for (k, v) in old_shares { + let key_package = KeyPackage::try_from(v).unwrap(); + old_key_packages.insert(k, key_package); + } + + //////////////////////////////////////////////////////////////////////////// + // New Key generation + //////////////////////////////////////////////////////////////////////////// + + // Signer 2 will be removed and Signers 1, 3, 4 & 5 will remain + + let remaining_ids = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + + const NEW_MAX_SIGNERS: u16 = 4; + + // Trusted Dealer generates zero keys and new public key package + + let e = compute_refreshing_shares( + incorrect_pub_key_package, + NEW_MAX_SIGNERS, + MIN_SIGNERS, + &remaining_ids, + &mut rng, + ) + .unwrap_err(); + + assert_eq!(e, Error::UnknownIdentifier) +} + +/// Check serialisation +pub fn check_refresh_shares_with_dealer_serialisation( + mut rng: R, +) { + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + const MAX_SIGNERS: u16 = 5; + const MIN_SIGNERS: u16 = 3; + let (_old_shares, pub_key_package) = generate_with_dealer( + MAX_SIGNERS, + MIN_SIGNERS, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + //////////////////////////////////////////////////////////////////////////// + // New Key generation + // + // Zero key is calculated by trusted dealer + // Participant 2 will be removed and Participants 1, 3, 4 & 5 will remain + //////////////////////////////////////////////////////////////////////////// + + let remaining_ids = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + + const NEW_MAX_SIGNERS: u16 = 4; + + let (zero_shares, new_pub_key_package) = compute_refreshing_shares( + pub_key_package, + NEW_MAX_SIGNERS, + MIN_SIGNERS, + &remaining_ids, + &mut rng, + ) + .unwrap(); + + // Trusted dealer serialises zero shares and key package + + let zero_shares_serialised = SecretShare::::serialize(&zero_shares[0]); + + assert!(zero_shares_serialised.is_ok()); + + let new_pub_key_package_serialised = PublicKeyPackage::::serialize(&new_pub_key_package); + + assert!(new_pub_key_package_serialised.is_ok()); + + // Participant 1 deserialises zero share and key package + + let zero_share = SecretShare::::deserialize(&zero_shares_serialised.unwrap()); + + assert!(zero_share.is_ok()); + + let new_pub_key_package = + PublicKeyPackage::::deserialize(&new_pub_key_package_serialised.unwrap()); + + assert!(new_pub_key_package.is_ok()); + + // Participant 1 checks Key Package can be created from Secret Share + + let key_package = KeyPackage::::try_from(zero_share.unwrap()); + + assert!(key_package.is_ok()); +} diff --git a/frost-ed25519/src/keys/refresh.rs b/frost-ed25519/src/keys/refresh.rs new file mode 100644 index 000000000..c270fc202 --- /dev/null +++ b/frost-ed25519/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 1421079a4..6c5647882 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -64,6 +64,122 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Ed25519Sha512, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed25519Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-ed448/src/keys/refresh.rs b/frost-ed448/src/keys/refresh.rs new file mode 100644 index 000000000..c270fc202 --- /dev/null +++ b/frost-ed448/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 3409a7e02..70061503c 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -64,6 +64,122 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Ed448Shake256, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ed448Shake256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-p256/src/keys/refresh.rs b/frost-p256/src/keys/refresh.rs new file mode 100644 index 000000000..c270fc202 --- /dev/null +++ b/frost-p256/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 7491d3d6f..3e798f4bf 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -343,6 +343,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment

; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index f25733538..8d44312db 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -64,6 +64,122 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + P256Sha256, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + P256Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-ristretto255/src/keys/refresh.rs b/frost-ristretto255/src/keys/refresh.rs new file mode 100644 index 000000000..c270fc202 --- /dev/null +++ b/frost-ristretto255/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index d82d54052..0929c228e 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -312,6 +312,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index bacc9fb47..af536ac3c 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -64,6 +64,123 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::< + Ristretto255Sha512, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Ristretto255Sha512, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Ristretto255Sha512, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-secp256k1/src/keys/refresh.rs b/frost-secp256k1/src/keys/refresh.rs new file mode 100644 index 000000000..c270fc202 --- /dev/null +++ b/frost-secp256k1/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 58ba3e088..9581384b9 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -64,6 +64,122 @@ fn check_rts() { frost_core::tests::repairable::check_rts::(rng); } +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( + rng, + ); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Secp256K1Sha256, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/gencode/src/main.rs b/gencode/src/main.rs index 0806901ed..eda52d4e9 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -321,6 +321,7 @@ fn main() -> ExitCode { "README.md", "dkg.md", "src/keys/dkg.rs", + "src/keys/refresh.rs", "src/keys/repairable.rs", "src/tests/batch.rs", "src/tests/coefficient_commitment.rs", From 16bdfc763bb06876d4574c0057d9d91adc4abea1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 22:37:05 +0000 Subject: [PATCH 036/175] Bump reviewdog/action-actionlint from 1.51.0 to 1.54.0 (#705) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.51.0 to 1.54.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.51.0...v1.54.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dd1b60422..97b3f2de1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -193,7 +193,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.7 - - uses: reviewdog/action-actionlint@v1.51.0 + - uses: reviewdog/action-actionlint@v1.54.0 with: level: warning fail_on_error: false From 5368611b7b12f495159dd8d3ec69b9596a29d4a2 Mon Sep 17 00:00:00 2001 From: Za Wilcox Date: Mon, 22 Jul 2024 03:48:32 -0600 Subject: [PATCH 037/175] add 'be' (#708) --- book/src/tutorial/importing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/tutorial/importing.md b/book/src/tutorial/importing.md index 56d01746f..68d1225fd 100644 --- a/book/src/tutorial/importing.md +++ b/book/src/tutorial/importing.md @@ -25,7 +25,7 @@ help with serialization in the following ways: ### Default byte-oriented serialization With the `serialization` feature, which is enabled by default, all structs that -need to communicated will have `serialize()` and `deserialize()` methods. The +need to be communicated will have `serialize()` and `deserialize()` methods. The serialization format is described in [Serialization Format](../user/serialization.md). From c6c3f2f1673fa7fb99d458d2c5a2323dcecbad0d Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 23 Jul 2024 05:34:31 -0300 Subject: [PATCH 038/175] docs: update trusted dealer figure to use 'Dealer' instead of 'Coordinator' (#709) --- book/src/tutorial/tkg.png | Bin 61752 -> 61073 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/book/src/tutorial/tkg.png b/book/src/tutorial/tkg.png index 4b9fa2bd009889bda6256a1a2072a41921705d15..1ae5cc39ea46ad9639df9886a347a77a943bded2 100644 GIT binary patch literal 61073 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 z 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 CWetJ= From 067f3935927b6051cb797f61007d56a3fe1844f2 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Tue, 6 Aug 2024 01:02:45 +0300 Subject: [PATCH 039/175] chore(docs): fix clippy warnings in examples (#712) --- frost-ed25519/README.md | 4 ++-- frost-ed448/README.md | 4 ++-- frost-p256/README.md | 4 ++-- frost-ristretto255/README.md | 4 ++-- frost-secp256k1/README.md | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frost-ed25519/README.md b/frost-ed25519/README.md index 6560d5b07..f9f267c73 100644 --- a/frost-ed25519/README.md +++ b/frost-ed25519/README.md @@ -45,14 +45,14 @@ let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // In practice, each iteration of this loop will be executed by its respective participant. -for participant_index in 1..(min_signers as u16 + 1) { +for participant_index in 1..=min_signers { let participant_identifier = participant_index.try_into().expect("should be nonzero"); let key_package = &key_packages[&participant_identifier]; // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - key_packages[&participant_identifier].signing_share(), + key_package.signing_share(), &mut rng, ); # // ANCHOR_END: round1_commit diff --git a/frost-ed448/README.md b/frost-ed448/README.md index 74d8fa6dc..7cf30ff1e 100644 --- a/frost-ed448/README.md +++ b/frost-ed448/README.md @@ -45,14 +45,14 @@ let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // In practice, each iteration of this loop will be executed by its respective participant. -for participant_index in 1..(min_signers as u16 + 1) { +for participant_index in 1..=min_signers { let participant_identifier = participant_index.try_into().expect("should be nonzero"); let key_package = &key_packages[&participant_identifier]; // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - key_packages[&participant_identifier].signing_share(), + key_package.signing_share(), &mut rng, ); # // ANCHOR_END: round1_commit diff --git a/frost-p256/README.md b/frost-p256/README.md index 7f0a20fe4..df0cc4a0e 100644 --- a/frost-p256/README.md +++ b/frost-p256/README.md @@ -45,14 +45,14 @@ let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // In practice, each iteration of this loop will be executed by its respective participant. -for participant_index in 1..(min_signers as u16 + 1) { +for participant_index in 1..=min_signers { let participant_identifier = participant_index.try_into().expect("should be nonzero"); let key_package = &key_packages[&participant_identifier]; // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - key_packages[&participant_identifier].signing_share(), + key_package.signing_share(), &mut rng, ); # // ANCHOR_END: round1_commit diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index 436b953d9..a2d284254 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -45,14 +45,14 @@ let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // In practice, each iteration of this loop will be executed by its respective participant. -for participant_index in 1..(min_signers as u16 + 1) { +for participant_index in 1..=min_signers { let participant_identifier = participant_index.try_into().expect("should be nonzero"); let key_package = &key_packages[&participant_identifier]; // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - key_packages[&participant_identifier].signing_share(), + key_package.signing_share(), &mut rng, ); # // ANCHOR_END: round1_commit diff --git a/frost-secp256k1/README.md b/frost-secp256k1/README.md index 0d420737a..9e4928b3f 100644 --- a/frost-secp256k1/README.md +++ b/frost-secp256k1/README.md @@ -45,14 +45,14 @@ let mut commitments_map = BTreeMap::new(); //////////////////////////////////////////////////////////////////////////// // In practice, each iteration of this loop will be executed by its respective participant. -for participant_index in 1..(min_signers as u16 + 1) { +for participant_index in 1..=min_signers { let participant_identifier = participant_index.try_into().expect("should be nonzero"); let key_package = &key_packages[&participant_identifier]; // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _threshold_. # // ANCHOR: round1_commit let (nonces, commitments) = frost::round1::commit( - key_packages[&participant_identifier].signing_share(), + key_package.signing_share(), &mut rng, ); # // ANCHOR_END: round1_commit From 052fb2590b07bedb2044db490b34af36a329bc0a Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 6 Aug 2024 13:12:52 -0300 Subject: [PATCH 040/175] frost_core: add 'static bound to Ciphersuite trait (#701) --- frost-core/CHANGELOG.md | 4 ++++ frost-core/src/traits.rs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 515941b56..915ef475d 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -6,6 +6,10 @@ Entries are listed in reverse chronological order. * Added refresh share functionality for trusted dealer: `frost_core::keys::refresh::{compute_refreshing_shares, refresh_share}` +* Added a `'static` bound to the `Ciphersuite` trait. This is a breaking change, + but it's likely to not require any code changes since most ciphersuite + implementations are probably just empty structs. The bound makes it possible + to use `frost_core::Error` in `Box`. ## 2.0.0-rc.0 diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index a1ff62fe1..3e0d9365c 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -138,7 +138,8 @@ pub type Element = <::Group as Group>::Element; /// function. /// /// [FROST ciphersuite]: https://datatracker.ietf.org/doc/html/rfc9591#name-ciphersuites -pub trait Ciphersuite: Copy + Clone + PartialEq + Debug { +// See https://github.com/ZcashFoundation/frost/issues/693 for reasoning about the 'static bound. +pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// The ciphersuite ID string. It should be equal to the contextString in /// the spec. For new ciphersuites, this should be a string that identifies /// the ciphersuite; it's recommended to use a similar format to the From 0ea837edc3dbf2b3dcbfee6f262cb5b923a4d538 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 8 Aug 2024 10:41:40 -0300 Subject: [PATCH 041/175] docs: improve DKG docs (#702) * docs: improve DKG docs * fix typo --- book/src/tutorial/dkg.md | 17 +++++++---- frost-core/src/keys/dkg.rs | 48 ++++++++++++++++-------------- frost-ed25519/src/keys/dkg.rs | 42 ++++++++++++++++++-------- frost-ed448/src/keys/dkg.rs | 42 ++++++++++++++++++-------- frost-p256/src/keys/dkg.rs | 42 ++++++++++++++++++-------- frost-ristretto255/src/keys/dkg.rs | 42 ++++++++++++++++++-------- frost-secp256k1/src/keys/dkg.rs | 42 ++++++++++++++++++-------- 7 files changed, 181 insertions(+), 94 deletions(-) diff --git a/book/src/tutorial/dkg.md b/book/src/tutorial/dkg.md index ce467b543..5bab3dd6f 100644 --- a/book/src/tutorial/dkg.md +++ b/book/src/tutorial/dkg.md @@ -56,9 +56,13 @@ insecure.** Upon receiving the other participants' `round1::Package`s, each participant then calls [`dkg::part2()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/keys/dkg/fn.part2.html) -passing their own previously created `round1::SecretPackage` and the list of -received `round1::Packages`. It returns a `round2::SecretPackage` and a -`BTreeMap` mapping other participants's `Identifier`s to `round2::Package`s: +passing their own previously created `round1::SecretPackage` and a map of the +received `round1::Packages`, keyed by the Identifiers of the participant that +sent each one of them. (These identifiers must come from whatever mapping the +coordinator has between communication channels and participants, i.e. they must +have assurance that the `round1::Package` came from the participant with that +identifier.) It returns a `round2::SecretPackage` and a `BTreeMap` mapping other +participants's `Identifier`s to `round2::Package`s: ```rust,no_run,noplayground {{#include ../../../frost-ristretto255/dkg.md:dkg_part2}} @@ -77,9 +81,10 @@ Finally, upon receiving the other participant's `round2::Package`, the DKG is concluded by calling [`dkg::part3()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/keys/dkg/fn.part3.html) passing the same `round1::Package`s received in Part 2, the `round2::Package`s -just received, and the previously stored `round2::SecretPackage` for the -participant. It returns a `KeyPackage`, with the participant's secret share, -and a `PublicKeyPackage` containing the group verifying key: +just received (again keyed by the Identifier of the participant that sent each +one of them), and the previously stored `round2::SecretPackage` for the +participant. It returns a `KeyPackage`, with the participant's secret share, and +a `PublicKeyPackage` containing the group verifying key: ```rust,no_run,noplayground {{#include ../../../frost-ristretto255/dkg.md:dkg_part3}} diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 3850662f2..aa3a56ae6 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -277,7 +277,7 @@ pub mod round2 { /// /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that -/// must be sent to other participants. +/// must be sent to each other participant in the DKG run. pub fn part1( identifier: Identifier, max_signers: u16, @@ -388,19 +388,21 @@ pub(crate) fn verify_proof_of_knowledge( Ok(()) } -/// Performs the second part of the distributed key generation protocol -/// for the participant holding the given [`round1::SecretPackage`], -/// given the received [`round1::Package`]s received from the other participants. +/// Performs the second part of the distributed key generation protocol for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. /// -/// `round1_packages` maps the identifier of each participant to the -/// [`round1::Package`] they sent. These identifiers must come from whatever mapping -/// the coordinator has between communication channels and participants, i.e. -/// they must have assurance that the [`round1::Package`] came from -/// the participant with that identifier. +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. /// -/// It returns the [`round2::SecretPackage`] that must be kept in memory -/// by the participant for the final step, and the a map of [`round2::Package`]s that -/// must be sent to each participant who has the given identifier in the map key. +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. pub fn part2( secret_package: round1::SecretPackage, round1_packages: &BTreeMap, round1::Package>, @@ -461,22 +463,22 @@ pub fn part2( } /// Performs the third and final part of the distributed key generation protocol -/// for the participant holding the given [`round2::SecretPackage`], -/// given the received [`round1::Package`]s and [`round2::Package`]s received from -/// the other participants. +/// for the participant holding the given [`round2::SecretPackage`], given the +/// received [`round1::Package`]s and [`round2::Package`]s received from the +/// other participants. /// /// `round1_packages` must be the same used in [`part2()`]. /// -/// `round2_packages` maps the identifier of each participant to the -/// [`round2::Package`] they sent. These identifiers must come from whatever mapping -/// the coordinator has between communication channels and participants, i.e. -/// they must have assurance that the [`round2::Package`] came from -/// the participant with that identifier. +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. /// /// It returns the [`KeyPackage`] that has the long-lived key share for the -/// participant, and the [`PublicKeyPackage`]s that has public information -/// about all participants; both of which are required to compute FROST -/// signatures. +/// participant, and the [`PublicKeyPackage`]s that has public information about +/// all participants; both of which are required to compute FROST signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, round1_packages: &BTreeMap, round1::Package>, diff --git a/frost-ed25519/src/keys/dkg.rs b/frost-ed25519/src/keys/dkg.rs index 082477589..42407bbd3 100644 --- a/frost-ed25519/src/keys/dkg.rs +++ b/frost-ed25519/src/keys/dkg.rs @@ -45,7 +45,7 @@ pub mod round2 { /// /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that -/// must be sent to other participants. +/// must be sent to each other participant in the DKG run. pub fn part1( identifier: Identifier, max_signers: u16, @@ -55,13 +55,21 @@ pub fn part1( frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) } -/// Performs the second part of the distributed key generation protocol -/// for the participant holding the given [`round1::SecretPackage`], -/// given the received [`round1::Package`]s received from the other participants. +/// Performs the second part of the distributed key generation protocol for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. /// -/// It returns the [`round2::SecretPackage`] that must be kept in memory -/// by the participant for the final step, and the [`round2::Package`]s that -/// must be sent to other participants. +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. pub fn part2( secret_package: round1::SecretPackage, round1_packages: &BTreeMap, @@ -70,14 +78,22 @@ pub fn part2( } /// Performs the third and final part of the distributed key generation protocol -/// for the participant holding the given [`round2::SecretPackage`], -/// given the received [`round1::Package`]s and [`round2::Package`]s received from -/// the other participants. +/// for the participant holding the given [`round2::SecretPackage`], given the +/// received [`round1::Package`]s and [`round2::Package`]s received from the +/// other participants. +/// +/// `round1_packages` must be the same used in [`part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. /// /// It returns the [`KeyPackage`] that has the long-lived key share for the -/// participant, and the [`PublicKeyPackage`]s that has public information -/// about all participants; both of which are required to compute FROST -/// signatures. +/// participant, and the [`PublicKeyPackage`]s that has public information about +/// all participants; both of which are required to compute FROST signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, round1_packages: &BTreeMap, diff --git a/frost-ed448/src/keys/dkg.rs b/frost-ed448/src/keys/dkg.rs index 082477589..42407bbd3 100644 --- a/frost-ed448/src/keys/dkg.rs +++ b/frost-ed448/src/keys/dkg.rs @@ -45,7 +45,7 @@ pub mod round2 { /// /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that -/// must be sent to other participants. +/// must be sent to each other participant in the DKG run. pub fn part1( identifier: Identifier, max_signers: u16, @@ -55,13 +55,21 @@ pub fn part1( frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) } -/// Performs the second part of the distributed key generation protocol -/// for the participant holding the given [`round1::SecretPackage`], -/// given the received [`round1::Package`]s received from the other participants. +/// Performs the second part of the distributed key generation protocol for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. /// -/// It returns the [`round2::SecretPackage`] that must be kept in memory -/// by the participant for the final step, and the [`round2::Package`]s that -/// must be sent to other participants. +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. pub fn part2( secret_package: round1::SecretPackage, round1_packages: &BTreeMap, @@ -70,14 +78,22 @@ pub fn part2( } /// Performs the third and final part of the distributed key generation protocol -/// for the participant holding the given [`round2::SecretPackage`], -/// given the received [`round1::Package`]s and [`round2::Package`]s received from -/// the other participants. +/// for the participant holding the given [`round2::SecretPackage`], given the +/// received [`round1::Package`]s and [`round2::Package`]s received from the +/// other participants. +/// +/// `round1_packages` must be the same used in [`part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. /// /// It returns the [`KeyPackage`] that has the long-lived key share for the -/// participant, and the [`PublicKeyPackage`]s that has public information -/// about all participants; both of which are required to compute FROST -/// signatures. +/// participant, and the [`PublicKeyPackage`]s that has public information about +/// all participants; both of which are required to compute FROST signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, round1_packages: &BTreeMap, diff --git a/frost-p256/src/keys/dkg.rs b/frost-p256/src/keys/dkg.rs index ae7dec6cf..dfd95384a 100644 --- a/frost-p256/src/keys/dkg.rs +++ b/frost-p256/src/keys/dkg.rs @@ -45,7 +45,7 @@ pub mod round2 { /// /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that -/// must be sent to other participants. +/// must be sent to each other participant in the DKG run. pub fn part1( identifier: Identifier, max_signers: u16, @@ -55,13 +55,21 @@ pub fn part1( frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) } -/// Performs the second part of the distributed key generation protocol -/// for the participant holding the given [`round1::SecretPackage`], -/// given the received [`round1::Package`]s received from the other participants. +/// Performs the second part of the distributed key generation protocol for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. /// -/// It returns the [`round2::SecretPackage`] that must be kept in memory -/// by the participant for the final step, and the [`round2::Package`]s that -/// must be sent to other participants. +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. pub fn part2( secret_package: round1::SecretPackage, round1_packages: &BTreeMap, @@ -70,14 +78,22 @@ pub fn part2( } /// Performs the third and final part of the distributed key generation protocol -/// for the participant holding the given [`round2::SecretPackage`], -/// given the received [`round1::Package`]s and [`round2::Package`]s received from -/// the other participants. +/// for the participant holding the given [`round2::SecretPackage`], given the +/// received [`round1::Package`]s and [`round2::Package`]s received from the +/// other participants. +/// +/// `round1_packages` must be the same used in [`part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. /// /// It returns the [`KeyPackage`] that has the long-lived key share for the -/// participant, and the [`PublicKeyPackage`]s that has public information -/// about all participants; both of which are required to compute FROST -/// signatures. +/// participant, and the [`PublicKeyPackage`]s that has public information about +/// all participants; both of which are required to compute FROST signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, round1_packages: &BTreeMap, diff --git a/frost-ristretto255/src/keys/dkg.rs b/frost-ristretto255/src/keys/dkg.rs index 6f8b71247..2d13265bb 100644 --- a/frost-ristretto255/src/keys/dkg.rs +++ b/frost-ristretto255/src/keys/dkg.rs @@ -45,7 +45,7 @@ pub mod round2 { /// /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that -/// must be sent to other participants. +/// must be sent to each other participant in the DKG run. pub fn part1( identifier: Identifier, max_signers: u16, @@ -55,13 +55,21 @@ pub fn part1( frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) } -/// Performs the second part of the distributed key generation protocol -/// for the participant holding the given [`round1::SecretPackage`], -/// given the received [`round1::Package`]s received from the other participants. +/// Performs the second part of the distributed key generation protocol for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. /// -/// It returns the [`round2::SecretPackage`] that must be kept in memory -/// by the participant for the final step, and the [`round2::Package`]s that -/// must be sent to other participants. +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. pub fn part2( secret_package: round1::SecretPackage, round1_packages: &BTreeMap, @@ -70,14 +78,22 @@ pub fn part2( } /// Performs the third and final part of the distributed key generation protocol -/// for the participant holding the given [`round2::SecretPackage`], -/// given the received [`round1::Package`]s and [`round2::Package`]s received from -/// the other participants. +/// for the participant holding the given [`round2::SecretPackage`], given the +/// received [`round1::Package`]s and [`round2::Package`]s received from the +/// other participants. +/// +/// `round1_packages` must be the same used in [`part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. /// /// It returns the [`KeyPackage`] that has the long-lived key share for the -/// participant, and the [`PublicKeyPackage`]s that has public information -/// about all participants; both of which are required to compute FROST -/// signatures. +/// participant, and the [`PublicKeyPackage`]s that has public information about +/// all participants; both of which are required to compute FROST signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, round1_packages: &BTreeMap, diff --git a/frost-secp256k1/src/keys/dkg.rs b/frost-secp256k1/src/keys/dkg.rs index 91c6286ce..9ea40b263 100644 --- a/frost-secp256k1/src/keys/dkg.rs +++ b/frost-secp256k1/src/keys/dkg.rs @@ -45,7 +45,7 @@ pub mod round2 { /// /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that -/// must be sent to other participants. +/// must be sent to each other participant in the DKG run. pub fn part1( identifier: Identifier, max_signers: u16, @@ -55,13 +55,21 @@ pub fn part1( frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) } -/// Performs the second part of the distributed key generation protocol -/// for the participant holding the given [`round1::SecretPackage`], -/// given the received [`round1::Package`]s received from the other participants. +/// Performs the second part of the distributed key generation protocol for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. /// -/// It returns the [`round2::SecretPackage`] that must be kept in memory -/// by the participant for the final step, and the [`round2::Package`]s that -/// must be sent to other participants. +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. pub fn part2( secret_package: round1::SecretPackage, round1_packages: &BTreeMap, @@ -70,14 +78,22 @@ pub fn part2( } /// Performs the third and final part of the distributed key generation protocol -/// for the participant holding the given [`round2::SecretPackage`], -/// given the received [`round1::Package`]s and [`round2::Package`]s received from -/// the other participants. +/// for the participant holding the given [`round2::SecretPackage`], given the +/// received [`round1::Package`]s and [`round2::Package`]s received from the +/// other participants. +/// +/// `round1_packages` must be the same used in [`part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. /// /// It returns the [`KeyPackage`] that has the long-lived key share for the -/// participant, and the [`PublicKeyPackage`]s that has public information -/// about all participants; both of which are required to compute FROST -/// signatures. +/// participant, and the [`PublicKeyPackage`]s that has public information about +/// all participants; both of which are required to compute FROST signatures. pub fn part3( round2_secret_package: &round2::SecretPackage, round1_packages: &BTreeMap, From 15fd2b1c795110be1563292b17ed2021668b1cfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Aug 2024 14:03:44 +0000 Subject: [PATCH 042/175] Update derive-getters requirement from 0.4.0 to 0.5.0 (#716) Updates the requirements on derive-getters to permit the latest version. --- updated-dependencies: - dependency-name: derive-getters dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frost-core/Cargo.toml | 2 +- frost-rerandomized/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index be3531415..cba929146 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -26,7 +26,7 @@ byteorder = { version = "1.4", default-features = false } const-crc32 = { version = "1.2.0", package = "const-crc32-nostd" } document-features = "0.2.7" debugless-unwrap = "0.0.4" -derive-getters = "0.4.0" +derive-getters = "0.5.0" hex = { version = "0.4.3", default-features = false, features = ["alloc"] } postcard = { version = "1.0.0", features = ["alloc"], optional = true } rand_core = { version = "0.6", default-features = false } diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index b4394f560..1b011eb4f 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -23,7 +23,7 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -derive-getters = "0.4.0" +derive-getters = "0.5.0" document-features = "0.2.7" frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = [ "internals" From 04220385ad47c40a59740c6262bcb4ed355eeedc Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Fri, 9 Aug 2024 21:25:52 -0300 Subject: [PATCH 043/175] docs: update Ywallet demo section for the new signing tool (#719) * docs: update Ywallet demo for the new signing tool * improve file names --- book/src/zcash/ywallet-demo.md | 94 ++++++++++------------------------ 1 file changed, 26 insertions(+), 68 deletions(-) diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index cccc95b4f..9e987a88e 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -17,26 +17,12 @@ Install `cargo` and `git`. [Install Ywallet](https://ywallet.app/installation/). -Clone the repositories: +Clone the repository: ``` git clone https://github.com/ZcashFoundation/frost-zcash-demo.git -git clone --recurse-submodules --branch frost-demo https://github.com/ZcashFoundation/zwallet.git -git clone https://github.com/ZcashFoundation/zcash.git ``` -Download Sprout and Sapling parameters: - - -[Sprout params](https://download.z.cash/downloads/sprout-groth16.params) - -[Sapling spend params](https://download.z.cash/downloads/sapling-spend.params) - -[Sapling output params](https://download.z.cash/downloads/sapling-output.params) - -Move the params files into `zwallet/native/zcash-params/src/` - - ## Generating FROST key shares First we will generate the FROST key shares. For simplicity we'll use trusted @@ -67,46 +53,23 @@ copy&pasting!) ## Generating the Full Viewing Key for the wallet -In a new terminal, switch to the folder of the signer tool: - +Get the `verifying_key` value that is listed inside the Public Key Package in +`public-key-package.json`. For example, in the following package ``` -cd zwallet/native/zcash-sync/ +{"verifying_shares": ...snip... ,"verifying_key":"d2bf40ca860fb97e9d6d15d7d25e4f17d2e8ba5dd7069188cbf30b023910a71b","ciphersuite":"FROST(Pallas, BLAKE2b-512)"} ``` -Before running it, you will need to create a seed phrase which is used to -generate the Sapling address. This wouldn't be needed since the demo only works -with an Orchard address, but due to current limitations in the underlying -crates, we also need to generate a Sapling address which won't be used in the -demo. Generate a fresh 24-word seed phrase, for example using [this -site](https://iancoleman.io/bip39/) (reminder: don't use random sites to -generate seed phrases unless for testing purposes!), then write to a file called -`.env` in the signer folder in the following format, putting the seed phrase -inside the quotes: - - ``` - KEY="seed phrase" - ``` +you would need to copy +`d2bf40ca860fb97e9d6d15d7d25e4f17d2e8ba5dd7069188cbf30b023910a71b`. -We can finally generate a new wallet. Run the following command; it will -take a bit to compile. It will show a bunch of warnings which is normal. +The run the following command, replacing `` with the value you copied. ``` -cargo run --release --bin sign --features dotenv -- -g +cd zcash-sign/ +cargo run --release -- generate --ak --danger-dummy-sapling ``` -When prompted for the `ak`, paste the `verifying_key` value that is listed -inside the Public Key Package in `public-key-package.json`. For example, in the -following package - -``` -{"verifying_shares": ...snip... ,"verifying_key":"d2bf40ca860fb97e9d6d15d7d25e4f17d2e8ba5dd7069188cbf30b023910a71b","ciphersuite":"FROST(Pallas, BLAKE2b-512)"} -``` - -you would need to use -`d2bf40ca860fb97e9d6d15d7d25e4f17d2e8ba5dd7069188cbf30b023910a71b`. Press -enter to submit. - It will print an Orchard address, and a Unified Full Viewing Key. Copy and paste both somewhere to use them later. @@ -116,23 +79,18 @@ Open Ywallet and click "New account". Check "Restore an account" and paste the Unified Full Viewing Key created in the previous step. Click "Import". -In the "Rescan from..." window, pick today's date (since the wallet was just -created) and press OK. The wallet should open. - -You will need to change some of Ywallet configurations. Click the three dots -at the top right and go to Settings. Switch to Advanced mode and click -OK. Go back to the Settings and uncheck "Use QR for offline signing". - ## Funding the wallet Now you will need to fund this wallet with some ZEC. Use the Orchard address printed by the signer (see warning below). Send ZEC to that address using -another account (or try [ZecFaucet](https://zecfaucet.com/)). Wait until the -funds become spendable (this may take ~10 minutes). You can check if the funds -are spendable by clicking the arrow button and checking "Spendable Balance" +another account (or try [ZecFaucet](https://zecfaucet.com/)). ```admonish warning -The address being show by Ywallet is a unified address that includes both an Orchard and Sapling address. For the demo to work, you need to receive funds in you Orchard address. Whether that will happen depends on multiple factors so it's probably easier to use just the Orchard-only address printed by the signer. +The address being show by Ywallet is a unified address that includes both an +Orchard and Sapling address. For the demo to work, you need to receive funds in +you Orchard address. Whether that will happen depends on multiple factors so +it's probably easier to use just the Orchard-only address printed by the signer. +**If you send it to the Sapling address, the funds will be unspendable and lost!** ``` ## Creating the transaction @@ -140,24 +98,23 @@ The address being show by Ywallet is a unified address that includes both an Orc Now you will create the transaction that you wish to sign with FROST. Click the arrow button and paste the destination address (send it to yourself if you don't know where to send it). Type the amount you want to send and -click "Send". +click the arrow button. -The wallet will show the transaction plan. Click "Send". It won't actually -send - it will prompt you for where to save the transaction plan. Save it -somewhere. +The wallet will show the transaction plan. Click the snowflake button. It will +show a QR code, but we want that information as a file, so click the floppy disk +button and save the file somewhere (e.g. `tx.raw` as suggested by Ywallet). ## Signing the transaction -Go back to the signer terminal and run (adjust paths accordingly. The "tx.json" -input parameters must point to the file you save in the previous step, and the -"tx.raw" output parameter is where the signed transaction will be written). +Go back to the signer terminal and run the following, replacing `` +with the path to the file you saved in the previous step, `` with the UFVK +hex string printed previously, and `` with the path where you +want to write the signed transaction (e.g. `tx-signed.raw`). ``` -cargo run --release --bin sign --features dotenv -- -t ~/Downloads/tx.json -o ~/Downloads/tx.raw +cargo run --release -- sign --tx-plan --ufvk -o ``` -When prompted, paste the UFVK generated previously. - The program will print a SIGHASH and a Randomizer. Now you will need to simulate two participants and a Coordinator to sign the @@ -222,6 +179,7 @@ transaction to the file you specified. ## Broadcasting the transaction Go back to Ywallet and return to its main screen. In the menu, select "Advanced" -and "Broadcast". Select the raw signed transaction file you have just generated. +and "Broadcast". Select the raw signed transaction file you have just generated +(`tx-signed.raw` if you followed the suggestion). That's it! You just sent a FROST-signed Zcash transaction. From dcf17732f791cd5c69aeed5bb4ff60d019a57ee8 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 14 Aug 2024 15:54:40 -0300 Subject: [PATCH 044/175] docs: update demo docs for user registration (#721) * docs: update demo docs for user registration * address comments --- book/src/zcash/ywallet-demo.md | 78 ++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index 9e987a88e..71bb3ca0e 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -117,6 +117,9 @@ cargo run --release -- sign --tx-plan --ufvk -o ` with the session ID you have just copied: +Feel free to close this terminal, or reuse it for the next step. +```admonish warning +Do not use passwords that you use in practice; use dummy ones instead. (You +shouldn't reuse passwords anyway!) For real world usage you would need to take +more care to not end up writing the password to your shell history. (In real +world usage we'd expect this to be done by applications anyway.) ``` -cargo run --bin participant -- -C redpallas --http --key-package key-package-1.json -s -``` -In the fourth terminal, for Participant 2, run the following, again replacing -the session ID: +### Coordinator + +In the second terminal, the Coordinator, run (in the same folder where key +generation was run): ``` -cargo run --bin participant -- -C redpallas --http --key-package key-package-2.json -s +export PW=foobar10 +cargo run --bin coordinator -- -C redpallas --http -u alice -w PW -S alice,bob -r - ``` -Go back to the Coordinator CLI: +We will use "alice" as the Coordinator, so change the value next to `export PW=` +if you used another password when registering "alice". +And then: + +- It should read the public key package from `public-key-package.json`. - When prompted for the message to be signed, paste the SIGHASH printed by the signer above (just the hex value, e.g. ``4d065453cfa4cfb4f98dbc9cff60c4a3904ed91c523b8ef8d67d28bea7f12ea3``). @@ -169,9 +188,32 @@ If you prefer to pass the randomizer as a file by using the `--randomizer` argument, you will need to convert it to binary format. ``` -The protocol should run and complete succesfully. You should still be at the -Coordinator CLI. It has just printed the final FROST-generated signature. -Hurrah! Copy it (just the hex value). +### Participant 1 (alice) + +In the third terminal, Participant 1, run the following: + +``` +export PW=foobar10 +cargo run --bin participant -- -C redpallas --http --key-package key-package-1.json -u alice -w PW +``` + +(We are using "alice" again. There's nothing stopping a Coordinator from being a +Partcipant too!) + +### Participant 2 (bob) + +In the fourth terminal, for Participant 2, run the following: + +``` +export PW=foobar10 +cargo run --bin participant -- -C redpallas --http --key-package key-package-2.json -u bob -w PW +``` + +### Coordinator + +Go back to the Coordinator CLI. The protocol should run and complete +succesfully. It will print the final FROST-generated signature. Hurrah! Copy it +(just the hex value). Go back to the signer and paste the signature. It will write the raw signed transaction to the file you specified. From d31257f9321a264bbd112568b1215d2538a1e85a Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 2 Oct 2024 11:26:38 -0300 Subject: [PATCH 045/175] ci: pin nightly toolchain when using minimal-versions (#742) --- .github/workflows/main.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 97b3f2de1..1ac32783f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,8 +30,13 @@ jobs: steps: - uses: actions/checkout@v4.1.7 - # Re-resolve Cargo.lock with minimal versions - - uses: dtolnay/rust-toolchain@nightly + # Re-resolve Cargo.lock with minimal versions. + # This only works with nightly. We pin to a specific version because + # newer versions use lock file version 4, but the MSRV cargo does not + # support that. + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-09-20 - run: cargo update -Z minimal-versions # Now check that `cargo build` works with respect to the oldest possible # deps and the stated MSRV From 30977f6b53e1c5887f5959cfdbc31a46c95839f8 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Wed, 2 Oct 2024 19:12:53 +0400 Subject: [PATCH 046/175] Add `internal` feature for `validate_num_of_signers` (#740) Co-authored-by: Conrado Gouvea --- frost-core/src/keys.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 5a884e425..9112e1085 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -760,6 +760,8 @@ where } } +/// Validates the number of signers. +#[cfg_attr(feature = "internals", visibility::make(pub))] fn validate_num_of_signers( min_signers: u16, max_signers: u16, From 0da193817ab72d0e3998b675d7318fb25749f32c Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:57:24 +0300 Subject: [PATCH 047/175] chore(docs): fix rustdoc warning (#745) --- frost-core/src/keys.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 9112e1085..916e46726 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -327,8 +327,8 @@ where .collect::>>() } - /// Returns VerifiableSecretSharingCommitment from a iterator of serialized - /// CoefficientCommitments (e.g. a Vec>). + /// Returns VerifiableSecretSharingCommitment from an iterator of serialized + /// CoefficientCommitments (e.g. a [`Vec>`]). pub fn deserialize(serialized_coefficient_commitments: I) -> Result> where I: IntoIterator, From 16d527c830bdca11cf5c1f45a7cfc8db1df9eaf7 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Tue, 8 Oct 2024 00:23:37 +0300 Subject: [PATCH 048/175] Add missing `derive(Getters)` for `dkg::{round1, round2}` (#741) * Add missing `derive(Getters)` for `dkg::{round1, round2}` * update CHANGELOG --------- Co-authored-by: Conrado Gouvea --- frost-core/CHANGELOG.md | 1 + frost-core/src/keys/dkg.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 915ef475d..43f03eb02 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -10,6 +10,7 @@ Entries are listed in reverse chronological order. but it's likely to not require any code changes since most ciphersuite implementations are probably just empty structs. The bound makes it possible to use `frost_core::Error` in `Box`. +* Added getters to `round1::SecretPackage` and `round2::SecretPackage`. ## 2.0.0-rc.0 diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index aa3a56ae6..6eece0039 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -116,12 +116,13 @@ pub mod round1 { /// # Security /// /// This package MUST NOT be sent to other participants! - #[derive(Clone, PartialEq, Eq)] + #[derive(Clone, PartialEq, Eq, Getters)] pub struct SecretPackage { /// The identifier of the participant holding the secret. pub(crate) identifier: Identifier, /// Coefficients of the temporary secret polynomial for the participant. /// These are (a_{i0}, ..., a_{i(t−1)})) which define the polynomial f_i(x) + #[getter(skip)] pub(crate) coefficients: Vec>, /// The public commitment for the participant (C_i) pub(crate) commitment: VerifiableSecretSharingCommitment, @@ -233,7 +234,7 @@ pub mod round2 { /// # Security /// /// This package MUST NOT be sent to other participants! - #[derive(Clone, PartialEq, Eq)] + #[derive(Clone, PartialEq, Eq, Getters)] pub struct SecretPackage { /// The identifier of the participant holding the secret. pub(crate) identifier: Identifier, From cef42174294f56badc5bd9877cb4ef6c2be6d5b0 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:33:53 +0300 Subject: [PATCH 049/175] chore(frost-core): fix dead_code warning (#748) --- frost-core/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index fbecfe07b..1f4e2828c 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -634,6 +634,7 @@ where /// Optional cheater detection feature /// Each share is verified to find the cheater +#[cfg(feature = "cheater-detection")] fn detect_cheater( group_commitment: GroupCommitment, pubkeys: &keys::PublicKeyPackage, From b1f946f3ef974dc9b650a50e95934fa6ab9439c9 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 15 Oct 2024 10:39:15 -0300 Subject: [PATCH 050/175] add a verify_signature_share() function (#727) --- frost-core/CHANGELOG.md | 4 + frost-core/src/lib.rs | 106 +++++++++++++++----- frost-core/src/tests/ciphersuite_generic.rs | 35 +++++++ 3 files changed, 122 insertions(+), 23 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 43f03eb02..1ca9cc41c 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -11,6 +11,10 @@ Entries are listed in reverse chronological order. implementations are probably just empty structs. The bound makes it possible to use `frost_core::Error` in `Box`. * Added getters to `round1::SecretPackage` and `round2::SecretPackage`. +* Added a `frost_core::verify_signature_share()` function which allows verifying + individual signature shares. This is not required for regular FROST usage but + might useful in certain situations where it is desired to verify each + individual signature share before aggregating the signature. ## 2.0.0-rc.0 diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 1f4e2828c..b6914e197 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -650,37 +650,97 @@ fn detect_cheater( )?; // Verify the signature shares. - for (signature_share_identifier, signature_share) in signature_shares { + for (identifier, signature_share) in signature_shares { // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, // and where s[i] is a secret share of the constant term of _f_, the secret polynomial. - let signer_pubkey = pubkeys + let verifying_share = pubkeys .verifying_shares - .get(signature_share_identifier) + .get(identifier) .ok_or(Error::UnknownIdentifier)?; - // Compute Lagrange coefficient. - let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?; - - let binding_factor = binding_factor_list - .get(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)?; - - // Compute the commitment share. - let R_share = signing_package - .signing_commitment(signature_share_identifier) - .ok_or(Error::UnknownIdentifier)? - .to_group_commitment_share(binding_factor); - - // Compute relation values to verify this signature share. - signature_share.verify( - *signature_share_identifier, - &R_share, - signer_pubkey, - lambda_i, - &challenge, + verify_signature_share_precomputed( + *identifier, + signing_package, + binding_factor_list, + signature_share, + verifying_share, + challenge, )?; } // We should never reach here; but we return an error to be safe. Err(Error::InvalidSignature) } + +/// Verify a signature share for the given participant `identifier`, +/// `verifying_share` and `signature_share`; with the `signing_package` +/// for which the signature share was produced and with the group's +/// `verifying_key`. +/// +/// This is not required for regular FROST usage but might useful in certain +/// situations where it is desired to verify each individual signature share +/// before aggregating the signature. +pub fn verify_signature_share( + identifier: Identifier, + verifying_share: &keys::VerifyingShare, + signature_share: &round2::SignatureShare, + signing_package: &SigningPackage, + verifying_key: &VerifyingKey, +) -> Result<(), Error> { + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the + // binding factor. + let binding_factor_list: BindingFactorList = + compute_binding_factor_list(signing_package, verifying_key, &[])?; + // Compute the group commitment from signing commitments produced in round one. + let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + + // Compute the per-message challenge. + let challenge = crate::challenge::( + &group_commitment.to_element(), + verifying_key, + signing_package.message().as_slice(), + )?; + + verify_signature_share_precomputed( + identifier, + signing_package, + &binding_factor_list, + signature_share, + verifying_share, + challenge, + ) +} + +/// Similar to [`verify_signature_share()`] but using a precomputed +/// `binding_factor_list` and `challenge`. +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] +fn verify_signature_share_precomputed( + signature_share_identifier: Identifier, + signing_package: &SigningPackage, + binding_factor_list: &BindingFactorList, + signature_share: &round2::SignatureShare, + verifying_share: &keys::VerifyingShare, + challenge: Challenge, +) -> Result<(), Error> { + let lambda_i = derive_interpolating_value(&signature_share_identifier, signing_package)?; + + let binding_factor = binding_factor_list + .get(&signature_share_identifier) + .ok_or(Error::UnknownIdentifier)?; + + let R_share = signing_package + .signing_commitment(&signature_share_identifier) + .ok_or(Error::UnknownIdentifier)? + .to_group_commitment_share(binding_factor); + + signature_share.verify( + signature_share_identifier, + &R_share, + verifying_share, + lambda_i, + &challenge, + )?; + + Ok(()) +} diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 4528e9b74..df23fb8a1 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -263,6 +263,8 @@ pub fn check_sign( signature_shares.clone(), ); + check_verify_signature_share(&pubkey_package, &signing_package, &signature_shares); + // Aggregate (also verifies the signature shares) let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; @@ -860,3 +862,36 @@ fn check_verifying_shares( assert_eq!(e.culprit(), Some(id)); assert_eq!(e, Error::InvalidSignatureShare { culprit: id }); } + +// Checks if `verify_signature_share()` works correctly. +fn check_verify_signature_share( + pubkeys: &PublicKeyPackage, + signing_package: &SigningPackage, + signature_shares: &BTreeMap, SignatureShare>, +) { + for (identifier, signature_share) in signature_shares { + frost::verify_signature_share( + *identifier, + pubkeys.verifying_shares().get(identifier).unwrap(), + signature_share, + signing_package, + pubkeys.verifying_key(), + ) + .expect("should pass"); + } + + for (identifier, signature_share) in signature_shares { + let one = <::Group as Group>::Field::one(); + // Corrupt share + let signature_share = SignatureShare::new(signature_share.to_scalar() + one); + + frost::verify_signature_share( + *identifier, + pubkeys.verifying_shares().get(identifier).unwrap(), + &signature_share, + signing_package, + pubkeys.verifying_key(), + ) + .expect_err("should have failed"); + } +} From cd352bb9087fdacfe93b795926451e263bd8a336 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 15 Oct 2024 11:28:57 -0300 Subject: [PATCH 051/175] ci: improve coverage speed (#743) --- .github/workflows/coverage.yaml | 4 ++-- Cargo.toml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 39e503019..1a43374f0 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -38,10 +38,10 @@ jobs: run: cargo install cargo-llvm-cov - name: Run tests - run: cargo llvm-cov --lcov --no-report --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs' + run: cargo llvm-cov --lcov --no-report --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' - name: Generate coverage report - run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs' --output-path lcov.info + run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' --output-path lcov.info - name: Upload coverage report to Codecov uses: codecov/codecov-action@v4.5.0 diff --git a/Cargo.toml b/Cargo.toml index cbf054ff8..08346be97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,6 @@ members = [ "frost-rerandomized", "gencode" ] + +[profile.test.package."*"] +opt-level = 3 \ No newline at end of file From cffaff67e03de6b0c486cc9cf5dce98c83b41492 Mon Sep 17 00:00:00 2001 From: natalie Date: Wed, 16 Oct 2024 17:34:11 +0100 Subject: [PATCH 052/175] Release v2.0.0 (#756) Update Changelog and Version numbers for Release v2.0.0 (#755) --- book/src/tutorial/importing.md | 4 ++-- frost-core/CHANGELOG.md | 5 +++++ frost-core/Cargo.toml | 2 +- frost-ed25519/Cargo.toml | 10 +++++----- frost-ed448/Cargo.toml | 10 +++++----- frost-p256/Cargo.toml | 10 +++++----- frost-rerandomized/Cargo.toml | 4 ++-- frost-ristretto255/Cargo.toml | 10 +++++----- frost-secp256k1/Cargo.toml | 10 +++++----- 9 files changed, 35 insertions(+), 30 deletions(-) diff --git a/book/src/tutorial/importing.md b/book/src/tutorial/importing.md index 68d1225fd..1777bd42d 100644 --- a/book/src/tutorial/importing.md +++ b/book/src/tutorial/importing.md @@ -6,7 +6,7 @@ Add to your `Cargo.toml` file: ``` [dependencies] -frost-ristretto255 = "2.0.0-rc.0" +frost-ristretto255 = "2.0.0" ``` ## Handling errors @@ -38,7 +38,7 @@ needs to be transmitted. The importing would look like: ``` [dependencies] -frost-ristretto255 = { version = "2.0.0-rc.0", features = ["serde"] } +frost-ristretto255 = { version = "2.0.0", features = ["serde"] } ``` Note that serde usage is optional. Applications can use different encodings, and diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 1ca9cc41c..6ef2ae2fe 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,6 +4,11 @@ Entries are listed in reverse chronological order. ## Unreleased +## 2.0.0 + +* Updated docs +* Added missing `derive(Getters)` for `dkg::{round1, round2}` +* Added `internal` feature for `validate_num_of_signers` * Added refresh share functionality for trusted dealer: `frost_core::keys::refresh::{compute_refreshing_shares, refresh_share}` * Added a `'static` bound to the `Ciphersuite` trait. This is a breaking change, diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index cba929146..5b1ab2e87 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" # When releasing to crates.io: # - Update CHANGELOG.md # - Create git tag. -version = "2.0.0-rc.0" +version = "2.0.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 5c1833bbb..add2afc6d 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # - Update html_root_url # - Update CHANGELOG.md # - Create git tag. -version = "2.0.0-rc.0" +version = "2.0.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -25,15 +25,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } ed25519-dalek = "2.0.0" insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index ead6ef371..17ba4b5fa 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" # When releasing to crates.io: # - Update CHANGELOG.md # - Create git tag. -version = "2.0.0-rc.0" +version = "2.0.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -24,15 +24,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" ed448-goldilocks = { version = "0.9.0" } -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } rand_core = "0.6" sha3 = { version = "0.10.6", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } lazy_static = "1.4" insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index b55969e9b..5b94d488c 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # - Update html_root_url # - Update CHANGELOG.md # - Create git tag. -version = "2.0.0-rc.0" +version = "2.0.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -25,15 +25,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" p256 = { version = "0.13.0", features = ["hash2curve"], default-features = false } -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 1b011eb4f..da4d24405 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # - Update html_root_url # - Update CHANGELOG.md # - Create git tag. -version = "2.0.0-rc.0" +version = "2.0.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -25,7 +25,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] derive-getters = "0.5.0" document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = [ +frost-core = { path = "../frost-core", version = "2.0.0", features = [ "internals" ], default-features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 677ecc926..e666dc83d 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" # - Update html_root_url # - Update CHANGELOG.md # - Create git tag. -version = "2.0.0-rc.0" +version = "2.0.0" authors = ["Deirdre Connolly ", "Chelsea Komlo ", "Conrado Gouvea "] readme = "README.md" license = "MIT OR Apache-2.0" @@ -21,15 +21,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index cf3afc6c0..cdc8adcb2 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" # When releasing to crates.io: # - Update CHANGELOG.md # - Create git tag. -version = "2.0.0-rc.0" +version = "2.0.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -23,16 +23,16 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" From 2d88edf1623ee29f671a43966aae0bd4ead2ea7a Mon Sep 17 00:00:00 2001 From: natalie Date: Mon, 21 Oct 2024 14:40:17 +0100 Subject: [PATCH 053/175] Expose to_element() for GroupCommitment (#757) * Expose to_element() for GroupCommitment * Update to_element() for GroupCommitment to pub(crate) Co-authored-by: Conrado Gouvea --- frost-core/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index b6914e197..a01cd3c26 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -478,8 +478,9 @@ where C: Ciphersuite, { /// Return the underlying element. - #[cfg(feature = "internals")] - pub fn to_element(self) -> ::Element { + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn to_element(self) -> ::Element { self.0 } } From ae6d2e8e454c511a2465b22f2e69c5d3b3182054 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 23 Oct 2024 10:07:18 -0300 Subject: [PATCH 054/175] docs: make clearer the risks of refresh shares (#753) --- book/src/frost.md | 21 ++++++++++++++++++--- book/src/tutorial/refreshing-shares.md | 6 ++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/book/src/frost.md b/book/src/frost.md index 1daa881f9..3f9b00302 100644 --- a/book/src/frost.md +++ b/book/src/frost.md @@ -78,12 +78,12 @@ be able to produce the final signature. Of course, the Coordinator is still free to start the process with only 2 participants if they wish. ``` -## Verifying +## Verifying Signatures Signature verification is carried out as normal with single-party signatures, along with the signed message and the group verifying key as inputs. -## Repairing +## Repairing Shares Repairing shares allow participants to help another participant recover their share if they have lost it, or also issue a new share to a new participant @@ -97,7 +97,7 @@ The functionality works in such a way that each participant running the repair share function is not able to obtain the share that is being recovered or issued. -## Refreshing +## Refreshing Shares Refreshing shares allow participants (or a subset of them) to update their shares in a way that maintains the same group public key. Some applications are: @@ -117,6 +117,21 @@ This is also possible via Distributed Key Generation but this has not yet been implemented. ``` +```admonish danger +It is critically important to keep in mind that the **Refresh Shares +functionality does not "restore full security" to a group**. While the group +evolves and participants are removed and new participants are added, the +security of the group does not depend only on the threshold of the current +participants being honest, but also **on the threshold of all previous set of +participants being honest**! For example, if Alice, Mallory and Eve form a group +and Mallory is eventually excluded from the group and replaced with Bob, it is +not enough to trust 2 out of 3 between Alice, Bob and Eve. **You also need to +trust that Mallory won't collude with, say, Eve which could have kept her +original pre-refresh share and they could both together recompute the original +key and compromise the group.** If that's a unnaceptable risk to your use case, +you will need to migrate to a new group if that makes sense to your application. +``` + ## Ciphersuites FROST is a generic protocol that works with any adequate prime-order group, diff --git a/book/src/tutorial/refreshing-shares.md b/book/src/tutorial/refreshing-shares.md index cf75b3daa..5a65f1acc 100644 --- a/book/src/tutorial/refreshing-shares.md +++ b/book/src/tutorial/refreshing-shares.md @@ -32,3 +32,9 @@ Applications should first ensure that all participants who refreshed their `KeyPackages`. How this is done is up to the application; it might require sucessfully generating a signature with all of those participants. ``` + +```admonish danger +Refreshing Shares may be not enough to address security concerns +after a share has been compromised. Refer to to the [Understanding +FROST](../frost.md#refreshing-shares) section. +``` \ No newline at end of file From 9a4394cbb66a98fdb68ac02bf6c9fd452dd095ff Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Mon, 4 Nov 2024 11:05:28 -0300 Subject: [PATCH 055/175] allow identifying culprit in error returned by dkg::part3() (#728) --- frost-core/CHANGELOG.md | 3 + frost-core/src/error.rs | 12 +++- frost-core/src/keys.rs | 13 +++- frost-core/src/keys/dkg.rs | 11 ++- frost-core/src/tests/ciphersuite_generic.rs | 75 ++++++++++++++++++++- 5 files changed, 108 insertions(+), 6 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 6ef2ae2fe..61ff4f32c 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -20,6 +20,9 @@ Entries are listed in reverse chronological order. individual signature shares. This is not required for regular FROST usage but might useful in certain situations where it is desired to verify each individual signature share before aggregating the signature. +* It is now possible to identify the culprit in `frost_core::keys::dkg::part3()` + if an invalid secret share was sent by one of the participants (by calling + frost_core::Error::culprit()`). ## 2.0.0-rc.0 diff --git a/frost-core/src/error.rs b/frost-core/src/error.rs index 6fdd36069..78662df7e 100644 --- a/frost-core/src/error.rs +++ b/frost-core/src/error.rs @@ -74,7 +74,11 @@ pub enum Error { }, /// Secret share verification failed. #[error("Invalid secret share.")] - InvalidSecretShare, + InvalidSecretShare { + /// The identifier of the signer whose secret share validation failed, + /// if possible to identify. + culprit: Option>, + }, /// Round 1 package not found for Round 2 participant. #[error("Round 1 package not found for Round 2 participant.")] PackageNotFound, @@ -132,8 +136,10 @@ where | Error::InvalidProofOfKnowledge { culprit: identifier, } => Some(*identifier), - Error::InvalidSecretShare - | Error::InvalidMinSigners + Error::InvalidSecretShare { + culprit: identifier, + } => *identifier, + Error::InvalidMinSigners | Error::InvalidMaxSigners | Error::InvalidCoefficients | Error::MalformedIdentifier diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 916e46726..9834665d7 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -423,7 +423,18 @@ where let result = evaluate_vss(self.identifier, &self.commitment); if !(f_result == result) { - return Err(Error::InvalidSecretShare); + // The culprit needs to be identified by the caller if needed, + // because this function is called in two different contexts: + // - after trusted dealer key generation, by the participant who + // receives the SecretShare. In that case it does not make sense + // to identify themselves as the culprit, since the issue was with + // the Coordinator or in the communication. + // - during DKG, where a "fake" SecretShare is built just to reuse + // the verification logic and it does make sense to identify the + // culprit. Note that in this case, self.identifier is the caller's + // identifier and not the culprit's, so we couldn't identify + // the culprit inside this function anyway. + return Err(Error::InvalidSecretShare { culprit: None }); } Ok(( diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 6eece0039..60b33cdc8 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -525,7 +525,16 @@ pub fn part3( }; // Verify the share. We don't need the result. - let _ = secret_share.verify()?; + // Identify the culprit if an InvalidSecretShare error is returned. + let _ = secret_share.verify().map_err(|e| { + if let Error::InvalidSecretShare { .. } = e { + Error::InvalidSecretShare { + culprit: Some(*sender_identifier), + } + } else { + e + } + })?; // Round 2, Step 3 // diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index df23fb8a1..b41ef4439 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -4,6 +4,7 @@ use alloc::collections::BTreeMap; use crate as frost; +use crate::keys::SigningShare; use crate::round2::SignatureShare; use crate::{ keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningPackage, @@ -499,7 +500,7 @@ where // for each signature before being aggregated. let mut pubkey_packages_by_participant = BTreeMap::new(); - check_part3_different_participants( + check_part3_errors( max_signers, round2_secret_packages.clone(), received_round1_packages.clone(), @@ -538,6 +539,33 @@ where check_sign(min_signers, key_packages, rng, pubkeys).unwrap() } +/// Check for error cases related to DKG part3. +fn check_part3_errors( + max_signers: u16, + round2_secret_packages: BTreeMap, frost::keys::dkg::round2::SecretPackage>, + received_round1_packages: BTreeMap< + Identifier, + BTreeMap, frost::keys::dkg::round1::Package>, + >, + received_round2_packages: BTreeMap< + Identifier, + BTreeMap, frost::keys::dkg::round2::Package>, + >, +) { + check_part3_different_participants( + max_signers, + round2_secret_packages.clone(), + received_round1_packages.clone(), + received_round2_packages.clone(), + ); + check_part3_corrupted_share( + max_signers, + round2_secret_packages, + received_round1_packages, + received_round2_packages, + ); +} + /// Check that calling dkg::part3() with distinct sets of participants fail. fn check_part3_different_participants( max_signers: u16, @@ -575,6 +603,49 @@ fn check_part3_different_participants( } } +/// Check that calling dkg::part3() with a corrupted share fail, and the +/// culprit is correctly identified. +fn check_part3_corrupted_share( + max_signers: u16, + round2_secret_packages: BTreeMap, frost::keys::dkg::round2::SecretPackage>, + received_round1_packages: BTreeMap< + Identifier, + BTreeMap, frost::keys::dkg::round1::Package>, + >, + received_round2_packages: BTreeMap< + Identifier, + BTreeMap, frost::keys::dkg::round2::Package>, + >, +) { + // For each participant, perform the third part of the DKG protocol. + // In practice, each participant will perform this on their own environments. + for participant_index in 1..=max_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + + // Remove the first package from the map, and reinsert it with an unrelated + // Do the same for Round 2 packages + let mut received_round2_packages = + received_round2_packages[&participant_identifier].clone(); + let culprit = *received_round2_packages.keys().next().unwrap(); + let package = received_round2_packages.get_mut(&culprit).unwrap(); + let one = <::Group as Group>::Field::one(); + package.signing_share = SigningShare::new(package.signing_share().to_scalar() + one); + + let r = frost::keys::dkg::part3( + &round2_secret_packages[&participant_identifier], + &received_round1_packages[&participant_identifier], + &received_round2_packages, + ) + .expect_err("Should have failed due to corrupted share"); + assert_eq!( + r, + Error::InvalidSecretShare { + culprit: Some(culprit) + } + ) + } +} + /// Test FROST signing with trusted dealer with a Ciphersuite, using specified /// Identifiers. pub fn check_sign_with_dealer_and_identifiers( @@ -648,10 +719,12 @@ pub fn check_sign_with_dealer_and_identifiers( round1_secret_package: frost::keys::dkg::round1::SecretPackage, mut round1_packages: BTreeMap, frost::keys::dkg::round1::Package>, ) { + // Check if a corrupted proof of knowledge results in failure. let one = <::Group as Group>::Field::one(); // Corrupt a PoK let id = *round1_packages.keys().next().unwrap(); From ca33dad959e55bc66d5feac53a1275faae79377b Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:43:37 +0300 Subject: [PATCH 056/175] fix(changelog): now it's possible to identify culprit in part3(), but only in unreleased version (#764) --- frost-core/CHANGELOG.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 61ff4f32c..4057939c2 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,6 +4,10 @@ Entries are listed in reverse chronological order. ## Unreleased +* It is now possible to identify the culprit in `frost_core::keys::dkg::part3()` + if an invalid secret share was sent by one of the participants (by calling + frost_core::Error::culprit()`). + ## 2.0.0 * Updated docs @@ -20,9 +24,6 @@ Entries are listed in reverse chronological order. individual signature shares. This is not required for regular FROST usage but might useful in certain situations where it is desired to verify each individual signature share before aggregating the signature. -* It is now possible to identify the culprit in `frost_core::keys::dkg::part3()` - if an invalid secret share was sent by one of the participants (by calling - frost_core::Error::culprit()`). ## 2.0.0-rc.0 From 958fde3f1153de0b5473866631ade9a43f83b925 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Tue, 12 Nov 2024 01:27:55 +0300 Subject: [PATCH 057/175] chore(frost-p256, frost-secp256k1): do not use allocator for domain separators (#767) --- frost-p256/src/lib.rs | 23 ++++++++--------------- frost-secp256k1/src/lib.rs | 23 ++++++++--------------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 3e798f4bf..35c2620f7 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -8,7 +8,6 @@ extern crate alloc; -use alloc::borrow::ToOwned; use alloc::collections::BTreeMap; use frost_rerandomized::RandomizedCiphersuite; @@ -159,9 +158,9 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { output } -fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { +fn hash_to_scalar(domain: &[&[u8]], msg: &[u8]) -> Scalar { let mut u = [P256ScalarField::zero()]; - hash_to_field::, Scalar>(&[msg], &[domain], &mut u) + hash_to_field::, Scalar>(&[msg], domain, &mut u) .expect("should never return error according to error cases described in ExpandMsgXmd"); u[0] } @@ -188,21 +187,21 @@ impl Ciphersuite for P256Sha256 { /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.2 fn H1(m: &[u8]) -> <::Field as Field>::Scalar { - hash_to_scalar((CONTEXT_STRING.to_owned() + "rho").as_bytes(), m) + hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"rho"], m) } /// H2 for FROST(P-256, SHA-256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.4 fn H2(m: &[u8]) -> <::Field as Field>::Scalar { - hash_to_scalar((CONTEXT_STRING.to_owned() + "chal").as_bytes(), m) + hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"chal"], m) } /// H3 for FROST(P-256, SHA-256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.4-2.4.2.6 fn H3(m: &[u8]) -> <::Field as Field>::Scalar { - hash_to_scalar((CONTEXT_STRING.to_owned() + "nonce").as_bytes(), m) + hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"nonce"], m) } /// H4 for FROST(P-256, SHA-256) @@ -221,25 +220,19 @@ impl Ciphersuite for P256Sha256 { /// HDKG for FROST(P-256, SHA-256) fn HDKG(m: &[u8]) -> Option<<::Field as Field>::Scalar> { - Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "dkg").as_bytes(), - m, - )) + Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg"], m)) } /// HID for FROST(P-256, SHA-256) fn HID(m: &[u8]) -> Option<<::Field as Field>::Scalar> { - Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "id").as_bytes(), - m, - )) + Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"id"], m)) } } impl RandomizedCiphersuite for P256Sha256 { fn hash_randomizer(m: &[u8]) -> Option<<::Field as Field>::Scalar> { Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "randomizer").as_bytes(), + &[CONTEXT_STRING.as_bytes(), b"randomizer"], m, )) } diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 4d25266ba..903ffda30 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -8,7 +8,6 @@ extern crate alloc; -use alloc::borrow::ToOwned; use alloc::collections::BTreeMap; use frost_rerandomized::RandomizedCiphersuite; @@ -159,9 +158,9 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { output } -fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { +fn hash_to_scalar(domain: &[&[u8]], msg: &[u8]) -> Scalar { let mut u = [Secp256K1ScalarField::zero()]; - hash_to_field::, Scalar>(&[msg], &[domain], &mut u) + hash_to_field::, Scalar>(&[msg], domain, &mut u) .expect("should never return error according to error cases described in ExpandMsgXmd"); u[0] } @@ -188,21 +187,21 @@ impl Ciphersuite for Secp256K1Sha256 { /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.2 fn H1(m: &[u8]) -> <::Field as Field>::Scalar { - hash_to_scalar((CONTEXT_STRING.to_owned() + "rho").as_bytes(), m) + hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"rho"], m) } /// H2 for FROST(secp256k1, SHA-256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.4 fn H2(m: &[u8]) -> <::Field as Field>::Scalar { - hash_to_scalar((CONTEXT_STRING.to_owned() + "chal").as_bytes(), m) + hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"chal"], m) } /// H3 for FROST(secp256k1, SHA-256) /// /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#section-6.5-2.4.2.6 fn H3(m: &[u8]) -> <::Field as Field>::Scalar { - hash_to_scalar((CONTEXT_STRING.to_owned() + "nonce").as_bytes(), m) + hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"nonce"], m) } /// H4 for FROST(secp256k1, SHA-256) @@ -221,25 +220,19 @@ impl Ciphersuite for Secp256K1Sha256 { /// HDKG for FROST(secp256k1, SHA-256) fn HDKG(m: &[u8]) -> Option<<::Field as Field>::Scalar> { - Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "dkg").as_bytes(), - m, - )) + Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg"], m)) } /// HID for FROST(secp256k1, SHA-256) fn HID(m: &[u8]) -> Option<<::Field as Field>::Scalar> { - Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "id").as_bytes(), - m, - )) + Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"id"], m)) } } impl RandomizedCiphersuite for Secp256K1Sha256 { fn hash_randomizer(m: &[u8]) -> Option<<::Field as Field>::Scalar> { Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "randomizer").as_bytes(), + &[CONTEXT_STRING.as_bytes(), b"randomizer"], m, )) } From c88fadde8e3c2d6676dab817364abf224c28e384 Mon Sep 17 00:00:00 2001 From: conduition Date: Thu, 14 Nov 2024 08:50:18 -0500 Subject: [PATCH 058/175] Add frost-secp256k1-tr crate (BIP340/BIP341) [moved] (#730) * modify frost-core traits to enable taproot compatibility This commit contains changes to the frost-core crate which allow ciphersuites to better customize how signatures are computed. This will enable taproot support without requiring major changes to existing frost ciphersuites. Co-authored by @zebra-lucky and @mimoo This work sponsored by dlcbtc.com and lightspark.com * add frost-secp256k1-tr crate and ciphersuite Co-authored by @zebra-lucky and @mimoo This work sponsored by dlcbtc.com and lightspark.com * test coverage for taproot crate Co-authored by @zebra-lucky and @mimoo This work sponsored by dlcbtc.com and lightspark.com * clippy fixes * tweak DKG output to avoid rogue taproot tweaks * add interoperability tests * cleanup taproot implementation to minimize impact in frost_core * Update PoK test vector to use nonce which generates an even-parity point Uses r = e99ae2676eab512a3572c7b7655d633642a717250af57a7e0ccd5f9618b69f3f * BIP341 key package tweaks shouldn't cause key negation * prune the Context type, instead negate signature.R before verifying With a couple of small adjustments to the code, we can remove the need for this extra associated type on the Ciphersuite crate. Accepting signature with odd-parity nonce values is OK, because BIP340 discard the nonce parity bit anyway. * proper TapTweak point-addition operates on even internal key representation Thanks to @conradoplg for spotting this. The internal key is supposed to be represented as an even-parity point when adding the TapTweak point t*G. I added a regression test to ensure the tweaked verifying key and its parity match the BIP341 spec. * clippy test fixes * fix no-std issues and warnings --------- Co-authored-by: Conrado Gouvea --- Cargo.toml | 1 + frost-core/src/batch.rs | 12 +- frost-core/src/keys/dkg.rs | 5 +- frost-core/src/lib.rs | 81 +- frost-core/src/round1.rs | 20 + frost-core/src/round2.rs | 27 +- frost-core/src/signature.rs | 24 +- frost-core/src/signing_key.rs | 19 +- frost-core/src/tests/ciphersuite_generic.rs | 1 - frost-core/src/traits.rs | 213 +++- frost-core/src/verifying_key.rs | 2 + frost-ed25519/tests/helpers/samples.json | 3 +- frost-ed25519/tests/helpers/samples.rs | 11 +- frost-ed448/tests/helpers/samples.json | 3 +- frost-ed448/tests/helpers/samples.rs | 11 +- frost-p256/tests/helpers/samples.json | 3 +- frost-p256/tests/helpers/samples.rs | 11 +- frost-ristretto255/tests/helpers/samples.json | 3 +- frost-ristretto255/tests/helpers/samples.rs | 11 +- frost-secp256k1-tr/Cargo.toml | 68 ++ frost-secp256k1-tr/README.md | 121 +++ frost-secp256k1-tr/benches/bench.rs | 19 + frost-secp256k1-tr/dkg.md | 168 ++++ frost-secp256k1-tr/src/keys/dkg.rs | 103 ++ frost-secp256k1-tr/src/keys/refresh.rs | 35 + frost-secp256k1-tr/src/keys/repairable.rs | 103 ++ frost-secp256k1-tr/src/lib.rs | 923 ++++++++++++++++++ frost-secp256k1-tr/src/tests.rs | 5 + frost-secp256k1-tr/src/tests/batch.rs | 24 + .../src/tests/coefficient_commitment.rs | 46 + frost-secp256k1-tr/src/tests/deserialize.rs | 38 + frost-secp256k1-tr/src/tests/proptests.rs | 33 + .../src/tests/vss_commitment.rs | 42 + .../tests/common_traits_tests.rs | 74 ++ .../tests/helpers/elements.json | 5 + frost-secp256k1-tr/tests/helpers/mod.rs | 24 + .../tests/helpers/repair-share.json | 15 + frost-secp256k1-tr/tests/helpers/samples.json | 7 + frost-secp256k1-tr/tests/helpers/samples.rs | 128 +++ .../tests/helpers/vectors-big-identifier.json | 77 ++ frost-secp256k1-tr/tests/helpers/vectors.json | 77 ++ .../tests/helpers/vectors_dkg.json | 41 + frost-secp256k1-tr/tests/integration_tests.rs | 359 +++++++ .../tests/interoperability_tests.rs | 53 + frost-secp256k1-tr/tests/recreation_tests.rs | 133 +++ .../tests/rerandomized_tests.rs | 10 + frost-secp256k1-tr/tests/serde_tests.rs | 632 ++++++++++++ .../tests/serialization_tests.rs | 105 ++ ...ck_key_package_postcard_serialization.snap | 5 + ...ic_key_package_postcard_serialization.snap | 5 + ...round1_package_postcard_serialization.snap | 5 + ...round2_package_postcard_serialization.snap | 5 + ...k_secret_share_postcard_serialization.snap | 5 + ...ignature_share_postcard_serialization.snap | 5 + ...ng_commitments_postcard_serialization.snap | 5 + ...signing_nonces_postcard_serialization.snap | 5 + ...igning_package_postcard_serialization.snap | 5 + frost-secp256k1-tr/tests/tweaking_tests.rs | 149 +++ frost-secp256k1/tests/helpers/samples.json | 3 +- frost-secp256k1/tests/helpers/samples.rs | 11 +- gencode/src/main.rs | 48 +- 61 files changed, 4060 insertions(+), 125 deletions(-) create mode 100644 frost-secp256k1-tr/Cargo.toml create mode 100644 frost-secp256k1-tr/README.md create mode 100644 frost-secp256k1-tr/benches/bench.rs create mode 100644 frost-secp256k1-tr/dkg.md create mode 100644 frost-secp256k1-tr/src/keys/dkg.rs create mode 100644 frost-secp256k1-tr/src/keys/refresh.rs create mode 100644 frost-secp256k1-tr/src/keys/repairable.rs create mode 100644 frost-secp256k1-tr/src/lib.rs create mode 100644 frost-secp256k1-tr/src/tests.rs create mode 100644 frost-secp256k1-tr/src/tests/batch.rs create mode 100644 frost-secp256k1-tr/src/tests/coefficient_commitment.rs create mode 100644 frost-secp256k1-tr/src/tests/deserialize.rs create mode 100644 frost-secp256k1-tr/src/tests/proptests.rs create mode 100644 frost-secp256k1-tr/src/tests/vss_commitment.rs create mode 100644 frost-secp256k1-tr/tests/common_traits_tests.rs create mode 100644 frost-secp256k1-tr/tests/helpers/elements.json create mode 100644 frost-secp256k1-tr/tests/helpers/mod.rs create mode 100644 frost-secp256k1-tr/tests/helpers/repair-share.json create mode 100644 frost-secp256k1-tr/tests/helpers/samples.json create mode 100644 frost-secp256k1-tr/tests/helpers/samples.rs create mode 100644 frost-secp256k1-tr/tests/helpers/vectors-big-identifier.json create mode 100644 frost-secp256k1-tr/tests/helpers/vectors.json create mode 100644 frost-secp256k1-tr/tests/helpers/vectors_dkg.json create mode 100644 frost-secp256k1-tr/tests/integration_tests.rs create mode 100644 frost-secp256k1-tr/tests/interoperability_tests.rs create mode 100644 frost-secp256k1-tr/tests/recreation_tests.rs create mode 100644 frost-secp256k1-tr/tests/rerandomized_tests.rs create mode 100644 frost-secp256k1-tr/tests/serde_tests.rs create mode 100644 frost-secp256k1-tr/tests/serialization_tests.rs create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_key_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_secret_share_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signature_share_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_commitments_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_nonces_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/tweaking_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 08346be97..658adb74d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "frost-p256", "frost-ristretto255", "frost-secp256k1", + "frost-secp256k1-tr", "frost-rerandomized", "gencode" ] diff --git a/frost-core/src/batch.rs b/frost-core/src/batch.rs index 3aa1e8065..a30a109e4 100644 --- a/frost-core/src/batch.rs +++ b/frost-core/src/batch.rs @@ -33,10 +33,14 @@ where where M: AsRef<[u8]>, { - // Compute c now to avoid dependency on the msg lifetime. - let c = crate::challenge(&sig.R, &vk, msg.as_ref())?; - - Ok(Self { vk, sig, c }) + let (msg, sig, vk) = ::pre_verify(msg.as_ref(), &sig, &vk)?; + let c = ::challenge(&sig.R, &vk, &msg)?; + + Ok(Self { + vk: *vk, + sig: *sig, + c, + }) } } diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 60b33cdc8..dcdb9b6ce 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -355,8 +355,7 @@ pub(crate) fn compute_proof_of_knowledge // > a_{i0} by calculating σ_i = (R_i, μ_i), such that k ← Z_q, R_i = g^k, // > c_i = H(i, Φ, g^{a_{i0}} , R_i), μ_i = k + a_{i0} · c_i, with Φ being // > a context string to prevent replay attacks. - let k = <::Field>::random(&mut rng); - let R_i = ::generator() * k; + let (k, R_i) = ::generate_nonce(&mut rng); let c_i = challenge::(identifier, &commitment.verifying_key()?, &R_i)?; let a_i0 = *coefficients .first() @@ -570,5 +569,5 @@ pub fn part3( min_signers: round2_secret_package.min_signers, }; - Ok((key_package, public_key_package)) + C::post_dkg(key_package, public_key_package) } diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index a01cd3c26..7d78e2dfd 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -25,6 +25,7 @@ use alloc::{ use derive_getters::Getters; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; +use keys::PublicKeyPackage; use rand_core::{CryptoRng, RngCore}; use serialization::SerializableScalar; use zeroize::Zeroize; @@ -64,11 +65,7 @@ pub use verifying_key::VerifyingKey; /// /// [challenge]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-challenge-computa #[derive(Copy, Clone)] -#[cfg_attr(feature = "internals", visibility::make(pub))] -#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -pub(crate) struct Challenge( - pub(crate) <::Field as Field>::Scalar, -); +pub struct Challenge(pub(crate) <::Field as Field>::Scalar); impl Challenge where @@ -138,6 +135,8 @@ where /// Generates a random nonzero scalar. /// /// It assumes that the Scalar Eq/PartialEq implementation is constant-time. +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn random_nonzero(rng: &mut R) -> Scalar { loop { let scalar = <::Field>::random(rng); @@ -192,9 +191,7 @@ where /// /// #[derive(Clone, PartialEq, Eq)] -#[cfg_attr(feature = "internals", visibility::make(pub))] -#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -pub(crate) struct BindingFactor(Scalar); +pub struct BindingFactor(Scalar); impl BindingFactor where @@ -469,9 +466,7 @@ where /// The product of all signers' individual commitments, published as part of the /// final signature. #[derive(Clone, PartialEq, Eq)] -#[cfg_attr(feature = "internals", visibility::make(pub))] -#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -pub(crate) struct GroupCommitment(pub(crate) Element); +pub struct GroupCommitment(pub(crate) Element); impl GroupCommitment where @@ -483,6 +478,12 @@ where pub(crate) fn to_element(self) -> ::Element { self.0 } + + /// Return the underlying element. + #[cfg(feature = "internals")] + pub fn from_element(element: Element) -> Self { + Self(element) + } } /// Generates the group commitment which is published as part of the joint @@ -584,12 +585,15 @@ where return Err(Error::UnknownIdentifier); } + let (signing_package, signature_shares, pubkeys) = + ::pre_aggregate(signing_package, signature_shares, pubkeys)?; + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. let binding_factor_list: BindingFactorList = - compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?; + compute_binding_factor_list(&signing_package, &pubkeys.verifying_key, &[])?; // Compute the group commitment from signing commitments produced in round one. - let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + let group_commitment = compute_group_commitment(&signing_package, &binding_factor_list)?; // The aggregation of the signature shares by summing them up, resulting in // a plain Schnorr signature. @@ -619,10 +623,10 @@ where #[cfg(feature = "cheater-detection")] if verification_result.is_err() { detect_cheater( - group_commitment, - pubkeys, - signing_package, - signature_shares, + &group_commitment, + &pubkeys, + &signing_package, + &signature_shares, &binding_factor_list, )?; } @@ -637,17 +641,17 @@ where /// Each share is verified to find the cheater #[cfg(feature = "cheater-detection")] fn detect_cheater( - group_commitment: GroupCommitment, + group_commitment: &GroupCommitment, pubkeys: &keys::PublicKeyPackage, signing_package: &SigningPackage, signature_shares: &BTreeMap, round2::SignatureShare>, binding_factor_list: &BindingFactorList, ) -> Result<(), Error> { // Compute the per-message challenge. - let challenge = crate::challenge::( + let challenge = ::challenge( &group_commitment.0, &pubkeys.verifying_key, - signing_package.message().as_slice(), + signing_package.message(), )?; // Verify the signature shares. @@ -663,6 +667,7 @@ fn detect_cheater( *identifier, signing_package, binding_factor_list, + group_commitment, signature_share, verifying_share, challenge, @@ -688,24 +693,44 @@ pub fn verify_signature_share( signing_package: &SigningPackage, verifying_key: &VerifyingKey, ) -> Result<(), Error> { + // In order to reuse `pre_aggregate()`, we need to create some "dummy" containers + let signature_shares = BTreeMap::from([(identifier, *signature_share)]); + let verifying_shares = BTreeMap::from([(identifier, *verifying_share)]); + let public_key_package = PublicKeyPackage::new(verifying_shares, *verifying_key); + + let (signing_package, signature_shares, pubkeys) = + ::pre_aggregate(signing_package, &signature_shares, &public_key_package)?; + + // Extract the processed values back from the "dummy" containers + let verifying_share = pubkeys + .verifying_shares() + .get(&identifier) + .expect("pre_aggregate() must keep the identifiers"); + let verifying_key = pubkeys.verifying_key(); + let signature_share = signature_shares + .get(&identifier) + .expect("pre_aggregate() must keep the identifiers"); + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. let binding_factor_list: BindingFactorList = - compute_binding_factor_list(signing_package, verifying_key, &[])?; + compute_binding_factor_list(&signing_package, verifying_key, &[])?; + // Compute the group commitment from signing commitments produced in round one. - let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + let group_commitment = compute_group_commitment(&signing_package, &binding_factor_list)?; // Compute the per-message challenge. - let challenge = crate::challenge::( - &group_commitment.to_element(), + let challenge = ::challenge( + &group_commitment.clone().to_element(), verifying_key, signing_package.message().as_slice(), )?; verify_signature_share_precomputed( identifier, - signing_package, + &signing_package, &binding_factor_list, + &group_commitment, signature_share, verifying_share, challenge, @@ -720,6 +745,7 @@ fn verify_signature_share_precomputed( signature_share_identifier: Identifier, signing_package: &SigningPackage, binding_factor_list: &BindingFactorList, + group_commitment: &GroupCommitment, signature_share: &round2::SignatureShare, verifying_share: &keys::VerifyingShare, challenge: Challenge, @@ -735,7 +761,10 @@ fn verify_signature_share_precomputed( .ok_or(Error::UnknownIdentifier)? .to_group_commitment_share(binding_factor); - signature_share.verify( + // Compute relation values to verify this signature share. + ::verify_share( + group_commitment, + signature_share, signature_share_identifier, &R_share, verifying_share, diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 27e1c229e..694043b2c 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -54,10 +54,16 @@ where Self::nonce_generate_from_random_bytes(secret, random_bytes) } + /// Create a nonce from a scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] fn from_scalar(scalar: <<::Group as Group>::Field as Field>::Scalar) -> Self { Self(SerializableScalar(scalar)) } + /// Convert a nonce into a scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn to_scalar( self, ) -> <<::Group as Group>::Field as Field>::Scalar { @@ -358,6 +364,20 @@ where #[derive(Clone, Copy, PartialEq)] pub struct GroupCommitmentShare(pub(super) Element); +impl GroupCommitmentShare { + /// Create from an element. + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn from_element(element: Element) -> Self { + Self(element) + } + + /// Return the underlying element. + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn to_element(self) -> Element { + self.0 + } +} + /// Encode the list of group signing commitments. /// /// Implements [`encode_group_commitment_list()`] from the spec. diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 7b0fd4502..3d8639968 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -4,7 +4,7 @@ use core::fmt::{self, Debug}; use crate as frost; use crate::{ - challenge, Challenge, Ciphersuite, Error, Field, Group, {round1, *}, + Challenge, Ciphersuite, Error, Field, Group, {round1, *}, }; /// A participant's signature share, which the coordinator will aggregate with all other signer's @@ -71,7 +71,8 @@ where challenge: &Challenge, ) -> Result<(), Error> { if (::generator() * self.to_scalar()) - != (group_commitment_share.0 + (verifying_share.to_element() * challenge.0 * lambda_i)) + != (group_commitment_share.to_element() + + (verifying_share.to_element() * challenge.0 * lambda_i)) { return Err(Error::InvalidSignatureShare { culprit: identifier, @@ -96,7 +97,7 @@ where /// Compute the signature share for a signing operation. #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -fn compute_signature_share( +pub(super) fn compute_signature_share( signer_nonces: &round1::SigningNonces, binding_factor: BindingFactor, lambda_i: <<::Group as Group>::Field as Field>::Scalar, @@ -142,34 +143,38 @@ pub fn sign( return Err(Error::IncorrectCommitment); } + let (signing_package, signer_nonces, key_package) = + ::pre_sign(signing_package, signer_nonces, key_package)?; + // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. let binding_factor_list: BindingFactorList = - compute_binding_factor_list(signing_package, &key_package.verifying_key, &[])?; + compute_binding_factor_list(&signing_package, &key_package.verifying_key, &[])?; let binding_factor: frost::BindingFactor = binding_factor_list .get(&key_package.identifier) .ok_or(Error::UnknownIdentifier)? .clone(); // Compute the group commitment from signing commitments produced in round one. - let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?; + let group_commitment = compute_group_commitment(&signing_package, &binding_factor_list)?; // Compute Lagrange coefficient. - let lambda_i = frost::derive_interpolating_value(key_package.identifier(), signing_package)?; + let lambda_i = frost::derive_interpolating_value(key_package.identifier(), &signing_package)?; // Compute the per-message challenge. - let challenge = challenge::( + let challenge = ::challenge( &group_commitment.0, &key_package.verifying_key, - signing_package.message.as_slice(), + signing_package.message(), )?; // Compute the Schnorr signature share. - let signature_share = compute_signature_share( - signer_nonces, + let signature_share = ::compute_signature_share( + &group_commitment, + &signer_nonces, binding_factor, lambda_i, - key_package, + &key_package, challenge, ); diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index 7150b0852..91d3ade9d 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -1,11 +1,12 @@ //! Schnorr signatures over prime order groups (or subgroups) use alloc::{string::ToString, vec::Vec}; +use derive_getters::Getters; use crate::{Ciphersuite, Element, Error, Field, Group, Scalar}; /// A Schnorr signature over some prime order group (or subgroup). -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Getters)] pub struct Signature { /// The commitment `R` to the signature nonce. pub(crate) R: Element, @@ -29,8 +30,10 @@ where Self { R, z } } - /// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature`. - pub fn deserialize(bytes: &[u8]) -> Result> { + /// Converts default-encoded bytes as + /// [`Ciphersuite::SignatureSerialization`] into a `Signature`. + #[cfg(feature = "internals")] + pub fn default_deserialize(bytes: &[u8]) -> Result> { // To compute the expected length of the encoded point, encode the generator // and get its length. Note that we can't use the identity because it can be encoded // shorter in some cases (e.g. P-256, which uses SEC1 encoding). @@ -66,8 +69,14 @@ where }) } - /// Converts this signature to its byte serialization. - pub fn serialize(&self) -> Result, Error> { + /// Converts bytes as [`Ciphersuite::SignatureSerialization`] into a `Signature`. + pub fn deserialize(bytes: &[u8]) -> Result> { + C::deserialize_signature(bytes) + } + + /// Converts this signature to its default byte serialization. + #[cfg(feature = "internals")] + pub fn default_serialize(&self) -> Result, Error> { let mut bytes = Vec::::new(); bytes.extend(::serialize(&self.R)?.as_ref()); @@ -75,6 +84,11 @@ where Ok(bytes) } + + /// Converts this signature to its byte serialization. + pub fn serialize(&self) -> Result, Error> { + ::serialize_signature(self) + } } #[cfg(feature = "serde")] diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index 4ba306ed2..93a096c3e 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -5,8 +5,8 @@ use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; use crate::{ - random_nonzero, serialization::SerializableScalar, Ciphersuite, Error, Field, Group, Scalar, - Signature, VerifyingKey, + random_nonzero, serialization::SerializableScalar, Challenge, Ciphersuite, Error, Field, Group, + Scalar, Signature, VerifyingKey, }; /// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`]. @@ -40,13 +40,20 @@ where } /// Create a signature `msg` using this `SigningKey`. - pub fn sign(&self, mut rng: R, msg: &[u8]) -> Signature { - let k = random_nonzero::(&mut rng); + pub fn sign(&self, rng: R, message: &[u8]) -> Signature { + ::single_sign(self, rng, message) + } + + /// Create a signature `msg` using this `SigningKey` using the default + /// signing. + #[cfg(feature = "internals")] + pub fn default_sign(&self, mut rng: R, message: &[u8]) -> Signature { + let public = VerifyingKey::::from(*self); - let R = ::generator() * k; + let (k, R) = ::generate_nonce(&mut rng); // Generate Schnorr challenge - let c = crate::challenge::(&R, &VerifyingKey::::from(*self), msg).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero."); + let c: Challenge = ::challenge(&R, &public, message).expect("should not return error since that happens only if one of the inputs is the identity. R is not since k is nonzero. The verifying_key is not because signing keys are not allowed to be zero."); let z = k + (c.0 * self.scalar); diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index b41ef4439..3271f26ac 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -10,7 +10,6 @@ use crate::{ keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningPackage, VerifyingKey, }; -use alloc::borrow::ToOwned; use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 3e0d9365c..4e3f959d7 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -5,10 +5,18 @@ use core::{ ops::{Add, Mul, Sub}, }; -use alloc::vec::Vec; +use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec}; use rand_core::{CryptoRng, RngCore}; -use crate::{Error, FieldError, GroupError, Signature, VerifyingKey}; +use crate::{ + challenge, + keys::{KeyPackage, PublicKeyPackage, VerifyingShare}, + random_nonzero, + round1::{self}, + round2::{self, SignatureShare}, + BindingFactor, Challenge, Error, FieldError, GroupCommitment, GroupError, Identifier, + Signature, SigningKey, SigningPackage, VerifyingKey, +}; /// A prime order finite field GF(q) over which all scalar values for our prime order group can be /// multiplied are defined. @@ -213,22 +221,205 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { None } - /// Verify a signature for this ciphersuite. The default implementation uses the "cofactored" - /// equation (it multiplies by the cofactor returned by [`Group::cofactor()`]). + // The following are optional methods that allow customizing steps of the + // protocol if required. + + /// Optional. Do regular (non-FROST) signing with a [`SigningKey`]. Called + /// by [`SigningKey::sign()`]. This is not used by FROST. Can be overriden + /// if required which is useful if FROST signing has been changed by the + /// other Ciphersuite trait methods and regular signing should be changed + /// accordingly to match. + fn single_sign( + signing_key: &SigningKey, + rng: R, + message: &[u8], + ) -> Signature { + signing_key.default_sign(rng, message) + } + + /// Optional. Verify a signature for this ciphersuite. Called by + /// [`VerifyingKey::verify()`]. The default implementation uses the + /// "cofactored" equation (it multiplies by the cofactor returned by + /// [`Group::cofactor()`]). /// /// # Cryptographic Safety /// - /// You may override this to provide a tailored implementation, but if the ciphersuite defines it, - /// it must also multiply by the cofactor to comply with the RFC. Note that batch verification - /// (see [`crate::batch::Verifier`]) also uses the default implementation regardless whether a - /// tailored implementation was provided. + /// You may override this to provide a tailored implementation, but if the + /// ciphersuite defines it, it must also multiply by the cofactor to comply + /// with the RFC. Note that batch verification (see + /// [`crate::batch::Verifier`]) also uses the default implementation + /// regardless whether a tailored implementation was provided. fn verify_signature( - msg: &[u8], + message: &[u8], signature: &Signature, public_key: &VerifyingKey, ) -> Result<(), Error> { - let c = crate::challenge::(&signature.R, public_key, msg)?; + let (message, signature, public_key) = ::pre_verify(message, signature, public_key)?; + + let c = ::challenge(&signature.R, &public_key, &message)?; + + public_key.verify_prehashed(c, &signature) + } + + /// Optional. Pre-process [`round2::sign()`] inputs. The default + /// implementation returns them as-is. [`Cow`] is used so implementations + /// can choose to return the same passed reference or a modified clone. + #[allow(clippy::type_complexity)] + fn pre_sign<'a>( + signing_package: &'a SigningPackage, + signer_nonces: &'a round1::SigningNonces, + key_package: &'a KeyPackage, + ) -> Result< + ( + Cow<'a, SigningPackage>, + Cow<'a, round1::SigningNonces>, + Cow<'a, KeyPackage>, + ), + Error, + > { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signer_nonces), + Cow::Borrowed(key_package), + )) + } + + /// Optional. Pre-process [`crate::aggregate()`] and + /// [`crate::verify_signature_share()`] inputs. In the latter case, "dummy" + /// container BTreeMap and PublicKeyPackage are passed with the relevant + /// values. The default implementation returns them as-is. [`Cow`] is used + /// so implementations can choose to return the same passed reference or a + /// modified clone. + #[allow(clippy::type_complexity)] + fn pre_aggregate<'a>( + signing_package: &'a SigningPackage, + signature_shares: &'a BTreeMap, round2::SignatureShare>, + public_key_package: &'a PublicKeyPackage, + ) -> Result< + ( + Cow<'a, SigningPackage>, + Cow<'a, BTreeMap, round2::SignatureShare>>, + Cow<'a, PublicKeyPackage>, + ), + Error, + > { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signature_shares), + Cow::Borrowed(public_key_package), + )) + } + + /// Optional. Pre-process [`VerifyingKey::verify()`] inputs. The default + /// implementation returns them as-is. [`Cow`] is used so implementations + /// can choose to return the same passed reference or a modified clone. + #[allow(clippy::type_complexity)] + fn pre_verify<'a>( + msg: &'a [u8], + signature: &'a Signature, + public_key: &'a VerifyingKey, + ) -> Result< + ( + Cow<'a, [u8]>, + Cow<'a, Signature>, + Cow<'a, VerifyingKey>, + ), + Error, + > { + Ok(( + Cow::Borrowed(msg), + Cow::Borrowed(signature), + Cow::Borrowed(public_key), + )) + } + + /// Optional. Generate a nonce and a commitment to it. Used by + /// [`SigningKey`] for regular (non-FROST) signing and internally by the DKG + /// to generate proof-of-knowledge signatures. + fn generate_nonce( + rng: &mut R, + ) -> ( + <::Field as Field>::Scalar, + ::Element, + ) { + let k = random_nonzero::(rng); + let R = ::generator() * k; + (k, R) + } + + /// Optional. Generates the challenge as is required for Schnorr signatures. + /// Called by [`round2::sign()`] and [`crate::aggregate()`]. + fn challenge( + R: &Element, + verifying_key: &VerifyingKey, + message: &[u8], + ) -> Result, Error> { + challenge(R, verifying_key, message) + } + + /// Optional. Compute the signature share for a particular signer on a given + /// challenge. Called by [`round2::sign()`]. + fn compute_signature_share( + _group_commitment: &GroupCommitment, + signer_nonces: &round1::SigningNonces, + binding_factor: BindingFactor, + lambda_i: <::Field as Field>::Scalar, + key_package: &KeyPackage, + challenge: Challenge, + ) -> round2::SignatureShare { + round2::compute_signature_share( + signer_nonces, + binding_factor, + lambda_i, + key_package, + challenge, + ) + } + + /// Optional. Verify a signing share. Called by [`crate::aggregate()`] if + /// cheater detection is enabled. + fn verify_share( + _group_commitment: &GroupCommitment, + signature_share: &SignatureShare, + identifier: Identifier, + group_commitment_share: &round1::GroupCommitmentShare, + verifying_share: &VerifyingShare, + lambda_i: Scalar, + challenge: &Challenge, + ) -> Result<(), Error> { + signature_share.verify( + identifier, + group_commitment_share, + verifying_share, + lambda_i, + challenge, + ) + } + + /// Optional. Converts a signature to its + /// [`Ciphersuite::SignatureSerialization`] in bytes. + /// + /// The default implementation serializes a signature by serializing its `R` + /// point and `z` component independently, and then concatenating them. + fn serialize_signature(signature: &Signature) -> Result, Error> { + signature.default_serialize() + } + + /// Optional. Converts bytes as [`Ciphersuite::SignatureSerialization`] into + /// a `Signature`. + /// + /// The default implementation assumes the serialization is a serialized `R` + /// point followed by a serialized `z` component with no padding or extra + /// fields. + fn deserialize_signature(bytes: &[u8]) -> Result, Error> { + Signature::::default_deserialize(bytes) + } - public_key.verify_prehashed(c, signature) + /// Post-process the output of the DKG for a given participant. + fn post_dkg( + key_package: KeyPackage, + public_key_package: PublicKeyPackage, + ) -> Result<(KeyPackage, PublicKeyPackage), Error> { + Ok((key_package, public_key_package)) } } diff --git a/frost-core/src/verifying_key.rs b/frost-core/src/verifying_key.rs index c3a1cfc53..24f054019 100644 --- a/frost-core/src/verifying_key.rs +++ b/frost-core/src/verifying_key.rs @@ -51,6 +51,8 @@ where /// Verify a purported `signature` with a pre-hashed [`Challenge`] made by this verification /// key. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn verify_prehashed( &self, challenge: Challenge, diff --git a/frost-ed25519/tests/helpers/samples.json b/frost-ed25519/tests/helpers/samples.json index 3402fbe74..a61e0c4b8 100644 --- a/frost-ed25519/tests/helpers/samples.json +++ b/frost-ed25519/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "proof_of_knowledge": "5866666666666666666666666666666666666666666666666666666666666666498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "element1": "5866666666666666666666666666666666666666666666666666666666666666", "element2": "c9a3f86aae465f0e56513864510f3997561fa2c9e85ea21dc2292309f3cd6022", "scalar1": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a" -} \ No newline at end of file +} diff --git a/frost-ed25519/tests/helpers/samples.rs b/frost-ed25519/tests/helpers/samples.rs index 6f22aed3d..4d785e5cd 100644 --- a/frost-ed25519/tests/helpers/samples.rs +++ b/frost-ed25519/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-ed448/tests/helpers/samples.json b/frost-ed448/tests/helpers/samples.json index 36e862866..f93c3e9ce 100644 --- a/frost-ed448/tests/helpers/samples.json +++ b/frost-ed448/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "proof_of_knowledge": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f69004d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00", "element1": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", "element2": "ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae80", "scalar1": "4d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a00" -} \ No newline at end of file +} diff --git a/frost-ed448/tests/helpers/samples.rs b/frost-ed448/tests/helpers/samples.rs index ec5567369..afb26d509 100644 --- a/frost-ed448/tests/helpers/samples.rs +++ b/frost-ed448/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-p256/tests/helpers/samples.json b/frost-p256/tests/helpers/samples.json index 928e355c7..3fe4c6987 100644 --- a/frost-p256/tests/helpers/samples.json +++ b/frost-p256/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "proof_of_knowledge": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1", "element1": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", "element2": "037cf27b188d034f7e8a52380304b51ac3c08969e277f21b35a60b48fc47669978", "scalar1": "aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1" -} \ No newline at end of file +} diff --git a/frost-p256/tests/helpers/samples.rs b/frost-p256/tests/helpers/samples.rs index 340bc7635..432af4757 100644 --- a/frost-p256/tests/helpers/samples.rs +++ b/frost-p256/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-ristretto255/tests/helpers/samples.json b/frost-ristretto255/tests/helpers/samples.json index bb80d1b87..1fff13375 100644 --- a/frost-ristretto255/tests/helpers/samples.json +++ b/frost-ristretto255/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "2a00000000000000000000000000000000000000000000000000000000000000", + "proof_of_knowledge": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a", "element1": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", "element2": "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919", "scalar1": "498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a" -} \ No newline at end of file +} diff --git a/frost-ristretto255/tests/helpers/samples.rs b/frost-ristretto255/tests/helpers/samples.rs index f598c53f4..da9e99191 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml new file mode 100644 index 000000000..99d936871 --- /dev/null +++ b/frost-secp256k1-tr/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "frost-secp256k1-tr" +edition = "2021" +# When releasing to crates.io: +# - Update CHANGELOG.md +# - Create git tag. +version = "2.0.0-rc.0" +authors = [ + "Deirdre Connolly ", + "Chelsea Komlo ", + "Conrado Gouvea " +] +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/ZcashFoundation/frost" +categories = ["cryptography"] +keywords = ["cryptography", "crypto", "threshold", "signature"] +description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot." + +[package.metadata.docs.rs] +features = ["serde"] +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +document-features = "0.2.7" +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } +k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } +serde = { version = "1.0.160", features = ["derive"], optional = true } +rand_core = "0.6" +sha2 = { version = "0.10.2", default-features = false } + +[dev-dependencies] +criterion = "0.5" +frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } +insta = { version = "1.31.0", features = ["yaml"] } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +lazy_static = "1.4" +proptest = "1.0" +rand = "0.8" +rand_chacha = "0.3" +secp256k1 = "0.30.0" +serde_json = "1.0" + +[features] +nightly = [] +default = ["serialization", "cheater-detection", "std"] +#! ## Features +## Enable standard library support. +std = ["frost-core/std"] +## Enable `serde` support for types that need to be communicated. You +## can use `serde` to serialize structs with any encoder that supports +## `serde` (e.g. JSON with `serde_json`). +serde = ["frost-core/serde", "dep:serde"] +## Enable a default serialization format. Enables `serde`. +serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] +## Enable cheater detection +cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] + +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false diff --git a/frost-secp256k1-tr/README.md b/frost-secp256k1-tr/README.md new file mode 100644 index 000000000..f4d2205f1 --- /dev/null +++ b/frost-secp256k1-tr/README.md @@ -0,0 +1,121 @@ +An implementation of Schnorr signatures on the secp256k1 curve (Taproot) for both single and threshold numbers +of signers (FROST). + +## Example: key generation with trusted dealer and FROST signing + +Creating a key with a trusted dealer and splitting into shares; then signing a message +and aggregating the signature. Note that the example just simulates a distributed +scenario in a single thread and it abstracts away any communication between peers. + + +```rust +# // ANCHOR: tkg_gen +use frost_secp256k1_tr as frost; +use rand::thread_rng; +use std::collections::BTreeMap; + +let mut rng = thread_rng(); +let max_signers = 5; +let min_signers = 3; +let (shares, pubkey_package) = frost::keys::generate_with_dealer( + max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, +)?; +# // ANCHOR_END: tkg_gen + +// Verifies the secret shares from the dealer and store them in a BTreeMap. +// In practice, the KeyPackages must be sent to its respective participants +// through a confidential and authenticated channel. +let mut key_packages: BTreeMap<_, _> = BTreeMap::new(); + +for (identifier, secret_share) in shares { + # // ANCHOR: tkg_verify + let key_package = frost::keys::KeyPackage::try_from(secret_share)?; + # // ANCHOR_END: tkg_verify + key_packages.insert(identifier, key_package); +} + +let mut nonces_map = BTreeMap::new(); +let mut commitments_map = BTreeMap::new(); + +//////////////////////////////////////////////////////////////////////////// +// Round 1: generating nonces and signing commitments for each participant +//////////////////////////////////////////////////////////////////////////// + +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_index in 1..=min_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let key_package = &key_packages[&participant_identifier]; + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _threshold_. + # // ANCHOR: round1_commit + let (nonces, commitments) = frost::round1::commit( + key_package.signing_share(), + &mut rng, + ); + # // ANCHOR_END: round1_commit + // In practice, the nonces must be kept by the participant to use in the + // next round, while the commitment must be sent to the coordinator + // (or to every other participant if there is no coordinator) using + // an authenticated channel. + nonces_map.insert(participant_identifier, nonces); + commitments_map.insert(participant_identifier, commitments); +} + +// This is what the signature aggregator / coordinator needs to do: +// - decide what message to sign +// - take one (unused) commitment per signing participant +let mut signature_shares = BTreeMap::new(); +# // ANCHOR: round2_package +let message = "message to sign".as_bytes(); +# // In practice, the SigningPackage must be sent to all participants +# // involved in the current signing (at least min_signers participants), +# // using an authenticate channel (and confidential if the message is secret). +let signing_package = frost::SigningPackage::new(commitments_map, message); +# // ANCHOR_END: round2_package + +//////////////////////////////////////////////////////////////////////////// +// Round 2: each participant generates their signature share +//////////////////////////////////////////////////////////////////////////// + +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_identifier in nonces_map.keys() { + let key_package = &key_packages[participant_identifier]; + + let nonces = &nonces_map[participant_identifier]; + + // Each participant generates their signature share. + # // ANCHOR: round2_sign + let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?; + # // ANCHOR_END: round2_sign + + // In practice, the signature share must be sent to the Coordinator + // using an authenticated channel. + signature_shares.insert(*participant_identifier, signature_share); +} + +//////////////////////////////////////////////////////////////////////////// +// Aggregation: collects the signing shares from all participants, +// generates the final signature. +//////////////////////////////////////////////////////////////////////////// + +// Aggregate (also verifies the signature shares) +# // ANCHOR: aggregate +let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; +# // ANCHOR_END: aggregate + + +// Check that the threshold signature can be verified by the group public +// key (the verification key). +# // ANCHOR: verify +let is_signature_valid = pubkey_package + .verifying_key() + .verify(message, &group_signature) + .is_ok(); +# // ANCHOR_END: verify +assert!(is_signature_valid); + +# Ok::<(), frost::Error>(()) +``` diff --git a/frost-secp256k1-tr/benches/bench.rs b/frost-secp256k1-tr/benches/bench.rs new file mode 100644 index 000000000..e9097bdd3 --- /dev/null +++ b/frost-secp256k1-tr/benches/bench.rs @@ -0,0 +1,19 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::thread_rng; + +use frost_secp256k1_tr::*; + +fn bench_secp256k1_batch_verify(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); +} + +fn bench_secp256k1_sign(c: &mut Criterion) { + let mut rng = thread_rng(); + + frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); +} + +criterion_group!(benches, bench_secp256k1_batch_verify, bench_secp256k1_sign); +criterion_main!(benches); diff --git a/frost-secp256k1-tr/dkg.md b/frost-secp256k1-tr/dkg.md new file mode 100644 index 000000000..217cbc99e --- /dev/null +++ b/frost-secp256k1-tr/dkg.md @@ -0,0 +1,168 @@ +# Distributed Key Generation (DKG) + +The DKG module supports generating FROST key shares in a distributed manner, +without a trusted dealer. + +Before starting, each participant needs an unique identifier, which can be built from +a `u16`. The process in which these identifiers are allocated is up to the application. + +The distributed key generation process has 3 parts, with 2 communication rounds +between them, in which each participant needs to send a "package" to every other +participant. In the first round, each participant sends the same package +(a [`round1::Package`]) to every other. In the second round, each receiver gets +their own package (a [`round2::Package`]). + +Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] +that MUST be kept secret. Between part 2 and 3, each participant needs to hold +onto a [`round2::SecretPackage`]. + +After the third part, each participant will get a [`KeyPackage`] with their +long-term secret share that must be kept secret, and a [`PublicKeyPackage`] +that is public (and will be the same between all participants). With those +they can proceed to sign messages with FROST. + + +## Example + +```rust +# // ANCHOR: dkg_import +use rand::thread_rng; +use std::collections::BTreeMap; + +use frost_secp256k1_tr as frost; + +let mut rng = thread_rng(); + +let max_signers = 5; +let min_signers = 3; +# // ANCHOR_END: dkg_import + +//////////////////////////////////////////////////////////////////////////// +// Key generation, Round 1 +//////////////////////////////////////////////////////////////////////////// + +// Keep track of each participant's round 1 secret package. +// In practice each participant will keep its copy; no one +// will have all the participant's packages. +let mut round1_secret_packages = BTreeMap::new(); + +// Keep track of all round 1 packages sent to the given participant. +// This is used to simulate the broadcast; in practice the packages +// will be sent through some communication channel. +let mut received_round1_packages = BTreeMap::new(); + +// For each participant, perform the first part of the DKG protocol. +// In practice, each participant will perform this on their own environments. +for participant_index in 1..=max_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + # // ANCHOR: dkg_part1 + let (round1_secret_package, round1_package) = frost::keys::dkg::part1( + participant_identifier, + max_signers, + min_signers, + &mut rng, + )?; + # // ANCHOR_END: dkg_part1 + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round1_secret_packages.insert(participant_identifier, round1_secret_package); + + // "Send" the round 1 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + for receiver_participant_index in 1..=max_signers { + if receiver_participant_index == participant_index { + continue; + } + let receiver_participant_identifier: frost::Identifier = receiver_participant_index + .try_into() + .expect("should be nonzero"); + received_round1_packages + .entry(receiver_participant_identifier) + .or_insert_with(BTreeMap::new) + .insert(participant_identifier, round1_package.clone()); + } +} + +//////////////////////////////////////////////////////////////////////////// +// Key generation, Round 2 +//////////////////////////////////////////////////////////////////////////// + +// Keep track of each participant's round 2 secret package. +// In practice each participant will keep its copy; no one +// will have all the participant's packages. +let mut round2_secret_packages = BTreeMap::new(); + +// Keep track of all round 2 packages sent to the given participant. +// This is used to simulate the broadcast; in practice the packages +// will be sent through some communication channel. +let mut received_round2_packages = BTreeMap::new(); + +// For each participant, perform the second part of the DKG protocol. +// In practice, each participant will perform this on their own environments. +for participant_index in 1..=max_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let round1_secret_package = round1_secret_packages + .remove(&participant_identifier) + .unwrap(); + let round1_packages = &received_round1_packages[&participant_identifier]; + # // ANCHOR: dkg_part2 + let (round2_secret_package, round2_packages) = + frost::keys::dkg::part2(round1_secret_package, round1_packages)?; + # // ANCHOR_END: dkg_part2 + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round2_secret_packages.insert(participant_identifier, round2_secret_package); + + // "Send" the round 2 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + // Note that, in contrast to the previous part, here each other participant + // gets its own specific package. + for (receiver_identifier, round2_package) in round2_packages { + received_round2_packages + .entry(receiver_identifier) + .or_insert_with(BTreeMap::new) + .insert(participant_identifier, round2_package); + } +} + +//////////////////////////////////////////////////////////////////////////// +// Key generation, final computation +//////////////////////////////////////////////////////////////////////////// + +// Keep track of each participant's long-lived key package. +// In practice each participant will keep its copy; no one +// will have all the participant's packages. +let mut key_packages = BTreeMap::new(); + +// Keep track of each participant's public key package. +// In practice, if there is a Coordinator, only they need to store the set. +// If there is not, then all candidates must store their own sets. +// All participants will have the same exact public key package. +let mut pubkey_packages = BTreeMap::new(); + +// For each participant, perform the third part of the DKG protocol. +// In practice, each participant will perform this on their own environments. +for participant_index in 1..=max_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let round2_secret_package = &round2_secret_packages[&participant_identifier]; + let round1_packages = &received_round1_packages[&participant_identifier]; + let round2_packages = &received_round2_packages[&participant_identifier]; + # // ANCHOR: dkg_part3 + let (key_package, pubkey_package) = frost::keys::dkg::part3( + round2_secret_package, + round1_packages, + round2_packages, + )?; + # // ANCHOR_END: dkg_part3 + key_packages.insert(participant_identifier, key_package); + pubkey_packages.insert(participant_identifier, pubkey_package); +} + +// With its own key package and the pubkey package, each participant can now proceed +// to sign with FROST. +# Ok::<(), frost::Error>(()) +``` diff --git a/frost-secp256k1-tr/src/keys/dkg.rs b/frost-secp256k1-tr/src/keys/dkg.rs new file mode 100644 index 000000000..9ea40b263 --- /dev/null +++ b/frost-secp256k1-tr/src/keys/dkg.rs @@ -0,0 +1,103 @@ +#![doc = include_str!("../../dkg.md")] +use super::*; + +/// DKG Round 1 structures. +pub mod round1 { + use super::*; + + /// The secret package that must be kept in memory by the participant + /// between the first and second parts of the DKG protocol (round 1). + /// + /// # Security + /// + /// This package MUST NOT be sent to other participants! + pub type SecretPackage = frost::keys::dkg::round1::SecretPackage; + + /// The package that must be broadcast by each participant to all other participants + /// between the first and second parts of the DKG protocol (round 1). + pub type Package = frost::keys::dkg::round1::Package; +} + +/// DKG Round 2 structures. +pub mod round2 { + use super::*; + + /// The secret package that must be kept in memory by the participant + /// between the second and third parts of the DKG protocol (round 2). + /// + /// # Security + /// + /// This package MUST NOT be sent to other participants! + pub type SecretPackage = frost::keys::dkg::round2::SecretPackage; + + /// A package that must be sent by each participant to some other participants + /// in Round 2 of the DKG protocol. Note that there is one specific package + /// for each specific recipient, in contrast to Round 1. + /// + /// # Security + /// + /// The package must be sent on an *confidential* and *authenticated* channel. + pub type Package = frost::keys::dkg::round2::Package; +} + +/// Performs the first part of the distributed key generation protocol +/// for the given participant. +/// +/// It returns the [`round1::SecretPackage`] that must be kept in memory +/// by the participant for the other steps, and the [`round1::Package`] that +/// must be sent to each other participant in the DKG run. +pub fn part1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::dkg::part1(identifier, max_signers, min_signers, &mut rng) +} + +/// Performs the second part of the distributed key generation protocol for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. +/// +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. +pub fn part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, +) -> Result<(round2::SecretPackage, BTreeMap), Error> { + frost::keys::dkg::part2(secret_package, round1_packages) +} + +/// Performs the third and final part of the distributed key generation protocol +/// for the participant holding the given [`round2::SecretPackage`], given the +/// received [`round1::Package`]s and [`round2::Package`]s received from the +/// other participants. +/// +/// `round1_packages` must be the same used in [`part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// coordinator has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`KeyPackage`] that has the long-lived key share for the +/// participant, and the [`PublicKeyPackage`]s that has public information about +/// all participants; both of which are required to compute FROST signatures. +pub fn part3( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, + round2_packages: &BTreeMap, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::dkg::part3(round2_secret_package, round1_packages, round2_packages) +} diff --git a/frost-secp256k1-tr/src/keys/refresh.rs b/frost-secp256k1-tr/src/keys/refresh.rs new file mode 100644 index 000000000..c270fc202 --- /dev/null +++ b/frost-secp256k1-tr/src/keys/refresh.rs @@ -0,0 +1,35 @@ +//! Refresh Shares +//! +//! Implements the functionality to refresh a share. This requires the participation +//! of all the remaining signers. This can be done using a Trusted Dealer or +//! DKG (not yet implemented) + +use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; +use alloc::vec::Vec; + +use super::{KeyPackage, PublicKeyPackage, SecretShare}; + +/// Refreshes shares using a trusted dealer +pub fn compute_refreshing_shares( + old_pub_key_package: PublicKeyPackage, + max_signers: u16, + min_signers: u16, + identifiers: &[Identifier], + mut rng: &mut R, +) -> Result<(Vec, PublicKeyPackage), Error> { + frost::keys::refresh::compute_refreshing_shares( + old_pub_key_package, + max_signers, + min_signers, + identifiers, + &mut rng, + ) +} + +/// Each participant refreshed their shares +pub fn refresh_share( + zero_share: SecretShare, + current_share: &KeyPackage, +) -> Result { + frost::keys::refresh::refresh_share(zero_share, current_share) +} diff --git a/frost-secp256k1-tr/src/keys/repairable.rs b/frost-secp256k1-tr/src/keys/repairable.rs new file mode 100644 index 000000000..9b538030b --- /dev/null +++ b/frost-secp256k1-tr/src/keys/repairable.rs @@ -0,0 +1,103 @@ +//! Repairable Threshold Scheme +//! +//! Implements the Repairable Threshold Scheme (RTS) from . +//! The RTS is used to help a signer (participant) repair their lost share. This is achieved +//! using a subset of the other signers know here as `helpers`. + +use alloc::collections::BTreeMap; + +// This is imported separately to make `gencode` work. +// (if it were below, the position of the import would vary between ciphersuites +// after `cargo fmt`) +use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; +use crate::{Error, Secp256K1Sha256TR}; + +use super::{SecretShare, VerifiableSecretSharingCommitment}; + +/// Step 1 of RTS. +/// +/// Generates the "delta" values from `helper_i` to help `participant` recover their share +/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` +/// is the share of `helper_i`. +/// +/// Returns a BTreeMap mapping which value should be sent to which participant. +pub fn repair_share_step_1( + helpers: &[Identifier], + share_i: &SecretShare, + rng: &mut R, + participant: Identifier, +) -> Result, Error> { + frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) +} + +/// Step 2 of RTS. +/// +/// Generates the `sigma` values from all `deltas` received from `helpers` +/// to help `participant` recover their share. +/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. +/// +/// Returns a scalar +pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { + frost::keys::repairable::repair_share_step_2::(deltas_j) +} + +/// Step 3 of RTS +/// +/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` +/// is made up of the `identifier`and `commitment` of the `participant` as well as the +/// `value` which is the `SigningShare`. +pub fn repair_share_step_3( + sigmas: &[Scalar], + identifier: Identifier, + commitment: &VerifiableSecretSharingCommitment, +) -> SecretShare { + frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) +} + +#[cfg(test)] +mod tests { + + use lazy_static::lazy_static; + use rand::thread_rng; + use serde_json::Value; + + use crate::Secp256K1Sha256TR; + + lazy_static! { + pub static ref REPAIR_SHARE: Value = + serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()) + .unwrap(); + } + + #[test] + fn check_repair_share_step_1() { + let rng = thread_rng(); + + frost_core::tests::repairable::check_repair_share_step_1::(rng); + } + + #[test] + fn check_repair_share_step_2() { + frost_core::tests::repairable::check_repair_share_step_2::( + &REPAIR_SHARE, + ); + } + + #[test] + fn check_repair_share_step_3() { + let rng = thread_rng(); + frost_core::tests::repairable::check_repair_share_step_3::( + rng, + &REPAIR_SHARE, + ); + } + + #[test] + fn check_repair_share_step_1_fails_with_invalid_min_signers() { + let rng = thread_rng(); + frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< + Secp256K1Sha256TR, + _, + >(rng); + } +} diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs new file mode 100644 index 000000000..56aa52caa --- /dev/null +++ b/frost-secp256k1-tr/src/lib.rs @@ -0,0 +1,923 @@ +#![allow(non_snake_case)] +#![deny(missing_docs)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +#![doc = document_features::document_features!()] + +extern crate alloc; + +use alloc::borrow::Cow; +use alloc::borrow::ToOwned; +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + +use frost_rerandomized::RandomizedCiphersuite; +use k256::{ + elliptic_curve::{ + bigint::U256, + group::prime::PrimeCurveAffine, + hash2curve::{hash_to_field, ExpandMsgXmd}, + point::AffineCoordinates, + sec1::{FromEncodedPoint, ToEncodedPoint}, + Field as FFField, PrimeField, ScalarPrimitive, + }, + AffinePoint, ProjectivePoint, Scalar, +}; +use rand_core::{CryptoRng, RngCore}; +use sha2::{Digest, Sha256}; + +use frost_core::{self as frost, random_nonzero}; + +use keys::EvenY; +use keys::Tweak; + +#[cfg(test)] +mod tests; + +// Re-exports in our public API +#[cfg(feature = "serde")] +pub use frost_core::serde; +pub use frost_core::{ + Challenge, Ciphersuite, Element, Field, FieldError, Group, GroupCommitment, GroupError, +}; +pub use rand_core; + +/// An error. +pub type Error = frost_core::Error; + +/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite scalar field. +#[derive(Clone, Copy)] +pub struct Secp256K1ScalarField; + +impl Field for Secp256K1ScalarField { + type Scalar = Scalar; + + type Serialization = [u8; 32]; + + fn zero() -> Self::Scalar { + Scalar::ZERO + } + + fn one() -> Self::Scalar { + Scalar::ONE + } + + fn invert(scalar: &Self::Scalar) -> Result { + // [`Scalar`]'s Eq/PartialEq does a constant-time comparison + if *scalar == ::zero() { + Err(FieldError::InvalidZeroScalar) + } else { + Ok(scalar.invert().unwrap()) + } + } + + fn random(rng: &mut R) -> Self::Scalar { + Scalar::random(rng) + } + + fn serialize(scalar: &Self::Scalar) -> Self::Serialization { + scalar.to_bytes().into() + } + + fn deserialize(buf: &Self::Serialization) -> Result { + let field_bytes: &k256::FieldBytes = buf.into(); + match Scalar::from_repr(*field_bytes).into() { + Some(s) => Ok(s), + None => Err(FieldError::MalformedScalar), + } + } + + fn little_endian_serialize(scalar: &Self::Scalar) -> Self::Serialization { + let mut array = Self::serialize(scalar); + array.reverse(); + array + } +} + +/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite group. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Secp256K1Group; + +impl Group for Secp256K1Group { + type Field = Secp256K1ScalarField; + + type Element = ProjectivePoint; + + /// [SEC 1][1] serialization of a compressed point in secp256k1 takes 33 bytes + /// (1-byte prefix and 32 bytes for the coordinate). + /// + /// Note that, in the SEC 1 spec, the identity is encoded as a single null byte; + /// but here we pad with zeroes. This is acceptable as the identity _should_ never + /// be serialized in FROST, else we error. + /// + /// [1]: https://secg.org/sec1-v2.pdf + type Serialization = [u8; 33]; + + fn cofactor() -> ::Scalar { + Scalar::ONE + } + + fn identity() -> Self::Element { + ProjectivePoint::IDENTITY + } + + fn generator() -> Self::Element { + ProjectivePoint::GENERATOR + } + + fn serialize(element: &Self::Element) -> Result { + if *element == Self::identity() { + return Err(GroupError::InvalidIdentityElement); + } + let mut fixed_serialized = [0; 33]; + let serialized_point = element.to_affine().to_encoded_point(true); + let serialized = serialized_point.as_bytes(); + fixed_serialized.copy_from_slice(serialized); + Ok(fixed_serialized) + } + + fn deserialize(buf: &Self::Serialization) -> Result { + let encoded_point = + k256::EncodedPoint::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?; + + match Option::::from(AffinePoint::from_encoded_point(&encoded_point)) { + Some(point) => { + if point.is_identity().into() { + // This is actually impossible since the identity is encoded a a single byte + // which will never happen since we receive a 33-byte buffer. + // We leave the check for consistency. + Err(GroupError::InvalidIdentityElement) + } else { + Ok(ProjectivePoint::from(point)) + } + } + None => Err(GroupError::MalformedElement), + } + } +} + +fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { + let mut h = Sha256::new(); + for i in inputs { + h.update(i); + } + let mut output = [0u8; 32]; + output.copy_from_slice(h.finalize().as_slice()); + output +} + +fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { + let mut u = [Secp256K1ScalarField::zero()]; + hash_to_field::, Scalar>(&[msg], &[domain], &mut u) + .expect("should never return error according to error cases described in ExpandMsgXmd"); + u[0] +} + +/// Context string from the ciphersuite in the [spec]. +/// +/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-1 +const CONTEXT_STRING: &str = "FROST-secp256k1-SHA256-TR-v1"; + +/// An implementation of the FROST(secp256k1, SHA-256) ciphersuite. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Secp256K1Sha256TR; + +/// Digest the hasher to a Scalar +fn hasher_to_scalar(hasher: Sha256) -> Scalar { + let sp = ScalarPrimitive::new(U256::from_be_slice(&hasher.finalize())).unwrap(); + Scalar::from(&sp) +} + +/// Create a BIP340 compliant tagged hash +fn tagged_hash(tag: &str) -> Sha256 { + let mut hasher = Sha256::new(); + let mut tag_hasher = Sha256::new(); + tag_hasher.update(tag.as_bytes()); + let tag_hash = tag_hasher.finalize(); + hasher.update(tag_hash); + hasher.update(tag_hash); + hasher +} + +/// Create a BIP341 compliant taproot tweak +fn tweak>( + public_key: &<::Group as Group>::Element, + merkle_root: Option, +) -> Scalar { + match merkle_root { + None => Secp256K1ScalarField::zero(), + Some(root) => { + let mut hasher = tagged_hash("TapTweak"); + hasher.update(public_key.to_affine().x()); + hasher.update(root.as_ref()); + hasher_to_scalar(hasher) + } + } +} + +// Negate a Nonce +fn negate_nonce(nonce: &frost_core::round1::Nonce) -> frost_core::round1::Nonce { + frost_core::round1::Nonce::::from_scalar(-nonce.to_scalar()) +} + +// Negate a SigningNonces +fn negate_nonces(signing_nonces: &round1::SigningNonces) -> round1::SigningNonces { + // TODO: this recomputes commitments which is expensive, and not needed. + // Create an `internals` SigningNonces::from_nonces_and_commitments or + // something similar. + round1::SigningNonces::from_nonces( + negate_nonce(signing_nonces.hiding()), + negate_nonce(signing_nonces.binding()), + ) +} + +impl Ciphersuite for Secp256K1Sha256TR { + const ID: &'static str = CONTEXT_STRING; + + type Group = Secp256K1Group; + + type HashOutput = [u8; 32]; + + type SignatureSerialization = [u8; 64]; + + /// H1 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.1 + fn H1(m: &[u8]) -> <::Field as Field>::Scalar { + hash_to_scalar((CONTEXT_STRING.to_owned() + "rho").as_bytes(), m) + } + + /// H2 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.2 + fn H2(m: &[u8]) -> <::Field as Field>::Scalar { + let mut hasher = tagged_hash("BIP0340/challenge"); + hasher.update(m); + hasher_to_scalar(hasher) + } + + /// H3 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.3 + fn H3(m: &[u8]) -> <::Field as Field>::Scalar { + hash_to_scalar((CONTEXT_STRING.to_owned() + "nonce").as_bytes(), m) + } + + /// H4 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.4 + fn H4(m: &[u8]) -> Self::HashOutput { + hash_to_array(&[CONTEXT_STRING.as_bytes(), b"msg", m]) + } + + /// H5 for FROST(secp256k1, SHA-256) + /// + /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.5 + fn H5(m: &[u8]) -> Self::HashOutput { + hash_to_array(&[CONTEXT_STRING.as_bytes(), b"com", m]) + } + + /// HDKG for FROST(secp256k1, SHA-256) + fn HDKG(m: &[u8]) -> Option<<::Field as Field>::Scalar> { + Some(hash_to_scalar( + (CONTEXT_STRING.to_owned() + "dkg").as_bytes(), + m, + )) + } + + /// HID for FROST(secp256k1, SHA-256) + fn HID(m: &[u8]) -> Option<<::Field as Field>::Scalar> { + Some(hash_to_scalar( + (CONTEXT_STRING.to_owned() + "id").as_bytes(), + m, + )) + } + + // Sign, negating the key if required by BIP-340. + fn single_sign( + signing_key: &SigningKey, + rng: R, + message: &[u8], + ) -> Signature { + let signing_key = signing_key.into_even_y(None); + signing_key.default_sign(rng, message) + } + + // Preprocess sign inputs, negating the keys in the KeyPackage if required + // by BIP-340. + fn pre_sign<'a>( + signing_package: &'a SigningPackage, + signer_nonces: &'a round1::SigningNonces, + key_package: &'a keys::KeyPackage, + ) -> Result< + ( + Cow<'a, SigningPackage>, + Cow<'a, round1::SigningNonces>, + Cow<'a, keys::KeyPackage>, + ), + Error, + > { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signer_nonces), + Cow::Owned(key_package.clone().into_even_y(None)), + )) + } + + // Preprocess sign inputs, negating the keys in the PublicKeyPackage if + // required by BIP-340. + fn pre_aggregate<'a>( + signing_package: &'a SigningPackage, + signature_shares: &'a BTreeMap, + public_key_package: &'a keys::PublicKeyPackage, + ) -> Result< + ( + Cow<'a, SigningPackage>, + Cow<'a, BTreeMap>, + Cow<'a, keys::PublicKeyPackage>, + ), + Error, + > { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signature_shares), + Cow::Owned(public_key_package.clone().into_even_y(None)), + )) + } + + // Preprocess verify inputs, negating the VerifyingKey and `signature.R` if required by + // BIP-340. + fn pre_verify<'a>( + message: &'a [u8], + signature: &'a Signature, + public_key: &'a VerifyingKey, + ) -> Result<(Cow<'a, [u8]>, Cow<'a, Signature>, Cow<'a, VerifyingKey>), Error> { + let public_key = public_key.into_even_y(None); + let signature = signature.into_even_y(None); + Ok(( + Cow::Borrowed(message), + Cow::Owned(signature), + Cow::Owned(public_key), + )) + } + + // Generate a nonce, negating it if required by BIP-340. + fn generate_nonce( + rng: &mut R, + ) -> ( + <::Field as Field>::Scalar, + ::Element, + ) { + let k = random_nonzero::(rng); + let R = ::generator() * k; + if R.to_affine().y_is_odd().into() { + (-k, -R) + } else { + (k, R) + } + } + + // Compute the challenge. Per BIP-340, only the X coordinate of R and + // verifying_key are hashed, unlike vanilla FROST. + fn challenge( + R: &Element, + verifying_key: &VerifyingKey, + message: &[u8], + ) -> Result, Error> { + let mut preimage = vec![]; + preimage.extend_from_slice(&R.to_affine().x()); + preimage.extend_from_slice(&verifying_key.to_element().to_affine().x()); + preimage.extend_from_slice(message); + Ok(Challenge::from_scalar(S::H2(&preimage[..]))) + } + + /// Compute a signature share, negating the nonces if required by BIP-340. + fn compute_signature_share( + group_commitment: &GroupCommitment, + signer_nonces: &round1::SigningNonces, + binding_factor: frost::BindingFactor, + lambda_i: <::Field as Field>::Scalar, + key_package: &frost::keys::KeyPackage, + challenge: Challenge, + ) -> round2::SignatureShare { + let signer_nonces = if !group_commitment.has_even_y() { + negate_nonces(signer_nonces) + } else { + signer_nonces.clone() + }; + + frost::round2::compute_signature_share( + &signer_nonces, + binding_factor, + lambda_i, + key_package, + challenge, + ) + } + + /// Verify a signature share, negating the group commitment share if + /// required by BIP-340. + fn verify_share( + group_commitment: &GroupCommitment, + signature_share: &frost_core::round2::SignatureShare, + identifier: Identifier, + group_commitment_share: &frost_core::round1::GroupCommitmentShare, + verifying_share: &frost_core::keys::VerifyingShare, + lambda_i: Scalar, + challenge: &Challenge, + ) -> Result<(), Error> { + let group_commitment_share = if !group_commitment.has_even_y() { + frost_core::round1::GroupCommitmentShare::from_element( + -group_commitment_share.to_element(), + ) + } else { + *group_commitment_share + }; + signature_share.verify( + identifier, + &group_commitment_share, + verifying_share, + lambda_i, + challenge, + ) + } + + /// Serialize a signature in compact BIP340 format, with an x-only R point. + fn serialize_signature(signature: &Signature) -> Result, Error> { + let R_bytes = Self::Group::serialize(signature.R())?; + let z_bytes = ::Field::serialize(signature.z()); + + let mut bytes = vec![0u8; 64]; + bytes[..32].copy_from_slice(&R_bytes[1..]); + bytes[32..].copy_from_slice(&z_bytes); + Ok(bytes) + } + + /// Deserialize a signature in compact BIP340 format, with an x-only R point. + fn deserialize_signature(bytes: &[u8]) -> Result { + if bytes.len() != 64 { + return Err(Error::MalformedSignature); + } + + let mut R_bytes = [0u8; 33]; + R_bytes[0] = 0x02; // taproot signatures always have an even R point + R_bytes[1..].copy_from_slice(&bytes[..32]); + + let mut z_bytes = [0u8; 32]; + z_bytes.copy_from_slice(&bytes[32..]); + + let R = Self::Group::deserialize(&R_bytes)?; + let z = ::Field::deserialize(&z_bytes)?; + + Ok(Signature::new(R, z)) + } + + /// Post-process the DKG output. We add an unusable taproot tweak to the + /// group key computed by a DKG run, to prevent peers from inserting rogue + /// tapscript tweaks into the group's joint public key. + fn post_dkg( + key_package: keys::KeyPackage, + public_key_package: keys::PublicKeyPackage, + ) -> Result<(keys::KeyPackage, keys::PublicKeyPackage), Error> { + // From BIP-341: + // > If the spending conditions do not require a script path, the output + // > key should commit to an unspendable script path instead of having + // > no script path. This can be achieved by computing the output key + // > point as Q = P + int(hashTapTweak(bytes(P)))G. + let merkle_root = [0u8; 0]; + Ok(( + key_package.tweak(Some(&merkle_root)), + public_key_package.tweak(Some(&merkle_root)), + )) + } +} + +impl RandomizedCiphersuite for Secp256K1Sha256TR { + fn hash_randomizer(m: &[u8]) -> Option<<::Field as Field>::Scalar> { + Some(hash_to_scalar( + (CONTEXT_STRING.to_owned() + "randomizer").as_bytes(), + m, + )) + } +} + +type S = Secp256K1Sha256TR; + +/// A FROST(secp256k1, SHA-256) participant identifier. +pub type Identifier = frost::Identifier; + +/// FROST(secp256k1, SHA-256) keys, key generation, key shares. +pub mod keys { + use super::*; + use std::collections::BTreeMap; + + /// The identifier list to use when generating key shares. + pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, S>; + + /// Allows all participants' keys to be generated using a central, trusted + /// dealer. + pub fn generate_with_dealer( + max_signers: u16, + min_signers: u16, + identifiers: IdentifierList, + mut rng: RNG, + ) -> Result<(BTreeMap, PublicKeyPackage), Error> { + frost::keys::generate_with_dealer(max_signers, min_signers, identifiers, &mut rng) + } + + /// Splits an existing key into FROST shares. + /// + /// This is identical to [`generate_with_dealer`] but receives an existing key + /// instead of generating a fresh one. This is useful in scenarios where + /// the key needs to be generated externally or must be derived from e.g. a + /// seed phrase. + pub fn split( + secret: &SigningKey, + max_signers: u16, + min_signers: u16, + identifiers: IdentifierList, + rng: &mut R, + ) -> Result<(BTreeMap, PublicKeyPackage), Error> { + frost::keys::split(secret, max_signers, min_signers, identifiers, rng) + } + + /// Recompute the secret from t-of-n secret shares using Lagrange interpolation. + /// + /// This can be used if for some reason the original key must be restored; e.g. + /// if threshold signing is not required anymore. + /// + /// This is NOT required to sign with FROST; the whole point of FROST is being + /// able to generate signatures only using the shares, without having to + /// reconstruct the original key. + /// + /// The caller is responsible for providing at least `min_signers` shares; + /// if less than that is provided, a different key will be returned. + pub fn reconstruct(secret_shares: &[KeyPackage]) -> Result { + frost::keys::reconstruct(secret_shares) + } + + /// Secret and public key material generated by a dealer performing + /// [`generate_with_dealer`]. + /// + /// # Security + /// + /// To derive a FROST(secp256k1, SHA-256) keypair, the receiver of the [`SecretShare`] *must* call + /// .into(), which under the hood also performs validation. + pub type SecretShare = frost::keys::SecretShare; + + /// A secret scalar value representing a signer's share of the group secret. + pub type SigningShare = frost::keys::SigningShare; + + /// A public group element that represents a single signer's public verification share. + pub type VerifyingShare = frost::keys::VerifyingShare; + + /// A FROST(secp256k1, SHA-256) keypair, which can be generated either by a trusted dealer or using + /// a DKG. + /// + /// When using a central dealer, [`SecretShare`]s are distributed to + /// participants, who then perform verification, before deriving + /// [`KeyPackage`]s, which they store to later use during signing. + pub type KeyPackage = frost::keys::KeyPackage; + + /// Public data that contains all the signers' public keys as well as the + /// group public key. + /// + /// Used for verification purposes before publishing a signature. + pub type PublicKeyPackage = frost::keys::PublicKeyPackage; + + /// Contains the commitments to the coefficients for our secret polynomial _f_, + /// used to generate participants' key shares. + /// + /// [`VerifiableSecretSharingCommitment`] contains a set of commitments to the coefficients (which + /// themselves are scalars) for a secret polynomial f, where f is used to + /// generate each ith participant's key share f(i). Participants use this set of + /// commitments to perform verifiable secret sharing. + /// + /// Note that participants MUST be assured that they have the *same* + /// [`VerifiableSecretSharingCommitment`], either by performing pairwise comparison, or by using + /// some agreed-upon public location for publication, where each participant can + /// ensure that they received the correct (and same) value. + pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; + + /// Trait for ensuring the group public key has an even Y coordinate. + /// + /// In BIP-320, public keys are encoded with only the X coordinate, which + /// means that two Y coordinates are possible. The specification says that + /// the coordinate which is even must be used. Alternatively, something + /// equivalent can be accomplished by simply converting any existing + /// (non-encoded) public key to have an even Y coordinate. + /// + /// This trait is used to enable this procedure, by changing the private and + /// public keys to ensure that the public key has a even Y coordinate. This + /// is done by simply negating both keys if Y is even (in a field, negating + /// is equivalent to computing p - x where p is the prime modulus. Since p + /// is odd, if x is odd then the result will be even). Fortunately this + /// works even after Shamir secret sharing, in the individual signing and + /// verifying shares, since it's linear. + pub trait EvenY { + /// Return if the given type has a group public key with an even Y + /// coordinate. + fn has_even_y(&self) -> bool; + + /// Convert the given type to make sure the group public key has an even + /// Y coordinate. `is_even` can be specified if evenness was already + /// determined beforehand. + fn into_even_y(self, is_even: Option) -> Self; + } + + impl EvenY for PublicKeyPackage { + fn has_even_y(&self) -> bool { + let verifying_key = self.verifying_key(); + (!verifying_key.to_element().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + // Negate verifying key + let verifying_key = VerifyingKey::new(-self.verifying_key().to_element()); + // Recreate verifying share map with negated VerifyingShares + // values. + let verifying_shares: BTreeMap<_, _> = self + .verifying_shares() + .iter() + .map(|(i, vs)| { + let vs = VerifyingShare::new(-vs.to_element()); + (*i, vs) + }) + .collect(); + PublicKeyPackage::new(verifying_shares, verifying_key) + } else { + self + } + } + } + + impl EvenY for KeyPackage { + fn has_even_y(&self) -> bool { + let verifying_key = self.verifying_key(); + (!verifying_key.to_element().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + // Negate all components + let verifying_key = VerifyingKey::new(-self.verifying_key().to_element()); + let signing_share = SigningShare::new(-self.signing_share().to_scalar()); + let verifying_share = VerifyingShare::new(-self.verifying_share().to_element()); + KeyPackage::new( + *self.identifier(), + signing_share, + verifying_share, + verifying_key, + *self.min_signers(), + ) + } else { + self + } + } + } + + impl EvenY for VerifyingKey { + fn has_even_y(&self) -> bool { + (!self.to_element().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + VerifyingKey::new(-self.to_element()) + } else { + self + } + } + } + + impl EvenY for GroupCommitment { + fn has_even_y(&self) -> bool { + (!self.clone().to_element().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + Self::from_element(-self.to_element()) + } else { + self + } + } + } + + impl EvenY for Signature { + fn has_even_y(&self) -> bool { + (!self.R().to_affine().y_is_odd()).into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + Self::new(-*self.R(), *self.z()) + } else { + self + } + } + } + + impl EvenY for SigningKey { + fn has_even_y(&self) -> bool { + (!Into::::into(self) + .to_element() + .to_affine() + .y_is_odd()) + .into() + } + + fn into_even_y(self, is_even: Option) -> Self { + let is_even = is_even.unwrap_or_else(|| self.has_even_y()); + if !is_even { + SigningKey::from_scalar(-self.to_scalar()) + .expect("the original SigningKey must be nonzero") + } else { + self + } + } + } + + /// Trait for tweaking a key component following BIP-341 + pub trait Tweak: EvenY { + /// Convert the given type to add a tweak. + fn tweak>(self, merkle_root: Option) -> Self; + } + + impl Tweak for PublicKeyPackage { + fn tweak>(self, merkle_root: Option) -> Self { + let t = tweak(&self.verifying_key().to_element(), merkle_root); + let tp = ProjectivePoint::GENERATOR * t; + let public_key_package = self.into_even_y(None); + let verifying_key = + VerifyingKey::new(public_key_package.verifying_key().to_element() + tp); + // Recreate verifying share map with negated VerifyingShares + // values. + let verifying_shares: BTreeMap<_, _> = public_key_package + .verifying_shares() + .iter() + .map(|(i, vs)| { + let vs = VerifyingShare::new(vs.to_element() + tp); + (*i, vs) + }) + .collect(); + PublicKeyPackage::new(verifying_shares, verifying_key) + } + } + + impl Tweak for KeyPackage { + fn tweak>(self, merkle_root: Option) -> Self { + let t = tweak(&self.verifying_key().to_element(), merkle_root); + let tp = ProjectivePoint::GENERATOR * t; + let key_package = self.into_even_y(None); + let verifying_key = VerifyingKey::new(key_package.verifying_key().to_element() + tp); + let signing_share = SigningShare::new(key_package.signing_share().to_scalar() + t); + let verifying_share = + VerifyingShare::new(key_package.verifying_share().to_element() + tp); + KeyPackage::new( + *key_package.identifier(), + signing_share, + verifying_share, + verifying_key, + *key_package.min_signers(), + ) + } + } + + pub mod dkg; + pub mod repairable; +} + +/// FROST(secp256k1, SHA-256) Round 1 functionality and types. +pub mod round1 { + use crate::keys::SigningShare; + + use super::*; + + /// Comprised of FROST(secp256k1, SHA-256) hiding and binding nonces. + /// + /// Note that [`SigningNonces`] must be used *only once* for a signing + /// operation; re-using nonces will result in leakage of a signer's long-lived + /// signing key. + pub type SigningNonces = frost::round1::SigningNonces; + + /// Published by each participant in the first round of the signing protocol. + /// + /// This step can be batched if desired by the implementation. Each + /// SigningCommitment can be used for exactly *one* signature. + pub type SigningCommitments = frost::round1::SigningCommitments; + + /// A commitment to a signing nonce share. + pub type NonceCommitment = frost::round1::NonceCommitment; + + /// Performed once by each participant selected for the signing operation. + /// + /// Generates the signing nonces and commitments to be used in the signing + /// operation. + pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) + where + RNG: CryptoRng + RngCore, + { + frost::round1::commit::(secret, rng) + } +} + +/// Generated by the coordinator of the signing operation and distributed to +/// each signing party. +pub type SigningPackage = frost::SigningPackage; + +/// FROST(secp256k1, SHA-256) Round 2 functionality and types, for signature share generation. +pub mod round2 { + use keys::Tweak; + + use super::*; + + /// A FROST(secp256k1, SHA-256) participant's signature share, which the Coordinator will aggregate with all other signer's + /// shares into the joint signature. + pub type SignatureShare = frost::round2::SignatureShare; + + /// Performed once by each participant selected for the signing operation. + /// + /// Receives the message to be signed and a set of signing commitments and a set + /// of randomizing commitments to be used in that signing operation, including + /// that for this participant. + /// + /// Assumes the participant has already determined which nonce corresponds with + /// the commitment that was assigned by the coordinator in the SigningPackage. + pub fn sign( + signing_package: &SigningPackage, + signer_nonces: &round1::SigningNonces, + key_package: &keys::KeyPackage, + ) -> Result { + frost::round2::sign(signing_package, signer_nonces, key_package) + } + + /// Same as [`sign()`], but using a Taproot tweak as specified in BIP-341. + pub fn sign_with_tweak( + signing_package: &SigningPackage, + signer_nonces: &round1::SigningNonces, + key_package: &keys::KeyPackage, + merkle_root: Option<&[u8]>, + ) -> Result { + if merkle_root.is_some() { + let key_package = key_package.clone().tweak(merkle_root); + frost::round2::sign(signing_package, signer_nonces, &key_package) + } else { + frost::round2::sign(signing_package, signer_nonces, key_package) + } + } +} + +/// A Schnorr signature on FROST(secp256k1, SHA-256). +pub type Signature = frost_core::Signature; + +/// Verifies each FROST(secp256k1, SHA-256) participant's signature share, and if all are valid, +/// aggregates the shares into a signature to publish. +/// +/// Resulting signature is compatible with verification of a plain Schnorr +/// signature. +/// +/// This operation is performed by a coordinator that can communicate with all +/// the signing participants before publishing the final signature. The +/// coordinator can be one of the participants or a semi-trusted third party +/// (who is trusted to not perform denial of service attacks, but does not learn +/// any secret information). Note that because the coordinator is trusted to +/// report misbehaving parties in order to avoid publishing an invalid +/// signature, if the coordinator themselves is a signer and misbehaves, they +/// can avoid that step. However, at worst, this results in a denial of +/// service attack due to publishing an invalid signature. +pub fn aggregate( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + public_key_package: &keys::PublicKeyPackage, +) -> Result { + frost::aggregate(signing_package, signature_shares, public_key_package) +} + +/// Same as [`aggregate()`], but using a Taproot tweak as specified in BIP-341. +pub fn aggregate_with_tweak( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + public_key_package: &keys::PublicKeyPackage, + merkle_root: Option<&[u8]>, +) -> Result { + if merkle_root.is_some() { + let public_key_package = public_key_package.clone().tweak(merkle_root); + frost::aggregate(signing_package, signature_shares, &public_key_package) + } else { + frost::aggregate(signing_package, signature_shares, public_key_package) + } +} + +/// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256). +pub type SigningKey = frost_core::SigningKey; + +/// A valid verifying key for Schnorr signatures on FROST(secp256k1, SHA-256). +pub type VerifyingKey = frost_core::VerifyingKey; diff --git a/frost-secp256k1-tr/src/tests.rs b/frost-secp256k1-tr/src/tests.rs new file mode 100644 index 000000000..15a3e184f --- /dev/null +++ b/frost-secp256k1-tr/src/tests.rs @@ -0,0 +1,5 @@ +mod batch; +mod coefficient_commitment; +mod deserialize; +mod proptests; +mod vss_commitment; diff --git a/frost-secp256k1-tr/src/tests/batch.rs b/frost-secp256k1-tr/src/tests/batch.rs new file mode 100644 index 000000000..f88793a35 --- /dev/null +++ b/frost-secp256k1-tr/src/tests/batch.rs @@ -0,0 +1,24 @@ +use rand::thread_rng; + +use crate::*; + +#[test] +fn check_batch_verify() { + let rng = thread_rng(); + + frost_core::tests::batch::batch_verify::(rng); +} + +#[test] +fn check_bad_batch_verify() { + let rng = thread_rng(); + + frost_core::tests::batch::bad_batch_verify::(rng); +} + +#[test] +fn empty_batch_verify() { + let rng = thread_rng(); + + frost_core::tests::batch::empty_batch_verify::(rng); +} diff --git a/frost-secp256k1-tr/src/tests/coefficient_commitment.rs b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs new file mode 100644 index 000000000..a63259c2b --- /dev/null +++ b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs @@ -0,0 +1,46 @@ +use lazy_static::lazy_static; +use rand::thread_rng; +use serde_json::Value; + +use crate::*; + +// Tests for serialization and deserialization of CoefficientCommitment + +lazy_static! { + pub static ref ELEMENTS: Value = + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); +} + +#[test] +fn check_serialization_of_coefficient_commitment() { + let rng = thread_rng(); + frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< + Secp256K1Sha256TR, + _, + >(rng); +} + +#[test] +fn check_create_coefficient_commitment() { + let rng = thread_rng(); + frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< + Secp256K1Sha256TR, + _, + >(rng); +} +#[test] +fn check_create_coefficient_commitment_error() { + frost_core::tests::coefficient_commitment::check_create_coefficient_commitment_error::< + Secp256K1Sha256TR, + >(&ELEMENTS); +} + +#[test] +fn check_get_value_of_coefficient_commitment() { + let rng = thread_rng(); + + frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< + Secp256K1Sha256TR, + _, + >(rng); +} diff --git a/frost-secp256k1-tr/src/tests/deserialize.rs b/frost-secp256k1-tr/src/tests/deserialize.rs new file mode 100644 index 000000000..7d4c630b6 --- /dev/null +++ b/frost-secp256k1-tr/src/tests/deserialize.rs @@ -0,0 +1,38 @@ +use crate::*; + +#[test] +fn check_deserialize_non_canonical() { + let mut encoded_generator = ::Group::serialize( + &::Group::generator(), + ) + .unwrap(); + + let r = ::Group::deserialize(&encoded_generator); + assert!(r.is_ok()); + + // The first byte should be 0x02 or 0x03. Set other value to + // create a non-canonical encoding. + encoded_generator[0] = 0xFF; + let r = ::Group::deserialize(&encoded_generator); + assert_eq!(r, Err(GroupError::MalformedElement)); + + // Besides the first byte, it is still possible to get non-canonical encodings. + // This is x = p + 2 which is non-canonical and maps to a valid prime-order point. + let encoded_point = + hex::decode("02fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc31") + .unwrap() + .try_into() + .unwrap(); + let r = ::Group::deserialize(&encoded_point); + assert_eq!(r, Err(GroupError::MalformedElement)); +} + +#[test] +fn check_deserialize_identity() { + // The identity is actually encoded as a single byte; but the API does not + // allow us to change that. Try to send something similar. + let encoded_identity = [0u8; 33]; + + let r = ::Group::deserialize(&encoded_identity); + assert_eq!(r, Err(GroupError::MalformedElement)); +} diff --git a/frost-secp256k1-tr/src/tests/proptests.rs b/frost-secp256k1-tr/src/tests/proptests.rs new file mode 100644 index 000000000..cad88c330 --- /dev/null +++ b/frost-secp256k1-tr/src/tests/proptests.rs @@ -0,0 +1,33 @@ +use crate::*; +use frost_core::tests::proptests::{tweak_strategy, SignatureCase}; +use proptest::prelude::*; + +use rand_chacha::ChaChaRng; +use rand_core::SeedableRng; + +proptest! { + + #[test] + fn tweak_signature( + tweaks in prop::collection::vec(tweak_strategy(), (0,5)), + rng_seed in prop::array::uniform32(any::()), + ) { + // Use a deterministic RNG so that test failures can be reproduced. + // Seeding with 64 bits of entropy is INSECURE and this code should + // not be copied outside of this test! + let rng = ChaChaRng::from_seed(rng_seed); + + // Create a test case for each signature type. + let msg = b"test message for proptests"; + let mut sig = SignatureCase::::new(rng, msg.to_vec()); + + // Apply tweaks to each case. + for t in &tweaks { + sig.apply_tweak(t); + } + + assert!(sig.check()); + } + + +} diff --git a/frost-secp256k1-tr/src/tests/vss_commitment.rs b/frost-secp256k1-tr/src/tests/vss_commitment.rs new file mode 100644 index 000000000..80fb1ca7b --- /dev/null +++ b/frost-secp256k1-tr/src/tests/vss_commitment.rs @@ -0,0 +1,42 @@ +use lazy_static::lazy_static; +use rand::thread_rng; +use serde_json::Value; + +use crate::*; + +// Tests for serialization and deserialization VerifiableSecretSharingCommitment + +lazy_static! { + pub static ref ELEMENTS: Value = + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); +} + +#[test] +fn check_serialize_vss_commitment() { + let rng = thread_rng(); + frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); +} + +#[test] +fn check_deserialize_vss_commitment() { + let rng = thread_rng(); + frost_core::tests::vss_commitment::check_deserialize_vss_commitment::( + rng, + ); +} + +#[test] +fn check_deserialize_vss_commitment_error() { + let rng = thread_rng(); + frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( + rng, &ELEMENTS, + ); +} + +#[test] +fn check_compute_public_key_package() { + let rng = thread_rng(); + frost_core::tests::vss_commitment::check_compute_public_key_package::( + rng, + ); +} diff --git a/frost-secp256k1-tr/tests/common_traits_tests.rs b/frost-secp256k1-tr/tests/common_traits_tests.rs new file mode 100644 index 000000000..81b97a951 --- /dev/null +++ b/frost-secp256k1-tr/tests/common_traits_tests.rs @@ -0,0 +1,74 @@ +#![cfg(feature = "serde")] + +mod helpers; + +use frost_secp256k1_tr::SigningKey; +use helpers::samples; +use rand::thread_rng; + +#[allow(clippy::unnecessary_literal_unwrap)] +fn check_common_traits_for_type(v: T) { + // Make sure can be debug-printed. This also catches if the Debug does not + // have an endless recursion (a popular mistake). + println!("{:?}", v); + // Test Clone and Eq + assert_eq!(v, v.clone()); + // Make sure it can be unwrapped in a Result (which requires Debug). + let e: Result = Ok(v.clone()); + assert_eq!(v, e.unwrap()); +} + +#[test] +fn check_signing_key_common_traits() { + let mut rng = thread_rng(); + let signing_key = SigningKey::new(&mut rng); + check_common_traits_for_type(signing_key); +} + +#[test] +fn check_signing_commitments_common_traits() { + let commitments = samples::signing_commitments(); + check_common_traits_for_type(commitments); +} + +#[test] +fn check_signing_package_common_traits() { + let signing_package = samples::signing_package(); + check_common_traits_for_type(signing_package); +} + +#[test] +fn check_signature_share_common_traits() { + let signature_share = samples::signature_share(); + check_common_traits_for_type(signature_share); +} + +#[test] +fn check_secret_share_common_traits() { + let secret_share = samples::secret_share(); + check_common_traits_for_type(secret_share); +} + +#[test] +fn check_key_package_common_traits() { + let key_package = samples::key_package(); + check_common_traits_for_type(key_package); +} + +#[test] +fn check_public_key_package_common_traits() { + let public_key_package = samples::public_key_package(); + check_common_traits_for_type(public_key_package); +} + +#[test] +fn check_round1_package_common_traits() { + let round1_package = samples::round1_package(); + check_common_traits_for_type(round1_package); +} + +#[test] +fn check_round2_package_common_traits() { + let round2_package = samples::round2_package(); + check_common_traits_for_type(round2_package); +} diff --git a/frost-secp256k1-tr/tests/helpers/elements.json b/frost-secp256k1-tr/tests/helpers/elements.json new file mode 100644 index 000000000..e8cd4082d --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/elements.json @@ -0,0 +1,5 @@ +{ + "elements": { + "invalid_element": "123456afdf4a7f88885ab26b20d18edb7d4d9589812a6cf1a5a1a09d3808dae5d8" + } +} diff --git a/frost-secp256k1-tr/tests/helpers/mod.rs b/frost-secp256k1-tr/tests/helpers/mod.rs new file mode 100644 index 000000000..0de6147cc --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/mod.rs @@ -0,0 +1,24 @@ +// Required since each integration test is compiled as a separated crate, +// and each one uses only part of the module. +#![allow(dead_code)] + +use frost_secp256k1_tr::Secp256K1Sha256TR; +use secp256k1::Secp256k1; + +pub mod samples; + +pub fn verify_signature( + msg: &[u8], + group_signature: &frost_core::Signature, + group_pubkey: &frost_core::VerifyingKey, +) { + let secp = Secp256k1::new(); + let sig = secp256k1::schnorr::Signature::from_byte_array( + group_signature.serialize().unwrap().try_into().unwrap(), + ); + let pubkey = secp256k1::XOnlyPublicKey::from_byte_array( + &group_pubkey.serialize().unwrap()[1..33].try_into().unwrap(), + ) + .unwrap(); + secp.verify_schnorr(&sig, msg, &pubkey).unwrap(); +} diff --git a/frost-secp256k1-tr/tests/helpers/repair-share.json b/frost-secp256k1-tr/tests/helpers/repair-share.json new file mode 100644 index 000000000..f4db9bc84 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/repair-share.json @@ -0,0 +1,15 @@ +{ + "scalar_generation": { + "random_scalar_1": "1847f6c4a85096e5dbc9e200c9691c5164f8e276d32d4a54ebaf4275474a1403", + "random_scalar_2": "eac5595269d108812eaa865bf62c703a2c128a61fa3bd4dc837b9314bc515204", + "random_scalar_3": "5b3b6084e41c273a39a8d9bbbd87fbcd626c07030142bf78c6c91247bf175700", + "random_scalar_sum": "5e48b09bf63dc6a1441d42187d1d885a38c896f51f633e6e76218944f27c7bc6" + }, + "sigma_generation": { + "sigma_1": "ec3aa83140065181d75b746bfd6bbbbaf212bdfbb3a91670f924d1ca899cbc0c", + "sigma_2": "5dd288d659e0a2dd3ef7523a9cc4f80f4a7f919e9980005c7fbec0961d3fb500", + "sigma_3": "3e62e7461db9ca1ed2f1549a8114bbc87fa9242ce0012ed3f9ac9dcf23f4c30a", + "sigma_4": "684c44e7aba416a1982a8db8ec2a3095f5cc6a3f958a4716b69ae76524dd7200", + "sigma_sum": "f0bc5d356344d51f816ea8fa076fa029f7590120136bec7c6958b9081f7864d5" + } +} diff --git a/frost-secp256k1-tr/tests/helpers/samples.json b/frost-secp256k1-tr/tests/helpers/samples.json new file mode 100644 index 000000000..1cc174a9e --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/samples.json @@ -0,0 +1,7 @@ +{ + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "proof_of_knowledge": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "element1": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "element2": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", + "scalar1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" +} diff --git a/frost-secp256k1-tr/tests/helpers/samples.rs b/frost-secp256k1-tr/tests/helpers/samples.rs new file mode 100644 index 000000000..11ef99e3e --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/samples.rs @@ -0,0 +1,128 @@ +//! Generate sample, fixed instances of structs for testing. + +use std::collections::BTreeMap; + +use frost_core::{round1::Nonce, Ciphersuite, Element, Group, Scalar}; +use frost_secp256k1_tr::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, SigningShare, VerifiableSecretSharingCommitment, + VerifyingShare, + }, + round1::{NonceCommitment, SigningCommitments, SigningNonces}, + round2::SignatureShare, + Field, Signature, SigningPackage, VerifyingKey, +}; + +type C = frost_secp256k1_tr::Secp256K1Sha256TR; + +fn element1() -> Element { + ::Group::generator() +} + +fn element2() -> Element { + element1() + element1() +} + +fn scalar1() -> Scalar { + let one = <::Group as Group>::Field::one(); + let three = one + one + one; + // To return a fixed non-small number, get the inverse of 3 + <::Group as Group>::Field::invert(&three) + .expect("nonzero elements have inverses") +} + +/// Generate a sample SigningCommitments. +pub fn signing_nonces() -> SigningNonces { + let serialized_scalar1 = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_scalar2 = <::Group as Group>::Field::serialize(&scalar1()); + let hiding_nonce = Nonce::deserialize(serialized_scalar1.as_ref()).unwrap(); + let binding_nonce = Nonce::deserialize(serialized_scalar2.as_ref()).unwrap(); + + SigningNonces::from_nonces(hiding_nonce, binding_nonce) +} + +/// Generate a sample SigningCommitments. +pub fn signing_commitments() -> SigningCommitments { + let serialized_element1 = ::Group::serialize(&element1()).unwrap(); + let serialized_element2 = ::Group::serialize(&element2()).unwrap(); + let hiding_nonce_commitment = + NonceCommitment::deserialize(serialized_element1.as_ref()).unwrap(); + let binding_nonce_commitment = + NonceCommitment::deserialize(serialized_element2.as_ref()).unwrap(); + + SigningCommitments::new(hiding_nonce_commitment, binding_nonce_commitment) +} + +/// Generate a sample SigningPackage. +pub fn signing_package() -> SigningPackage { + let identifier = 42u16.try_into().unwrap(); + let commitments = BTreeMap::from([(identifier, signing_commitments())]); + let message = "hello world".as_bytes(); + + SigningPackage::new(commitments, message) +} + +/// Generate a sample SignatureShare. +pub fn signature_share() -> SignatureShare { + let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + + SignatureShare::deserialize(serialized_scalar.as_ref()).unwrap() +} + +/// Generate a sample SecretShare. +pub fn secret_share() -> SecretShare { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let vss_commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + SecretShare::new(identifier, signing_share, vss_commitment) +} + +/// Generate a sample KeyPackage. +pub fn key_package() -> KeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + + KeyPackage::new(identifier, signing_share, verifying_share, verifying_key, 2) +} + +/// Generate a sample PublicKeyPackage. +pub fn public_key_package() -> PublicKeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); + + PublicKeyPackage::new(verifying_shares, verifying_key) +} + +/// Generate a sample round1::Package. +pub fn round1_package() -> round1::Package { + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let vss_commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + round1::Package::new(vss_commitment, signature) +} + +/// Generate a sample round2::Package. +pub fn round2_package() -> round2::Package { + let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let signing_share = SigningShare::deserialize(serialized_scalar.as_ref()).unwrap(); + + round2::Package::new(signing_share) +} diff --git a/frost-secp256k1-tr/tests/helpers/vectors-big-identifier.json b/frost-secp256k1-tr/tests/helpers/vectors-big-identifier.json new file mode 100644 index 000000000..2fedbec24 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/vectors-big-identifier.json @@ -0,0 +1,77 @@ +{ + "config": { + "MAX_PARTICIPANTS": "3", + "NUM_PARTICIPANTS": "2", + "MIN_PARTICIPANTS": "2", + "name": "FROST(secp256k1, SHA-256)", + "group": "secp256k1", + "hash": "SHA-256" + }, + "inputs": { + "participant_list": [ + 1, + 3 + ], + "group_secret_key": "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114", + "verifying_key_key": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f", + "message": "74657374", + "share_polynomial_coefficients": [ + "fbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579" + ], + "participant_shares": [ + { + "identifier": 1, + "participant_share": "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c" + }, + { + "identifier": 2, + "participant_share": "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984" + }, + { + "identifier": 3, + "participant_share": "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc" + } + ] + }, + "round_one_outputs": { + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "bda8e748e599187762cff956f03dc6ea13fc8e04491a0427b7e6e78600f41c52", + "binding_nonce_randomness": "2ca682429bf05df435b9927b8edb1d748278f3e42fa11ef358e49bbf4a1b780d", + "hiding_nonce": "58cd30723da418156fe9b71870a118e0bbc3d0353ba7c760f9bbc8d60c3dab29", + "binding_nonce": "c22289cc43b82ed938d4b2288efb7381c405fb59f5d43bddc543d98838c60b19", + "hiding_nonce_commitment": "024e34ab3a7ad6b4563dbfe97e9f1206b3378cceb2502491ed0fb709765e1e5ba8", + "binding_nonce_commitment": "03d4b1f3a61dc67e64dfb4abfccabb712f1f6914a6ec9b67749d171370453192cb", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "55a3e44879db6daf00c81eb28e828869560c0901f347baff524f1c91a6669604" + }, + { + "identifier": 3, + "hiding_nonce_randomness": "70818dd5170672c4a4285fd593d4f222417f941f3118e1244955e7a1098a35d8", + "binding_nonce_randomness": "74ca2da071ed4a2a6cad5087d6758b48a558ab5861c61117fee05757e4b1309e", + "hiding_nonce": "a4109db0a5db30fac8cd1f4e272ff02e08258928f067d82c63d97279b114514a", + "binding_nonce": "ce3837bd963f0d81002279f7bb9eefceac64435f638885c2beae6f1dd881fd9e", + "hiding_nonce_commitment": "02d768658a1b94225645401a1512b803657770c7a21bf9ccccccfa09930a44951b", + "binding_nonce_commitment": "034570a4e5217ee8770a28401185f50b4fce4d3f3933a3af9df7ab39b42381d0eb", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "444c1cc7cfe48f4577cce65488f337a9cc8c33dea4cfa986eb590bd8e2b1fa2d" + } + ] + }, + "round_two_outputs": { + "outputs": [ + { + "identifier": 1, + "sig_share": "2ffc305d1694fd84108b84d98306a1af807c6ad9bc3a2d8e448a09643202a15b" + }, + { + "identifier": 3, + "sig_share": "a8c392566ea29e852b4080a028bf5547166c87e703e4fb7136d4ebef65f99b3f" + } + ] + }, + "final_output": { + "sig": "0c776a9516a77808b70a31e74f1464814a6fcf897fb3a6bd84c7a9a9a7a5bcb8d8bfc2b385379c093bcc0579abc5f6f696e8f2c0c01f28ff7b5ef55397fc3c9a" + } +} diff --git a/frost-secp256k1-tr/tests/helpers/vectors.json b/frost-secp256k1-tr/tests/helpers/vectors.json new file mode 100644 index 000000000..2fedbec24 --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/vectors.json @@ -0,0 +1,77 @@ +{ + "config": { + "MAX_PARTICIPANTS": "3", + "NUM_PARTICIPANTS": "2", + "MIN_PARTICIPANTS": "2", + "name": "FROST(secp256k1, SHA-256)", + "group": "secp256k1", + "hash": "SHA-256" + }, + "inputs": { + "participant_list": [ + 1, + 3 + ], + "group_secret_key": "0d004150d27c3bf2a42f312683d35fac7394b1e9e318249c1bfe7f0795a83114", + "verifying_key_key": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4f", + "message": "74657374", + "share_polynomial_coefficients": [ + "fbf85eadae3058ea14f19148bb72b45e4399c0b16028acaf0395c9b03c823579" + ], + "participant_shares": [ + { + "identifier": 1, + "participant_share": "08f89ffe80ac94dcb920c26f3f46140bfc7f95b493f8310f5fc1ea2b01f4254c" + }, + { + "identifier": 2, + "participant_share": "04f0feac2edcedc6ce1253b7fab8c86b856a797f44d83d82a385554e6e401984" + }, + { + "identifier": 3, + "participant_share": "00e95d59dd0d46b0e303e500b62b7ccb0e555d49f5b849f5e748c071da8c0dbc" + } + ] + }, + "round_one_outputs": { + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "bda8e748e599187762cff956f03dc6ea13fc8e04491a0427b7e6e78600f41c52", + "binding_nonce_randomness": "2ca682429bf05df435b9927b8edb1d748278f3e42fa11ef358e49bbf4a1b780d", + "hiding_nonce": "58cd30723da418156fe9b71870a118e0bbc3d0353ba7c760f9bbc8d60c3dab29", + "binding_nonce": "c22289cc43b82ed938d4b2288efb7381c405fb59f5d43bddc543d98838c60b19", + "hiding_nonce_commitment": "024e34ab3a7ad6b4563dbfe97e9f1206b3378cceb2502491ed0fb709765e1e5ba8", + "binding_nonce_commitment": "03d4b1f3a61dc67e64dfb4abfccabb712f1f6914a6ec9b67749d171370453192cb", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "55a3e44879db6daf00c81eb28e828869560c0901f347baff524f1c91a6669604" + }, + { + "identifier": 3, + "hiding_nonce_randomness": "70818dd5170672c4a4285fd593d4f222417f941f3118e1244955e7a1098a35d8", + "binding_nonce_randomness": "74ca2da071ed4a2a6cad5087d6758b48a558ab5861c61117fee05757e4b1309e", + "hiding_nonce": "a4109db0a5db30fac8cd1f4e272ff02e08258928f067d82c63d97279b114514a", + "binding_nonce": "ce3837bd963f0d81002279f7bb9eefceac64435f638885c2beae6f1dd881fd9e", + "hiding_nonce_commitment": "02d768658a1b94225645401a1512b803657770c7a21bf9ccccccfa09930a44951b", + "binding_nonce_commitment": "034570a4e5217ee8770a28401185f50b4fce4d3f3933a3af9df7ab39b42381d0eb", + "binding_factor_input": "02f37c34b66ced1fb51c34a90bdae006901f10625cc06c4f64663b0eae87d87b4fc709887b880e002210593616e086c2a0652d18bad338a3d3987251602e45e0a5a875faacc4377f0b6e4638c16db01b5f88070873ee789f5e9e6acf91f52fdd030000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "444c1cc7cfe48f4577cce65488f337a9cc8c33dea4cfa986eb590bd8e2b1fa2d" + } + ] + }, + "round_two_outputs": { + "outputs": [ + { + "identifier": 1, + "sig_share": "2ffc305d1694fd84108b84d98306a1af807c6ad9bc3a2d8e448a09643202a15b" + }, + { + "identifier": 3, + "sig_share": "a8c392566ea29e852b4080a028bf5547166c87e703e4fb7136d4ebef65f99b3f" + } + ] + }, + "final_output": { + "sig": "0c776a9516a77808b70a31e74f1464814a6fcf897fb3a6bd84c7a9a9a7a5bcb8d8bfc2b385379c093bcc0579abc5f6f696e8f2c0c01f28ff7b5ef55397fc3c9a" + } +} diff --git a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json new file mode 100644 index 000000000..9fcbc839e --- /dev/null +++ b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json @@ -0,0 +1,41 @@ +{ + "config": { + "MAX_PARTICIPANTS": 3, + "MIN_PARTICIPANTS": 2, + "name": "FROST(secp256k1, SHA-256)", + "group": "secp256k1", + "hash": "SHA-256" + }, + "inputs": { + "verifying_key": "03849089de77b56bd35fcbfc70bf38e73448131090acc75d538a5cea63cc3dcefe", + "1": { + "identifier": 1, + "signing_key": "68e3f6904c6043973515a36bf7801a71597da35733f21305d75a5234f06e4529", + "coefficient": "25d2d840a3e2718a431ec69e14ee8a015b000d43c7a9868060f01d5aa52a19d1", + "vss_commitments": ["03e7ba4acb164d2bd5eba4f47b3a788109ddb3f88f1181792424fa332123a25ea8", "037495e920a1f032916193aa80ea97a4c3a611dec9ab47ccc969deb664f5f88bbe"], + "proof_of_knowledge": "6689a8d414eb4961308e21f8caa1045236efded4f3de9209dc07547e88be3b42e192de9bed27fb78a7a4d4e35a0422f11f52631b8e66d69e609398eaff2770b8", + "signing_shares": { + "2": "1dd3cb3e2370e6af22917415f0ad584514807b58b3cc40d2230a26e115f02771", + "3": "dd25ee86acd01f996618aa0d1153f5e8fbc929a8e8a18b8f0a15f91d087217e2" + }, + "verifying_share": "02a8bf413b5d7af0e692fba967540cde8009f161a4d721f8c88649c1933bbb7531", + "signing_share": "f1be455a8ec9ab86ef8438f23a5cfdf70153aa2785d4bebba83e0840403e4bf3" + }, + "2": { + "identifier": 2, + "signing_key": "2619be8223b23e0453ddc630a4d164e81f7d8a9e07af33c4d4d02190df8bec13", + "coefficient": "f7ba0cbbffbea8aaceb3ade54bdbf35bafb1cda15b65ad490e0c63dd069a7c9f", + "vss_commitments": ["03ef10370a008cd95e179dc51e2cb7828f30b72d254e5166484f927c84ab326582", "022ce0dac0db217ba326fbbe3e6132d45e2a4bfa0a0c3790d91eacce9a1c2d6a10"], + "proof_of_knowledge": "a319dd51cf64b3896c22f54154812d4ae76cfa95f46f53ef69241fd702456fef32da76cc93d3a541ca495b723e793ee90c32440da5f314e2e58a2dc30550314a", + "verifying_share": "029ecb3a4db28a82e7b8d600d42711b02790dde3f063f0ecec6f812c1c5d7dcefc" + }, + "3": { + "identifier": 3, + "signing_key": "9a267f4cde8087a6eca0969425846209b41b515b73195ebbeeef8a991103f1ec", + "coefficient": "42ff6f39ce4f97f279781378ebcf93df47add84d75882cd31b266e83f76e25f6", + "vss_commitments": ["02da186c3863c5600b471a2799cb6f15ae4d8315a2f225c177798880e75ac820a0", "03e6a36e7fa4b117c1aa428886672e3a35d926bb4c585a9b07d8ee9a3387420067"], + "proof_of_knowledge": "6e115d9e63fd15d432b380ccf1ec4ed03340fcf96caeae8985aedb5f905b1a65dc422ffe5878988fbbc55454857736c7755d9c8f5ee6822c8833ea21d54dba36", + "verifying_share": "02c98b3c2e9f4bde4cf90dc9c7be639e5adda6ea09fc605239880a22cb836f7145" + } + } +} diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs new file mode 100644 index 000000000..9187285b3 --- /dev/null +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -0,0 +1,359 @@ +use frost_secp256k1_tr::*; +use lazy_static::lazy_static; +use rand::thread_rng; +use serde_json::Value; + +#[test] +fn check_zero_key_fails() { + frost_core::tests::ciphersuite_generic::check_zero_key_fails::(); +} + +#[test] +fn check_sign_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); +} + +#[test] +fn check_dkg_part1_fails_with_invalid_signers_min_signers() { + let rng = thread_rng(); + + let min_signers = 1; + let max_signers = 3; + let error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_dkg_part1_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 2; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_dkg_part1_fails_with_invalid_signers_max_signers() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_rts() { + let rng = thread_rng(); + + frost_core::tests::repairable::check_rts::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_serialisation() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::< + Secp256K1Sha256TR, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< + Secp256K1Sha256TR, + _, + >(rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 1; + let max_signers = 4; + let error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 3; + let max_signers = 3; + let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + let min_signers = 6; + let max_signers = 4; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + let identifiers = vec![Identifier::try_from(1).unwrap()]; + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { + let rng = thread_rng(); + let identifiers = vec![ + Identifier::try_from(8).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(6).unwrap(), + ]; + let min_signers = 2; + let max_signers = 4; + let error = Error::UnknownIdentifier; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(max_signers, min_signers, &identifiers, error, rng); +} + +#[test] +fn check_sign_with_dealer() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); +} + +#[test] +fn check_sign_with_dealer_fails_with_invalid_min_signers() { + let rng = thread_rng(); + + let min_signers = 1; + let max_signers = 3; + let error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 2; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_sign_with_dealer_fails_with_invalid_max_signers() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 1; + let error = Error::InvalidMaxSigners; + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +/// This is testing that Shamir's secret sharing to compute and arbitrary +/// value is working. +#[test] +fn check_share_generation_secp256k1_tr_sha256() { + let rng = thread_rng(); + frost_core::tests::ciphersuite_generic::check_share_generation::(rng); +} + +#[test] +fn check_share_generation_fails_with_invalid_min_signers() { + let rng = thread_rng(); + + let min_signers = 0; + let max_signers = 3; + let error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_share_generation_fails_with_min_signers_greater_than_max() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 2; + let error: frost_core::Error = Error::InvalidMinSigners; + + frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +#[test] +fn check_share_generation_fails_with_invalid_max_signers() { + let rng = thread_rng(); + + let min_signers = 3; + let max_signers = 0; + let error = Error::InvalidMaxSigners; + + frost_core::tests::ciphersuite_generic::check_share_generation_fails_with_invalid_signers::< + Secp256K1Sha256TR, + _, + >(min_signers, max_signers, error, rng); +} + +lazy_static! { + pub static ref VECTORS: Value = + serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) + .expect("Test vector is valid JSON"); + pub static ref VECTORS_BIG_IDENTIFIER: Value = + serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) + .expect("Test vector is valid JSON"); + pub static ref VECTORS_DKG: Value = + serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) + .expect("Test vector is valid JSON"); +} + +#[test] +fn check_sign_with_test_vectors() { + frost_core::tests::vectors::check_sign_with_test_vectors::(&VECTORS); +} + +#[test] +fn check_sign_with_test_vectors_dkg() { + frost_core::tests::vectors_dkg::check_dkg_keygen::(&VECTORS_DKG); +} + +#[test] +fn check_sign_with_test_vectors_with_big_identifiers() { + frost_core::tests::vectors::check_sign_with_test_vectors::( + &VECTORS_BIG_IDENTIFIER, + ); +} + +#[test] +fn check_error_culprit() { + frost_core::tests::ciphersuite_generic::check_error_culprit::(); +} + +#[test] +fn check_identifier_derivation() { + frost_core::tests::ciphersuite_generic::check_identifier_derivation::(); +} + +// Explicit test which is used in a documentation snippet +#[test] +#[allow(unused_variables)] +fn check_identifier_generation() -> Result<(), Error> { + // ANCHOR: dkg_identifier + let participant_identifier = Identifier::try_from(7u16)?; + let participant_identifier = Identifier::derive("alice@example.com".as_bytes())?; + // ANCHOR_END: dkg_identifier + Ok(()) +} + +#[test] +fn check_sign_with_dealer_and_identifiers() { + let rng = thread_rng(); + + frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< + Secp256K1Sha256TR, + _, + >(rng); +} + +#[test] +fn check_sign_with_missing_identifier() { + let rng = thread_rng(); + frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::< + Secp256K1Sha256TR, + _, + >(rng); +} + +#[test] +fn check_sign_with_incorrect_commitments() { + let rng = thread_rng(); + frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< + Secp256K1Sha256TR, + _, + >(rng); +} diff --git a/frost-secp256k1-tr/tests/interoperability_tests.rs b/frost-secp256k1-tr/tests/interoperability_tests.rs new file mode 100644 index 000000000..b2e3f9a0e --- /dev/null +++ b/frost-secp256k1-tr/tests/interoperability_tests.rs @@ -0,0 +1,53 @@ +use frost_secp256k1_tr::*; + +use crate::Secp256K1Sha256TR; +use rand::thread_rng; + +mod helpers; + +#[test] +fn check_interoperability_in_regular_sign() { + let mut rng = thread_rng(); + + for _ in 0..256 { + let signing_key = SigningKey::new(&mut rng); + let verifying_key = signing_key.into(); + let signature = signing_key.sign(&mut rng, b"message"); + helpers::verify_signature(b"message", &signature, &verifying_key); + } +} + +#[test] +fn check_interoperability_in_sign_with_dkg() { + let rng = thread_rng(); + + // Test with multiple keys/signatures to better exercise the key generation + // and the interoperability check. A smaller number of iterations is used + // because DKG takes longer and otherwise the test would be too slow. + for _ in 0..32 { + let (message, group_signature, group_pubkey) = + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( + rng.clone(), + ); + + helpers::verify_signature(&message, &group_signature, &group_pubkey); + } +} + +#[test] +fn check_interoperability_in_sign_with_dealer() { + let rng = thread_rng(); + + // Test with multiple keys/signatures to better exercise the key generation + // and the interoperability check. + for _ in 0..256 { + let (message, group_signature, group_pubkey) = + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( + rng.clone(), + ); + + // Check that the threshold signature can be verified by the `ed25519_dalek` crate + // public key (interoperability test) + helpers::verify_signature(&message, &group_signature, &group_pubkey); + } +} diff --git a/frost-secp256k1-tr/tests/recreation_tests.rs b/frost-secp256k1-tr/tests/recreation_tests.rs new file mode 100644 index 000000000..477de37eb --- /dev/null +++ b/frost-secp256k1-tr/tests/recreation_tests.rs @@ -0,0 +1,133 @@ +//! Test for recreating packages from their components, which shows that they +//! can be serialized and deserialized as the user wishes. + +use frost_secp256k1_tr::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, + }, + round1::{SigningCommitments, SigningNonces}, + round2::SignatureShare, + SigningPackage, +}; + +mod helpers; + +use helpers::samples; + +/// Check if SigningNonces can be recreated. +#[test] +fn check_signing_nonces_recreation() { + let nonces = samples::signing_nonces(); + let hiding = nonces.hiding(); + let binding = nonces.binding(); + let new_nonces = SigningNonces::from_nonces(*hiding, *binding); + assert!(nonces == new_nonces); +} + +/// Check if SigningCommitments can be recreated. +#[test] +fn check_signing_commitments_recreation() { + let commitments = samples::signing_commitments(); + let hiding = commitments.hiding(); + let binding = commitments.binding(); + let new_commitments = SigningCommitments::new(*hiding, *binding); + assert!(commitments == new_commitments); +} + +/// Check if SigningPackage can be recreated. +#[test] +fn check_signing_package_recreation() { + let signing_package = samples::signing_package(); + + let commitments = signing_package.signing_commitments(); + let message = signing_package.message(); + + let new_signing_package = SigningPackage::new(commitments.clone(), message); + assert!(signing_package == new_signing_package); +} + +/// Check if SignatureShare can be recreated. +#[test] +fn check_signature_share_recreation() { + let signature_share = samples::signature_share(); + + let encoded = signature_share.serialize(); + + let new_signature_share = SignatureShare::deserialize(&encoded).unwrap(); + assert!(signature_share == new_signature_share); +} + +/// Check if SecretShare can be recreated. +#[test] +fn check_secret_share_recreation() { + let secret_share = samples::secret_share(); + + let identifier = secret_share.identifier(); + let value = secret_share.signing_share(); + let commitment = secret_share.commitment(); + + let new_secret_share = SecretShare::new(*identifier, *value, commitment.clone()); + + assert!(secret_share == new_secret_share); +} + +/// Check if KeyPackage can be recreated. +#[test] +fn check_key_package_recreation() { + let key_package = samples::key_package(); + + let identifier = key_package.identifier(); + let signing_share = key_package.signing_share(); + let verifying_share = key_package.verifying_share(); + let verifying_key = key_package.verifying_key(); + let min_signers = key_package.min_signers(); + + let new_key_package = KeyPackage::new( + *identifier, + *signing_share, + *verifying_share, + *verifying_key, + *min_signers, + ); + + assert!(key_package == new_key_package); +} + +/// Check if PublicKeyPackage can be recreated. +#[test] +fn check_public_key_package_recreation() { + let public_key_package = samples::public_key_package(); + + let verifying_shares = public_key_package.verifying_shares(); + let verifying_key = public_key_package.verifying_key(); + + let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key); + + assert!(public_key_package == new_public_key_package); +} + +/// Check if round1::Package can be recreated. +#[test] +fn check_round1_package_recreation() { + let round1_package = samples::round1_package(); + + let vss_commitment = round1_package.commitment(); + let signature = round1_package.proof_of_knowledge(); + + let new_round1_package = round1::Package::new(vss_commitment.clone(), *signature); + + assert!(round1_package == new_round1_package); +} + +/// Check if round2::Package can be recreated. +#[test] +fn check_round2_package_recreation() { + let round2_package = samples::round2_package(); + + let signing_share = round2_package.signing_share(); + + let new_round2_package = round2::Package::new(*signing_share); + + assert!(round2_package == new_round2_package); +} diff --git a/frost-secp256k1-tr/tests/rerandomized_tests.rs b/frost-secp256k1-tr/tests/rerandomized_tests.rs new file mode 100644 index 000000000..67e143138 --- /dev/null +++ b/frost-secp256k1-tr/tests/rerandomized_tests.rs @@ -0,0 +1,10 @@ +use frost_secp256k1_tr::Secp256K1Sha256TR; +use rand::thread_rng; + +#[test] +fn check_randomized_sign_with_dealer() { + let rng = thread_rng(); + + let (_msg, _group_signature, _group_pubkey) = + frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); +} diff --git a/frost-secp256k1-tr/tests/serde_tests.rs b/frost-secp256k1-tr/tests/serde_tests.rs new file mode 100644 index 000000000..62a70e70c --- /dev/null +++ b/frost-secp256k1-tr/tests/serde_tests.rs @@ -0,0 +1,632 @@ +#![cfg(feature = "serde")] + +mod helpers; + +use frost_secp256k1_tr::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, + }, + round1::SigningCommitments, + round2::SignatureShare, + SigningPackage, +}; + +use helpers::samples; + +#[test] +fn check_signing_commitments_serialization() { + let commitments = samples::signing_commitments(); + + let json = serde_json::to_string_pretty(&commitments).unwrap(); + println!("{}", json); + + let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); + assert!(commitments == decoded_commitments); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + }"#; + let decoded_commitments: SigningCommitments = serde_json::from_str(json).unwrap(); + assert!(commitments == decoded_commitments); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Wrong ciphersuite + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST(Wrong, SHA-512)" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "0000000000000000000000000000000000000000000000000000000000000000", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST(Ed25519, SHA-512)" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_signing_package_serialization() { + let signing_package = samples::signing_package(); + + let json = serde_json::to_string_pretty(&signing_package).unwrap(); + println!("{}", json); + + let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); + assert!(signing_package == decoded_signing_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "000000000000000000000000000000000000000000000000000000000000002a": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "message": "68656c6c6f20776f726c64" + }"#; + let decoded_signing_package: SigningPackage = serde_json::from_str(json).unwrap(); + assert!(signing_package == decoded_signing_package); + + // Invalid identifier + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "0000000000000000000000000000000000000000000000000000000000000000": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "message": "68656c6c6f20776f726c64" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "000000000000000000000000000000000000000000000000000000000000002a": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "message": "68656c6c6f20776f726c64" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "000000000000000000000000000000000000000000000000000000000000002a": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "message": "68656c6c6f20776f726c64" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_commitments": { + "000000000000000000000000000000000000000000000000000000000000002a": { + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "hiding": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "binding": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5" + } + }, + "message": "68656c6c6f20776f726c64", + "extra": 1 + } + "#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_signature_share_serialization() { + let signature_share = samples::signature_share(); + + let json = serde_json::to_string_pretty(&signature_share).unwrap(); + println!("{}", json); + + let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); + assert!(signature_share == decoded_signature_share); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + let decoded_commitments: SignatureShare = serde_json::from_str(json).unwrap(); + assert!(signature_share == decoded_commitments); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_secret_share_serialization() { + let secret_share = samples::secret_share(); + + let json = serde_json::to_string_pretty(&secret_share).unwrap(); + println!("{}", json); + + let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); + assert!(secret_share == decoded_secret_share); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + let decoded_secret_share: SecretShare = serde_json::from_str(json).unwrap(); + assert!(secret_share == decoded_secret_share); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + "extra": 1, + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_key_package_serialization() { + let key_package = samples::key_package(); + + let json = serde_json::to_string_pretty(&key_package).unwrap(); + println!("{}", json); + + let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); + assert!(key_package == decoded_key_package); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "min_signers": 2 + }"#; + let decoded_key_package: KeyPackage = serde_json::from_str(json).unwrap(); + assert!(key_package == decoded_key_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "0000000000000000000000000000000000000000000000000000000000000000", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "min_signers": 2 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "verifying_share": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "extra_field": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid version + let invalid_json = r#"{ + "header": { + "version": 1, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "secret_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "public": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "group_public": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "min_signers": 2 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_public_key_package_serialization() { + let public_key_package = samples::public_key_package(); + + let json = serde_json::to_string_pretty(&public_key_package).unwrap(); + println!("{}", json); + + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); + assert!(public_key_package == decoded_public_key_package); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package == decoded_public_key_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid identifier + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "0000000000000000000000000000000000000000000000000000000000000000": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "foo": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_round1_package_serialization() { + let round1_package = samples::round1_package(); + + let json = serde_json::to_string_pretty(&round1_package).unwrap(); + println!("{}", json); + + let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); + assert!(round1_package == decoded_round1_package); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ], + "proof_of_knowledge": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + let decoded_round1_package: round1::Package = serde_json::from_str(json).unwrap(); + assert!(round1_package == decoded_round1_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ], + "foo": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ] + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "commitment": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + ], + "proof_of_knowledge": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} + +#[test] +fn check_round2_package_serialization() { + let round2_package = samples::round2_package(); + + let json = serde_json::to_string_pretty(&round2_package).unwrap(); + println!("{}", json); + + let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); + assert!(round2_package == decoded_round2_package); + + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + let decoded_round2_package: round2::Package = serde_json::from_str(json).unwrap(); + assert!(round2_package == decoded_round2_package); + + let invalid_json = "{}"; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Invalid field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "foo": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Missing field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + } + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); + + // Extra field + let invalid_json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "signing_share": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", + "extra": 1 + }"#; + assert!(serde_json::from_str::(invalid_json).is_err()); +} diff --git a/frost-secp256k1-tr/tests/serialization_tests.rs b/frost-secp256k1-tr/tests/serialization_tests.rs new file mode 100644 index 000000000..8f93cfb52 --- /dev/null +++ b/frost-secp256k1-tr/tests/serialization_tests.rs @@ -0,0 +1,105 @@ +#![cfg(feature = "serialization")] + +mod helpers; + +use frost_secp256k1_tr::{ + keys::{ + dkg::{round1, round2}, + KeyPackage, PublicKeyPackage, SecretShare, + }, + round1::{SigningCommitments, SigningNonces}, + round2::SignatureShare, + SigningPackage, +}; + +use helpers::samples; +use insta::assert_snapshot; + +#[test] +fn check_signing_nonces_postcard_serialization() { + let nonces = samples::signing_nonces(); + let bytes: Vec<_> = nonces.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!(nonces, SigningNonces::deserialize(&bytes).unwrap()); +} + +#[test] +fn check_signing_commitments_postcard_serialization() { + let commitments = samples::signing_commitments(); + let bytes: Vec<_> = commitments.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + commitments, + SigningCommitments::deserialize(&bytes).unwrap() + ); +} + +#[test] +fn check_signing_package_postcard_serialization() { + let signing_package = samples::signing_package(); + let bytes: Vec<_> = signing_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signing_package, + SigningPackage::deserialize(&bytes).unwrap() + ); +} + +#[test] +fn check_signature_share_postcard_serialization() { + let signature_share = samples::signature_share(); + let bytes = signature_share.serialize(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + signature_share, + SignatureShare::deserialize(&bytes).unwrap() + ); +} +#[test] +fn check_secret_share_postcard_serialization() { + let secret_share = samples::secret_share(); + let bytes: Vec<_> = secret_share.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!(secret_share, SecretShare::deserialize(&bytes).unwrap()); +} + +#[test] +fn check_key_package_postcard_serialization() { + let key_package = samples::key_package(); + let bytes: Vec<_> = key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!(key_package, KeyPackage::deserialize(&bytes).unwrap()); +} + +#[test] +fn check_public_key_package_postcard_serialization() { + let public_key_package = samples::public_key_package(); + let bytes: Vec<_> = public_key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + public_key_package, + PublicKeyPackage::deserialize(&bytes).unwrap() + ); +} + +#[test] +fn check_round1_package_postcard_serialization() { + let round1_package = samples::round1_package(); + let bytes: Vec<_> = round1_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round1_package, + round1::Package::deserialize(&bytes).unwrap() + ); +} + +#[test] +fn check_round2_package_postcard_serialization() { + let round2_package = samples::round2_package(); + let bytes: Vec<_> = round2_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round2_package, + round2::Package::deserialize(&bytes).unwrap() + ); +} diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_key_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_key_package_postcard_serialization.snap new file mode 100644 index 000000000..ca169f4c9 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_key_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3000000000000000000000000000000000000000000000000000000000000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b810279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_postcard_serialization.snap new file mode 100644 index 000000000..5600403fa --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab301000000000000000000000000000000000000000000000000000000000000002a0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_package_postcard_serialization.snap new file mode 100644 index 000000000..9099b14ed --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817984079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_package_postcard_serialization.snap new file mode 100644 index 000000000..218294fb3 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_secret_share_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_secret_share_postcard_serialization.snap new file mode 100644 index 000000000..82e3585a9 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_secret_share_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3000000000000000000000000000000000000000000000000000000000000002aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signature_share_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signature_share_postcard_serialization.snap new file mode 100644 index 000000000..aa7a50309 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signature_share_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(bytes)" +--- +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_commitments_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_commitments_postcard_serialization.snap new file mode 100644 index 000000000..66962d3c1 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_commitments_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_nonces_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_nonces_postcard_serialization.snap new file mode 100644 index 000000000..537b8e388 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_nonces_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab3aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b8100230f8ab3034c7ff4f2ba8603998339c8e42675ceac23ef2e9623fdb260b24b1c944a2ea1a9034c7ff4f2ba8603998339c8e42675ceac23ef2e9623fdb260b24b1c944a2ea1a9 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap new file mode 100644 index 000000000..b398e5e13 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_signing_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab301000000000000000000000000000000000000000000000000000000000000002a00230f8ab30279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee50b68656c6c6f20776f726c64 diff --git a/frost-secp256k1-tr/tests/tweaking_tests.rs b/frost-secp256k1-tr/tests/tweaking_tests.rs new file mode 100644 index 000000000..3fc74aefb --- /dev/null +++ b/frost-secp256k1-tr/tests/tweaking_tests.rs @@ -0,0 +1,149 @@ +use std::{error::Error, vec}; + +use k256::elliptic_curve::point::AffineCoordinates; +use k256::ProjectivePoint; +use keys::Tweak; +use sha2::{Digest, Sha256}; + +use frost_secp256k1_tr::*; + +mod helpers; + +#[test] +fn check_tweaked_sign_with_dealer() -> Result<(), Box> { + use frost_secp256k1_tr as frost; + use rand::thread_rng; + use std::collections::BTreeMap; + + let merkle_root: Vec = vec![12; 32]; + + let mut rng = thread_rng(); + let max_signers = 5; + let min_signers = 3; + let (shares, pubkey_package) = frost::keys::generate_with_dealer( + max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, + )?; + let mut key_packages: BTreeMap<_, _> = BTreeMap::new(); + for (identifier, secret_share) in shares { + let key_package = frost::keys::KeyPackage::try_from(secret_share)?; + key_packages.insert(identifier, key_package); + } + + let mut nonces_map = BTreeMap::new(); + let mut commitments_map = BTreeMap::new(); + + for participant_index in 1..=min_signers { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + let key_package = &key_packages[&participant_identifier]; + let (nonces, commitments) = frost::round1::commit(key_package.signing_share(), &mut rng); + nonces_map.insert(participant_identifier, nonces); + commitments_map.insert(participant_identifier, commitments); + } + + let mut signature_shares = BTreeMap::new(); + let message = "message to sign".as_bytes(); + let signing_package = frost::SigningPackage::new(commitments_map, message); + + for participant_identifier in nonces_map.keys() { + let key_package = &key_packages[participant_identifier]; + let nonces = &nonces_map[participant_identifier]; + let signature_share = frost::round2::sign_with_tweak( + &signing_package, + nonces, + key_package, + Some(&merkle_root), + )?; + signature_shares.insert(*participant_identifier, signature_share); + } + + let group_signature = frost::aggregate_with_tweak( + &signing_package, + &signature_shares, + &pubkey_package, + Some(&merkle_root), + )?; + + pubkey_package + .verifying_key() + .verify(message, &group_signature) + .expect_err("signature should not be valid for untweaked pubkey_package"); + + let pubkey_package_tweaked = pubkey_package.clone().tweak(Some(&merkle_root)); + pubkey_package_tweaked + .verifying_key() + .verify(message, &group_signature) + .expect("signature should be valid for tweaked pubkey_package"); + + helpers::verify_signature( + message, + &group_signature, + pubkey_package_tweaked.verifying_key(), + ); + + // Confirm the internal (untweaked) group key can be provided to access + // script spending paths under the output (tweaked) group key. + let (expected_parity, expected_tr_output_pubkey) = taproot_tweak_pubkey( + pubkey_package + .verifying_key() + .to_element() + .to_affine() + .x() + .into(), + &merkle_root, + ); + + let tr_output_point = pubkey_package_tweaked + .verifying_key() + .to_element() + .to_affine(); + + let tr_output_pubkey: [u8; 32] = tr_output_point.x().into(); + let tr_output_parity: bool = tr_output_point.y_is_odd().into(); + + assert_eq!( + tr_output_pubkey, expected_tr_output_pubkey, + "taproot output pubkey does not match" + ); + + assert_eq!( + tr_output_parity, expected_parity, + "taproot output pubkey parity bit does not match" + ); + + Ok(()) +} + +/// Emulates the BIP341 helper function: +/// +/// def taproot_tweak_pubkey(pubkey, h): +/// t = int_from_bytes(tagged_hash("TapTweak", pubkey + h)) +/// if t >= SECP256K1_ORDER: +/// raise ValueError +/// P = lift_x(int_from_bytes(pubkey)) +/// if P is None: +/// raise ValueError +/// Q = point_add(P, point_mul(G, t)) +/// return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q)) +/// +fn taproot_tweak_pubkey(pubkey: [u8; 32], merkle_root: &[u8]) -> (bool, [u8; 32]) { + let prefix = Sha256::digest(b"TapTweak"); + let tweak_hash = Sha256::new() + .chain_update(prefix) + .chain_update(prefix) + .chain_update(pubkey) + .chain_update(merkle_root) + .finalize(); + let t = k256::Scalar::from( + k256::elliptic_curve::ScalarPrimitive::new(k256::U256::from_be_slice(&tweak_hash)).unwrap(), + ); + + let mut pubkey_even_bytes = [0x02; 33]; + pubkey_even_bytes[1..].copy_from_slice(&pubkey); + let pubkey_even = Secp256K1Group::deserialize(&pubkey_even_bytes).unwrap(); + + let tr_output_key = (pubkey_even + ProjectivePoint::GENERATOR * t).to_affine(); + (tr_output_key.y_is_odd().into(), tr_output_key.x().into()) +} diff --git a/frost-secp256k1/tests/helpers/samples.json b/frost-secp256k1/tests/helpers/samples.json index 210c6f27e..54f6e1e1c 100644 --- a/frost-secp256k1/tests/helpers/samples.json +++ b/frost-secp256k1/tests/helpers/samples.json @@ -1,6 +1,7 @@ { "identifier": "000000000000000000000000000000000000000000000000000000000000002a", + "proof_of_knowledge": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81", "element1": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", "element2": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5", "scalar1": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81" -} \ No newline at end of file +} diff --git a/frost-secp256k1/tests/helpers/samples.rs b/frost-secp256k1/tests/helpers/samples.rs index 3afe78f9c..11b840854 100644 --- a/frost-secp256k1/tests/helpers/samples.rs +++ b/frost-secp256k1/tests/helpers/samples.rs @@ -109,17 +109,12 @@ pub fn public_key_package() -> PublicKeyPackage { /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { - let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); + let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); + let signature = Signature::deserialize(&serialized_signature).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); - let serialized_signature = serialized_element - .as_ref() - .iter() - .chain(serialized_scalar.as_ref().iter()) - .cloned() - .collect::>(); let vss_commitment = VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); - let signature = Signature::deserialize(&serialized_signature).unwrap(); round1::Package::new(vss_commitment, signature) } diff --git a/gencode/src/main.rs b/gencode/src/main.rs index eda52d4e9..8a420fca9 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -121,17 +121,14 @@ fn write_docs( // To be able to replace the documentation properly, start from the end, which // will keep the string positions consistent for (old_name, _, old_start, old_end) in old_docs.iter().rev() { - let new_doc = docs - .get(old_name) - .unwrap_or_else(|| { - panic!( - "documentation for {} is not available in base file", - old_name - ) - }) - .1 - .clone(); - + let new_doc = docs.get(old_name).map(|v| v.1.clone()); + let Some(new_doc) = new_doc else { + eprintln!( + "WARNING: documentation for {} is not available in base file. This can mean it's a specific type for the ciphersuite, or that there is a bug in gencode", + old_name + ); + continue; + }; // Replaces ciphersuite-references in documentation let mut new_doc = new_doc.to_string(); for (old_n, new_n) in zip(original_suite_strings.iter(), new_suite_strings.iter()) { @@ -227,7 +224,13 @@ fn main() -> ExitCode { &std::fs::read_to_string(format!("{original_folder}/tests/helpers/samples.json")).unwrap(), ) .unwrap(); - for key in &["identifier", "element1", "element2", "scalar1"] { + for key in &[ + "identifier", + "proof_of_knowledge", + "element1", + "element2", + "scalar1", + ] { original_strings.push(samples[key].as_str().unwrap().to_owned()); } let original_strings: Vec<&str> = original_strings.iter().map(|s| s.as_ref()).collect(); @@ -290,6 +293,19 @@ fn main() -> ExitCode { "", ], ), + ( + "frost-secp256k1-tr", + &[ + "Secp256K1Sha256TR", + "secp256k1 curve (Taproot)", + "Secp256K1", + "FROST(secp256k1, SHA-256)", + "FROST-secp256k1-SHA256-TR-v1", + "secp256k1_tr_sha256", + "secp256k1_tr", + "", + ], + ), ] { // Some test use "sample" values. To make these tests work for another ciphersuites, // these values must be replaced. To make it cleaner, the strings are @@ -300,7 +316,13 @@ fn main() -> ExitCode { &std::fs::read_to_string(format!("{folder}/tests/helpers/samples.json")).unwrap(), ) .unwrap(); - for key in &["identifier", "element1", "element2", "scalar1"] { + for key in &[ + "identifier", + "proof_of_knowledge", + "element1", + "element2", + "scalar1", + ] { replacement_strings.push(samples[key].as_str().unwrap().to_owned()); } let replacement_strings: Vec<&str> = From 94c00422d0cce4f6647eaa7a9de9f1727a74f6b9 Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:28:25 +0200 Subject: [PATCH 059/175] fix: typos in documentation files (#774) * Update dkg.md * Update dkg.md * Update dkg.md --- frost-p256/dkg.md | 2 +- frost-ristretto255/dkg.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frost-p256/dkg.md b/frost-p256/dkg.md index 9a3c4327d..f3dbb237b 100644 --- a/frost-p256/dkg.md +++ b/frost-p256/dkg.md @@ -3,7 +3,7 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs an unique identifier, which can be built from +Before starting, each participant needs a unique identifier, which can be built from a `u16`. The process in which these identifiers are allocated is up to the application. The distributed key generation process has 3 parts, with 2 communication rounds diff --git a/frost-ristretto255/dkg.md b/frost-ristretto255/dkg.md index 481f24ace..18954d449 100644 --- a/frost-ristretto255/dkg.md +++ b/frost-ristretto255/dkg.md @@ -3,7 +3,7 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs an unique identifier, which can be built from +Before starting, each participant needs a unique identifier, which can be built from a `u16`. The process in which these identifiers are allocated is up to the application. The distributed key generation process has 3 parts, with 2 communication rounds From cf6b0c06917e3e149abf03983599e4b42cd249a7 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:20:35 +0300 Subject: [PATCH 060/175] docs: add secp256k1-tr to changelog and book (#776) --- .github/workflows/main.yml | 2 +- README.md | 21 ++++++++++---------- book/src/dev/frost-dependencies-for-audit.md | 1 + book/src/dev/release-checklist.md | 10 ++++++---- book/src/tutorial.md | 4 ++-- book/src/user.md | 1 + book/src/user/frost-secp256k1-tr.md | 1 + book/src/user/frost-secp256k1-tr/dkg.md | 1 + frost-core/CHANGELOG.md | 2 ++ frost-secp256k1-tr/Cargo.toml | 2 +- 10 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 book/src/user/frost-secp256k1-tr.md create mode 100644 book/src/user/frost-secp256k1-tr/dkg.md diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1ac32783f..35967f836 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,7 +69,7 @@ jobs: # Skip ed448 which does not support it. strategy: matrix: - crate: [ristretto255, ed25519, p256, secp256k1, rerandomized] + crate: [ristretto255, ed25519, p256, secp256k1, secp256k1-tr, rerandomized] steps: - uses: actions/checkout@v4.1.7 - uses: dtolnay/rust-toolchain@master diff --git a/README.md b/README.md index 8f22461a4..019b60d3d 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,16 @@ [![CI](https://github.com/ZcashFoundation/frost/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/ZcashFoundation/frost/actions/workflows/main.yml) -| Crate | | Crates.io | Documentation | -| ---------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| Generic FROST implementation | [`frost-core`] | [![crates.io](https://img.shields.io/crates/v/frost-core.svg)](https://crates.io/crates/frost-core) | [![Documentation](https://docs.rs/frost-core/badge.svg)](https://docs.rs/frost-core) | -| Ristretto255 ciphersuite | [`frost-ristretto255`] | [![crates.io](https://img.shields.io/crates/v/frost-ristretto255.svg)](https://crates.io/crates/frost-ristretto255) | [![Documentation](https://docs.rs/frost-ristretto255/badge.svg)](https://docs.rs/frost-ristretto255) | -| Ed25519 ciphersuite | [`frost-ed25519`] | [![crates.io](https://img.shields.io/crates/v/frost-ed25519.svg)](https://crates.io/crates/frost-ed25519) | [![Documentation](https://docs.rs/frost-ed25519/badge.svg)](https://docs.rs/frost-ed25519) | -| Ed448 ciphersuite | [`frost-ed448`] | [![crates.io](https://img.shields.io/crates/v/frost-ed448.svg)](https://crates.io/crates/frost-ed448) | [![Documentation](https://docs.rs/frost-ed448/badge.svg)](https://docs.rs/frost-ed448) | -| P-256 ciphersuite | [`frost-p256`] | [![crates.io](https://img.shields.io/crates/v/frost-p256.svg)](https://crates.io/crates/frost-p256) | [![Documentation](https://docs.rs/frost-p256/badge.svg)](https://docs.rs/frost-p256) | -| secp256k1 ciphersuite | [`frost-secp256k1`] | [![crates.io](https://img.shields.io/crates/v/frost-secp256k1.svg)](https://crates.io/crates/frost-secp256k1) | [![Documentation](https://docs.rs/frost-secp256k1/badge.svg)](https://docs.rs/frost-secp256k1) | -| Generic Re-randomized FROST | [`frost-rerandomized`] | [![crates.io](https://img.shields.io/crates/v/frost-rerandomized.svg)](https://crates.io/crates/frost-rerandomized) | [![Documentation](https://docs.rs/frost-rerandomized/badge.svg)](https://docs.rs/frost-rerandomized) | +| Crate | | Crates.io | Documentation | +| ------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| Generic FROST implementation | [`frost-core`] | [![crates.io](https://img.shields.io/crates/v/frost-core.svg)](https://crates.io/crates/frost-core) | [![Documentation](https://docs.rs/frost-core/badge.svg)](https://docs.rs/frost-core) | +| Ristretto255 ciphersuite | [`frost-ristretto255`] | [![crates.io](https://img.shields.io/crates/v/frost-ristretto255.svg)](https://crates.io/crates/frost-ristretto255) | [![Documentation](https://docs.rs/frost-ristretto255/badge.svg)](https://docs.rs/frost-ristretto255) | +| Ed25519 ciphersuite | [`frost-ed25519`] | [![crates.io](https://img.shields.io/crates/v/frost-ed25519.svg)](https://crates.io/crates/frost-ed25519) | [![Documentation](https://docs.rs/frost-ed25519/badge.svg)](https://docs.rs/frost-ed25519) | +| Ed448 ciphersuite | [`frost-ed448`] | [![crates.io](https://img.shields.io/crates/v/frost-ed448.svg)](https://crates.io/crates/frost-ed448) | [![Documentation](https://docs.rs/frost-ed448/badge.svg)](https://docs.rs/frost-ed448) | +| P-256 ciphersuite | [`frost-p256`] | [![crates.io](https://img.shields.io/crates/v/frost-p256.svg)](https://crates.io/crates/frost-p256) | [![Documentation](https://docs.rs/frost-p256/badge.svg)](https://docs.rs/frost-p256) | +| secp256k1 ciphersuite | [`frost-secp256k1`] | [![crates.io](https://img.shields.io/crates/v/frost-secp256k1.svg)](https://crates.io/crates/frost-secp256k1) | [![Documentation](https://docs.rs/frost-secp256k1/badge.svg)](https://docs.rs/frost-secp256k1) | +| secp256k1 ciphersuite (Taproot) | [`frost-secp256k1-tr`] | [![crates.io](https://img.shields.io/crates/v/frost-secp256k1-tr.svg)](https://crates.io/crates/frost-secp256k1-tr) | [![Documentation](https://docs.rs/frost-secp256k1-tr/badge.svg)](https://docs.rs/frost-secp256k1-tr) | +| Generic Re-randomized FROST | [`frost-rerandomized`] | [![crates.io](https://img.shields.io/crates/v/frost-rerandomized.svg)](https://crates.io/crates/frost-rerandomized) | [![Documentation](https://docs.rs/frost-rerandomized/badge.svg)](https://docs.rs/frost-rerandomized) | Rust implementations of ['Two-Round Threshold Schnorr Signatures with FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/). @@ -59,7 +60,7 @@ of the v0.6.0 release (corresponding to commit 5fa17ed) of the following crates: - frost-ristretto255 This includes key generation (both trusted dealer and DKG) and FROST signing. -This does not include rerandomized FROST. +This does not include frost-secp256k1-tr and rerandomized FROST. The parts of the [`Ed448-Goldilocks`](https://github.com/crate-crypto/Ed448-Goldilocks) diff --git a/book/src/dev/frost-dependencies-for-audit.md b/book/src/dev/frost-dependencies-for-audit.md index 95227aaf2..cc8c13c55 100644 --- a/book/src/dev/frost-dependencies-for-audit.md +++ b/book/src/dev/frost-dependencies-for-audit.md @@ -46,6 +46,7 @@ The following crates and dependencies are out of scope for the audit. | Name | Version | Notes |------| ------- | ----- | frost-rerandomized | v0.2.0 | To be audited after the security proof is complete. +| frost-secp256k1-tr | N/A | frost-secp256k1 with Taproot support, has not been audited yet. ### `frost-core` Dependencies diff --git a/book/src/dev/release-checklist.md b/book/src/dev/release-checklist.md index d4ba809f3..84e59e088 100644 --- a/book/src/dev/release-checklist.md +++ b/book/src/dev/release-checklist.md @@ -23,6 +23,7 @@ 5. [Frost re randomized version number](https://github.com/ZcashFoundation/frost/blob/main/frost-rerandomized/Cargo.toml#L8) 6. [Frost ristretto255 version number](https://github.com/ZcashFoundation/frost/blob/main/frost-ristretto255/Cargo.toml#L8) 7. [Frost secp256k1 version number](https://github.com/ZcashFoundation/frost/blob/main/frost-secp256k1/Cargo.toml#L7) + 8. [Frost secp256k1 tr version number](https://github.com/ZcashFoundation/frost/blob/main/frost-secp256k1-tr/Cargo.toml#L7) 5. Decide which version to tag the release with (e.g. v0.3.0). Currently we always use the same release number for all crates, but it's possible for them to get out of sync in the future. @@ -74,7 +75,7 @@ 20. Publish it with `cargo publish -p frost-rerandomized` -21. Check if other crates are ready to be published: `for cs in ristretto255 ed25519 secp256k1 p256 ed448; do cargo publish -p frost-$cs --dry-run; done`. Fix any issues if needed. +21. Check if other crates are ready to be published: `for cs in ristretto255 ed25519 secp256k1 secp256k1-tr p256 ed448; do cargo publish -p frost-$cs --dry-run; done`. Fix any issues if needed. 1. If you get an error like this: @@ -86,12 +87,12 @@ 1. Use the same process as described for frost-core above (actions 1 - 3), but you can leave the changelog empty and **uncheck** “Set as the latest release” -23. Publish those crates: `for cs in ristretto255 ed25519 secp256k1 p256 ed448; do cargo publish -p frost-$cs; done` +23. Publish those crates: `for cs in ristretto255 ed25519 secp256k1 secp256k1-tr p256 ed448; do cargo publish -p frost-$cs; done` ## Confirm -24. Check versions in the crates to confirm everything worked:  +24. Check versions in the crates to confirm everything worked: 1. [Frost core](https://crates.io/crates/frost-core/versions) 2. [Frost ed25519](https://crates.io/crates/frost-ed25519/versions) @@ -99,7 +100,8 @@ 4. [Frost p256](https://crates.io/crates/frost-p256/versions) 5. [Frost ristretto255](https://crates.io/crates/frost-ristretto255/versions) 6. [Frost secp256k1](https://crates.io/crates/frost-secp256k1/versions) - 7. [Frost rerandomized](https://crates.io/crates/frost-rerandomized/versions) + 7. [Frost secp256k1 tr](https://crates.io/crates/frost-secp256k1-tr/versions) + 8. [Frost rerandomized](https://crates.io/crates/frost-rerandomized/versions) 25. Let the team know in the #frost slack channel that the release is complete and successful diff --git a/book/src/tutorial.md b/book/src/tutorial.md index 0f80f4915..15df64c92 100644 --- a/book/src/tutorial.md +++ b/book/src/tutorial.md @@ -5,8 +5,8 @@ a generic implementation of the protocol, which can't be used directly without a concrete instantiation. The ciphersuite crates (`frost-ristretto255`, `frost-ed25519`, `frost-ed448`, -`frost-p256`, and `frost-secp256k1`) provide ciphersuites to use with -`frost-core`, but also re-expose the `frost-core` functions without +`frost-p256`, `frost-secp256k1` and `frost-secp256k1-tr`) provide ciphersuites +to use with `frost-core`, but also re-expose the `frost-core` functions without generics. If you will only use a single ciphersuite, then we recommend using those functions, and this tutorial will follow this approach. If you need to support multiple ciphersuites then feel free to use diff --git a/book/src/user.md b/book/src/user.md index 30c1cc8b5..f0c4635e9 100644 --- a/book/src/user.md +++ b/book/src/user.md @@ -7,3 +7,4 @@ - [frost-p256](https://docs.rs/frost-p256/) - [frost-ristretto255](https://docs.rs/frost-ristretto255/) - [frost-secp256k1](https://docs.rs/frost-secp256k1/) +- [frost-secp256k1-tr](https://docs.rs/frost-secp256k1-tr/) diff --git a/book/src/user/frost-secp256k1-tr.md b/book/src/user/frost-secp256k1-tr.md new file mode 100644 index 000000000..733406447 --- /dev/null +++ b/book/src/user/frost-secp256k1-tr.md @@ -0,0 +1 @@ +{{#include ../../../frost-secp256k1-tr/README.md}} \ No newline at end of file diff --git a/book/src/user/frost-secp256k1-tr/dkg.md b/book/src/user/frost-secp256k1-tr/dkg.md new file mode 100644 index 000000000..b93feef5f --- /dev/null +++ b/book/src/user/frost-secp256k1-tr/dkg.md @@ -0,0 +1 @@ +{{#include ../../../../frost-secp256k1-tr/dkg.md}} \ No newline at end of file diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 4057939c2..be9108bc1 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -7,6 +7,8 @@ Entries are listed in reverse chronological order. * It is now possible to identify the culprit in `frost_core::keys::dkg::part3()` if an invalid secret share was sent by one of the participants (by calling frost_core::Error::culprit()`). +* Added frost-secp256k1-tr crate, allowing to generate Bitcoin Taproot (BIP340/BIP341) + compatible signatures. ## 2.0.0 diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index 99d936871..fd6ef947e 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" # When releasing to crates.io: # - Update CHANGELOG.md # - Create git tag. -version = "2.0.0-rc.0" +version = "2.0.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", From 6c452a245c0fbda956ae1a1433498ab9aed1c13e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:54:44 +0000 Subject: [PATCH 061/175] build(deps): bump reviewdog/action-actionlint from 1.54.0 to 1.57.0 (#739) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.54.0 to 1.57.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.54.0...v1.57.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 35967f836..254724339 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -198,7 +198,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.1.7 - - uses: reviewdog/action-actionlint@v1.54.0 + - uses: reviewdog/action-actionlint@v1.57.0 with: level: warning fail_on_error: false From 682ab50dfb88497bf4940752bdc09f903f542dd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:10:05 +0000 Subject: [PATCH 062/175] build(deps): bump actions/checkout from 4.1.7 to 4.2.2 (#760) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.7 to 4.2.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.7...v4.2.2) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 1a43374f0..db9f98ad2 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -23,7 +23,7 @@ jobs: RUST_BACKTRACE: full steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 with: persist-credentials: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bed5b0428..aebf2fa88 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the source code - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v4.2.2 with: persist-credentials: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 254724339..a8088ac16 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 - uses: actions-rs/toolchain@v1.0.7 with: toolchain: beta @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 # Re-resolve Cargo.lock with minimal versions. # This only works with nightly. We pin to a specific version because # newer versions use lock file version 4, but the MSRV cargo does not @@ -51,7 +51,7 @@ jobs: # runs-on: ubuntu-latest # steps: - # - uses: actions/checkout@v4.1.7 + # - uses: actions/checkout@v4.2.2 # - uses: dtolnay/rust-toolchain@stable # - run: cargo install cargo-all-features # # We check and then test because some test dependencies could help @@ -71,7 +71,7 @@ jobs: matrix: crate: [ristretto255, ed25519, p256, secp256k1, secp256k1-tr, rerandomized] steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 - uses: actions-rs/toolchain@v1.0.7 with: toolchain: beta @@ -99,7 +99,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 with: persist-credentials: false @@ -134,7 +134,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 with: persist-credentials: false @@ -156,7 +156,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 with: persist-credentials: false @@ -178,7 +178,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 with: persist-credentials: false @@ -197,7 +197,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4.2.2 - uses: reviewdog/action-actionlint@v1.57.0 with: level: warning From 95e16413812fe627a55a5f49daa79d35432aae83 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 20 Nov 2024 12:05:50 -0300 Subject: [PATCH 063/175] secp256k1-tr: fix hashes_to_scalar to avoid panics (#777) --- frost-secp256k1-tr/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index 56aa52caa..edb360bfa 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -13,6 +13,7 @@ use alloc::collections::BTreeMap; use alloc::vec::Vec; use frost_rerandomized::RandomizedCiphersuite; +use k256::elliptic_curve::ops::Reduce; use k256::{ elliptic_curve::{ bigint::U256, @@ -20,7 +21,7 @@ use k256::{ hash2curve::{hash_to_field, ExpandMsgXmd}, point::AffineCoordinates, sec1::{FromEncodedPoint, ToEncodedPoint}, - Field as FFField, PrimeField, ScalarPrimitive, + Field as FFField, PrimeField, }, AffinePoint, ProjectivePoint, Scalar, }; @@ -185,8 +186,10 @@ pub struct Secp256K1Sha256TR; /// Digest the hasher to a Scalar fn hasher_to_scalar(hasher: Sha256) -> Scalar { - let sp = ScalarPrimitive::new(U256::from_be_slice(&hasher.finalize())).unwrap(); - Scalar::from(&sp) + // This is acceptable because secp256k1 curve order is close to 2^256, + // and the input is uniformly random since it is a hash output, therefore + // the bias is negligibly small. + Scalar::reduce(U256::from_be_slice(&hasher.finalize())) } /// Create a BIP340 compliant tagged hash From 5abeb8411cd3380175437761ad65776c5a14d914 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:31:32 +0000 Subject: [PATCH 064/175] build(deps): update thiserror requirement from 1.0.29 to 2.0.3 (#771) Updates the requirements on [thiserror](https://github.com/dtolnay/thiserror) to permit the latest version. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.29...2.0.3) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frost-core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 5b1ab2e87..ee2271cfc 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -33,7 +33,7 @@ rand_core = { version = "0.6", default-features = false } serde = { version = "1.0.160", default-features = false, features = ["derive"], optional = true } serdect = { version = "0.2.0", optional = true } thiserror-nostd-notrait = { version = "1.0.29", default-features = false } -thiserror = { version = "1.0.29", default-features = false, optional = true } +thiserror = { version = "2.0.3", default-features = false, optional = true } visibility = "0.1.0" zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } itertools = { version = "0.13.0", default-features = false } From 102320bef758b0800b30e4343e58d972b50a7da7 Mon Sep 17 00:00:00 2001 From: Dmitry <98899785+mdqst@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:36:58 +0300 Subject: [PATCH 065/175] Typo fix Update importing.md (#788) Fix Typographical Error in "Importing and General Information" Section --- book/src/tutorial/importing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/tutorial/importing.md b/book/src/tutorial/importing.md index 1777bd42d..ec909a652 100644 --- a/book/src/tutorial/importing.md +++ b/book/src/tutorial/importing.md @@ -31,7 +31,7 @@ Format](../user/serialization.md). ### serde -Alternatively, if you would like to user another format such as JSON, you can +Alternatively, if you would like to use another format such as JSON, you can enable the `serde` feature (which is *not* enabled by default). When it is enabled, you can use [serde](https://serde.rs/) to serialize any structure that needs to be transmitted. The importing would look like: From 908289db90a585ce64b7e7b965046c87e6f88153 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Mon, 25 Nov 2024 12:27:51 -0300 Subject: [PATCH 066/175] secp256k1-tr: fix no-std support (#782) * secp256k1-tr: fix no-std support * apply recent secp256k1 changes --- frost-ed25519/dkg.md | 2 +- frost-ed448/dkg.md | 2 +- frost-secp256k1-tr/Cargo.toml | 11 +++++------ frost-secp256k1-tr/dkg.md | 2 +- frost-secp256k1-tr/src/lib.rs | 28 ++++++++++------------------ frost-secp256k1/dkg.md | 2 +- 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/frost-ed25519/dkg.md b/frost-ed25519/dkg.md index 797422c74..19e243398 100644 --- a/frost-ed25519/dkg.md +++ b/frost-ed25519/dkg.md @@ -3,7 +3,7 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs an unique identifier, which can be built from +Before starting, each participant needs a unique identifier, which can be built from a `u16`. The process in which these identifiers are allocated is up to the application. The distributed key generation process has 3 parts, with 2 communication rounds diff --git a/frost-ed448/dkg.md b/frost-ed448/dkg.md index 2d60b14d6..6471d7647 100644 --- a/frost-ed448/dkg.md +++ b/frost-ed448/dkg.md @@ -3,7 +3,7 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs an unique identifier, which can be built from +Before starting, each participant needs a unique identifier, which can be built from a `u16`. The process in which these identifiers are allocated is up to the application. The distributed key generation process has 3 parts, with 2 communication rounds diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index fd6ef947e..d769c4957 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -23,17 +23,16 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", default-features = false } +frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } -serde = { version = "1.0.160", features = ["derive"], optional = true } rand_core = "0.6" sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0-rc.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0-rc.0", features = ["test-impl"] } +frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } insta = { version = "1.31.0", features = ["yaml"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } lazy_static = "1.4" @@ -52,7 +51,7 @@ std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). -serde = ["frost-core/serde", "dep:serde"] +serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] ## Enable cheater detection diff --git a/frost-secp256k1-tr/dkg.md b/frost-secp256k1-tr/dkg.md index 217cbc99e..9643d37b4 100644 --- a/frost-secp256k1-tr/dkg.md +++ b/frost-secp256k1-tr/dkg.md @@ -3,7 +3,7 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs an unique identifier, which can be built from +Before starting, each participant needs a unique identifier, which can be built from a `u16`. The process in which these identifiers are allocated is up to the application. The distributed key generation process has 3 parts, with 2 communication rounds diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index edb360bfa..5310ced08 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -7,10 +8,8 @@ extern crate alloc; -use alloc::borrow::Cow; -use alloc::borrow::ToOwned; -use alloc::collections::BTreeMap; -use alloc::vec::Vec; +use alloc::vec; +use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec}; use frost_rerandomized::RandomizedCiphersuite; use k256::elliptic_curve::ops::Reduce; @@ -168,9 +167,9 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { output } -fn hash_to_scalar(domain: &[u8], msg: &[u8]) -> Scalar { +fn hash_to_scalar(domain: &[&[u8]], msg: &[u8]) -> Scalar { let mut u = [Secp256K1ScalarField::zero()]; - hash_to_field::, Scalar>(&[msg], &[domain], &mut u) + hash_to_field::, Scalar>(&[msg], domain, &mut u) .expect("should never return error according to error cases described in ExpandMsgXmd"); u[0] } @@ -248,7 +247,7 @@ impl Ciphersuite for Secp256K1Sha256TR { /// /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.1 fn H1(m: &[u8]) -> <::Field as Field>::Scalar { - hash_to_scalar((CONTEXT_STRING.to_owned() + "rho").as_bytes(), m) + hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"rho"], m) } /// H2 for FROST(secp256k1, SHA-256) @@ -264,7 +263,7 @@ impl Ciphersuite for Secp256K1Sha256TR { /// /// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-6.5-2.2.2.3 fn H3(m: &[u8]) -> <::Field as Field>::Scalar { - hash_to_scalar((CONTEXT_STRING.to_owned() + "nonce").as_bytes(), m) + hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"nonce"], m) } /// H4 for FROST(secp256k1, SHA-256) @@ -283,18 +282,12 @@ impl Ciphersuite for Secp256K1Sha256TR { /// HDKG for FROST(secp256k1, SHA-256) fn HDKG(m: &[u8]) -> Option<<::Field as Field>::Scalar> { - Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "dkg").as_bytes(), - m, - )) + Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"dkg"], m)) } /// HID for FROST(secp256k1, SHA-256) fn HID(m: &[u8]) -> Option<<::Field as Field>::Scalar> { - Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "id").as_bytes(), - m, - )) + Some(hash_to_scalar(&[CONTEXT_STRING.as_bytes(), b"id"], m)) } // Sign, negating the key if required by BIP-340. @@ -499,7 +492,7 @@ impl Ciphersuite for Secp256K1Sha256TR { impl RandomizedCiphersuite for Secp256K1Sha256TR { fn hash_randomizer(m: &[u8]) -> Option<<::Field as Field>::Scalar> { Some(hash_to_scalar( - (CONTEXT_STRING.to_owned() + "randomizer").as_bytes(), + &[CONTEXT_STRING.as_bytes(), b"randomizer"], m, )) } @@ -513,7 +506,6 @@ pub type Identifier = frost::Identifier; /// FROST(secp256k1, SHA-256) keys, key generation, key shares. pub mod keys { use super::*; - use std::collections::BTreeMap; /// The identifier list to use when generating key shares. pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, S>; diff --git a/frost-secp256k1/dkg.md b/frost-secp256k1/dkg.md index ac0980bad..5d62857c4 100644 --- a/frost-secp256k1/dkg.md +++ b/frost-secp256k1/dkg.md @@ -3,7 +3,7 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs an unique identifier, which can be built from +Before starting, each participant needs a unique identifier, which can be built from a `u16`. The process in which these identifiers are allocated is up to the application. The distributed key generation process has 3 parts, with 2 communication rounds From 3100f8f25ef2da26e58c999458c64e47568afffe Mon Sep 17 00:00:00 2001 From: futreall <86553580+futreall@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:48:17 +0200 Subject: [PATCH 067/175] Explanation of the Importance of Correcting These Errors (#791) * Update technical-details.md * Update technical-details.md * Update technical-details.md * revert changes to last line in doc * Revert "revert changes to last line in doc" This reverts commit 4af66d6bce2a9ab129af572357df84fa93a03a78. * second attempt at reverting changes to last line --------- Co-authored-by: Pili Guerra --- book/src/zcash/technical-details.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/book/src/zcash/technical-details.md b/book/src/zcash/technical-details.md index 76cd1207c..56ae65523 100644 --- a/book/src/zcash/technical-details.md +++ b/book/src/zcash/technical-details.md @@ -1,7 +1,7 @@ # Technical Details -FROST only works with Schnorr signatures. Zcash transaprent transactions use -ECDSA, therefore FROST does not work with Zcash transaparent addresses. (This +FROST only works with Schnorr signatures. Zcash transparent transactions use +ECDSA, therefore FROST does not work with Zcash transparent addresses. (This could change if the Taproot upgrade from Bitcoin is ported to Zcash, but it seems unlikely.) @@ -18,7 +18,7 @@ protocol](https://zips.z.cash/protocol/protocol.pdf#addressesandkeys): To use FROST with Zcash, the key that needs to be split is the **Spend Authorizing Key** or `ask`. This is the key that signs transactions and allow -they to go through. +them to go through. ## Key Derivation and DKG Support @@ -70,7 +70,7 @@ For this reason it seems impossible to easily encode a FROST wallet, so using something like a JSON file with all this information is advisable. Of course, unlike regular Zcash wallets, a user losing their FROST wallet is -not catastrophical. Users can recover their key share with the help of other +not catastrophic. Users can recover their key share with the help of other participants, and would only need to remember their identifier (and other participants can probably help with that). From f8dcc21f72e9d31df5f609acacac3143a8c58078 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 3 Dec 2024 08:09:37 -0300 Subject: [PATCH 068/175] clippy fixes (#798) --- frost-core/src/keys/repairable.rs | 14 +++++++------- frost-core/src/lib.rs | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/frost-core/src/keys/repairable.rs b/frost-core/src/keys/repairable.rs index ea2211fad..df0be45c7 100644 --- a/frost-core/src/keys/repairable.rs +++ b/frost-core/src/keys/repairable.rs @@ -80,10 +80,10 @@ fn compute_last_random_value( Ok(out) } -/// Communication round -/// -/// `helper_i` sends 1 `delta_j` to all other helpers (j) -/// `helper_i` retains 1 `delta_j` +// Communication round +// +// `helper_i` sends 1 `delta_j` to all other helpers (j) +// `helper_i` retains 1 `delta_j` /// Step 2 of RTS. /// @@ -102,9 +102,9 @@ pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar sigma_j } -/// Communication round -/// -/// `helper_j` sends 1 `sigma_j` to the `participant` repairing their share. +// Communication round +// +// `helper_j` sends 1 `sigma_j` to the `participant` repairing their share. /// Step 3 of RTS /// diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 7d78e2dfd..25c8ff990 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -561,7 +561,6 @@ where /// signature, if the coordinator themselves is a signer and misbehaves, they /// can avoid that step. However, at worst, this results in a denial of /// service attack due to publishing an invalid signature. - pub fn aggregate( signing_package: &SigningPackage, signature_shares: &BTreeMap, round2::SignatureShare>, From 670cd1c5de9592ed7cf76721ce8774edae14cbec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:24:00 +0000 Subject: [PATCH 069/175] build(deps): bump reviewdog/action-actionlint from 1.57.0 to 1.60.0 (#800) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.57.0 to 1.60.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.57.0...v1.60.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a8088ac16..1e91bca7c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -198,7 +198,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.2.2 - - uses: reviewdog/action-actionlint@v1.57.0 + - uses: reviewdog/action-actionlint@v1.60.0 with: level: warning fail_on_error: false From e5fdcf159d9ceeace58a56cbcb322b8764ecda18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:24:33 +0000 Subject: [PATCH 070/175] build(deps): bump codecov/codecov-action from 4.5.0 to 5.1.1 (#801) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.5.0 to 5.1.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4.5.0...v5.1.1) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index db9f98ad2..a6ac56d54 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v4.5.0 + uses: codecov/codecov-action@v5.1.1 From 7685ff301ceb12fef442d6f7e7c137b1f6ad39b8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:55:37 +0000 Subject: [PATCH 071/175] ci(mergify): upgrade configuration to current format (#794) Co-authored-by: Mergify <37929162+mergify[bot]@users.noreply.github.com> --- .mergify.yml | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/.mergify.yml b/.mergify.yml index dff68c01c..491837eb1 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,28 +1,33 @@ queue_rules: - name: main - allow_inplace_checks: True - allow_checks_interruption: True - speculative_checks: 1 - batch_size: 2 - # Wait for a few minutes to embark 2 tickets together in a merge train - batch_max_wait_time: "3 minutes" - conditions: + queue_conditions: + - base=main + - -draft + - label!=do-not-merge + merge_conditions: # Mergify automatically applies status check, approval, and conversation rules, # which are the same as the GitHub main branch protection rules # https://docs.mergify.com/conditions/#about-branch-protection - base=main + allow_inplace_checks: true + batch_size: 2 + # Wait for a few minutes to embark 2 tickets together in a merge train + batch_max_wait_time: "3 minutes" + merge_method: squash pull_request_rules: - name: main queue triggered when CI passes with 1 review + conditions: [] + actions: + queue: + +priority_rules: + - name: Priority rule from queue `main` conditions: - # This queue handles a PR if: - # - it targets main - # - is not in draft - # including automated dependabot PRs. - base=main - -draft - label!=do-not-merge - actions: - queue: - name: main - method: squash \ No newline at end of file + allow_checks_interruption: true + +merge_queue: + max_parallel_checks: 1 From d6c9f7314038064f170dfc3aaf079e044c9eb559 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 10 Dec 2024 11:16:41 -0300 Subject: [PATCH 072/175] core: prevent creating a zero identifier with deserialization (#797) * core: prevent creating a zero identifier with deserialization * move test to file which is not copied to other ciphersuites --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- frost-core/src/identifier.rs | 2 +- frost-secp256k1/src/tests/deserialize.rs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/frost-core/src/identifier.rs b/frost-core/src/identifier.rs index 0ca710c38..9dc4e8fc8 100644 --- a/frost-core/src/identifier.rs +++ b/frost-core/src/identifier.rs @@ -69,7 +69,7 @@ where /// Deserialize an Identifier from a serialized buffer. /// Returns an error if it attempts to deserialize zero. pub fn deserialize(bytes: &[u8]) -> Result> { - Ok(Self(SerializableScalar::deserialize(bytes)?)) + Self::new(SerializableScalar::deserialize(bytes)?.0) } } diff --git a/frost-secp256k1/src/tests/deserialize.rs b/frost-secp256k1/src/tests/deserialize.rs index a744832be..bb8d555bd 100644 --- a/frost-secp256k1/src/tests/deserialize.rs +++ b/frost-secp256k1/src/tests/deserialize.rs @@ -36,3 +36,12 @@ fn check_deserialize_identity() { let r = ::Group::deserialize(&encoded_identity); assert_eq!(r, Err(GroupError::MalformedElement)); } + +// Test if deserializing the identifier 0 fails. +// https://github.com/ZcashFoundation/frost/issues/793 +#[test] +fn check_zero_identifier_deserialization() { + let arr: [u8; 32] = [0; 32]; + let r = Identifier::deserialize(&arr); + assert_eq!(r, Err(Error::FieldError(FieldError::InvalidZeroScalar))); +} From dee685d596e4a9c9bbff90893fa795d49a16385e Mon Sep 17 00:00:00 2001 From: natalie Date: Wed, 11 Dec 2024 08:57:49 +0000 Subject: [PATCH 073/175] Refresh Shares with DKG (#766) * Refresh shares with DKG (#663) * Add verification step for round2_packages for refreshing shares with DKG (#663) * Clean up clippy issues for correct indexing with refreshing shares with DKG (#663) * Import refresh tests for all ciphersuites (#663) * Fix formatting (#663) --- frost-core/src/keys/refresh.rs | 269 +++++++++++++++++- frost-core/src/tests/ciphersuite_generic.rs | 2 +- frost-core/src/tests/refresh.rs | 198 ++++++++++++- frost-ed25519/src/lib.rs | 1 + frost-ed25519/tests/integration_tests.rs | 7 + frost-ed448/src/lib.rs | 1 + frost-ed448/tests/integration_tests.rs | 7 + frost-p256/tests/integration_tests.rs | 7 + frost-ristretto255/tests/integration_tests.rs | 7 + frost-secp256k1/src/lib.rs | 1 + frost-secp256k1/tests/integration_tests.rs | 7 + 11 files changed, 498 insertions(+), 9 deletions(-) diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index 53a3cdd91..79f61185e 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -8,14 +8,18 @@ use alloc::collections::BTreeMap; use alloc::vec::Vec; use crate::{ + keys::dkg::{compute_proof_of_knowledge, round1, round2}, keys::{ - generate_coefficients, generate_secret_shares, validate_num_of_signers, - CoefficientCommitment, PublicKeyPackage, SigningKey, SigningShare, VerifyingShare, + evaluate_polynomial, generate_coefficients, generate_secret_polynomial, + generate_secret_shares, validate_num_of_signers, CoefficientCommitment, PublicKeyPackage, + SigningKey, SigningShare, VerifyingShare, }, - Ciphersuite, CryptoRng, Error, Field, Group, Identifier, RngCore, + Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier, RngCore, }; -use super::{KeyPackage, SecretShare, VerifiableSecretSharingCommitment}; +use core::iter; + +use super::{dkg::round1::Package, KeyPackage, SecretShare, VerifiableSecretSharingCommitment}; /// Generates new zero key shares and a public key package using a trusted /// dealer Building a new public key package is done by taking the verifying @@ -114,3 +118,260 @@ pub fn refresh_share( Ok(new_key_package) } + +/// Part 1 of refresh share with DKG. A refreshing_key is generated and a new package and secret_package are generated. +/// The identity commitment is removed from the packages. +pub fn refresh_dkg_part_1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + validate_num_of_signers::(min_signers, max_signers)?; + + // Build refreshing shares + let refreshing_key = SigningKey { + scalar: <::Field>::zero(), + }; + + // Round 1, Step 1 + let coefficients = generate_coefficients::(min_signers as usize - 1, &mut rng); + + let (coefficients, commitment) = + generate_secret_polynomial(&refreshing_key, max_signers, min_signers, coefficients)?; + + // Remove identity element from coefficients + let mut coeff_comms = commitment.0; + coeff_comms.remove(0); + let commitment = VerifiableSecretSharingCommitment::new(coeff_comms.clone()); + + let proof_of_knowledge = + compute_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?; + + let secret_package = round1::SecretPackage { + identifier, + coefficients: coefficients.clone(), + commitment: commitment.clone(), + min_signers, + max_signers, + }; + let package = round1::Package { + header: Header::default(), + commitment, + proof_of_knowledge, + }; + + Ok((secret_package, package)) +} + +/// Part 2 of refresh share with DKG. The identity commitment needs to be added back into the secret package. +pub fn refresh_dkg_part2( + mut secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, round1::Package>, +) -> Result< + ( + round2::SecretPackage, + BTreeMap, round2::Package>, + ), + Error, +> { + if round1_packages.len() != (secret_package.max_signers - 1) as usize { + return Err(Error::IncorrectNumberOfPackages); + } + + // The identity commitment needs to be added to the VSS commitment for secret package + let identity_commitment: Vec> = + vec![CoefficientCommitment::new(C::Group::identity())]; + + let refreshing_secret_share_commitments: Vec> = identity_commitment + .into_iter() + .chain(secret_package.commitment.0.clone()) + .collect(); + + secret_package.commitment = + VerifiableSecretSharingCommitment::::new(refreshing_secret_share_commitments); + + let mut round2_packages = BTreeMap::new(); + + for (sender_identifier, round1_package) in round1_packages { + // The identity commitment needs to be added to the VSS commitment for every round 1 package + let identity_commitment: Vec> = + vec![CoefficientCommitment::new(C::Group::identity())]; + + let refreshing_share_commitments: Vec> = identity_commitment + .into_iter() + .chain(round1_package.commitment.0.clone()) + .collect(); + + if refreshing_share_commitments.clone().len() != secret_package.min_signers as usize { + return Err(Error::IncorrectNumberOfCommitments); + } + + let ell = *sender_identifier; + + // Round 1, Step 5 + // We don't need to verify the proof of knowledge + + // Round 2, Step 1 + // + // > Each P_i securely sends to each other participant P_ℓ a secret share (ℓ, f_i(ℓ)), + // > deleting f_i and each share afterward except for (i, f_i(i)), + // > which they keep for themselves. + let signing_share = SigningShare::from_coefficients(&secret_package.coefficients, ell); + + round2_packages.insert( + ell, + round2::Package { + header: Header::default(), + signing_share, + }, + ); + } + let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients); + + Ok(( + round2::SecretPackage { + identifier: secret_package.identifier, + commitment: secret_package.commitment, + secret_share: fii, + min_signers: secret_package.min_signers, + max_signers: secret_package.max_signers, + }, + round2_packages, + )) +} + +/// This is the step that actually refreshes the shares. New public key packages +/// and key packages are created. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, round1::Package>, + round2_packages: &BTreeMap, round2::Package>, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + // Add identity commitment back into round1_packages + let mut new_round_1_packages = BTreeMap::new(); + for (sender_identifier, round1_package) in round1_packages { + // The identity commitment needs to be added to the VSS commitment for every round 1 package + let identity_commitment: Vec> = + vec![CoefficientCommitment::new(C::Group::identity())]; + + let refreshing_share_commitments: Vec> = identity_commitment + .into_iter() + .chain(round1_package.commitment.0.clone()) + .collect(); + + let new_commitments = + VerifiableSecretSharingCommitment::::new(refreshing_share_commitments); + + let new_round_1_package = Package { + header: round1_package.header, + commitment: new_commitments, + proof_of_knowledge: round1_package.proof_of_knowledge, + }; + + new_round_1_packages.insert(*sender_identifier, new_round_1_package); + } + + if new_round_1_packages.len() != (round2_secret_package.max_signers - 1) as usize { + return Err(Error::IncorrectNumberOfPackages); + } + if new_round_1_packages.len() != round2_packages.len() { + return Err(Error::IncorrectNumberOfPackages); + } + if new_round_1_packages + .keys() + .any(|id| !round2_packages.contains_key(id)) + { + return Err(Error::IncorrectPackage); + } + + let mut signing_share = <::Field>::zero(); + + for (sender_identifier, round2_package) in round2_packages { + // Round 2, Step 2 + // + // > Each P_i verifies their shares by calculating: + // > g^{f_ℓ(i)} ≟ ∏^{t−1}_{k=0} φ^{i^k mod q}_{ℓk}, aborting if the + // > check fails. + let ell = *sender_identifier; + let f_ell_i = round2_package.signing_share; + + let commitment = &new_round_1_packages + .get(&ell) + .ok_or(Error::PackageNotFound)? + .commitment; + + // The verification is exactly the same as the regular SecretShare verification; + // however the required components are in different places. + // Build a temporary SecretShare so what we can call verify(). + let secret_share = SecretShare { + header: Header::default(), + identifier: round2_secret_package.identifier, + signing_share: f_ell_i, + commitment: commitment.clone(), + }; + + // Verify the share. We don't need the result. + let _ = secret_share.verify()?; + + // Round 2, Step 3 + // + // > Each P_i calculates their long-lived private signing share by computing + // > s_i = ∑^n_{ℓ=1} f_ℓ(i), stores s_i securely, and deletes each f_ℓ(i). + signing_share = signing_share + f_ell_i.to_scalar(); + } + + signing_share = signing_share + round2_secret_package.secret_share; + + // Build new signing share + let old_signing_share = old_key_package.signing_share.to_scalar(); + signing_share = signing_share + old_signing_share; + let signing_share = SigningShare::new(signing_share); + + // Round 2, Step 4 + // + // > Each P_i calculates their public verification share Y_i = g^{s_i}. + let verifying_share = signing_share.into(); + + let commitments: BTreeMap<_, _> = new_round_1_packages + .iter() + .map(|(id, package)| (*id, &package.commitment)) + .chain(iter::once(( + round2_secret_package.identifier, + &round2_secret_package.commitment, + ))) + .collect(); + + let zero_shares_public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?; + + let mut new_verifying_shares = BTreeMap::new(); + + for (identifier, verifying_share) in zero_shares_public_key_package.verifying_shares { + let new_verifying_share = verifying_share.to_element() + + old_pub_key_package + .verifying_shares + .get(&identifier) + .ok_or(Error::UnknownIdentifier)? + .to_element(); + new_verifying_shares.insert(identifier, VerifyingShare::new(new_verifying_share)); + } + + let public_key_package = PublicKeyPackage { + header: old_pub_key_package.header, + verifying_shares: new_verifying_shares, + verifying_key: old_pub_key_package.verifying_key, + }; + + let key_package = KeyPackage { + header: Header::default(), + identifier: round2_secret_package.identifier, + signing_share, + verifying_share, + verifying_key: public_key_package.verifying_key, + min_signers: round2_secret_package.min_signers, + }; + + Ok((key_package, public_key_package)) +} diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 3271f26ac..2cf01831e 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -566,7 +566,7 @@ fn check_part3_errors( } /// Check that calling dkg::part3() with distinct sets of participants fail. -fn check_part3_different_participants( +pub fn check_part3_different_participants( max_signers: u16, round2_secret_packages: BTreeMap, frost::keys::dkg::round2::SecretPackage>, received_round1_packages: BTreeMap< diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 29330b90b..b35ee0d42 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -1,17 +1,22 @@ //! Test for Refreshing shares -use std::collections::BTreeMap; - use rand_core::{CryptoRng, RngCore}; use crate::keys::generate_with_dealer; -use crate::keys::refresh::{compute_refreshing_shares, refresh_share}; +use crate::keys::refresh::{ + compute_refreshing_shares, refresh_dkg_part2, refresh_dkg_part_1, refresh_share, +}; use crate::{self as frost}; use crate::{ keys::{KeyPackage, PublicKeyPackage, SecretShare}, - Ciphersuite, Error, Identifier, + Ciphersuite, Error, Identifier, Signature, VerifyingKey, }; +use crate::tests::ciphersuite_generic::check_part3_different_participants; + +use alloc::collections::BTreeMap; +use alloc::vec::Vec; + use super::ciphersuite_generic::check_sign; /// We want to test that recover share matches the original share @@ -239,3 +244,188 @@ pub fn check_refresh_shares_with_dealer_serialisation( + mut rng: R, +) -> (Vec, Signature, VerifyingKey) +where + C::Group: core::cmp::PartialEq, +{ + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + let old_max_signers = 5; + let min_signers = 3; + let (old_shares, pub_key_package) = generate_with_dealer( + old_max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + let mut old_key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); + + for (k, v) in old_shares { + let key_package = KeyPackage::try_from(v).unwrap(); + old_key_packages.insert(k, key_package); + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, Round 1 + //////////////////////////////////////////////////////////////////////////// + + let max_signers = 4; + let min_signers = 3; + + let remaining_ids = vec![ + Identifier::try_from(4).unwrap(), + Identifier::try_from(2).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(1).unwrap(), + ]; + + // Keep track of each participant's round 1 secret package. + // In practice each participant will keep its copy; no one + // will have all the participant's packages. + let mut round1_secret_packages: BTreeMap< + frost::Identifier, + frost::keys::dkg::round1::SecretPackage, + > = BTreeMap::new(); + + // Keep track of all round 1 packages sent to the given participant. + // This is used to simulate the broadcast; in practice the packages + // will be sent through some communication channel. + let mut received_round1_packages: BTreeMap< + frost::Identifier, + BTreeMap, frost::keys::dkg::round1::Package>, + > = BTreeMap::new(); + + // For each participant, perform the first part of the DKG protocol. + // In practice, each participant will perform this on their own environments. + for participant_identifier in remaining_ids.clone() { + let (round1_secret_package, round1_package) = + refresh_dkg_part_1(participant_identifier, max_signers, min_signers, &mut rng).unwrap(); + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round1_secret_packages.insert(participant_identifier, round1_secret_package); + + // "Send" the round 1 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + for receiver_participant_identifier in remaining_ids.clone() { + if receiver_participant_identifier == participant_identifier { + continue; + } + received_round1_packages + .entry(receiver_participant_identifier) + .or_default() + .insert(participant_identifier, round1_package.clone()); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, Round 2 + //////////////////////////////////////////////////////////////////////////// + // Keep track of each participant's round 2 secret package. + // In practice each participant will keep its copy; no one + // will have all the participant's packages. + let mut round2_secret_packages = BTreeMap::new(); + + // Keep track of all round 2 packages sent to the given participant. + // This is used to simulate the broadcast; in practice the packages + // will be sent through some communication channel. + let mut received_round2_packages = BTreeMap::new(); + + // For each participant, perform the second part of the DKG protocol. + // In practice, each participant will perform this on their own environments. + for participant_identifier in remaining_ids.clone() { + let round1_secret_package = round1_secret_packages + .remove(&participant_identifier) + .unwrap(); + let round1_packages = &received_round1_packages[&participant_identifier]; + let (round2_secret_package, round2_packages) = + refresh_dkg_part2(round1_secret_package, round1_packages).expect("should work"); + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round2_secret_packages.insert(participant_identifier, round2_secret_package); + + // "Send" the round 2 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + // Note that, in contrast to the previous part, here each other participant + // gets its own specific package. + for (receiver_identifier, round2_package) in round2_packages { + received_round2_packages + .entry(receiver_identifier) + .or_insert_with(BTreeMap::new) + .insert(participant_identifier, round2_package); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, final computation + //////////////////////////////////////////////////////////////////////////// + + // Keep track of each participant's long-lived key package. + // In practice each participant will keep its copy; no one + // will have all the participant's packages. + let mut key_packages = BTreeMap::new(); + + // Map of the verifying key of each participant. + // Used by the signing test that follows. + let mut verifying_keys = BTreeMap::new(); + // The group public key, used by the signing test that follows. + let mut verifying_key = None; + // For each participant, store the set of verifying keys they have computed. + // This is used to check if the set is correct (the same) for all participants. + // In practice, if there is a Coordinator, only they need to store the set. + // If there is not, then all candidates must store their own sets. + // The verifying keys are used to verify the signature shares produced + // for each signature before being aggregated. + let mut pubkey_packages_by_participant = BTreeMap::new(); + + check_part3_different_participants( + max_signers, + round2_secret_packages.clone(), + received_round1_packages.clone(), + received_round2_packages.clone(), + ); + + // For each participant, this is where they refresh thair shares + // In practice, each participant will perform this on their own environments. + for participant_identifier in remaining_ids.clone() { + let (key_package, pubkey_package_for_participant) = + frost::keys::refresh::refresh_dkg_shares( + &round2_secret_packages[&participant_identifier], + &received_round1_packages[&participant_identifier], + &received_round2_packages[&participant_identifier], + pub_key_package.clone(), + old_key_packages[&participant_identifier].clone(), + ) + .unwrap(); + verifying_keys.insert(participant_identifier, key_package.verifying_share); + // Test if all verifying_key are equal + if let Some(previous_verifying_key) = verifying_key { + assert_eq!(previous_verifying_key, key_package.verifying_key) + } + verifying_key = Some(key_package.verifying_key); + key_packages.insert(participant_identifier, key_package); + pubkey_packages_by_participant + .insert(participant_identifier, pubkey_package_for_participant); + } + + // Test if the set of verifying keys is correct for all participants. + for verifying_keys_for_participant in pubkey_packages_by_participant.values() { + assert!(verifying_keys_for_participant.verifying_shares == verifying_keys); + } + + let pubkeys = frost::keys::PublicKeyPackage::new(verifying_keys, verifying_key.unwrap()); + + // Proceed with the signing test. + check_sign(min_signers, key_packages, rng, pubkeys).unwrap() +} diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 1e33b2d9c..9c4bce8e8 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -326,6 +326,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 6c5647882..c8c27fc2a 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -180,6 +180,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 4ceb707e8..56523561d 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -321,6 +321,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 70061503c..b96d83287 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -180,6 +180,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index 8d44312db..acd3f81e9 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -180,6 +180,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index af536ac3c..743620271 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -181,6 +181,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 903ffda30..eec2c8ee8 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -336,6 +336,7 @@ pub mod keys { pub type VerifiableSecretSharingCommitment = frost::keys::VerifiableSecretSharingCommitment; pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 9581384b9..d098f266f 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -180,6 +180,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); From 061d1cb2036c8ce038392346d37588e0d696ec6f Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:50:54 +0300 Subject: [PATCH 074/175] fix(frost-secp256k1-tr): add missing `pub mod refresh` and missing test (#806) --- frost-secp256k1-tr/src/lib.rs | 1 + frost-secp256k1-tr/tests/integration_tests.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index 5310ced08..3aa51605f 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -787,6 +787,7 @@ pub mod keys { } pub mod dkg; + pub mod refresh; pub mod repairable; } diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs index 9187285b3..176aad67c 100644 --- a/frost-secp256k1-tr/tests/integration_tests.rs +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -181,6 +181,13 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dkg() { + let rng = thread_rng(); + + frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); +} + #[test] fn check_sign_with_dealer() { let rng = thread_rng(); From db7888699736e41f844a32397aa4c5b4f79a2c5a Mon Sep 17 00:00:00 2001 From: Dimitris Apostolou Date: Thu, 26 Dec 2024 20:43:54 +0200 Subject: [PATCH 075/175] Fix typos (#819) --- book/src/tutorial/refreshing-shares.md | 2 +- book/src/zcash/ywallet-demo.md | 8 ++++---- frost-core/src/keys.rs | 2 +- frost-core/src/lib.rs | 2 +- frost-core/src/scalar_mul.rs | 2 +- frost-core/src/tests/refresh.rs | 2 +- frost-core/src/traits.rs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/book/src/tutorial/refreshing-shares.md b/book/src/tutorial/refreshing-shares.md index 5a65f1acc..a32a3b8e2 100644 --- a/book/src/tutorial/refreshing-shares.md +++ b/book/src/tutorial/refreshing-shares.md @@ -30,7 +30,7 @@ The refreshed `KeyPackage` contents must be stored securely and the original Applications should first ensure that all participants who refreshed their `KeyPackages` were actually able to do so successfully, before deleting their old `KeyPackages`. How this is done is up to the application; it might require -sucessfully generating a signature with all of those participants. +successfully generating a signature with all of those participants. ``` ```admonish danger diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index 71bb3ca0e..7f358368b 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -37,9 +37,9 @@ cargo run --bin trusted-dealer -- -C redpallas This will by default generate a 2-of-3 key shares. The public key package will be written to `public-key-package.json`, while key packages will be -written to `key-package-1.json` through `-3`. You can change the threhsold, +written to `key-package-1.json` through `-3`. You can change the threshold, number of shares and file names using the command line; append `-- -h` -to the commend above for the command line help. +to the command above for the command line help. ```admonish info If you want to use DKG instead of Trusted Dealer, instead of the command above, @@ -198,7 +198,7 @@ cargo run --bin participant -- -C redpallas --http --key-package key-package-1.j ``` (We are using "alice" again. There's nothing stopping a Coordinator from being a -Partcipant too!) +Participant too!) ### Participant 2 (bob) @@ -212,7 +212,7 @@ cargo run --bin participant -- -C redpallas --http --key-package key-package-2.j ### Coordinator Go back to the Coordinator CLI. The protocol should run and complete -succesfully. It will print the final FROST-generated signature. Hurrah! Copy it +successfully. It will print the final FROST-generated signature. Hurrah! Copy it (just the hex value). Go back to the signer and paste the signature. It will write the raw signed diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 9834665d7..0e78c98b5 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -319,7 +319,7 @@ where Self(coefficients) } - /// Returns serialized coefficent commitments + /// Returns serialized coefficient commitments pub fn serialize(&self) -> Result>, Error> { self.0 .iter() diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 25c8ff990..76078d908 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -419,7 +419,7 @@ where ) -> Result, Vec)>, Error> { let mut binding_factor_input_prefix = Vec::new(); - // The length of a serialized verifying key of the same cipersuite does + // The length of a serialized verifying key of the same ciphersuite does // not change between runs of the protocol, so we don't need to hash to // get a fixed length. binding_factor_input_prefix.extend_from_slice(verifying_key.serialize()?.as_ref()); diff --git a/frost-core/src/scalar_mul.rs b/frost-core/src/scalar_mul.rs index 7e6fb3fa3..e8215c443 100644 --- a/frost-core/src/scalar_mul.rs +++ b/frost-core/src/scalar_mul.rs @@ -1,4 +1,4 @@ -//! Non-adjacent form (NAF) implementations for fast batch scalar multiplcation +//! Non-adjacent form (NAF) implementations for fast batch scalar multiplication // We expect slicings in this module to never panic due to algorithmic // constraints. diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index b35ee0d42..ca0171973 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -396,7 +396,7 @@ where received_round2_packages.clone(), ); - // For each participant, this is where they refresh thair shares + // For each participant, this is where they refresh their shares // In practice, each participant will perform this on their own environments. for participant_identifier in remaining_ids.clone() { let (key_package, pubkey_package_for_participant) = diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 4e3f959d7..91a5056af 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -225,7 +225,7 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { // protocol if required. /// Optional. Do regular (non-FROST) signing with a [`SigningKey`]. Called - /// by [`SigningKey::sign()`]. This is not used by FROST. Can be overriden + /// by [`SigningKey::sign()`]. This is not used by FROST. Can be overridden /// if required which is useful if FROST signing has been changed by the /// other Ciphersuite trait methods and regular signing should be changed /// accordingly to match. From fa5c842aebbb308084e4034a68d5f3e0ba7aa2e8 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Fri, 27 Dec 2024 18:32:54 -0300 Subject: [PATCH 076/175] ci: fail on doc warnings (#809) --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1e91bca7c..dbc0e1d5b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -176,6 +176,8 @@ jobs: docs: name: Check Rust doc runs-on: ubuntu-latest + env: + RUSTDOCFLAGS: -D warnings steps: - uses: actions/checkout@v4.2.2 From 39b61ec9da75c73476f1573a0781fb87069d86b1 Mon Sep 17 00:00:00 2001 From: Armin Sabouri Date: Mon, 30 Dec 2024 08:30:48 -0500 Subject: [PATCH 077/175] fix(frost-secp256k1-tr): empty merkle root tweak should still hash the x-only pk (#815) Per BIP-341 if there is no script paths the internal key should still be tapTweak'd by tG where t = TaggedHash(P_x). Before this commit the internal key and the taproot output key are the same if no script paths are used. This is because the tweak is the 0 scalar value so Q = P + tG = P. It is worth noting that Bitcoin's consensus would still accept a non-taptweak'd internal key as it verifies a signature against whatever pk is used in the witness program. So the outputs are still spendable, however it deviates from the spec. --- frost-secp256k1-tr/src/lib.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index 3aa51605f..f566f72ea 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -208,7 +208,11 @@ fn tweak>( merkle_root: Option, ) -> Scalar { match merkle_root { - None => Secp256K1ScalarField::zero(), + None => { + let mut hasher = tagged_hash("TapTweak"); + hasher.update(public_key.to_affine().x()); + hasher_to_scalar(hasher) + } Some(root) => { let mut hasher = tagged_hash("TapTweak"); hasher.update(public_key.to_affine().x()); @@ -481,10 +485,9 @@ impl Ciphersuite for Secp256K1Sha256TR { // > key should commit to an unspendable script path instead of having // > no script path. This can be achieved by computing the output key // > point as Q = P + int(hashTapTweak(bytes(P)))G. - let merkle_root = [0u8; 0]; Ok(( - key_package.tweak(Some(&merkle_root)), - public_key_package.tweak(Some(&merkle_root)), + key_package.tweak::<&[u8]>(None), + public_key_package.tweak::<&[u8]>(None), )) } } @@ -862,12 +865,8 @@ pub mod round2 { key_package: &keys::KeyPackage, merkle_root: Option<&[u8]>, ) -> Result { - if merkle_root.is_some() { - let key_package = key_package.clone().tweak(merkle_root); - frost::round2::sign(signing_package, signer_nonces, &key_package) - } else { - frost::round2::sign(signing_package, signer_nonces, key_package) - } + let key_package = key_package.clone().tweak(merkle_root); + frost::round2::sign(signing_package, signer_nonces, &key_package) } } @@ -904,12 +903,8 @@ pub fn aggregate_with_tweak( public_key_package: &keys::PublicKeyPackage, merkle_root: Option<&[u8]>, ) -> Result { - if merkle_root.is_some() { - let public_key_package = public_key_package.clone().tweak(merkle_root); - frost::aggregate(signing_package, signature_shares, &public_key_package) - } else { - frost::aggregate(signing_package, signature_shares, public_key_package) - } + let public_key_package = public_key_package.clone().tweak(merkle_root); + frost::aggregate(signing_package, signature_shares, &public_key_package) } /// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256). From ba4ec4f3ac1e774581d348f856f641233bab74b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:52:33 -0300 Subject: [PATCH 078/175] build(deps): bump codecov/codecov-action from 5.1.1 to 5.1.2 (#816) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.1 to 5.1.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.1.1...v5.1.2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index a6ac56d54..78b16a2b8 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v5.1.1 + uses: codecov/codecov-action@v5.1.2 From 2fa0238659144df21cd317588977285096f34e41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:55:07 -0300 Subject: [PATCH 079/175] build(deps): update itertools requirement from 0.13.0 to 0.14.0 (#824) Updates the requirements on [itertools](https://github.com/rust-itertools/itertools) to permit the latest version. - [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-itertools/itertools/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: itertools dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frost-core/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index ee2271cfc..b2bab7071 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -36,7 +36,7 @@ thiserror-nostd-notrait = { version = "1.0.29", default-features = false } thiserror = { version = "2.0.3", default-features = false, optional = true } visibility = "0.1.0" zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } -itertools = { version = "0.13.0", default-features = false } +itertools = { version = "0.14.0", default-features = false } # Test dependencies used with the test-impl feature proptest = { version = "1.0", optional = true } From 595ee333c3836b26336307b7545eaf550d76a846 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:57:47 +0000 Subject: [PATCH 080/175] build(deps): bump reviewdog/action-actionlint from 1.60.0 to 1.63.0 (#829) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.60.0 to 1.63.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.60.0...v1.63.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dbc0e1d5b..aa5ed2742 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -200,7 +200,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.2.2 - - uses: reviewdog/action-actionlint@v1.60.0 + - uses: reviewdog/action-actionlint@v1.63.0 with: level: warning fail_on_error: false From 7afd19fbddd96d9f0d2345e142b0cf78c0b91545 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 15 Jan 2025 08:38:35 -0300 Subject: [PATCH 081/175] frost-core: remove unneeded code (#831) --- frost-core/src/keys.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 0e78c98b5..d1461157b 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -485,11 +485,7 @@ pub fn generate_with_dealer( identifiers: IdentifierList, rng: &mut R, ) -> Result<(BTreeMap, SecretShare>, PublicKeyPackage), Error> { - let mut bytes = [0; 64]; - rng.fill_bytes(&mut bytes); - let key = SigningKey::new(rng); - split(&key, max_signers, min_signers, identifiers, rng) } From 3143a26deeb544d12df6a6609c6634f18562c4db Mon Sep 17 00:00:00 2001 From: root Date: Wed, 15 Jan 2025 19:38:37 +0800 Subject: [PATCH 082/175] fix typos in documentation files (#837) fix typos --- book/src/frost.md | 2 +- book/src/tutorial/refreshing-shares.md | 2 +- book/src/tutorial/trusted-dealer.md | 4 ++-- book/src/zcash/ywallet-demo.md | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/book/src/frost.md b/book/src/frost.md index 3f9b00302..ec778145f 100644 --- a/book/src/frost.md +++ b/book/src/frost.md @@ -128,7 +128,7 @@ and Mallory is eventually excluded from the group and replaced with Bob, it is not enough to trust 2 out of 3 between Alice, Bob and Eve. **You also need to trust that Mallory won't collude with, say, Eve which could have kept her original pre-refresh share and they could both together recompute the original -key and compromise the group.** If that's a unnaceptable risk to your use case, +key and compromise the group.** If that's an unacceptable risk to your use case, you will need to migrate to a new group if that makes sense to your application. ``` diff --git a/book/src/tutorial/refreshing-shares.md b/book/src/tutorial/refreshing-shares.md index a32a3b8e2..4e916ce5f 100644 --- a/book/src/tutorial/refreshing-shares.md +++ b/book/src/tutorial/refreshing-shares.md @@ -35,6 +35,6 @@ successfully generating a signature with all of those participants. ```admonish danger Refreshing Shares may be not enough to address security concerns -after a share has been compromised. Refer to to the [Understanding +after a share has been compromised. Refer to the [Understanding FROST](../frost.md#refreshing-shares) section. ``` \ No newline at end of file diff --git a/book/src/tutorial/trusted-dealer.md b/book/src/tutorial/trusted-dealer.md index 8dcfe9040..ee0700855 100644 --- a/book/src/tutorial/trusted-dealer.md +++ b/book/src/tutorial/trusted-dealer.md @@ -40,8 +40,8 @@ You can specify which identifiers to use by using [`IdentifierList::Custom`](htt Which [**authenticated** and **confidential** channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) to use is up to the application. Some examples: -- Manually require the dealer to sent the `SecretShare`s to the - partipants using some secure messenger such as Signal; +- Manually require the dealer to send the `SecretShare`s to the + participants using some secure messenger such as Signal; - Use a TLS connection, authenticating the server with a certificate and the client with some user/password or another suitable authentication mechanism; diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index 7f358368b..ea4f1343b 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -6,7 +6,7 @@ This tutorial explaining how to run the FROST demo using Ywallet that was Ywallet supports [offline signing](https://ywallet.app/advanced/offline_signature/), which allows having a view-only account that can generate a transaction plan, which can be signed by -a offline wallet also running Ywallet. The demo uses this mechanism but signs +an offline wallet also running Ywallet. The demo uses this mechanism but signs the transaction plan with a command line tool, using FROST. This tutorial assumes familiarity with the command line. @@ -88,7 +88,7 @@ another account (or try [ZecFaucet](https://zecfaucet.com/)). ```admonish warning The address being show by Ywallet is a unified address that includes both an Orchard and Sapling address. For the demo to work, you need to receive funds in -you Orchard address. Whether that will happen depends on multiple factors so +your Orchard address. Whether that will happen depends on multiple factors so it's probably easier to use just the Orchard-only address printed by the signer. **If you send it to the Sapling address, the funds will be unspendable and lost!** ``` From 80d53f2569e7903287ccd4e315f857ce6f83fb76 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 15 Jan 2025 11:02:41 -0300 Subject: [PATCH 083/175] all: make DKG SecretPackages serializable (#833) --- frost-core/src/keys/dkg.rs | 127 +++++++++++++++--- frost-core/src/keys/refresh.rs | 28 ++-- frost-core/src/tests/vectors_dkg.rs | 8 +- frost-ed25519/tests/helpers/samples.rs | 39 ++++++ frost-ed25519/tests/recreation_tests.rs | 44 ++++++ frost-ed25519/tests/serialization_tests.rs | 22 +++ ...secret_package_postcard_serialization.snap | 5 + ...secret_package_postcard_serialization.snap | 5 + frost-ed448/tests/helpers/samples.rs | 39 ++++++ frost-ed448/tests/recreation_tests.rs | 44 ++++++ frost-ed448/tests/serialization_tests.rs | 22 +++ ...secret_package_postcard_serialization.snap | 5 + ...secret_package_postcard_serialization.snap | 5 + frost-p256/tests/helpers/samples.rs | 39 ++++++ frost-p256/tests/recreation_tests.rs | 44 ++++++ frost-p256/tests/serialization_tests.rs | 22 +++ ...secret_package_postcard_serialization.snap | 5 + ...secret_package_postcard_serialization.snap | 5 + frost-ristretto255/tests/helpers/samples.rs | 39 ++++++ frost-ristretto255/tests/recreation_tests.rs | 44 ++++++ .../tests/serialization_tests.rs | 22 +++ ...secret_package_postcard_serialization.snap | 5 + ...secret_package_postcard_serialization.snap | 5 + frost-secp256k1-tr/tests/helpers/samples.rs | 39 ++++++ frost-secp256k1-tr/tests/recreation_tests.rs | 44 ++++++ .../tests/serialization_tests.rs | 22 +++ ...secret_package_postcard_serialization.snap | 5 + ...secret_package_postcard_serialization.snap | 5 + frost-secp256k1/tests/helpers/samples.rs | 39 ++++++ frost-secp256k1/tests/recreation_tests.rs | 44 ++++++ frost-secp256k1/tests/serialization_tests.rs | 22 +++ ...secret_package_postcard_serialization.snap | 5 + ...secret_package_postcard_serialization.snap | 5 + 33 files changed, 816 insertions(+), 37 deletions(-) create mode 100644 frost-ed25519/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap create mode 100644 frost-ed25519/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap create mode 100644 frost-ed448/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap create mode 100644 frost-ed448/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap create mode 100644 frost-p256/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap create mode 100644 frost-p256/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap create mode 100644 frost-ristretto255/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap create mode 100644 frost-ristretto255/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap create mode 100644 frost-secp256k1/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap create mode 100644 frost-secp256k1/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index dcdb9b6ce..2c82657e5 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -58,6 +58,7 @@ pub mod round1 { use super::*; + use crate::serialization::SerializableScalar; #[cfg(feature = "serialization")] use crate::serialization::{Deserialize, Serialize}; @@ -117,13 +118,16 @@ pub mod round1 { /// /// This package MUST NOT be sent to other participants! #[derive(Clone, PartialEq, Eq, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] + #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SecretPackage { /// The identifier of the participant holding the secret. pub(crate) identifier: Identifier, /// Coefficients of the temporary secret polynomial for the participant. /// These are (a_{i0}, ..., a_{i(t−1)})) which define the polynomial f_i(x) #[getter(skip)] - pub(crate) coefficients: Vec>, + pub(crate) coefficients: Vec>, /// The public commitment for the participant (C_i) pub(crate) commitment: VerifiableSecretSharingCommitment, /// The minimum number of signers. @@ -136,10 +140,45 @@ pub mod round1 { where C: Ciphersuite, { + /// Create a new Secret Package. This should be called only if + /// custom serialization is required; run the DKG to create + /// a valid SecretPackage. + pub fn new( + identifier: Identifier, + coefficients: Vec>, + commitment: VerifiableSecretSharingCommitment, + min_signers: u16, + max_signers: u16, + ) -> Self { + Self { + identifier, + coefficients: coefficients.into_iter().map(SerializableScalar).collect(), + commitment, + min_signers, + max_signers, + } + } + /// Returns the secret coefficients. #[cfg(feature = "internals")] - pub fn coefficients(&self) -> &[Scalar] { - &self.coefficients + pub fn coefficients(&self) -> Vec> { + self.coefficients.iter().map(|s| s.0).collect() + } + } + + #[cfg(feature = "serialization")] + impl SecretPackage + where + C: Ciphersuite, + { + /// Serialize the struct into a Vec. + pub fn serialize(&self) -> Result, Error> { + Serialize::serialize(&self) + } + + /// Deserialize the struct from a slice of bytes. + pub fn deserialize(bytes: &[u8]) -> Result> { + Deserialize::deserialize(bytes) } } @@ -164,7 +203,7 @@ pub mod round1 { { fn zeroize(&mut self) { for c in self.coefficients.iter_mut() { - *c = <::Field>::zero(); + *c = SerializableScalar(<::Field>::zero()); } } } @@ -178,6 +217,8 @@ pub mod round2 { #[cfg(feature = "serialization")] use alloc::vec::Vec; + use crate::serialization::SerializableScalar; + use super::*; /// A package that must be sent by each participant to some other participants @@ -235,19 +276,67 @@ pub mod round2 { /// /// This package MUST NOT be sent to other participants! #[derive(Clone, PartialEq, Eq, Getters)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] + #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SecretPackage { /// The identifier of the participant holding the secret. pub(crate) identifier: Identifier, /// The public commitment from the participant (C_i) pub(crate) commitment: VerifiableSecretSharingCommitment, /// The participant's own secret share (f_i(i)). - pub(crate) secret_share: Scalar, + #[getter(skip)] + pub(crate) secret_share: SerializableScalar, /// The minimum number of signers. pub(crate) min_signers: u16, /// The total number of signers. pub(crate) max_signers: u16, } + impl SecretPackage + where + C: Ciphersuite, + { + /// Create a new SecretPackage. Use this only if custom serialization is + /// required; run the DKG to create a valid SecretPackage. + pub fn new( + identifier: Identifier, + commitment: VerifiableSecretSharingCommitment, + secret_share: Scalar, + min_signers: u16, + max_signers: u16, + ) -> Self { + Self { + identifier, + commitment, + secret_share: SerializableScalar(secret_share), + min_signers, + max_signers, + } + } + + /// Return the SecretShare. + pub fn secret_share(&self) -> Scalar { + self.secret_share.0 + } + } + + #[cfg(feature = "serialization")] + impl SecretPackage + where + C: Ciphersuite, + { + /// Serialize the struct into a Vec. + pub fn serialize(&self) -> Result, Error> { + Serialize::serialize(&self) + } + + /// Deserialize the struct from a slice of bytes. + pub fn deserialize(bytes: &[u8]) -> Result> { + Deserialize::deserialize(bytes) + } + } + impl core::fmt::Debug for SecretPackage where C: Ciphersuite, @@ -268,7 +357,7 @@ pub mod round2 { C: Ciphersuite, { fn zeroize(&mut self) { - self.secret_share = <::Field>::zero(); + self.secret_share = SerializableScalar(<::Field>::zero()); } } } @@ -304,13 +393,13 @@ pub fn part1( let proof_of_knowledge = compute_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?; - let secret_package = round1::SecretPackage { + let secret_package = round1::SecretPackage::new( identifier, coefficients, - commitment: commitment.clone(), + commitment.clone(), min_signers, max_signers, - }; + ); let package = round1::Package { header: Header::default(), commitment, @@ -439,7 +528,7 @@ pub fn part2( // > Each P_i securely sends to each other participant P_ℓ a secret share (ℓ, f_i(ℓ)), // > deleting f_i and each share afterward except for (i, f_i(i)), // > which they keep for themselves. - let signing_share = SigningShare::from_coefficients(&secret_package.coefficients, ell); + let signing_share = SigningShare::from_coefficients(&secret_package.coefficients(), ell); round2_packages.insert( ell, @@ -449,15 +538,15 @@ pub fn part2( }, ); } - let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients); + let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients()); Ok(( - round2::SecretPackage { - identifier: secret_package.identifier, - commitment: secret_package.commitment, - secret_share: fii, - min_signers: secret_package.min_signers, - max_signers: secret_package.max_signers, - }, + round2::SecretPackage::new( + secret_package.identifier, + secret_package.commitment, + fii, + secret_package.min_signers, + secret_package.max_signers, + ), round2_packages, )) } @@ -542,7 +631,7 @@ pub fn part3( signing_share = signing_share + f_ell_i.to_scalar(); } - signing_share = signing_share + round2_secret_package.secret_share; + signing_share = signing_share + round2_secret_package.secret_share(); let signing_share = SigningShare::new(signing_share); // Round 2, Step 4 diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index 79f61185e..ef57c607e 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -148,13 +148,13 @@ pub fn refresh_dkg_part_1( let proof_of_knowledge = compute_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?; - let secret_package = round1::SecretPackage { + let secret_package = round1::SecretPackage::new( identifier, - coefficients: coefficients.clone(), - commitment: commitment.clone(), + coefficients.clone(), + commitment.clone(), min_signers, max_signers, - }; + ); let package = round1::Package { header: Header::default(), commitment, @@ -217,7 +217,7 @@ pub fn refresh_dkg_part2( // > Each P_i securely sends to each other participant P_ℓ a secret share (ℓ, f_i(ℓ)), // > deleting f_i and each share afterward except for (i, f_i(i)), // > which they keep for themselves. - let signing_share = SigningShare::from_coefficients(&secret_package.coefficients, ell); + let signing_share = SigningShare::from_coefficients(&secret_package.coefficients(), ell); round2_packages.insert( ell, @@ -227,16 +227,16 @@ pub fn refresh_dkg_part2( }, ); } - let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients); + let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients()); Ok(( - round2::SecretPackage { - identifier: secret_package.identifier, - commitment: secret_package.commitment, - secret_share: fii, - min_signers: secret_package.min_signers, - max_signers: secret_package.max_signers, - }, + round2::SecretPackage::new( + secret_package.identifier, + secret_package.commitment, + fii, + secret_package.min_signers, + secret_package.max_signers, + ), round2_packages, )) } @@ -323,7 +323,7 @@ pub fn refresh_dkg_shares( signing_share = signing_share + f_ell_i.to_scalar(); } - signing_share = signing_share + round2_secret_package.secret_share; + signing_share = signing_share + round2_secret_package.secret_share(); // Build new signing share let old_signing_share = old_key_package.signing_share.to_scalar(); diff --git a/frost-core/src/tests/vectors_dkg.rs b/frost-core/src/tests/vectors_dkg.rs index fe775bda8..894eda218 100644 --- a/frost-core/src/tests/vectors_dkg.rs +++ b/frost-core/src/tests/vectors_dkg.rs @@ -203,13 +203,13 @@ pub fn check_dkg_keygen(json_vectors: &Value) { ) .unwrap(); - let round1_secret_package = SecretPackage { - identifier: participant_id, + let round1_secret_package = SecretPackage::new( + participant_id, coefficients, - commitment: commitment.clone(), + commitment.clone(), min_signers, max_signers, - }; + ); let (round2_secret_package, _round2_packages_1) = part2(round1_secret_package, &round1_packages).unwrap(); diff --git a/frost-ed25519/tests/helpers/samples.rs b/frost-ed25519/tests/helpers/samples.rs index 4d785e5cd..e1d87e169 100644 --- a/frost-ed25519/tests/helpers/samples.rs +++ b/frost-ed25519/tests/helpers/samples.rs @@ -107,6 +107,26 @@ pub fn public_key_package() -> PublicKeyPackage { PublicKeyPackage::new(verifying_shares, verifying_key) } +/// Generate a sample round1::SecretPackage. +pub fn round1_secret_package() -> round1::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let coefficients = vec![scalar1(), scalar1()]; + let min_signers = 2; + let max_signers = 3; + + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + round1::SecretPackage::new( + identifier, + coefficients, + commitment, + min_signers, + max_signers, + ) +} + /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); @@ -119,6 +139,25 @@ pub fn round1_package() -> round1::Package { round1::Package::new(vss_commitment, signature) } +/// Generate a sample round1::SecretPackage. +pub fn round2_secret_package() -> round2::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + let secret_share = scalar1(); + let min_signers = 2; + let max_signers = 3; + + round2::SecretPackage::new( + identifier, + commitment, + secret_share, + min_signers, + max_signers, + ) +} + /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); diff --git a/frost-ed25519/tests/recreation_tests.rs b/frost-ed25519/tests/recreation_tests.rs index 0b1d44f13..5ae8964e4 100644 --- a/frost-ed25519/tests/recreation_tests.rs +++ b/frost-ed25519/tests/recreation_tests.rs @@ -107,6 +107,28 @@ fn check_public_key_package_recreation() { assert!(public_key_package == new_public_key_package); } +/// Check if round1::SecretPackage can be recreated. +#[test] +fn check_round1_secret_package_recreation() { + let round1_secret_package = samples::round1_secret_package(); + + let identifier = round1_secret_package.identifier(); + let coefficients = round1_secret_package.coefficients(); + let commitment = round1_secret_package.commitment(); + let min_signers = round1_secret_package.min_signers(); + let max_signers = round1_secret_package.max_signers(); + + let new_round1_secret_package = round1::SecretPackage::new( + *identifier, + coefficients.clone(), + commitment.clone(), + *min_signers, + *max_signers, + ); + + assert!(round1_secret_package == new_round1_secret_package); +} + /// Check if round1::Package can be recreated. #[test] fn check_round1_package_recreation() { @@ -120,6 +142,28 @@ fn check_round1_package_recreation() { assert!(round1_package == new_round1_package); } +/// Check if round2::SecretPackage can be recreated. +#[test] +fn check_round2_secret_package_recreation() { + let round2_secret_package = samples::round2_secret_package(); + + let identifier = round2_secret_package.identifier(); + let commitment = round2_secret_package.commitment(); + let secret_share = round2_secret_package.secret_share(); + let min_signers = round2_secret_package.min_signers(); + let max_signers = round2_secret_package.max_signers(); + + let new_round2_secret_package = round2::SecretPackage::new( + *identifier, + commitment.clone(), + secret_share, + *min_signers, + *max_signers, + ); + + assert!(round2_secret_package == new_round2_secret_package); +} + /// Check if round2::Package can be recreated. #[test] fn check_round2_package_recreation() { diff --git a/frost-ed25519/tests/serialization_tests.rs b/frost-ed25519/tests/serialization_tests.rs index 9acd53be8..7fe52b912 100644 --- a/frost-ed25519/tests/serialization_tests.rs +++ b/frost-ed25519/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_round1_secret_package_postcard_serialization() { + let round1_secret_package = samples::round1_secret_package(); + let bytes: Vec<_> = round1_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round1_secret_package, + round1::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_package_postcard_serialization() { let round1_package = samples::round1_package(); @@ -93,6 +104,17 @@ fn check_round1_package_postcard_serialization() { ); } +#[test] +fn check_round2_secret_package_postcard_serialization() { + let round2_secret_package = samples::round2_secret_package(); + let bytes: Vec<_> = round2_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round2_secret_package, + round2::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round2_package_postcard_serialization() { let round2_package = samples::round2_package(); diff --git a/frost-ed25519/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap b/frost-ed25519/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..66e213f81 --- /dev/null +++ b/frost-ed25519/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ed25519/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +2a0000000000000000000000000000000000000000000000000000000000000002498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a0158666666666666666666666666666666666666666666666666666666666666660203 diff --git a/frost-ed25519/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap b/frost-ed25519/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..e7d9f23af --- /dev/null +++ b/frost-ed25519/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ed25519/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +2a00000000000000000000000000000000000000000000000000000000000000015866666666666666666666666666666666666666666666666666666666666666498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a0203 diff --git a/frost-ed448/tests/helpers/samples.rs b/frost-ed448/tests/helpers/samples.rs index afb26d509..4994b515d 100644 --- a/frost-ed448/tests/helpers/samples.rs +++ b/frost-ed448/tests/helpers/samples.rs @@ -107,6 +107,26 @@ pub fn public_key_package() -> PublicKeyPackage { PublicKeyPackage::new(verifying_shares, verifying_key) } +/// Generate a sample round1::SecretPackage. +pub fn round1_secret_package() -> round1::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let coefficients = vec![scalar1(), scalar1()]; + let min_signers = 2; + let max_signers = 3; + + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + round1::SecretPackage::new( + identifier, + coefficients, + commitment, + min_signers, + max_signers, + ) +} + /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); @@ -119,6 +139,25 @@ pub fn round1_package() -> round1::Package { round1::Package::new(vss_commitment, signature) } +/// Generate a sample round1::SecretPackage. +pub fn round2_secret_package() -> round2::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + let secret_share = scalar1(); + let min_signers = 2; + let max_signers = 3; + + round2::SecretPackage::new( + identifier, + commitment, + secret_share, + min_signers, + max_signers, + ) +} + /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); diff --git a/frost-ed448/tests/recreation_tests.rs b/frost-ed448/tests/recreation_tests.rs index e51f8b6ed..44b7abfe8 100644 --- a/frost-ed448/tests/recreation_tests.rs +++ b/frost-ed448/tests/recreation_tests.rs @@ -107,6 +107,28 @@ fn check_public_key_package_recreation() { assert!(public_key_package == new_public_key_package); } +/// Check if round1::SecretPackage can be recreated. +#[test] +fn check_round1_secret_package_recreation() { + let round1_secret_package = samples::round1_secret_package(); + + let identifier = round1_secret_package.identifier(); + let coefficients = round1_secret_package.coefficients(); + let commitment = round1_secret_package.commitment(); + let min_signers = round1_secret_package.min_signers(); + let max_signers = round1_secret_package.max_signers(); + + let new_round1_secret_package = round1::SecretPackage::new( + *identifier, + coefficients.clone(), + commitment.clone(), + *min_signers, + *max_signers, + ); + + assert!(round1_secret_package == new_round1_secret_package); +} + /// Check if round1::Package can be recreated. #[test] fn check_round1_package_recreation() { @@ -120,6 +142,28 @@ fn check_round1_package_recreation() { assert!(round1_package == new_round1_package); } +/// Check if round2::SecretPackage can be recreated. +#[test] +fn check_round2_secret_package_recreation() { + let round2_secret_package = samples::round2_secret_package(); + + let identifier = round2_secret_package.identifier(); + let commitment = round2_secret_package.commitment(); + let secret_share = round2_secret_package.secret_share(); + let min_signers = round2_secret_package.min_signers(); + let max_signers = round2_secret_package.max_signers(); + + let new_round2_secret_package = round2::SecretPackage::new( + *identifier, + commitment.clone(), + secret_share, + *min_signers, + *max_signers, + ); + + assert!(round2_secret_package == new_round2_secret_package); +} + /// Check if round2::Package can be recreated. #[test] fn check_round2_package_recreation() { diff --git a/frost-ed448/tests/serialization_tests.rs b/frost-ed448/tests/serialization_tests.rs index 60a56b9a0..e6a177ec4 100644 --- a/frost-ed448/tests/serialization_tests.rs +++ b/frost-ed448/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_round1_secret_package_postcard_serialization() { + let round1_secret_package = samples::round1_secret_package(); + let bytes: Vec<_> = round1_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round1_secret_package, + round1::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_package_postcard_serialization() { let round1_package = samples::round1_package(); @@ -93,6 +104,17 @@ fn check_round1_package_postcard_serialization() { ); } +#[test] +fn check_round2_secret_package_postcard_serialization() { + let round2_secret_package = samples::round2_secret_package(); + let bytes: Vec<_> = round2_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round2_secret_package, + round2::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round2_package_postcard_serialization() { let round2_package = samples::round2_package(); diff --git a/frost-ed448/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap b/frost-ed448/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..3094d2379 --- /dev/null +++ b/frost-ed448/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ed448/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a004d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a000114fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f69000203 diff --git a/frost-ed448/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap b/frost-ed448/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..e4a9a0244 --- /dev/null +++ b/frost-ed448/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ed448/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +2a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000114fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f69004d83e51cb78150c2380ad9b3a18148166024e4c9db3cdf82466d3153aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2a000203 diff --git a/frost-p256/tests/helpers/samples.rs b/frost-p256/tests/helpers/samples.rs index 432af4757..be57e472e 100644 --- a/frost-p256/tests/helpers/samples.rs +++ b/frost-p256/tests/helpers/samples.rs @@ -107,6 +107,26 @@ pub fn public_key_package() -> PublicKeyPackage { PublicKeyPackage::new(verifying_shares, verifying_key) } +/// Generate a sample round1::SecretPackage. +pub fn round1_secret_package() -> round1::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let coefficients = vec![scalar1(), scalar1()]; + let min_signers = 2; + let max_signers = 3; + + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + round1::SecretPackage::new( + identifier, + coefficients, + commitment, + min_signers, + max_signers, + ) +} + /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); @@ -119,6 +139,25 @@ pub fn round1_package() -> round1::Package { round1::Package::new(vss_commitment, signature) } +/// Generate a sample round1::SecretPackage. +pub fn round2_secret_package() -> round2::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + let secret_share = scalar1(); + let min_signers = 2; + let max_signers = 3; + + round2::SecretPackage::new( + identifier, + commitment, + secret_share, + min_signers, + max_signers, + ) +} + /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); diff --git a/frost-p256/tests/recreation_tests.rs b/frost-p256/tests/recreation_tests.rs index a24e57f5c..0f4dbf7dd 100644 --- a/frost-p256/tests/recreation_tests.rs +++ b/frost-p256/tests/recreation_tests.rs @@ -107,6 +107,28 @@ fn check_public_key_package_recreation() { assert!(public_key_package == new_public_key_package); } +/// Check if round1::SecretPackage can be recreated. +#[test] +fn check_round1_secret_package_recreation() { + let round1_secret_package = samples::round1_secret_package(); + + let identifier = round1_secret_package.identifier(); + let coefficients = round1_secret_package.coefficients(); + let commitment = round1_secret_package.commitment(); + let min_signers = round1_secret_package.min_signers(); + let max_signers = round1_secret_package.max_signers(); + + let new_round1_secret_package = round1::SecretPackage::new( + *identifier, + coefficients.clone(), + commitment.clone(), + *min_signers, + *max_signers, + ); + + assert!(round1_secret_package == new_round1_secret_package); +} + /// Check if round1::Package can be recreated. #[test] fn check_round1_package_recreation() { @@ -120,6 +142,28 @@ fn check_round1_package_recreation() { assert!(round1_package == new_round1_package); } +/// Check if round2::SecretPackage can be recreated. +#[test] +fn check_round2_secret_package_recreation() { + let round2_secret_package = samples::round2_secret_package(); + + let identifier = round2_secret_package.identifier(); + let commitment = round2_secret_package.commitment(); + let secret_share = round2_secret_package.secret_share(); + let min_signers = round2_secret_package.min_signers(); + let max_signers = round2_secret_package.max_signers(); + + let new_round2_secret_package = round2::SecretPackage::new( + *identifier, + commitment.clone(), + secret_share, + *min_signers, + *max_signers, + ); + + assert!(round2_secret_package == new_round2_secret_package); +} + /// Check if round2::Package can be recreated. #[test] fn check_round2_package_recreation() { diff --git a/frost-p256/tests/serialization_tests.rs b/frost-p256/tests/serialization_tests.rs index 187e7fc9f..b83b12ee7 100644 --- a/frost-p256/tests/serialization_tests.rs +++ b/frost-p256/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_round1_secret_package_postcard_serialization() { + let round1_secret_package = samples::round1_secret_package(); + let bytes: Vec<_> = round1_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round1_secret_package, + round1::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_package_postcard_serialization() { let round1_package = samples::round1_package(); @@ -93,6 +104,17 @@ fn check_round1_package_postcard_serialization() { ); } +#[test] +fn check_round2_secret_package_postcard_serialization() { + let round2_secret_package = samples::round2_secret_package(); + let bytes: Vec<_> = round2_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round2_secret_package, + round2::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round2_package_postcard_serialization() { let round2_package = samples::round2_package(); diff --git a/frost-p256/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap b/frost-p256/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..5e9442903 --- /dev/null +++ b/frost-p256/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-p256/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +000000000000000000000000000000000000000000000000000000000000002a02aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e1aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e101036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2960203 diff --git a/frost-p256/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap b/frost-p256/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..f4c72b402 --- /dev/null +++ b/frost-p256/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-p256/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +000000000000000000000000000000000000000000000000000000000000002a01036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296aaaaaaaa00000000aaaaaaaaaaaaaaaa7def51c91a0fbf034d26872ca84218e10203 diff --git a/frost-ristretto255/tests/helpers/samples.rs b/frost-ristretto255/tests/helpers/samples.rs index da9e99191..d6f58b90d 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -107,6 +107,26 @@ pub fn public_key_package() -> PublicKeyPackage { PublicKeyPackage::new(verifying_shares, verifying_key) } +/// Generate a sample round1::SecretPackage. +pub fn round1_secret_package() -> round1::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let coefficients = vec![scalar1(), scalar1()]; + let min_signers = 2; + let max_signers = 3; + + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + round1::SecretPackage::new( + identifier, + coefficients, + commitment, + min_signers, + max_signers, + ) +} + /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); @@ -119,6 +139,25 @@ pub fn round1_package() -> round1::Package { round1::Package::new(vss_commitment, signature) } +/// Generate a sample round1::SecretPackage. +pub fn round2_secret_package() -> round2::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + let secret_share = scalar1(); + let min_signers = 2; + let max_signers = 3; + + round2::SecretPackage::new( + identifier, + commitment, + secret_share, + min_signers, + max_signers, + ) +} + /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); diff --git a/frost-ristretto255/tests/recreation_tests.rs b/frost-ristretto255/tests/recreation_tests.rs index a8ed937c0..a5974a965 100644 --- a/frost-ristretto255/tests/recreation_tests.rs +++ b/frost-ristretto255/tests/recreation_tests.rs @@ -107,6 +107,28 @@ fn check_public_key_package_recreation() { assert!(public_key_package == new_public_key_package); } +/// Check if round1::SecretPackage can be recreated. +#[test] +fn check_round1_secret_package_recreation() { + let round1_secret_package = samples::round1_secret_package(); + + let identifier = round1_secret_package.identifier(); + let coefficients = round1_secret_package.coefficients(); + let commitment = round1_secret_package.commitment(); + let min_signers = round1_secret_package.min_signers(); + let max_signers = round1_secret_package.max_signers(); + + let new_round1_secret_package = round1::SecretPackage::new( + *identifier, + coefficients.clone(), + commitment.clone(), + *min_signers, + *max_signers, + ); + + assert!(round1_secret_package == new_round1_secret_package); +} + /// Check if round1::Package can be recreated. #[test] fn check_round1_package_recreation() { @@ -120,6 +142,28 @@ fn check_round1_package_recreation() { assert!(round1_package == new_round1_package); } +/// Check if round2::SecretPackage can be recreated. +#[test] +fn check_round2_secret_package_recreation() { + let round2_secret_package = samples::round2_secret_package(); + + let identifier = round2_secret_package.identifier(); + let commitment = round2_secret_package.commitment(); + let secret_share = round2_secret_package.secret_share(); + let min_signers = round2_secret_package.min_signers(); + let max_signers = round2_secret_package.max_signers(); + + let new_round2_secret_package = round2::SecretPackage::new( + *identifier, + commitment.clone(), + secret_share, + *min_signers, + *max_signers, + ); + + assert!(round2_secret_package == new_round2_secret_package); +} + /// Check if round2::Package can be recreated. #[test] fn check_round2_package_recreation() { diff --git a/frost-ristretto255/tests/serialization_tests.rs b/frost-ristretto255/tests/serialization_tests.rs index ffc5dd8bc..1aa96a25a 100644 --- a/frost-ristretto255/tests/serialization_tests.rs +++ b/frost-ristretto255/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_round1_secret_package_postcard_serialization() { + let round1_secret_package = samples::round1_secret_package(); + let bytes: Vec<_> = round1_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round1_secret_package, + round1::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_package_postcard_serialization() { let round1_package = samples::round1_package(); @@ -93,6 +104,17 @@ fn check_round1_package_postcard_serialization() { ); } +#[test] +fn check_round2_secret_package_postcard_serialization() { + let round2_secret_package = samples::round2_secret_package(); + let bytes: Vec<_> = round2_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round2_secret_package, + round2::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round2_package_postcard_serialization() { let round2_package = samples::round2_package(); diff --git a/frost-ristretto255/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap b/frost-ristretto255/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..9c27582cc --- /dev/null +++ b/frost-ristretto255/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ristretto255/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +2a0000000000000000000000000000000000000000000000000000000000000002498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a01e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d760203 diff --git a/frost-ristretto255/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap b/frost-ristretto255/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..6019d5add --- /dev/null +++ b/frost-ristretto255/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ristretto255/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +2a0000000000000000000000000000000000000000000000000000000000000001e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76498d4e9311420c903913a56c94a694b8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a0203 diff --git a/frost-secp256k1-tr/tests/helpers/samples.rs b/frost-secp256k1-tr/tests/helpers/samples.rs index 11ef99e3e..337ae709f 100644 --- a/frost-secp256k1-tr/tests/helpers/samples.rs +++ b/frost-secp256k1-tr/tests/helpers/samples.rs @@ -107,6 +107,26 @@ pub fn public_key_package() -> PublicKeyPackage { PublicKeyPackage::new(verifying_shares, verifying_key) } +/// Generate a sample round1::SecretPackage. +pub fn round1_secret_package() -> round1::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let coefficients = vec![scalar1(), scalar1()]; + let min_signers = 2; + let max_signers = 3; + + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + round1::SecretPackage::new( + identifier, + coefficients, + commitment, + min_signers, + max_signers, + ) +} + /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); @@ -119,6 +139,25 @@ pub fn round1_package() -> round1::Package { round1::Package::new(vss_commitment, signature) } +/// Generate a sample round1::SecretPackage. +pub fn round2_secret_package() -> round2::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + let secret_share = scalar1(); + let min_signers = 2; + let max_signers = 3; + + round2::SecretPackage::new( + identifier, + commitment, + secret_share, + min_signers, + max_signers, + ) +} + /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); diff --git a/frost-secp256k1-tr/tests/recreation_tests.rs b/frost-secp256k1-tr/tests/recreation_tests.rs index 477de37eb..f6f71ea00 100644 --- a/frost-secp256k1-tr/tests/recreation_tests.rs +++ b/frost-secp256k1-tr/tests/recreation_tests.rs @@ -107,6 +107,28 @@ fn check_public_key_package_recreation() { assert!(public_key_package == new_public_key_package); } +/// Check if round1::SecretPackage can be recreated. +#[test] +fn check_round1_secret_package_recreation() { + let round1_secret_package = samples::round1_secret_package(); + + let identifier = round1_secret_package.identifier(); + let coefficients = round1_secret_package.coefficients(); + let commitment = round1_secret_package.commitment(); + let min_signers = round1_secret_package.min_signers(); + let max_signers = round1_secret_package.max_signers(); + + let new_round1_secret_package = round1::SecretPackage::new( + *identifier, + coefficients.clone(), + commitment.clone(), + *min_signers, + *max_signers, + ); + + assert!(round1_secret_package == new_round1_secret_package); +} + /// Check if round1::Package can be recreated. #[test] fn check_round1_package_recreation() { @@ -120,6 +142,28 @@ fn check_round1_package_recreation() { assert!(round1_package == new_round1_package); } +/// Check if round2::SecretPackage can be recreated. +#[test] +fn check_round2_secret_package_recreation() { + let round2_secret_package = samples::round2_secret_package(); + + let identifier = round2_secret_package.identifier(); + let commitment = round2_secret_package.commitment(); + let secret_share = round2_secret_package.secret_share(); + let min_signers = round2_secret_package.min_signers(); + let max_signers = round2_secret_package.max_signers(); + + let new_round2_secret_package = round2::SecretPackage::new( + *identifier, + commitment.clone(), + secret_share, + *min_signers, + *max_signers, + ); + + assert!(round2_secret_package == new_round2_secret_package); +} + /// Check if round2::Package can be recreated. #[test] fn check_round2_package_recreation() { diff --git a/frost-secp256k1-tr/tests/serialization_tests.rs b/frost-secp256k1-tr/tests/serialization_tests.rs index 8f93cfb52..d38bc5ece 100644 --- a/frost-secp256k1-tr/tests/serialization_tests.rs +++ b/frost-secp256k1-tr/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_round1_secret_package_postcard_serialization() { + let round1_secret_package = samples::round1_secret_package(); + let bytes: Vec<_> = round1_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round1_secret_package, + round1::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_package_postcard_serialization() { let round1_package = samples::round1_package(); @@ -93,6 +104,17 @@ fn check_round1_package_postcard_serialization() { ); } +#[test] +fn check_round2_secret_package_postcard_serialization() { + let round2_secret_package = samples::round2_secret_package(); + let bytes: Vec<_> = round2_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round2_secret_package, + round2::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round2_package_postcard_serialization() { let round2_package = samples::round2_package(); diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..0185b1dad --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +000000000000000000000000000000000000000000000000000000000000002a02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980203 diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..e8a499199 --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +000000000000000000000000000000000000000000000000000000000000002a010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b810203 diff --git a/frost-secp256k1/tests/helpers/samples.rs b/frost-secp256k1/tests/helpers/samples.rs index 11b840854..a69632d29 100644 --- a/frost-secp256k1/tests/helpers/samples.rs +++ b/frost-secp256k1/tests/helpers/samples.rs @@ -107,6 +107,26 @@ pub fn public_key_package() -> PublicKeyPackage { PublicKeyPackage::new(verifying_shares, verifying_key) } +/// Generate a sample round1::SecretPackage. +pub fn round1_secret_package() -> round1::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let coefficients = vec![scalar1(), scalar1()]; + let min_signers = 2; + let max_signers = 3; + + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + + round1::SecretPackage::new( + identifier, + coefficients, + commitment, + min_signers, + max_signers, + ) +} + /// Generate a sample round1::Package. pub fn round1_package() -> round1::Package { let serialized_signature = Signature::new(element1(), scalar1()).serialize().unwrap(); @@ -119,6 +139,25 @@ pub fn round1_package() -> round1::Package { round1::Package::new(vss_commitment, signature) } +/// Generate a sample round1::SecretPackage. +pub fn round2_secret_package() -> round2::SecretPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let commitment = + VerifiableSecretSharingCommitment::deserialize(vec![serialized_element]).unwrap(); + let secret_share = scalar1(); + let min_signers = 2; + let max_signers = 3; + + round2::SecretPackage::new( + identifier, + commitment, + secret_share, + min_signers, + max_signers, + ) +} + /// Generate a sample round2::Package. pub fn round2_package() -> round2::Package { let serialized_scalar = <::Group as Group>::Field::serialize(&scalar1()); diff --git a/frost-secp256k1/tests/recreation_tests.rs b/frost-secp256k1/tests/recreation_tests.rs index bb2f83158..e61b30333 100644 --- a/frost-secp256k1/tests/recreation_tests.rs +++ b/frost-secp256k1/tests/recreation_tests.rs @@ -107,6 +107,28 @@ fn check_public_key_package_recreation() { assert!(public_key_package == new_public_key_package); } +/// Check if round1::SecretPackage can be recreated. +#[test] +fn check_round1_secret_package_recreation() { + let round1_secret_package = samples::round1_secret_package(); + + let identifier = round1_secret_package.identifier(); + let coefficients = round1_secret_package.coefficients(); + let commitment = round1_secret_package.commitment(); + let min_signers = round1_secret_package.min_signers(); + let max_signers = round1_secret_package.max_signers(); + + let new_round1_secret_package = round1::SecretPackage::new( + *identifier, + coefficients.clone(), + commitment.clone(), + *min_signers, + *max_signers, + ); + + assert!(round1_secret_package == new_round1_secret_package); +} + /// Check if round1::Package can be recreated. #[test] fn check_round1_package_recreation() { @@ -120,6 +142,28 @@ fn check_round1_package_recreation() { assert!(round1_package == new_round1_package); } +/// Check if round2::SecretPackage can be recreated. +#[test] +fn check_round2_secret_package_recreation() { + let round2_secret_package = samples::round2_secret_package(); + + let identifier = round2_secret_package.identifier(); + let commitment = round2_secret_package.commitment(); + let secret_share = round2_secret_package.secret_share(); + let min_signers = round2_secret_package.min_signers(); + let max_signers = round2_secret_package.max_signers(); + + let new_round2_secret_package = round2::SecretPackage::new( + *identifier, + commitment.clone(), + secret_share, + *min_signers, + *max_signers, + ); + + assert!(round2_secret_package == new_round2_secret_package); +} + /// Check if round2::Package can be recreated. #[test] fn check_round2_package_recreation() { diff --git a/frost-secp256k1/tests/serialization_tests.rs b/frost-secp256k1/tests/serialization_tests.rs index d02c45beb..ec8d6f8ef 100644 --- a/frost-secp256k1/tests/serialization_tests.rs +++ b/frost-secp256k1/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_round1_secret_package_postcard_serialization() { + let round1_secret_package = samples::round1_secret_package(); + let bytes: Vec<_> = round1_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round1_secret_package, + round1::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_package_postcard_serialization() { let round1_package = samples::round1_package(); @@ -93,6 +104,17 @@ fn check_round1_package_postcard_serialization() { ); } +#[test] +fn check_round2_secret_package_postcard_serialization() { + let round2_secret_package = samples::round2_secret_package(); + let bytes: Vec<_> = round2_secret_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + round2_secret_package, + round2::SecretPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round2_package_postcard_serialization() { let round2_package = samples::round2_package(); diff --git a/frost-secp256k1/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap b/frost-secp256k1/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..bd66d2c99 --- /dev/null +++ b/frost-secp256k1/tests/snapshots/serialization_tests__check_round1_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +000000000000000000000000000000000000000000000000000000000000002a02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b81010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980203 diff --git a/frost-secp256k1/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap b/frost-secp256k1/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap new file mode 100644 index 000000000..b244188fe --- /dev/null +++ b/frost-secp256k1/tests/snapshots/serialization_tests__check_round2_secret_package_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +000000000000000000000000000000000000000000000000000000000000002a010279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9d1c9e899ca306ad27fe1945de0242b810203 From a879360a1bda05b9d4bcc0596b09f5886eda4525 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 15 Jan 2025 11:09:53 -0300 Subject: [PATCH 084/175] all: fix issues related to features (#835) --- frost-core/src/round1.rs | 2 +- frost-core/src/round2.rs | 1 - frost-core/src/signature.rs | 8 ++++---- frost-core/src/signing_key.rs | 8 ++++++-- frost-core/src/tests/ciphersuite_generic.rs | 5 ++--- frost-core/src/tests/refresh.rs | 8 ++++---- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 694043b2c..88cdeac1f 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -323,7 +323,6 @@ where /// Computes the [commitment share] from these round one signing commitments. /// /// [commitment share]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-share-aggregation - #[cfg(any(feature = "internals", feature = "cheater-detection"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(super) fn to_group_commitment_share( @@ -367,6 +366,7 @@ pub struct GroupCommitmentShare(pub(super) Element); impl GroupCommitmentShare { /// Create from an element. #[cfg_attr(feature = "internals", visibility::make(pub))] + #[allow(unused)] pub(crate) fn from_element(element: Element) -> Self { Self(element) } diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 3d8639968..d3147dbfa 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -59,7 +59,6 @@ where /// This is the final step of [`verify_signature_share`] from the spec. /// /// [`verify_signature_share`]: https://datatracker.ietf.org/doc/html/rfc9591#name-signature-share-aggregation - #[cfg(any(feature = "cheater-detection", feature = "internals"))] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn verify( diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index 91d3ade9d..915276955 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -32,8 +32,8 @@ where /// Converts default-encoded bytes as /// [`Ciphersuite::SignatureSerialization`] into a `Signature`. - #[cfg(feature = "internals")] - pub fn default_deserialize(bytes: &[u8]) -> Result> { + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn default_deserialize(bytes: &[u8]) -> Result> { // To compute the expected length of the encoded point, encode the generator // and get its length. Note that we can't use the identity because it can be encoded // shorter in some cases (e.g. P-256, which uses SEC1 encoding). @@ -75,8 +75,8 @@ where } /// Converts this signature to its default byte serialization. - #[cfg(feature = "internals")] - pub fn default_serialize(&self) -> Result, Error> { + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn default_serialize(&self) -> Result, Error> { let mut bytes = Vec::::new(); bytes.extend(::serialize(&self.R)?.as_ref()); diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index 93a096c3e..574bf969a 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -46,8 +46,12 @@ where /// Create a signature `msg` using this `SigningKey` using the default /// signing. - #[cfg(feature = "internals")] - pub fn default_sign(&self, mut rng: R, message: &[u8]) -> Signature { + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn default_sign( + &self, + mut rng: R, + message: &[u8], + ) -> Signature { let public = VerifyingKey::::from(*self); let (k, R) = ::generate_nonce(&mut rng); diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 2cf01831e..220bfb516 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -1,7 +1,8 @@ //! Ciphersuite-generic test functions. #![allow(clippy::type_complexity)] -use alloc::collections::BTreeMap; +use alloc::{borrow::ToOwned, collections::BTreeMap, vec::Vec}; +use rand_core::{CryptoRng, RngCore}; use crate as frost; use crate::keys::SigningShare; @@ -10,8 +11,6 @@ use crate::{ keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningPackage, VerifyingKey, }; -use alloc::vec::Vec; -use rand_core::{CryptoRng, RngCore}; use crate::Ciphersuite; diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index ca0171973..384924e5a 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -6,11 +6,10 @@ use crate::keys::generate_with_dealer; use crate::keys::refresh::{ compute_refreshing_shares, refresh_dkg_part2, refresh_dkg_part_1, refresh_share, }; +#[cfg(feature = "serialization")] +use crate::keys::{PublicKeyPackage, SecretShare}; use crate::{self as frost}; -use crate::{ - keys::{KeyPackage, PublicKeyPackage, SecretShare}, - Ciphersuite, Error, Identifier, Signature, VerifyingKey, -}; +use crate::{keys::KeyPackage, Ciphersuite, Error, Identifier, Signature, VerifyingKey}; use crate::tests::ciphersuite_generic::check_part3_different_participants; @@ -175,6 +174,7 @@ pub fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package< } /// Check serialisation +#[cfg(feature = "serialization")] pub fn check_refresh_shares_with_dealer_serialisation( mut rng: R, ) { From 1a3578bea05430ea6973c4b9b8e16c0dc117c12a Mon Sep 17 00:00:00 2001 From: natalie Date: Wed, 15 Jan 2025 14:09:58 +0000 Subject: [PATCH 085/175] Add refreshing shares image for book (#710) * Add refreshing shares image for book * Updated refresh_share with trusted dealer tutorial diagram --- book/src/tutorial/refreshing-shares.md | 2 +- book/src/tutorial/refreshing.png | Bin 0 -> 61016 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/tutorial/refreshing-shares.md b/book/src/tutorial/refreshing-shares.md index 4e916ce5f..fa45ce914 100644 --- a/book/src/tutorial/refreshing-shares.md +++ b/book/src/tutorial/refreshing-shares.md @@ -4,7 +4,7 @@ The diagram below shows the refresh share process. Dashed lines represent data being sent through an [authenticated and confidential communication channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). - +![Diagram of Refreshing shares, illustrating what is explained in the text](refreshing.png) The Trusted Dealer needs to first run `compute_refreshing_shares()` which returns SecretShares (the "refreshing shares") and a PublicKeyPackage. Each diff --git a/book/src/tutorial/refreshing.png b/book/src/tutorial/refreshing.png index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..34aa794564e29ce45666cda671853551e0feb5a1 100644 GIT binary patch literal 61016 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 NFMsh&>S=>}{{z;Leg6Oe literal 0 HcmV?d00001 From f7dcad9c0b909020c4bdc0222d844256ade01505 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 15 Jan 2025 11:30:46 -0300 Subject: [PATCH 086/175] Release 2.1.0 (#836) --- Cargo.toml | 26 ++++++++++++++++ book/src/dev/release-checklist.md | 3 +- book/src/tutorial/importing.md | 4 +-- frost-core/CHANGELOG.md | 12 ++++++-- frost-core/Cargo.toml | 33 ++++++++------------ frost-ed25519/Cargo.toml | 51 +++++++++++++------------------ frost-ed448/Cargo.toml | 47 ++++++++++++---------------- frost-p256/Cargo.toml | 48 ++++++++++++----------------- frost-rerandomized/Cargo.toml | 28 ++++++----------- frost-ristretto255/Cargo.toml | 42 ++++++++++++------------- frost-secp256k1-tr/Cargo.toml | 47 ++++++++++++---------------- frost-secp256k1/Cargo.toml | 47 ++++++++++++---------------- gencode/Cargo.toml | 4 +-- 13 files changed, 185 insertions(+), 207 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 658adb74d..d2615d4fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,5 +12,31 @@ members = [ "gencode" ] + +[workspace.package] +edition = "2021" +version = "2.1.0" +authors = [ + "Deirdre Connolly ", + "Chelsea Komlo ", + "Conrado Gouvea ", + "Natalie Eskinazi " +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/ZcashFoundation/frost" +categories = ["cryptography"] + +[workspace.dependencies] +criterion = "0.5" +document-features = "0.2.7" +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +insta = { version = "1.31.0", features = ["yaml"] } +lazy_static = "1.4" +proptest = "1.0" +rand = "0.8" +rand_chacha = "0.3" +rand_core = "0.6" +serde_json = "1.0" + [profile.test.package."*"] opt-level = 3 \ No newline at end of file diff --git a/book/src/dev/release-checklist.md b/book/src/dev/release-checklist.md index 84e59e088..7173eb8cc 100644 --- a/book/src/dev/release-checklist.md +++ b/book/src/dev/release-checklist.md @@ -32,7 +32,8 @@ ## Make changes -7. Bump the version of each crate in their Cargo.toml files +7. Bump the version of the crates in the root Cargo.toml file. (If they ever + get out of sync, you wil need to bump in each crate Cargo.toml file.) 8. Bump the version used in the tutorial (importing.md) diff --git a/book/src/tutorial/importing.md b/book/src/tutorial/importing.md index ec909a652..2c2dd4139 100644 --- a/book/src/tutorial/importing.md +++ b/book/src/tutorial/importing.md @@ -6,7 +6,7 @@ Add to your `Cargo.toml` file: ``` [dependencies] -frost-ristretto255 = "2.0.0" +frost-ristretto255 = "2.1.0" ``` ## Handling errors @@ -38,7 +38,7 @@ needs to be transmitted. The importing would look like: ``` [dependencies] -frost-ristretto255 = { version = "2.0.0", features = ["serde"] } +frost-ristretto255 = { version = "2.1.0", features = ["serde"] } ``` Note that serde usage is optional. Applications can use different encodings, and diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index be9108bc1..2d4016d65 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,11 +4,17 @@ Entries are listed in reverse chronological order. ## Unreleased +## 2.1.0 + * It is now possible to identify the culprit in `frost_core::keys::dkg::part3()` if an invalid secret share was sent by one of the participants (by calling - frost_core::Error::culprit()`). -* Added frost-secp256k1-tr crate, allowing to generate Bitcoin Taproot (BIP340/BIP341) - compatible signatures. + frost_core::Error::culprit()`) (#728) +* Added frost-secp256k1-tr crate, allowing to generate Bitcoin Taproot + (BIP340/BIP341) compatible signatures(#730). +* Support refreshing shares using the DKG approach using the + `frost_core::keys::refresh::refresh_dkg_{part1,part2,shares}()` functions + (#766). +* `frost_core::keys::dkg::part{1,2}::SecretPackage` are now serializable (#833). ## 2.0.0 diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index b2bab7071..a013a9c42 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -1,19 +1,12 @@ [package] name = "frost-core" -edition = "2021" -# When releasing to crates.io: -# - Update CHANGELOG.md -# - Create git tag. -version = "2.0.0" -authors = [ - "Deirdre Connolly ", - "Chelsea Komlo ", - "Conrado Gouvea ", -] +edition.workspace = true +version.workspace = true +authors.workspace = true readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license.workspace = true +repository.workspace = true +categories.workspace = true keywords = ["cryptography", "crypto", "threshold", "signature", "schnorr"] description = "Types and traits to support implementing Flexible Round-Optimized Schnorr Threshold signature schemes (FROST)." @@ -24,10 +17,10 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] byteorder = { version = "1.4", default-features = false } const-crc32 = { version = "1.2.0", package = "const-crc32-nostd" } -document-features = "0.2.7" +document-features.workspace = true debugless-unwrap = "0.0.4" derive-getters = "0.5.0" -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +hex.workspace = true postcard = { version = "1.0.0", features = ["alloc"], optional = true } rand_core = { version = "0.6", default-features = false } serde = { version = "1.0.160", default-features = false, features = ["derive"], optional = true } @@ -45,11 +38,11 @@ criterion = { version = "0.5", optional = true } [dev-dependencies] criterion = { version = "0.5" } -lazy_static = "1.4" -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" -serde_json = "1.0" +lazy_static.workspace = true +proptest.workspace = true +rand.workspace = true +rand_chacha.workspace = true +serde_json.workspace = true [features] default = ["serialization", "cheater-detection", "std"] diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index add2afc6d..b6d26d599 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -1,20 +1,13 @@ [package] name = "frost-ed25519" -edition = "2021" -# When releasing to crates.io: -# - Update html_root_url -# - Update CHANGELOG.md -# - Create git tag. -version = "2.0.0" -authors = [ - "Deirdre Connolly ", - "Chelsea Komlo ", - "Conrado Gouvea " -] +edition.workspace = true + +version.workspace = true +authors.workspace = true readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license.workspace = true +repository.workspace = true +categories.workspace = true keywords = ["cryptography", "crypto", "ed25519", "threshold", "signature"] description = "A Schnorr signature scheme over Ed25519 that supports FROST." @@ -24,24 +17,24 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } -document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } -rand_core = "0.6" +document-features.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] -criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } -ed25519-dalek = "2.0.0" -insta = { version = "1.31.0", features = ["yaml"] } -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -lazy_static = "1.4" -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" -serde_json = "1.0" +criterion.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +ed25519-dalek = "2.1.0" +insta.workspace = true +hex.workspace = true +lazy_static.workspace = true +proptest.workspace = true +rand.workspace = true +rand_chacha.workspace = true +serde_json.workspace = true [features] nightly = [] diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 17ba4b5fa..16282a97f 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -1,19 +1,12 @@ [package] name = "frost-ed448" -edition = "2021" -# When releasing to crates.io: -# - Update CHANGELOG.md -# - Create git tag. -version = "2.0.0" -authors = [ - "Deirdre Connolly ", - "Chelsea Komlo ", - "Conrado Gouvea " -] +edition.workspace = true +version.workspace = true +authors.workspace = true readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license.workspace = true +repository.workspace = true +categories.workspace = true keywords = ["cryptography", "crypto", "ed448", "threshold", "signature"] description = "A Schnorr signature scheme over Ed448 that supports FROST." @@ -22,24 +15,24 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -document-features = "0.2.7" +document-features.workspace = true ed448-goldilocks = { version = "0.9.0" } -frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } -rand_core = "0.6" +frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +rand_core.workspace = true sha3 = { version = "0.10.6", default-features = false } [dev-dependencies] -criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } -lazy_static = "1.4" -insta = { version = "1.31.0", features = ["yaml"] } -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" -serde_json = "1.0" +criterion.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +lazy_static.workspace = true +insta.workspace = true +hex.workspace = true +proptest.workspace = true +rand.workspace = true +rand_chacha.workspace = true +serde_json.workspace = true [features] nightly = [] diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index 5b94d488c..dc5e3edd9 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -1,20 +1,12 @@ [package] name = "frost-p256" -edition = "2021" -# When releasing to crates.io: -# - Update html_root_url -# - Update CHANGELOG.md -# - Create git tag. -version = "2.0.0" -authors = [ - "Deirdre Connolly ", - "Chelsea Komlo ", - "Conrado Gouvea " -] +edition.workspace = true +version.workspace = true +authors.workspace = true readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license.workspace = true +repository.workspace = true +categories.workspace = true keywords = ["cryptography", "crypto", "threshold", "signature"] description = "A Schnorr signature scheme over the NIST P-256 curve that supports FROST." @@ -23,24 +15,24 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -document-features = "0.2.7" +document-features.workspace = true p256 = { version = "0.13.0", features = ["hash2curve"], default-features = false } -frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } -rand_core = "0.6" +frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] -criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } -insta = { version = "1.31.0", features = ["yaml"] } -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -lazy_static = "1.4" -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" -serde_json = "1.0" +criterion.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +insta.workspace = true +hex.workspace = true +lazy_static.workspace = true +proptest.workspace = true +rand.workspace = true +rand_chacha.workspace = true +serde_json.workspace = true [features] nightly = [] diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index da4d24405..878f710c6 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -1,20 +1,12 @@ [package] name = "frost-rerandomized" -edition = "2021" -# When releasing to crates.io: -# - Update html_root_url -# - Update CHANGELOG.md -# - Create git tag. -version = "2.0.0" -authors = [ - "Deirdre Connolly ", - "Chelsea Komlo ", - "Conrado Gouvea ", -] +edition.workspace = true +version.workspace = true +authors.workspace = true readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license.workspace = true +repository.workspace = true +categories.workspace = true keywords = ["cryptography", "threshold", "signature", "schnorr", "randomized"] description = "Types and traits to support implementing a re-randomized variant of Flexible Round-Optimized Schnorr Threshold signature schemes (FROST)." @@ -24,12 +16,12 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] derive-getters = "0.5.0" -document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0", features = [ +document-features.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", features = [ "internals" ], default-features = false } -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -rand_core = "0.6" +hex.workspace = true +rand_core.workspace = true [dev-dependencies] diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index e666dc83d..ff67d8917 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -1,16 +1,12 @@ [package] name = "frost-ristretto255" -edition = "2021" -# When releasing to crates.io: -# - Update html_root_url -# - Update CHANGELOG.md -# - Create git tag. -version = "2.0.0" -authors = ["Deirdre Connolly ", "Chelsea Komlo ", "Conrado Gouvea "] +edition.workspace = true +version.workspace = true +authors.workspace = true readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license.workspace = true +repository.workspace = true +categories.workspace = true keywords = ["cryptography", "crypto", "ristretto", "threshold", "signature"] description = "A Schnorr signature scheme over the prime-order Ristretto group that supports FROST." @@ -20,24 +16,24 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } -document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } -rand_core = "0.6" +document-features.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } -frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } -insta = { version = "1.31.0", features = ["yaml"] } -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -lazy_static = "1.4" +frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +insta.workspace = true +hex.workspace = true +lazy_static.workspace = true postcard = { version = "1.0.0", features = ["use-std"] } -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" -serde_json = "1.0" +proptest.workspace = true +rand.workspace = true +rand_chacha.workspace = true +serde_json.workspace = true [features] nightly = [] diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index d769c4957..3a2292ca7 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -1,19 +1,12 @@ [package] name = "frost-secp256k1-tr" -edition = "2021" -# When releasing to crates.io: -# - Update CHANGELOG.md -# - Create git tag. -version = "2.0.0" -authors = [ - "Deirdre Connolly ", - "Chelsea Komlo ", - "Conrado Gouvea " -] +edition.workspace = true +version.workspace = true +authors.workspace = true readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license.workspace = true +repository.workspace = true +categories.workspace = true keywords = ["cryptography", "crypto", "threshold", "signature"] description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot." @@ -22,25 +15,25 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } +document-features.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } -rand_core = "0.6" +rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] -criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } -insta = { version = "1.31.0", features = ["yaml"] } -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -lazy_static = "1.4" -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" +criterion.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +insta.workspace = true +hex.workspace = true +lazy_static.workspace = true +proptest.workspace = true +rand.workspace = true +rand_chacha.workspace = true secp256k1 = "0.30.0" -serde_json = "1.0" +serde_json.workspace = true [features] nightly = [] diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index cdc8adcb2..4a6ac8e46 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -1,19 +1,12 @@ [package] name = "frost-secp256k1" -edition = "2021" -# When releasing to crates.io: -# - Update CHANGELOG.md -# - Create git tag. -version = "2.0.0" -authors = [ - "Deirdre Connolly ", - "Chelsea Komlo ", - "Conrado Gouvea " -] +edition.workspace = true +version.workspace = true +authors.workspace = true readme = "README.md" -license = "MIT OR Apache-2.0" -repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license.workspace = true +repository.workspace = true +categories.workspace = true keywords = ["cryptography", "crypto", "threshold", "signature"] description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST." @@ -22,24 +15,24 @@ features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -document-features = "0.2.7" -frost-core = { path = "../frost-core", version = "2.0.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", default-features = false } +document-features.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } -rand_core = "0.6" +rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] -criterion = "0.5" -frost-core = { path = "../frost-core", version = "2.0.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.0.0", features = ["test-impl"] } -insta = { version = "1.31.0", features = ["yaml"] } -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -lazy_static = "1.4" -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" -serde_json = "1.0" +criterion.workspace = true +frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } +frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +insta.workspace = true +hex.workspace = true +lazy_static.workspace = true +proptest.workspace = true +rand.workspace = true +rand_chacha.workspace = true +serde_json.workspace = true [features] nightly = [] diff --git a/gencode/Cargo.toml b/gencode/Cargo.toml index c99b7a1bb..8487d9ea6 100644 --- a/gencode/Cargo.toml +++ b/gencode/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "gencode" version = "0.1.0" -edition = "2021" +edition.workspace = true publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] regex = "1.6.0" -serde_json = "1.0" +serde_json.workspace = true [[bin]] name = "gencode" From 94dba95d8e44974fb4bf4314b1b8eac5844e06b7 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 15 Jan 2025 12:10:33 -0300 Subject: [PATCH 087/175] frost-core: fix visibility of coefficients() (#842) --- frost-core/src/keys/dkg.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 2c82657e5..4373a91b9 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -160,8 +160,8 @@ pub mod round1 { } /// Returns the secret coefficients. - #[cfg(feature = "internals")] - pub fn coefficients(&self) -> Vec> { + #[cfg_attr(feature = "internals", visibility::make(pub))] + pub(crate) fn coefficients(&self) -> Vec> { self.coefficients.iter().map(|s| s.0).collect() } } From b56839d1dc297f50e7d0efafbafa5a2f9ead583b Mon Sep 17 00:00:00 2001 From: Pili Guerra <1311133+mpguerra@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:16:11 +0100 Subject: [PATCH 088/175] doc: Refreshing shares is now implemented for DKG (#847) Refreshing shares is now implemented for DKG --- book/src/frost.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/book/src/frost.md b/book/src/frost.md index ec778145f..7c34073ca 100644 --- a/book/src/frost.md +++ b/book/src/frost.md @@ -112,11 +112,6 @@ shares in a way that maintains the same group public key. Some applications are: in signing sessions with the others. (They can also then use the repair share functionality to issue a new share and move from 2-of-2 back to 2-of-3.) -```admonish note -This is also possible via Distributed Key Generation but this has not yet been -implemented. -``` - ```admonish danger It is critically important to keep in mind that the **Refresh Shares functionality does not "restore full security" to a group**. While the group From d92fc2bed63565984acda1505d10fd640b22576c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 00:58:18 +0000 Subject: [PATCH 089/175] build(deps): bump reviewdog/action-actionlint from 1.63.0 to 1.64.1 (#846) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.63.0 to 1.64.1. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.63.0...v1.64.1) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aa5ed2742..b422c09f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -200,7 +200,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.2.2 - - uses: reviewdog/action-actionlint@v1.63.0 + - uses: reviewdog/action-actionlint@v1.64.1 with: level: warning fail_on_error: false From 2a7f8aece2b3b71242c63d106c3193876a2ce596 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jan 2025 00:58:21 +0000 Subject: [PATCH 090/175] build(deps): bump codecov/codecov-action from 5.1.2 to 5.2.0 (#848) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.1.2 to 5.2.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.1.2...v5.2.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 78b16a2b8..9c98055bd 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v5.1.2 + uses: codecov/codecov-action@v5.2.0 From 1f8e0e90dc8eb35361c16f94f796c7ca55e742a0 Mon Sep 17 00:00:00 2001 From: Pili Guerra <1311133+mpguerra@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:54:49 +0100 Subject: [PATCH 091/175] Update dependabot.yml (#856) Change to monthly schedule --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b21dd53e5..75dc8828b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,12 +3,12 @@ updates: - package-ecosystem: github-actions directory: "/" schedule: - interval: daily + interval: monthly timezone: America/New_York open-pull-requests-limit: 10 - package-ecosystem: cargo directory: "/" schedule: - interval: daily + interval: monthly timezone: America/New_York open-pull-requests-limit: 10 From 121a11650b44a068b81d22db2592f0093e330fdb Mon Sep 17 00:00:00 2001 From: Skylar Ray <137945430+sky-coderay@users.noreply.github.com> Date: Thu, 30 Jan 2025 16:32:53 +0200 Subject: [PATCH 092/175] fix errors (#858) * Update release-checklist.md * Update dkg.rs --- book/src/dev/release-checklist.md | 2 +- frost-core/src/keys/dkg.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/dev/release-checklist.md b/book/src/dev/release-checklist.md index 7173eb8cc..d5893e589 100644 --- a/book/src/dev/release-checklist.md +++ b/book/src/dev/release-checklist.md @@ -33,7 +33,7 @@ ## Make changes 7. Bump the version of the crates in the root Cargo.toml file. (If they ever - get out of sync, you wil need to bump in each crate Cargo.toml file.) + get out of sync, you will need to bump in each crate Cargo.toml file.) 8. Bump the version used in the tutorial (importing.md) diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 4373a91b9..541c05398 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -17,7 +17,7 @@ //! //! As required for any multi-party protocol using Feldman's VSS, the key //! generation stage in FROST requires participants to maintain a consistent -//! view of the pubic commitments to the secret polynomial coefficients. This +//! view of the public commitments to the secret polynomial coefficients. This //! DKG protocol requires participants to broadcast the commitment values //! honestly (e.g., participants do not provide different commitment values to a //! subset of participants) over a _[secure broadcast channel]_. From 703559343f4c48befbf2b3a72d29fb0855a1f43b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 23:25:13 +0000 Subject: [PATCH 093/175] build(deps): bump codecov/codecov-action from 5.2.0 to 5.3.1 (#851) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.2.0 to 5.3.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.2.0...v5.3.1) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 9c98055bd..dac117b03 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -44,4 +44,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v5.2.0 + uses: codecov/codecov-action@v5.3.1 From 1d41c21675f558439af9c826bf90e1199da806d5 Mon Sep 17 00:00:00 2001 From: Antoine Rondelet Date: Tue, 4 Feb 2025 09:36:25 +0000 Subject: [PATCH 094/175] Fixing a small typo (#863) --- book/src/zcash/technical-details.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/zcash/technical-details.md b/book/src/zcash/technical-details.md index 56ae65523..e75adfe7d 100644 --- a/book/src/zcash/technical-details.md +++ b/book/src/zcash/technical-details.md @@ -90,14 +90,14 @@ when recovering a wallet. The biggest challenge in using FROST with Zcash is allowing participants to communicate securely with each other, which is required to run the FROST -protocol. Since wallets don't currently need to communication to each other, a +protocol. Since wallets don't currently need to communicate with each other, a whole new mechanism will need to be implemented. For this to happen, two things are required: - Allowing wallets to actually communicate with each other (regardless of security). This is challenging because users are usually behind NATs and - firewalls, so they can't simply open TCP connections with each other. So + firewalls, so they can't simply open TCP connections with each other. So, some kind of signaling server may be needed. - Making the communication secure. This is actually fairly solvable while not trivial and we're planning on working on a library to address it. It needs to From 602157a6fd552e1535c85ba136fb811780f43857 Mon Sep 17 00:00:00 2001 From: XxAlex74xX <30472093+XxAlex74xX@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:36:28 +0100 Subject: [PATCH 095/175] docs: correction comments (#860) * Update repairable.rs * Update refresh.rs * Update repairable.rs * Update lib.rs --- frost-core/src/keys/repairable.rs | 2 +- frost-core/src/lib.rs | 2 +- frost-core/src/tests/refresh.rs | 2 +- frost-core/src/tests/repairable.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frost-core/src/keys/repairable.rs b/frost-core/src/keys/repairable.rs index df0be45c7..24cd32b60 100644 --- a/frost-core/src/keys/repairable.rs +++ b/frost-core/src/keys/repairable.rs @@ -2,7 +2,7 @@ //! //! Implements the Repairable Threshold Scheme (RTS) from . //! The RTS is used to help a signer (participant) repair their lost share. This is achieved -//! using a subset of the other signers know here as `helpers`. +//! using a subset of the other signers known here as `helpers`. use alloc::collections::{BTreeMap, BTreeSet}; diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 76078d908..a290984e1 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -682,7 +682,7 @@ fn detect_cheater( /// for which the signature share was produced and with the group's /// `verifying_key`. /// -/// This is not required for regular FROST usage but might useful in certain +/// This is not required for regular FROST usage but might be useful in certain /// situations where it is desired to verify each individual signature share /// before aggregating the signature. pub fn verify_signature_share( diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 384924e5a..0d7d617b8 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -18,7 +18,7 @@ use alloc::vec::Vec; use super::ciphersuite_generic::check_sign; -/// We want to test that recover share matches the original share +/// We want to test that recovered share matches the original share pub fn check_refresh_shares_with_dealer(mut rng: R) { // Compute shares diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index 532e1e061..a922e320c 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -16,7 +16,7 @@ use crate::{ Ciphersuite, Error, Field, Group, Identifier, Scalar, }; -/// We want to test that recover share matches the original share +/// We want to test that recovered share matches the original share pub fn check_rts(mut rng: R) { // Compute shares From 464cc050001e70713df10f5baf1f8589971cb5a0 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 4 Feb 2025 17:41:50 -0300 Subject: [PATCH 096/175] book: add FROST Server section (#811) * book: add FROST Server section * book: update Ywallet demo section * fill TODOs * Apply suggestions from code review Co-authored-by: natalie * Apply suggestions from code review --------- Co-authored-by: natalie --- book/src/SUMMARY.md | 1 + book/src/zcash/server.md | 363 +++++++++++++++++++++++++++++++++ book/src/zcash/ywallet-demo.md | 296 ++++++++++++++++++--------- 3 files changed, 563 insertions(+), 97 deletions(-) create mode 100644 book/src/zcash/server.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 3970f8e06..004ef64de 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -13,6 +13,7 @@ - [FROST with Zcash](zcash.md) - [Technical Details](zcash/technical-details.md) - [Ywallet Demo](zcash/ywallet-demo.md) + - [FROST Server](zcash/server.md) - [Terminology](terminology.md) - [Developer Documentation](dev.md) - [List of Dependencies for Audit](dev/frost-dependencies-for-audit.md) diff --git a/book/src/zcash/server.md b/book/src/zcash/server.md new file mode 100644 index 000000000..63503946b --- /dev/null +++ b/book/src/zcash/server.md @@ -0,0 +1,363 @@ +# ZF FROST Server (frostd) + +One challenge for using FROST is allowing participants to communicate securely +with one another. Devices are usually behind firewalls and NATs, which make +direct connections hard. + +To mitigate this issue and to make it easier to use FROST, the ZF FROST Server +(frostd) was created. It is a JSON-HTTP server with a small API to allow +participants to create signing sessions and to communicate with one another. + +It works like this: + +- Clients (coordinator or participants) authenticate to the server using a key + pair, which will likely be the same key pair they use to end-to-end encrypt + messages. +- The Coordinator creates a session, specifying the public keys of the + participants. +- Participants list sessions they're participating in, and choose the proceed + with the signing session. +- Coordinator and Participants run the FROST protocol, end-to-end encrypting + messages and sending them to the server. +- The Coordinator closes the session. + +Note that the server doesn't really care about the particular key pair being +used; it is only used to enforce who can send messages to who. + +## Compiling, Running and Deploying + +You will need to have [Rust and +Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) +installed. Run: + +``` +cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frostd +``` + +The `frostd` binary will be installed [per `cargo` +config](https://doc.rust-lang.org/cargo/commands/cargo-install.html#description) +and it will likely be in your `$PATH`, so you can run by simply running +`frostd`. + +To deploy the FROST Server, **you need TLS/HTTPS certificates**. We strongly +recommend using a reverse proxy such as `nginx` to handle TLS and to also add +denial of service protections. In that case, use the `--no-tls-very-insecure` +flag in `frostd` and make `nginx` connect to it (see example config below). + +If you want to expose `frostd` directly, use the `--tls-cert` and +`--tls-key` to specify the paths of the PEM-encoded certificate and key. You can +use [Let's Encrypt](https://letsencrypt.org/) to get a free certificate. + + +### Local Testing + +For local testing, you can use the [`mkcert` +tool](https://github.com/FiloSottile/mkcert). Install it and run: + +``` +mkcert -install +mkcert localhost 127.0.0.1 ::1 +``` + +Then start the server with: + +``` +frostd --tls-cert localhost+2.pem --tls-key localhost+2-key.pem +``` + + +### Sample nginx Config + +This is a sample nginx config file tested in a Ubuntu deployment (i.e. it +assumes it's in a `http` block and it's included by `/etc/nginx/nginx.conf`); +copy it to `/etc/nginx/sites-enabled/frostd` and run `sudo service nginx +restart`. + +The config assumes the certificates were copied to `/etc/ssl`. + + +``` +limit_req_zone $binary_remote_addr zone=challenge:10m rate=30r/m; +limit_req_zone $binary_remote_addr zone=create:10m rate=10r/m; +limit_req_zone $binary_remote_addr zone=other:10m rate=240r/m; +limit_conn_zone $binary_remote_addr zone=addr:10m; + +server { + listen 443 ssl; + listen [::]:443 ssl; + ssl_certificate /etc/ssl/localhost+2.pem; + ssl_certificate_key /etc/ssl/localhost+2-key.pem; + ssl_protocols TLSv1.3; + ssl_ecdh_curve X25519:prime256v1:secp384r1; + ssl_prefer_server_ciphers off; + + server_name localhost; + + client_body_timeout 5s; + client_header_timeout 5s; + + location / { + proxy_pass http://127.0.0.1:2744; + limit_req zone=other burst=5; + limit_conn addr 10; + } + location /challenge { + proxy_pass http://127.0.0.1:2744/challenge; + limit_req zone=challenge burst=3; + limit_conn addr 10; + } + location /create_new_session { + proxy_pass http://127.0.0.1:2744/create_new_session; + limit_req zone=create burst=3; + limit_conn addr 10; + } +} +``` + +## API + +The API uses JSON/HTTP. All requests should have `Content-Type: +application/json`. Errors are returned with status code 500 and the content +body will have a JSON such as: + +``` +{ code: 1, msg: "error message" } +``` + +The +[codes](https://github.com/ZcashFoundation/frost-zcash-demo/blob/548a8a7329c6eed8180464662f430d12cd71dfcc/frostd/src/lib.rs#L95-L98) +are: + +``` +pub const INVALID_ARGUMENT: usize = 1; +pub const UNAUTHORIZED: usize = 2; +pub const SESSION_NOT_FOUND: usize = 3; +pub const NOT_COORDINATOR: usize = 4; +``` + + +### Usage flow + +For the Coordinator: + +- Log in with `/challenge` and `/login` +- Create a new signing session with `/create_new_session` +- Wait for round 1 messages by repeatedly polling `/receive` each 2 seconds or longer +- Send round 2 messages by using `/send` +- Wait for round 2 message by repeatedly polling `/receive` each 2 seconds or longer +- Close the session with `/close_session` + +For Participants: + +- Log in with `/challenge` and `/login` +- Wait for signing sessions with `/list_sessions`, either by the user's request or by repeatedly + polling each 10 seconds or longer +- Get the session information with `/get_session_info` +- Show the user the session information (who the participants are) to select which + session (if more than one) +- Send round 1 message by using `/send` +- Wait for round 2 message by repeatedly polling `/receive` each 2 seconds or longer +- Send round 2 message by using `/send` + +```admonish info +**Polling** is not optimal. The server will support a better mechanism in the +future. +``` + +```admonish info +Selecting sessions is tricky. Ideally, the user should select what session +to proceed by checking the message being signed; however, that is usually +sent in Round 2. There are multiple ways to handle this: + +- Simply show the users who are participants, hoping that is enough to + disambiguate (we assume that concurrent signing sessions won't be that common) +- Quietly proceed with all sessions, and only prompt the user after the message + is received. (It's harmless to do round 1 of FROST even if the user might + not have agreed to sign the message yet.) +- Change the application so that the message is sent to the participants first + (the server does not really care how the protocol is run). +``` + +```admonish critical +Always gather consent from the user by showing them the message before +signing it. +``` + +### `/challenge` + +Input: empty + +Sample output: + +``` +{"challenge":"2c5cdb6d-a7db-470e-9e6f-2a7062532825"} +``` + +Returns a challenge that the client will need to sign in order to authenticate. + +### `/login` + +To call `/login`, you will need to sign the challenge with XEdDSA, see +[example](https://github.com/ZcashFoundation/frost-zcash-demo/blob/548a8a7329c6eed8180464662f430d12cd71dfcc/frostd/tests/integration_tests.rs#L443-L476). +Sign the challenge UUID, converted to bytes. + + +Input sample: + +``` +{ + "challenge":"b771757e-085a-4a88-ab8f-28bd4ba67f3a", + "pubkey":"f5bf1b8194e20ebdd28e662b1efcf1c5cd2aaade5d5dd83cf89b246b5492726b", + "signature":"bba398d0963ab88e28134ad41c127eeee816a219838db01dd7bcd9d7fcd975f082330c134e6f7238580ba8434652aa116891495452d9048f5615e07f4ad6b204" +} +``` + +Output sample: + +``` +{"access_token":"061a18ba-2c3c-4685-a79e-2c0c93000af5"} +``` + +The returned access token must be included as a bearer token in an +`Authorization` header; e.g. `Authorization: Bearer +061a18ba-2c3c-4685-a79e-2c0c93000af5`. + +Access tokens are currently valid for 1 hour. It's recommended to login at the +beginning of each FROST session; log in again if it needs to take longer. + +### `/logout` + +Input: empty (it will logout the authenticated user) + +Output: empty + +Logs out, invalidating the access token. Note that access tokens expire after +1 hour anyway. + +### `/create_new_session` + +Input sample: + +``` +{ + "pubkeys": [ + "3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c", + "edbd661dec0a9d0468b4a166a4afa80560d769f6bcb152fb8f4224059329a518" + ], + message_count: 1, +} +``` + +Output sample: + +``` +{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"} +``` + +Creates a new session. The requesting user will be the Coordinator, and the +users with the hex-encoded public keys given in `pubkeys` will be the +participants (which might or might not include the Coordinator itself). + +The `message_count` parameter allows signing more than one message in the same +signing session, which will save roundtrips. This does not impacts the server +itself and is used to signal the participants (via `/get_session_info`). + +### `/list_sessions` + +Input: empty (it will list for the authenticated user) + +Output sample: + +``` +{"session_ids": ["2c5cdb6d-a7db-470e-9e6f-2a7062532825"]} +``` + +List the sessions IDs of the session a participant is in. + +### `/get_session_info` + +Input sample: + +```{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"}``` + +Output sample: + +``` +{ + "message_count": 1, + "pubkeys": [ + "3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c", + "edbd661dec0a9d0468b4a166a4afa80560d769f6bcb152fb8f4224059329a518" + ], + "coordinator_pubkey": "3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c", +} +``` + +Returns information about the given session. + +### `/send` + +Input sample: + +``` +{ + "session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825", + "recipients": ["3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c"], + "msg": "000102", +} +``` + +Output: empty + +Sends a (hex-encoded) message to one or more participants. To send to the +Coordinator, pass an empty list in `recipients` (**do not** use the +Coordinator's public key, because that might be ambiguous if they're also a +Participant). + +```admonish critical +Messages **MUST** be end-to-end encrypted between recipients. The server can't +enforce this and if you fail to encrypt them then the server could read +all the messages. +``` + +### `/receive` + +Input sample: + +``` +{ + "session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825", + "as_coordinator": false, +} +``` + +Output sample: + +``` +{ + "msgs":[ + { + "sender": "3c9f4a3b2ae28c8e11fbc90b693a9712c181275fb4b554a140c68dc13cdd9b4c", + "msg": "000102", + } + ] +} +``` + +Receives messages sent to the requesting user. Note that if a user is both a +Coordinator and a Participant, it is not possible to distinguish if a message +received from them was sent as Coordinator or as a Participant. This does not +matter in FROST since this ambiguity never arises (Participants always receive +messages from the Coordinator, and vice-versa, except during DKG where there is +no Coordinator anyway). + +### `/close_session` + +Input sample: + +```{"session_id": "2c5cdb6d-a7db-470e-9e6f-2a7062532825"}``` + +Output: empty + +Closes the given session. Only the Coordinator who created the session can close +it. Sessions also expire by default after 24 hours. diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index ea4f1343b..c36513ee0 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -1,7 +1,8 @@ # Ywallet Demo Tutorial This tutorial explaining how to run the FROST demo using Ywallet that was -[presented during Zcon4](https://www.youtube.com/watch?v=xvzESdDtczo). +[presented during Zcon4](https://www.youtube.com/watch?v=xvzESdDtczo) (though it +has been updated and it differs from what was presented). Ywallet supports [offline signing](https://ywallet.app/advanced/offline_signature/), which allows having a @@ -11,88 +12,215 @@ the transaction plan with a command line tool, using FROST. This tutorial assumes familiarity with the command line. + ## Setting up Install `cargo` and `git`. [Install Ywallet](https://ywallet.app/installation/). -Clone the repository: +Install the `frost-client` tool: + +``` +cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frost-client +``` + +Install the `zcash-sign` tool: + +``` +cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked zcash-sign +``` + +Switch to an empty folder which will store the files generate in the demo. +For example: ``` -git clone https://github.com/ZcashFoundation/frost-zcash-demo.git +mkdir frost-demo +cd frost-demo/ +``` + + +### Running the server + +This demo uses the ZF FROST server (frostd) to help participants communicate. +While in practice users would use an existing online server, for the demo you +can run a local server by following [these instructions](./server.md) (the +"Compiling, Running and Deploying" and "Local Testing" sections). + +The rest of the tutorial assumes the server is up and running. + + +### Initializing the users + +Run the following command to initialize three users (in practice, each user +would run a similar command, but for demo purposes we're assuming +you will simulate all of them in the same machine, so run these +commands in your machine): + +``` +frost-client init -c alice.toml +frost-client init -c bob.toml +frost-client init -c eve.toml +``` + +This will create a config file for three users; Alice, Bob and Eve. + +```admonish note +If you really want to run the demo in separate machines, then you can omit the +`-c alice.toml` part of the command (i.e. run `frost-client init`); it will +save to a default location in the user's home directory. ``` + ## Generating FROST key shares First we will generate the FROST key shares. For simplicity we'll use trusted -dealer, DKG will be described later. +dealer; if you want to use Distributed Key Generation, skip to the next section. + +In a new terminal (in case the previous terminal is running the server), run the +following: + +``` +frost-client trusted-dealer -d "Alice, Bob and Eve's group" --names Alice,Bob,Eve -c alice.toml -c bob.toml -c eve.toml -C redpallas +``` + +This will by default generate a 2-of-3 key shares. The key shares will be +written into each participant's config file. You can change the threhsold, +number of shares and file names using the command line; append `-h` to the +commend above for the command line help. + + +## Generating FROST key shares using DKG + +For real-word usage we commend generating key shares using Distributed Key +Generation. If you did the previous section, skip to "Generating the Full +Viewing Key for the wallet". + + +```admonish note +This section assumes each participant is running the commands in their own +machine. If you want to simulate all of them in a single machine, +specify the config file for the user (e.g. `-c alice.toml`) accordingly. +``` + + +### Initializing config files + +If they haven't yet, each participant should run: + +``` +frost-client init +``` + + +### Sharing contacts + +Each participant must now generate a contact string that they will need to share +with the other participants. This contact string will include a name, which they +can choose when exporting and will be shown to whoever they send the contact to. + +Run the following, substituting the name accordingly: -Run the following (it will take a bit to compile): +``` +frost-client export --name 'Alice' +``` + +The command will print an encoded contact string such as +`zffrost1qyqq2stvd93k2g84hudcr98zp67a9rnx9v00euw9e5424hjathvre7ymy344fynjdvxmwxfg`. +Send it to the other participants using some trusted communication channel +(instant messaging, etc.). + +The other participants will send you their contacts. Import them by running the +following command for each contact (replace `` with the contact +string accordingly): ``` -cd frost-zcash-demo/ -cargo run --bin trusted-dealer -- -C redpallas +frost-client import ``` -This will by default generate a 2-of-3 key shares. The public key package -will be written to `public-key-package.json`, while key packages will be -written to `key-package-1.json` through `-3`. You can change the threshold, -number of shares and file names using the command line; append `-- -h` -to the command above for the command line help. -```admonish info -If you want to use DKG instead of Trusted Dealer, instead of the command above, - run this for each participant, in separate terminals for each: +### Generating shares + +Finally, to generate the shares, one of the participants will need to initiate +the process. They will need to public key of each participant, so they need to +first list them with the following command: + +``` +frost-client contacts +``` -`cargo run --bin dkg -- -C redpallas` +Then run the following command, replacing the `` and `` hex +strings with the public keys of the contacts which will participate (along with +the user running the command): -and follow the instructions. (There will be a considerable amount of -copy&pasting!) +``` +frost-client dkg -d 'Alice, Bob and Eve's group' -s localhost:2744 -S , -t 2 -C redpallas -c alice.toml ``` +The user should then notify the others that a signing session has started (e.g. +via instant messaging again), and also share the threshold number that was used. +They should then run the following, replacing the name of the group if they wish +and the threshold number with the one given by the first participant. + +``` +frost-client dkg -d 'Alice, Bob and Eve's group' -s localhost:2744 -t 2 -C redpallas +``` + +```admonish note +A future version might not require specifying the threshold and group name. +``` + + ## Generating the Full Viewing Key for the wallet -Get the `verifying_key` value that is listed inside the Public Key Package in -`public-key-package.json`. For example, in the following package +Next, we will need to generate a Zcash Full Viewing Key from the FROST group +material we have just generated; this address will then be imported into a wallet +so that we'll be able to create Zcash transactions for it. + +Run the following command: ``` -{"verifying_shares": ...snip... ,"verifying_key":"d2bf40ca860fb97e9d6d15d7d25e4f17d2e8ba5dd7069188cbf30b023910a71b","ciphersuite":"FROST(Pallas, BLAKE2b-512)"} +frost-client groups ``` -you would need to copy -`d2bf40ca860fb97e9d6d15d7d25e4f17d2e8ba5dd7069188cbf30b023910a71b`. +It will list all groups you're in - at this point it should list the only one +you have just created. Copy the Public Key it shows (it will look like e.g. +`79d6bcee79c88ad9ba259067772b97f5de12f1435b474d03bc98f255be08a610`) The run the following command, replacing `` with the value you copied. ``` -cd zcash-sign/ -cargo run --release -- generate --ak --danger-dummy-sapling +zcash-sign generate --ak --danger-dummy-sapling ``` It will print an Orchard address, and a Unified Full Viewing Key. Copy and paste both somewhere to use them later. + ## Importing the Full Viewing Key into Ywallet Open Ywallet and click "New account". Check "Restore an account" and paste the Unified Full Viewing Key created in the previous step. Click "Import". + ## Funding the wallet Now you will need to fund this wallet with some ZEC. Use the Orchard address printed by the signer (see warning below). Send ZEC to that address using another account (or try [ZecFaucet](https://zecfaucet.com/)). -```admonish warning +```admonish danger The address being show by Ywallet is a unified address that includes both an Orchard and Sapling address. For the demo to work, you need to receive funds in your Orchard address. Whether that will happen depends on multiple factors so it's probably easier to use just the Orchard-only address printed by the signer. -**If you send it to the Sapling address, the funds will be unspendable and lost!** +In Ywallet, you can also swipe right on the QR Code until it shows the "Orchard +Address". **IF YOU SEND IT TO THE SAPLING ADDRESS, THE FUNDS WILL BECOME +UNSPENDABLE AND WILL BE LOST!** ``` + ## Creating the transaction Now you will create the transaction that you wish to sign with FROST. Click @@ -102,112 +230,84 @@ click the arrow button. The wallet will show the transaction plan. Click the snowflake button. It will show a QR code, but we want that information as a file, so click the floppy disk -button and save the file somewhere (e.g. `tx.raw` as suggested by Ywallet). +button and save the file somewhere (e.g. `tx-plan.json`). + ## Signing the transaction +Now you will need to simulate two participants and a Coordinator to sign the +transaction, and you should still have the FROST server running which will +handle communications between them. It's probably easier to open three new +terminals. + Go back to the signer terminal and run the following, replacing `` with the path to the file you saved in the previous step, `` with the UFVK hex string printed previously, and `` with the path where you want to write the signed transaction (e.g. `tx-signed.raw`). ``` -cargo run --release -- sign --tx-plan --ufvk -o +zcash-sign sign --tx-plan --ufvk -o ``` -The program will print a SIGHASH and a Randomizer. - - -### Running the server - -Now you will need to simulate two participants and a Coordinator to sign the -transaction, and the FROST server that handles communications between them. -It's probably easier to open four terminals. +The program will print a SIGHASH and a Randomizer, and will prompt for a +signature. This is what you will get after running FROST, so let's do that; +leave the prompt there without typing anything. -In the first one, the server, run (in the same folder where key generation was -run): -``` -RUST_LOG=debug cargo run --bin server -``` - -### Registering users +### Coordinator -In order to interact with the server, you will need to register users. For this -guide we will need two. In a new terminal, run the following command for user -"alice" (replace the password if you want): +In the second terminal, the Coordinator, run (in the same folder where you +initialized the users and ran the key generation) the following: ``` -curl --data-binary '{"username": "alice", "password": "foobar10", "pubkey": ""}' -H 'Content-Type: application/json' http://127.0.0.1:2744/register +frost-client groups -c alice.toml ``` -It will output "null". (The "pubkey" parameter is not used currently and should -be empty.) Also register user "bob": +This will list the groups Alice is in; it should only list the one you created +earlier. You will need to copy some values in the command. Run the following, +replacing the value after `` with the "Public key" listed for the group; +replacing `` and `` with the public keys of Alice and Bob (the +hexadecimal values printed next to their names; Alice's name will be empty to +indicate it's her own). ``` -curl --data-binary '{"username": "bob", "password": "foobar10", "pubkey": ""}' -H 'Content-Type: application/json' http://127.0.0.1:2744/register +frost-client coordinator -c alice.toml --server-url localhost:2744 --group -S , -m - -r - ``` -You only need to do this once, even if you want to sign more than one -transaction. If for some reason you want to start over, close the server and -delete the `db.sqlite` file. +It will prompt you for a message. Paste the SIGHASH generated with the +`zcash-sign` tool and press enter. It will then prompt for a randomizer. Paste +the one generated with the `zcash-sign` tool and press enter. -Feel free to close this terminal, or reuse it for the next step. +The tool will connect to the server and wait for the other participants. ```admonish warning -Do not use passwords that you use in practice; use dummy ones instead. (You -shouldn't reuse passwords anyway!) For real world usage you would need to take -more care to not end up writing the password to your shell history. (In real -world usage we'd expect this to be done by applications anyway.) +If you prefer to pass the message (SIGHASH) or randomizer as files by using +the `-m` and `-r` arguments, you will need to convert them to binary format. ``` -### Coordinator -In the second terminal, the Coordinator, run (in the same folder where key -generation was run): +### Participant 1 (Alice) + +In the third terminal, Participant 1, run the following (replacing `` +with the same group public key used in the previous command): ``` -export PW=foobar10 -cargo run --bin coordinator -- -C redpallas --http -u alice -w PW -S alice,bob -r - +frost-client participant -c alice.toml --server-url localhost:2744 --group ``` -We will use "alice" as the Coordinator, so change the value next to `export PW=` -if you used another password when registering "alice". - -And then: - -- It should read the public key package from `public-key-package.json`. -- When prompted for the message to be signed, paste the SIGHASH printed by the - signer above (just the hex value, e.g. - ``4d065453cfa4cfb4f98dbc9cff60c4a3904ed91c523b8ef8d67d28bea7f12ea3``). -- When prompted for the randomizer, paste the randomizer printed by the signer - above (again just the hex value) +(We are using "Alice" again. There's nothing stopping a Coordinator from being a +Partcipant too!) -```admonish warning -If you prefer to pass the randomizer as a file by using the `--randomizer` -argument, you will need to convert it to binary format. -``` -### Participant 1 (alice) +### Participant 2 (Bob) -In the third terminal, Participant 1, run the following: +In the fourth terminal, for Participant 2, run the following (replacing `` +again): ``` -export PW=foobar10 -cargo run --bin participant -- -C redpallas --http --key-package key-package-1.json -u alice -w PW +frost-client participant -c bob.toml --server-url localhost:2744 --group ``` -(We are using "alice" again. There's nothing stopping a Coordinator from being a -Participant too!) - -### Participant 2 (bob) - -In the fourth terminal, for Participant 2, run the following: - -``` -export PW=foobar10 -cargo run --bin participant -- -C redpallas --http --key-package key-package-2.json -u bob -w PW -``` ### Coordinator @@ -218,10 +318,12 @@ successfully. It will print the final FROST-generated signature. Hurrah! Copy it Go back to the signer and paste the signature. It will write the raw signed transaction to the file you specified. + ## Broadcasting the transaction -Go back to Ywallet and return to its main screen. In the menu, select "Advanced" -and "Broadcast". Select the raw signed transaction file you have just generated -(`tx-signed.raw` if you followed the suggestion). +Go back to Ywallet and return to its main screen. In the menu, select "More" and +"Broadcast". Click the upper-right box-with-an-arrow icon and select the raw +signed transaction file you have just generated (`tx-signed.raw` if you followed +the suggestion). That's it! You just sent a FROST-signed Zcash transaction. From 4d3d8822f894890e11d51b5ae4aed55cd3e50394 Mon Sep 17 00:00:00 2001 From: shigeyuki azuchi Date: Fri, 7 Feb 2025 23:09:51 +0900 Subject: [PATCH 097/175] Fix typo (#866) --- frost-secp256k1-tr/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index f566f72ea..eee93fd6b 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -600,7 +600,7 @@ pub mod keys { /// Trait for ensuring the group public key has an even Y coordinate. /// - /// In BIP-320, public keys are encoded with only the X coordinate, which + /// In BIP-340, public keys are encoded with only the X coordinate, which /// means that two Y coordinates are possible. The specification says that /// the coordinate which is even must be used. Alternatively, something /// equivalent can be accomplished by simply converting any existing From 543e6f446a663f46f9e5a0499833261d4f867ae9 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 11 Feb 2025 13:07:35 -0300 Subject: [PATCH 098/175] all: use OsRng instead of thread_rng() (#861) --- frost-ed25519/README.md | 3 +- frost-ed25519/benches/bench.rs | 5 +- frost-ed25519/dkg.md | 3 +- frost-ed25519/src/keys/repairable.rs | 8 +-- frost-ed25519/src/tests/batch.rs | 8 ++- .../src/tests/coefficient_commitment.rs | 7 ++- frost-ed25519/src/tests/vss_commitment.rs | 9 ++-- frost-ed25519/tests/common_traits_tests.rs | 3 +- frost-ed25519/tests/integration_tests.rs | 51 +++++++++---------- frost-ed25519/tests/interoperability_tests.rs | 13 ++--- frost-ed25519/tests/rerandomized_tests.rs | 3 +- frost-ed448/README.md | 3 +- frost-ed448/benches/bench.rs | 5 +- frost-ed448/dkg.md | 3 +- frost-ed448/src/keys/repairable.rs | 8 +-- frost-ed448/src/tests/batch.rs | 8 ++- .../src/tests/coefficient_commitment.rs | 7 ++- frost-ed448/src/tests/vss_commitment.rs | 9 ++-- frost-ed448/tests/common_traits_tests.rs | 3 +- frost-ed448/tests/integration_tests.rs | 51 +++++++++---------- frost-ed448/tests/rerandomized_tests.rs | 3 +- frost-p256/README.md | 3 +- frost-p256/benches/bench.rs | 5 +- frost-p256/dkg.md | 3 +- frost-p256/src/keys/repairable.rs | 8 +-- frost-p256/src/tests/batch.rs | 8 ++- .../src/tests/coefficient_commitment.rs | 7 ++- frost-p256/src/tests/vss_commitment.rs | 9 ++-- frost-p256/tests/common_traits_tests.rs | 3 +- frost-p256/tests/integration_tests.rs | 51 +++++++++---------- frost-p256/tests/rerandomized_tests.rs | 3 +- frost-ristretto255/README.md | 3 +- frost-ristretto255/benches/bench.rs | 5 +- frost-ristretto255/dkg.md | 3 +- frost-ristretto255/src/keys/repairable.rs | 8 +-- frost-ristretto255/src/tests/batch.rs | 8 ++- .../src/tests/coefficient_commitment.rs | 7 ++- .../src/tests/vss_commitment.rs | 9 ++-- .../tests/common_traits_tests.rs | 3 +- frost-ristretto255/tests/integration_tests.rs | 51 +++++++++---------- .../tests/rerandomized_tests.rs | 3 +- frost-secp256k1-tr/README.md | 3 +- frost-secp256k1-tr/benches/bench.rs | 5 +- frost-secp256k1-tr/dkg.md | 3 +- frost-secp256k1-tr/src/keys/repairable.rs | 8 +-- frost-secp256k1-tr/src/tests/batch.rs | 8 ++- .../src/tests/coefficient_commitment.rs | 7 ++- .../src/tests/vss_commitment.rs | 9 ++-- .../tests/common_traits_tests.rs | 3 +- frost-secp256k1-tr/tests/integration_tests.rs | 51 +++++++++---------- .../tests/interoperability_tests.rs | 13 +++-- .../tests/rerandomized_tests.rs | 3 +- frost-secp256k1-tr/tests/tweaking_tests.rs | 6 +-- frost-secp256k1/README.md | 3 +- frost-secp256k1/benches/bench.rs | 5 +- frost-secp256k1/dkg.md | 3 +- frost-secp256k1/src/keys/repairable.rs | 8 +-- frost-secp256k1/src/tests/batch.rs | 8 ++- .../src/tests/coefficient_commitment.rs | 7 ++- frost-secp256k1/src/tests/vss_commitment.rs | 9 ++-- frost-secp256k1/tests/common_traits_tests.rs | 3 +- frost-secp256k1/tests/integration_tests.rs | 51 +++++++++---------- frost-secp256k1/tests/rerandomized_tests.rs | 3 +- 63 files changed, 283 insertions(+), 349 deletions(-) diff --git a/frost-ed25519/README.md b/frost-ed25519/README.md index f9f267c73..0b9a2c9a6 100644 --- a/frost-ed25519/README.md +++ b/frost-ed25519/README.md @@ -11,10 +11,9 @@ scenario in a single thread and it abstracts away any communication between peer ```rust # // ANCHOR: tkg_gen use frost_ed25519 as frost; -use rand::thread_rng; use std::collections::BTreeMap; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-ed25519/benches/bench.rs b/frost-ed25519/benches/bench.rs index 04cfbfb1a..4317e05b1 100644 --- a/frost-ed25519/benches/bench.rs +++ b/frost-ed25519/benches/bench.rs @@ -1,16 +1,15 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use rand::thread_rng; use frost_ed25519::*; fn bench_ed25519_batch_verify(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_batch_verify::(c, "ed25519", &mut rng); } fn bench_ed25519_sign(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_sign::(c, "ed25519", &mut rng); } diff --git a/frost-ed25519/dkg.md b/frost-ed25519/dkg.md index 19e243398..de3f6aef0 100644 --- a/frost-ed25519/dkg.md +++ b/frost-ed25519/dkg.md @@ -26,12 +26,11 @@ they can proceed to sign messages with FROST. ```rust # // ANCHOR: dkg_import -use rand::thread_rng; use std::collections::BTreeMap; use frost_ed25519 as frost; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; diff --git a/frost-ed25519/src/keys/repairable.rs b/frost-ed25519/src/keys/repairable.rs index 5e875a760..f0df91b75 100644 --- a/frost-ed25519/src/keys/repairable.rs +++ b/frost-ed25519/src/keys/repairable.rs @@ -58,7 +58,7 @@ pub fn repair_share_step_3( mod tests { use lazy_static::lazy_static; - use rand::thread_rng; + use serde_json::Value; use crate::Ed25519Sha512; @@ -71,7 +71,7 @@ mod tests { #[test] fn check_repair_share_step_1() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1::(rng); } @@ -83,7 +83,7 @@ mod tests { #[test] fn check_repair_share_step_3() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_3::( rng, &REPAIR_SHARE, @@ -92,7 +92,7 @@ mod tests { #[test] fn check_repair_share_step_1_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< Ed25519Sha512, _, diff --git a/frost-ed25519/src/tests/batch.rs b/frost-ed25519/src/tests/batch.rs index 26497935c..a57301f01 100644 --- a/frost-ed25519/src/tests/batch.rs +++ b/frost-ed25519/src/tests/batch.rs @@ -1,24 +1,22 @@ -use rand::thread_rng; - use crate::*; #[test] fn check_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-ed25519/src/tests/coefficient_commitment.rs b/frost-ed25519/src/tests/coefficient_commitment.rs index 113eb1c7c..d45994e83 100644 --- a/frost-ed25519/src/tests/coefficient_commitment.rs +++ b/frost-ed25519/src/tests/coefficient_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,7 +12,7 @@ lazy_static! { #[test] fn check_serialization_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Ed25519Sha512, _, @@ -22,7 +21,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Ed25519Sha512, _, @@ -37,7 +36,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Ed25519Sha512, diff --git a/frost-ed25519/src/tests/vss_commitment.rs b/frost-ed25519/src/tests/vss_commitment.rs index 2bd1bae10..c1c128fbf 100644 --- a/frost-ed25519/src/tests/vss_commitment.rs +++ b/frost-ed25519/src/tests/vss_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,19 +12,19 @@ lazy_static! { #[test] fn check_serialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment_error() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -33,6 +32,6 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); } diff --git a/frost-ed25519/tests/common_traits_tests.rs b/frost-ed25519/tests/common_traits_tests.rs index 933630274..db173929b 100644 --- a/frost-ed25519/tests/common_traits_tests.rs +++ b/frost-ed25519/tests/common_traits_tests.rs @@ -4,7 +4,6 @@ mod helpers; use frost_ed25519::SigningKey; use helpers::samples; -use rand::thread_rng; #[allow(clippy::unnecessary_literal_unwrap)] fn check_common_traits_for_type(v: T) { @@ -20,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index c8c27fc2a..d7b5d16bf 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -1,6 +1,5 @@ use frost_ed25519::*; use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; #[test] @@ -10,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -31,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -45,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -59,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( rng, @@ -82,7 +81,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Ed25519Sha512, @@ -92,7 +91,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -111,7 +110,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -130,7 +129,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s #[test] fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -149,7 +148,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; let min_signers = 3; let max_signers = 1; @@ -163,7 +162,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -182,21 +181,21 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -210,7 +209,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -224,7 +223,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -240,13 +239,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_ed25519_sha512() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 0; let max_signers = 3; @@ -260,7 +259,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -274,7 +273,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 0; @@ -338,7 +337,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ed25519Sha512, @@ -348,7 +347,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( rng, ); @@ -356,7 +355,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::( rng, ); diff --git a/frost-ed25519/tests/interoperability_tests.rs b/frost-ed25519/tests/interoperability_tests.rs index 9c27193fb..e758ee248 100644 --- a/frost-ed25519/tests/interoperability_tests.rs +++ b/frost-ed25519/tests/interoperability_tests.rs @@ -1,21 +1,18 @@ use crate::Ed25519Sha512; use frost_ed25519::*; -use rand::thread_rng; mod helpers; #[test] fn check_interoperability_in_sign_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. A smaller number of iterations is used // because DKG takes longer and otherwise the test would be too slow. for _ in 0..32 { let (msg, group_signature, group_pubkey) = - frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng.clone(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); helpers::verify_signature(&msg, group_signature, group_pubkey); } @@ -23,15 +20,13 @@ fn check_interoperability_in_sign_with_dkg() { #[test] fn check_interoperability_in_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. for _ in 0..256 { let (msg, group_signature, group_pubkey) = - frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng.clone(), - ); + frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); // Check that the threshold signature can be verified by the `ed25519_dalek` crate // public key (interoperability test) diff --git a/frost-ed25519/tests/rerandomized_tests.rs b/frost-ed25519/tests/rerandomized_tests.rs index e6981bdd9..121738744 100644 --- a/frost-ed25519/tests/rerandomized_tests.rs +++ b/frost-ed25519/tests/rerandomized_tests.rs @@ -1,9 +1,8 @@ use frost_ed25519::Ed25519Sha512; -use rand::thread_rng; #[test] fn check_randomized_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-ed448/README.md b/frost-ed448/README.md index 7cf30ff1e..34d4e537b 100644 --- a/frost-ed448/README.md +++ b/frost-ed448/README.md @@ -11,10 +11,9 @@ scenario in a single thread and it abstracts away any communication between peer ```rust # // ANCHOR: tkg_gen use frost_ed448 as frost; -use rand::thread_rng; use std::collections::BTreeMap; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-ed448/benches/bench.rs b/frost-ed448/benches/bench.rs index 343f72f25..ba40feb33 100644 --- a/frost-ed448/benches/bench.rs +++ b/frost-ed448/benches/bench.rs @@ -1,18 +1,17 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use rand::thread_rng; use frost_ed448::*; // bench_ed448_batch_verify not included until batch verification is fixed for Ed448 #[allow(unused)] fn bench_ed448_batch_verify(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_batch_verify::(c, "ed448", &mut rng); } fn bench_ed448_sign(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_sign::(c, "ed448", &mut rng); } diff --git a/frost-ed448/dkg.md b/frost-ed448/dkg.md index 6471d7647..039937846 100644 --- a/frost-ed448/dkg.md +++ b/frost-ed448/dkg.md @@ -26,12 +26,11 @@ they can proceed to sign messages with FROST. ```rust # // ANCHOR: dkg_import -use rand::thread_rng; use std::collections::BTreeMap; use frost_ed448 as frost; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; diff --git a/frost-ed448/src/keys/repairable.rs b/frost-ed448/src/keys/repairable.rs index 97771f3c5..7bbdecaff 100644 --- a/frost-ed448/src/keys/repairable.rs +++ b/frost-ed448/src/keys/repairable.rs @@ -58,7 +58,7 @@ pub fn repair_share_step_3( mod tests { use lazy_static::lazy_static; - use rand::thread_rng; + use serde_json::Value; use crate::Ed448Shake256; @@ -71,7 +71,7 @@ mod tests { #[test] fn check_repair_share_step_1() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1::(rng); } @@ -83,7 +83,7 @@ mod tests { #[test] fn check_repair_share_step_3() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_3::( rng, &REPAIR_SHARE, @@ -92,7 +92,7 @@ mod tests { #[test] fn check_repair_share_step_1_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< Ed448Shake256, _, diff --git a/frost-ed448/src/tests/batch.rs b/frost-ed448/src/tests/batch.rs index 85b6b1a6c..5c84b5e50 100644 --- a/frost-ed448/src/tests/batch.rs +++ b/frost-ed448/src/tests/batch.rs @@ -1,24 +1,22 @@ -use rand::thread_rng; - use crate::*; #[test] fn check_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-ed448/src/tests/coefficient_commitment.rs b/frost-ed448/src/tests/coefficient_commitment.rs index d088ad15b..0d2091aa6 100644 --- a/frost-ed448/src/tests/coefficient_commitment.rs +++ b/frost-ed448/src/tests/coefficient_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,7 +12,7 @@ lazy_static! { #[test] fn check_serialization_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Ed448Shake256, _, @@ -22,7 +21,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Ed448Shake256, _, @@ -37,7 +36,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Ed448Shake256, diff --git a/frost-ed448/src/tests/vss_commitment.rs b/frost-ed448/src/tests/vss_commitment.rs index 98810b382..c5beea6a2 100644 --- a/frost-ed448/src/tests/vss_commitment.rs +++ b/frost-ed448/src/tests/vss_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,19 +12,19 @@ lazy_static! { #[test] fn check_serialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment_error() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -33,6 +32,6 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); } diff --git a/frost-ed448/tests/common_traits_tests.rs b/frost-ed448/tests/common_traits_tests.rs index 44f389ad5..749cc7033 100644 --- a/frost-ed448/tests/common_traits_tests.rs +++ b/frost-ed448/tests/common_traits_tests.rs @@ -4,7 +4,6 @@ mod helpers; use frost_ed448::SigningKey; use helpers::samples; -use rand::thread_rng; #[allow(clippy::unnecessary_literal_unwrap)] fn check_common_traits_for_type(v: T) { @@ -20,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index b96d83287..872051aa1 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -1,6 +1,5 @@ use frost_ed448::*; use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; #[test] @@ -10,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -31,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -45,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -59,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( rng, @@ -82,7 +81,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Ed448Shake256, @@ -92,7 +91,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -111,7 +110,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -130,7 +129,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s #[test] fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -149,7 +148,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; let min_signers = 3; let max_signers = 1; @@ -163,7 +162,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -182,21 +181,21 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -210,7 +209,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -224,7 +223,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -240,13 +239,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_ed448_shake256() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 0; let max_signers = 3; @@ -260,7 +259,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -274,7 +273,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 0; @@ -338,7 +337,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ed448Shake256, @@ -348,7 +347,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( rng, ); @@ -356,7 +355,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::( rng, ); diff --git a/frost-ed448/tests/rerandomized_tests.rs b/frost-ed448/tests/rerandomized_tests.rs index 16e31f23a..e16d906ce 100644 --- a/frost-ed448/tests/rerandomized_tests.rs +++ b/frost-ed448/tests/rerandomized_tests.rs @@ -1,9 +1,8 @@ use frost_ed448::Ed448Shake256; -use rand::thread_rng; #[test] fn check_randomized_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-p256/README.md b/frost-p256/README.md index df0cc4a0e..ce0aa3d5c 100644 --- a/frost-p256/README.md +++ b/frost-p256/README.md @@ -11,10 +11,9 @@ scenario in a single thread and it abstracts away any communication between peer ```rust # // ANCHOR: tkg_gen use frost_p256 as frost; -use rand::thread_rng; use std::collections::BTreeMap; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-p256/benches/bench.rs b/frost-p256/benches/bench.rs index 1a4d835a4..8ae524f5c 100644 --- a/frost-p256/benches/bench.rs +++ b/frost-p256/benches/bench.rs @@ -1,16 +1,15 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use rand::thread_rng; use frost_p256::*; fn bench_p256_batch_verify(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_batch_verify::(c, "p256", &mut rng); } fn bench_p256_sign(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_sign::(c, "p256", &mut rng); } diff --git a/frost-p256/dkg.md b/frost-p256/dkg.md index f3dbb237b..afb4bd44a 100644 --- a/frost-p256/dkg.md +++ b/frost-p256/dkg.md @@ -26,12 +26,11 @@ they can proceed to sign messages with FROST. ```rust # // ANCHOR: dkg_import -use rand::thread_rng; use std::collections::BTreeMap; use frost_p256 as frost; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; diff --git a/frost-p256/src/keys/repairable.rs b/frost-p256/src/keys/repairable.rs index 233757456..484a9fcd6 100644 --- a/frost-p256/src/keys/repairable.rs +++ b/frost-p256/src/keys/repairable.rs @@ -58,7 +58,7 @@ pub fn repair_share_step_3( mod tests { use lazy_static::lazy_static; - use rand::thread_rng; + use serde_json::Value; use crate::P256Sha256; @@ -71,7 +71,7 @@ mod tests { #[test] fn check_repair_share_step_1() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1::(rng); } @@ -83,7 +83,7 @@ mod tests { #[test] fn check_repair_share_step_3() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_3::( rng, &REPAIR_SHARE, @@ -92,7 +92,7 @@ mod tests { #[test] fn check_repair_share_step_1_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< P256Sha256, _, diff --git a/frost-p256/src/tests/batch.rs b/frost-p256/src/tests/batch.rs index 1d4cff1a7..3a46bfdd3 100644 --- a/frost-p256/src/tests/batch.rs +++ b/frost-p256/src/tests/batch.rs @@ -1,24 +1,22 @@ -use rand::thread_rng; - use crate::*; #[test] fn check_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-p256/src/tests/coefficient_commitment.rs b/frost-p256/src/tests/coefficient_commitment.rs index 8083aea08..e52f8398f 100644 --- a/frost-p256/src/tests/coefficient_commitment.rs +++ b/frost-p256/src/tests/coefficient_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,7 +12,7 @@ lazy_static! { #[test] fn check_serialization_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< P256Sha256, _, @@ -22,7 +21,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::( rng, ); @@ -36,7 +35,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< P256Sha256, diff --git a/frost-p256/src/tests/vss_commitment.rs b/frost-p256/src/tests/vss_commitment.rs index a30d3f62f..24457911c 100644 --- a/frost-p256/src/tests/vss_commitment.rs +++ b/frost-p256/src/tests/vss_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,19 +12,19 @@ lazy_static! { #[test] fn check_serialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment_error() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -33,6 +32,6 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); } diff --git a/frost-p256/tests/common_traits_tests.rs b/frost-p256/tests/common_traits_tests.rs index 16f52d5b7..8dc2d739d 100644 --- a/frost-p256/tests/common_traits_tests.rs +++ b/frost-p256/tests/common_traits_tests.rs @@ -4,7 +4,6 @@ mod helpers; use frost_p256::SigningKey; use helpers::samples; -use rand::thread_rng; #[allow(clippy::unnecessary_literal_unwrap)] fn check_common_traits_for_type(v: T) { @@ -20,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index acd3f81e9..33e4c2048 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -1,6 +1,5 @@ use frost_p256::*; use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; #[test] @@ -10,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -31,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -45,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -59,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( rng, @@ -82,7 +81,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< P256Sha256, @@ -92,7 +91,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -111,7 +110,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -130,7 +129,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s #[test] fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -149,7 +148,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; let min_signers = 3; let max_signers = 1; @@ -163,7 +162,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -182,21 +181,21 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -210,7 +209,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -224,7 +223,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -240,13 +239,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_p256_sha256() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 0; let max_signers = 3; @@ -260,7 +259,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -274,7 +273,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 0; @@ -336,7 +335,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::( rng, @@ -345,7 +344,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( rng, ); @@ -353,7 +352,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::( rng, ); diff --git a/frost-p256/tests/rerandomized_tests.rs b/frost-p256/tests/rerandomized_tests.rs index d16a0c304..6dc482ce6 100644 --- a/frost-p256/tests/rerandomized_tests.rs +++ b/frost-p256/tests/rerandomized_tests.rs @@ -1,9 +1,8 @@ use frost_p256::P256Sha256; -use rand::thread_rng; #[test] fn check_randomized_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index a2d284254..4ba16fe16 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -11,10 +11,9 @@ scenario in a single thread and it abstracts away any communication between peer ```rust # // ANCHOR: tkg_gen use frost_ristretto255 as frost; -use rand::thread_rng; use std::collections::BTreeMap; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-ristretto255/benches/bench.rs b/frost-ristretto255/benches/bench.rs index b53e560ce..b7e9af3bd 100644 --- a/frost-ristretto255/benches/bench.rs +++ b/frost-ristretto255/benches/bench.rs @@ -1,16 +1,15 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use rand::thread_rng; use frost_ristretto255::*; fn bench_ristretto255_batch_verify(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_batch_verify::(c, "ristretto255", &mut rng); } fn bench_ristretto255_sign(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_sign::(c, "ristretto255", &mut rng); } diff --git a/frost-ristretto255/dkg.md b/frost-ristretto255/dkg.md index 18954d449..86995d721 100644 --- a/frost-ristretto255/dkg.md +++ b/frost-ristretto255/dkg.md @@ -26,12 +26,11 @@ they can proceed to sign messages with FROST. ```rust # // ANCHOR: dkg_import -use rand::thread_rng; use std::collections::BTreeMap; use frost_ristretto255 as frost; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; diff --git a/frost-ristretto255/src/keys/repairable.rs b/frost-ristretto255/src/keys/repairable.rs index a935eb8a7..e3d20a79e 100644 --- a/frost-ristretto255/src/keys/repairable.rs +++ b/frost-ristretto255/src/keys/repairable.rs @@ -58,7 +58,7 @@ pub fn repair_share_step_3( mod tests { use lazy_static::lazy_static; - use rand::thread_rng; + use serde_json::Value; use crate::Ristretto255Sha512; @@ -71,7 +71,7 @@ mod tests { #[test] fn check_repair_share_step_1() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1::(rng); } @@ -85,7 +85,7 @@ mod tests { #[test] fn check_repair_share_step_3() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_3::( rng, &REPAIR_SHARE, @@ -94,7 +94,7 @@ mod tests { #[test] fn check_repair_share_step_1_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< Ristretto255Sha512, _, diff --git a/frost-ristretto255/src/tests/batch.rs b/frost-ristretto255/src/tests/batch.rs index 9c08b77a7..b26d033cf 100644 --- a/frost-ristretto255/src/tests/batch.rs +++ b/frost-ristretto255/src/tests/batch.rs @@ -1,24 +1,22 @@ -use rand::thread_rng; - use crate::*; #[test] fn check_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-ristretto255/src/tests/coefficient_commitment.rs b/frost-ristretto255/src/tests/coefficient_commitment.rs index e5df3516c..a031d4c46 100644 --- a/frost-ristretto255/src/tests/coefficient_commitment.rs +++ b/frost-ristretto255/src/tests/coefficient_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,7 +12,7 @@ lazy_static! { #[test] fn check_serialization_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Ristretto255Sha512, _, @@ -22,7 +21,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Ristretto255Sha512, _, @@ -37,7 +36,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Ristretto255Sha512, diff --git a/frost-ristretto255/src/tests/vss_commitment.rs b/frost-ristretto255/src/tests/vss_commitment.rs index d7acaaa9a..66c6d37a8 100644 --- a/frost-ristretto255/src/tests/vss_commitment.rs +++ b/frost-ristretto255/src/tests/vss_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,13 +12,13 @@ lazy_static! { #[test] fn check_serialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::( rng, ); @@ -27,7 +26,7 @@ fn check_deserialize_vss_commitment() { #[test] fn check_deserialize_vss_commitment_error() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::< Ristretto255Sha512, _, @@ -36,7 +35,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_compute_public_key_package::( rng, ); diff --git a/frost-ristretto255/tests/common_traits_tests.rs b/frost-ristretto255/tests/common_traits_tests.rs index a985d0ad8..8d1fcf2b1 100644 --- a/frost-ristretto255/tests/common_traits_tests.rs +++ b/frost-ristretto255/tests/common_traits_tests.rs @@ -4,7 +4,6 @@ mod helpers; use frost_ristretto255::SigningKey; use helpers::samples; -use rand::thread_rng; #[allow(clippy::unnecessary_literal_unwrap)] fn check_common_traits_for_type(v: T) { @@ -20,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index 743620271..93a5574ee 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -1,6 +1,5 @@ use frost_ristretto255::*; use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; #[test] @@ -10,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -31,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -45,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -59,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::< Ristretto255Sha512, @@ -83,7 +82,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Ristretto255Sha512, @@ -93,7 +92,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -112,7 +111,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -131,7 +130,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s #[test] fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -150,7 +149,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; let min_signers = 3; let max_signers = 1; @@ -164,7 +163,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -183,21 +182,21 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -211,7 +210,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -225,7 +224,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -241,13 +240,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_ristretto255_sha512() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 0; let max_signers = 3; @@ -261,7 +260,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -275,7 +274,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 0; @@ -339,7 +338,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ristretto255Sha512, @@ -349,7 +348,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::< Ristretto255Sha512, _, @@ -358,7 +357,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< Ristretto255Sha512, _, diff --git a/frost-ristretto255/tests/rerandomized_tests.rs b/frost-ristretto255/tests/rerandomized_tests.rs index a7a884c74..23277d0a9 100644 --- a/frost-ristretto255/tests/rerandomized_tests.rs +++ b/frost-ristretto255/tests/rerandomized_tests.rs @@ -1,9 +1,8 @@ use frost_ristretto255::Ristretto255Sha512; -use rand::thread_rng; #[test] fn check_randomized_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-secp256k1-tr/README.md b/frost-secp256k1-tr/README.md index f4d2205f1..26e287012 100644 --- a/frost-secp256k1-tr/README.md +++ b/frost-secp256k1-tr/README.md @@ -11,10 +11,9 @@ scenario in a single thread and it abstracts away any communication between peer ```rust # // ANCHOR: tkg_gen use frost_secp256k1_tr as frost; -use rand::thread_rng; use std::collections::BTreeMap; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-secp256k1-tr/benches/bench.rs b/frost-secp256k1-tr/benches/bench.rs index e9097bdd3..d2ce56f2a 100644 --- a/frost-secp256k1-tr/benches/bench.rs +++ b/frost-secp256k1-tr/benches/bench.rs @@ -1,16 +1,15 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use rand::thread_rng; use frost_secp256k1_tr::*; fn bench_secp256k1_batch_verify(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); } fn bench_secp256k1_sign(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); } diff --git a/frost-secp256k1-tr/dkg.md b/frost-secp256k1-tr/dkg.md index 9643d37b4..31a96acf8 100644 --- a/frost-secp256k1-tr/dkg.md +++ b/frost-secp256k1-tr/dkg.md @@ -26,12 +26,11 @@ they can proceed to sign messages with FROST. ```rust # // ANCHOR: dkg_import -use rand::thread_rng; use std::collections::BTreeMap; use frost_secp256k1_tr as frost; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; diff --git a/frost-secp256k1-tr/src/keys/repairable.rs b/frost-secp256k1-tr/src/keys/repairable.rs index 9b538030b..44543354c 100644 --- a/frost-secp256k1-tr/src/keys/repairable.rs +++ b/frost-secp256k1-tr/src/keys/repairable.rs @@ -58,7 +58,7 @@ pub fn repair_share_step_3( mod tests { use lazy_static::lazy_static; - use rand::thread_rng; + use serde_json::Value; use crate::Secp256K1Sha256TR; @@ -71,7 +71,7 @@ mod tests { #[test] fn check_repair_share_step_1() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1::(rng); } @@ -85,7 +85,7 @@ mod tests { #[test] fn check_repair_share_step_3() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_3::( rng, &REPAIR_SHARE, @@ -94,7 +94,7 @@ mod tests { #[test] fn check_repair_share_step_1_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< Secp256K1Sha256TR, _, diff --git a/frost-secp256k1-tr/src/tests/batch.rs b/frost-secp256k1-tr/src/tests/batch.rs index f88793a35..d22efdf5d 100644 --- a/frost-secp256k1-tr/src/tests/batch.rs +++ b/frost-secp256k1-tr/src/tests/batch.rs @@ -1,24 +1,22 @@ -use rand::thread_rng; - use crate::*; #[test] fn check_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-secp256k1-tr/src/tests/coefficient_commitment.rs b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs index a63259c2b..71706ea62 100644 --- a/frost-secp256k1-tr/src/tests/coefficient_commitment.rs +++ b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,7 +12,7 @@ lazy_static! { #[test] fn check_serialization_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Secp256K1Sha256TR, _, @@ -22,7 +21,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Secp256K1Sha256TR, _, @@ -37,7 +36,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Secp256K1Sha256TR, diff --git a/frost-secp256k1-tr/src/tests/vss_commitment.rs b/frost-secp256k1-tr/src/tests/vss_commitment.rs index 80fb1ca7b..0a0906853 100644 --- a/frost-secp256k1-tr/src/tests/vss_commitment.rs +++ b/frost-secp256k1-tr/src/tests/vss_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,13 +12,13 @@ lazy_static! { #[test] fn check_serialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::( rng, ); @@ -27,7 +26,7 @@ fn check_deserialize_vss_commitment() { #[test] fn check_deserialize_vss_commitment_error() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -35,7 +34,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_compute_public_key_package::( rng, ); diff --git a/frost-secp256k1-tr/tests/common_traits_tests.rs b/frost-secp256k1-tr/tests/common_traits_tests.rs index 81b97a951..93265b7a8 100644 --- a/frost-secp256k1-tr/tests/common_traits_tests.rs +++ b/frost-secp256k1-tr/tests/common_traits_tests.rs @@ -4,7 +4,6 @@ mod helpers; use frost_secp256k1_tr::SigningKey; use helpers::samples; -use rand::thread_rng; #[allow(clippy::unnecessary_literal_unwrap)] fn check_common_traits_for_type(v: T) { @@ -20,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs index 176aad67c..2d5db0201 100644 --- a/frost-secp256k1-tr/tests/integration_tests.rs +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -1,6 +1,5 @@ use frost_secp256k1_tr::*; use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; #[test] @@ -10,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -31,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -45,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -59,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::< Secp256K1Sha256TR, @@ -83,7 +82,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Secp256K1Sha256TR, @@ -93,7 +92,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -112,7 +111,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -131,7 +130,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s #[test] fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -150,7 +149,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; let min_signers = 3; let max_signers = 1; @@ -164,7 +163,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -183,21 +182,21 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -211,7 +210,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -225,7 +224,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -241,13 +240,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_secp256k1_tr_sha256() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 0; let max_signers = 3; @@ -261,7 +260,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -275,7 +274,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 0; @@ -339,7 +338,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Secp256K1Sha256TR, @@ -349,7 +348,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::< Secp256K1Sha256TR, _, @@ -358,7 +357,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< Secp256K1Sha256TR, _, diff --git a/frost-secp256k1-tr/tests/interoperability_tests.rs b/frost-secp256k1-tr/tests/interoperability_tests.rs index b2e3f9a0e..5be7f623d 100644 --- a/frost-secp256k1-tr/tests/interoperability_tests.rs +++ b/frost-secp256k1-tr/tests/interoperability_tests.rs @@ -1,25 +1,24 @@ use frost_secp256k1_tr::*; use crate::Secp256K1Sha256TR; -use rand::thread_rng; mod helpers; #[test] fn check_interoperability_in_regular_sign() { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; for _ in 0..256 { let signing_key = SigningKey::new(&mut rng); let verifying_key = signing_key.into(); - let signature = signing_key.sign(&mut rng, b"message"); + let signature = signing_key.sign(rng, b"message"); helpers::verify_signature(b"message", &signature, &verifying_key); } } #[test] fn check_interoperability_in_sign_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. A smaller number of iterations is used @@ -27,7 +26,7 @@ fn check_interoperability_in_sign_with_dkg() { for _ in 0..32 { let (message, group_signature, group_pubkey) = frost_core::tests::ciphersuite_generic::check_sign_with_dkg::( - rng.clone(), + rng, ); helpers::verify_signature(&message, &group_signature, &group_pubkey); @@ -36,14 +35,14 @@ fn check_interoperability_in_sign_with_dkg() { #[test] fn check_interoperability_in_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. for _ in 0..256 { let (message, group_signature, group_pubkey) = frost_core::tests::ciphersuite_generic::check_sign_with_dealer::( - rng.clone(), + rng, ); // Check that the threshold signature can be verified by the `ed25519_dalek` crate diff --git a/frost-secp256k1-tr/tests/rerandomized_tests.rs b/frost-secp256k1-tr/tests/rerandomized_tests.rs index 67e143138..7b4144c9b 100644 --- a/frost-secp256k1-tr/tests/rerandomized_tests.rs +++ b/frost-secp256k1-tr/tests/rerandomized_tests.rs @@ -1,9 +1,8 @@ use frost_secp256k1_tr::Secp256K1Sha256TR; -use rand::thread_rng; #[test] fn check_randomized_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-secp256k1-tr/tests/tweaking_tests.rs b/frost-secp256k1-tr/tests/tweaking_tests.rs index 3fc74aefb..dddbed12b 100644 --- a/frost-secp256k1-tr/tests/tweaking_tests.rs +++ b/frost-secp256k1-tr/tests/tweaking_tests.rs @@ -12,19 +12,19 @@ mod helpers; #[test] fn check_tweaked_sign_with_dealer() -> Result<(), Box> { use frost_secp256k1_tr as frost; - use rand::thread_rng; + use std::collections::BTreeMap; let merkle_root: Vec = vec![12; 32]; - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( max_signers, min_signers, frost::keys::IdentifierList::Default, - &mut rng, + rng, )?; let mut key_packages: BTreeMap<_, _> = BTreeMap::new(); for (identifier, secret_share) in shares { diff --git a/frost-secp256k1/README.md b/frost-secp256k1/README.md index 9e4928b3f..bdec1acb7 100644 --- a/frost-secp256k1/README.md +++ b/frost-secp256k1/README.md @@ -11,10 +11,9 @@ scenario in a single thread and it abstracts away any communication between peer ```rust # // ANCHOR: tkg_gen use frost_secp256k1 as frost; -use rand::thread_rng; use std::collections::BTreeMap; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-secp256k1/benches/bench.rs b/frost-secp256k1/benches/bench.rs index c5773633d..cd89e8e2d 100644 --- a/frost-secp256k1/benches/bench.rs +++ b/frost-secp256k1/benches/bench.rs @@ -1,16 +1,15 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use rand::thread_rng; use frost_secp256k1::*; fn bench_secp256k1_batch_verify(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); } fn bench_secp256k1_sign(c: &mut Criterion) { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); } diff --git a/frost-secp256k1/dkg.md b/frost-secp256k1/dkg.md index 5d62857c4..24c6a5352 100644 --- a/frost-secp256k1/dkg.md +++ b/frost-secp256k1/dkg.md @@ -26,12 +26,11 @@ they can proceed to sign messages with FROST. ```rust # // ANCHOR: dkg_import -use rand::thread_rng; use std::collections::BTreeMap; use frost_secp256k1 as frost; -let mut rng = thread_rng(); +let mut rng = rand::rngs::OsRng; let max_signers = 5; let min_signers = 3; diff --git a/frost-secp256k1/src/keys/repairable.rs b/frost-secp256k1/src/keys/repairable.rs index 88bce01dc..98a2e7c51 100644 --- a/frost-secp256k1/src/keys/repairable.rs +++ b/frost-secp256k1/src/keys/repairable.rs @@ -58,7 +58,7 @@ pub fn repair_share_step_3( mod tests { use lazy_static::lazy_static; - use rand::thread_rng; + use serde_json::Value; use crate::Secp256K1Sha256; @@ -71,7 +71,7 @@ mod tests { #[test] fn check_repair_share_step_1() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1::(rng); } @@ -83,7 +83,7 @@ mod tests { #[test] fn check_repair_share_step_3() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_3::( rng, &REPAIR_SHARE, @@ -92,7 +92,7 @@ mod tests { #[test] fn check_repair_share_step_1_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< Secp256K1Sha256, _, diff --git a/frost-secp256k1/src/tests/batch.rs b/frost-secp256k1/src/tests/batch.rs index b87d22a90..d3b1c6800 100644 --- a/frost-secp256k1/src/tests/batch.rs +++ b/frost-secp256k1/src/tests/batch.rs @@ -1,24 +1,22 @@ -use rand::thread_rng; - use crate::*; #[test] fn check_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-secp256k1/src/tests/coefficient_commitment.rs b/frost-secp256k1/src/tests/coefficient_commitment.rs index d1b6c22c7..7be35ead6 100644 --- a/frost-secp256k1/src/tests/coefficient_commitment.rs +++ b/frost-secp256k1/src/tests/coefficient_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,7 +12,7 @@ lazy_static! { #[test] fn check_serialization_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Secp256K1Sha256, _, @@ -22,7 +21,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Secp256K1Sha256, _, @@ -37,7 +36,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Secp256K1Sha256, diff --git a/frost-secp256k1/src/tests/vss_commitment.rs b/frost-secp256k1/src/tests/vss_commitment.rs index 1a09195aa..5574f5918 100644 --- a/frost-secp256k1/src/tests/vss_commitment.rs +++ b/frost-secp256k1/src/tests/vss_commitment.rs @@ -1,5 +1,4 @@ use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; use crate::*; @@ -13,19 +12,19 @@ lazy_static! { #[test] fn check_serialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment_error() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -33,6 +32,6 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); } diff --git a/frost-secp256k1/tests/common_traits_tests.rs b/frost-secp256k1/tests/common_traits_tests.rs index 6048b4806..e9f788eb1 100644 --- a/frost-secp256k1/tests/common_traits_tests.rs +++ b/frost-secp256k1/tests/common_traits_tests.rs @@ -4,7 +4,6 @@ mod helpers; use frost_secp256k1::SigningKey; use helpers::samples; -use rand::thread_rng; #[allow(clippy::unnecessary_literal_unwrap)] fn check_common_traits_for_type(v: T) { @@ -20,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = thread_rng(); + let mut rng = rand::rngs::OsRng; let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index d098f266f..0676f6181 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -1,6 +1,5 @@ use frost_secp256k1::*; use lazy_static::lazy_static; -use rand::thread_rng; use serde_json::Value; #[test] @@ -10,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -31,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -45,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -59,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( rng, @@ -82,7 +81,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Secp256K1Sha256, @@ -92,7 +91,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -111,7 +110,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -130,7 +129,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s #[test] fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(1).unwrap(), Identifier::try_from(3).unwrap(), @@ -149,7 +148,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; let min_signers = 3; let max_signers = 1; @@ -163,7 +162,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -182,21 +181,21 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 1; let max_signers = 3; @@ -210,7 +209,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -224,7 +223,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 1; @@ -240,13 +239,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_secp256k1_sha256() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 0; let max_signers = 3; @@ -260,7 +259,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 2; @@ -274,7 +273,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let min_signers = 3; let max_signers = 0; @@ -338,7 +337,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Secp256K1Sha256, @@ -348,7 +347,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( rng, ); @@ -356,7 +355,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< Secp256K1Sha256, _, diff --git a/frost-secp256k1/tests/rerandomized_tests.rs b/frost-secp256k1/tests/rerandomized_tests.rs index 65cf0dff5..c7845f170 100644 --- a/frost-secp256k1/tests/rerandomized_tests.rs +++ b/frost-secp256k1/tests/rerandomized_tests.rs @@ -1,9 +1,8 @@ use frost_secp256k1::Secp256K1Sha256; -use rand::thread_rng; #[test] fn check_randomized_sign_with_dealer() { - let rng = thread_rng(); + let rng = rand::rngs::OsRng; let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); From f862ef18f1147232df44f9e0c3f693b103f93b6a Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 11 Feb 2025 14:26:10 -0300 Subject: [PATCH 099/175] update release checklist; misc cleanups (#843) * update release checklist; misc cleanups * remove links to versions * add semver-checks; change numbered items to nonnumbered --- book/src/dev/release-checklist.md | 111 ++++++++++++++++-------------- frost-core/CHANGELOG.md | 4 +- frost-core/src/keys/refresh.rs | 6 +- 3 files changed, 63 insertions(+), 58 deletions(-) diff --git a/book/src/dev/release-checklist.md b/book/src/dev/release-checklist.md index d5893e589..5cfb4b44c 100644 --- a/book/src/dev/release-checklist.md +++ b/book/src/dev/release-checklist.md @@ -1,110 +1,115 @@ # Release Checklist +## One-time `gh` setup + +Install the [GitHub command line +tool](https://github.com/cli/cli?tab=readme-ov-file#installation) to make +releases easier. ## One-time crates.io setup -1. Follow the steps in (you can create a token scoped to `publish-update`). -2. To get permissions to publish you’ll need to be in the [owners](https://github.com/orgs/ZcashFoundation/teams/owners) group. If you aren’t in there, ask someone in that group to add you +- Follow the steps in (you can create a token scoped to `publish-update`). +- To get permissions to publish you’ll need to be in the [owners](https://github.com/orgs/ZcashFoundation/teams/owners) group. If you aren’t in there, ask someone in that group to add you ## Communication -3. Post in #frost slack channel and tag the Frost team that you’re going to be doing a release to freeze PR approvals until it’s done. E.g “@frost-core I’m doing a release of \ of Frost. Please do not merge any more PRs until I’m finished. Thanks.” +- Post in #frost slack channel and tag the Frost team that you’re going to be doing a release to freeze PR approvals until it’s done. E.g “@frost-core I’m doing a release of \ of Frost. Please do not merge any more PRs until I’m finished. Thanks.” ## Checks -4. Check current version for each crate. This is in the Cargo.toml file for frost-core, frost-ed448 etc. - - 1. [Frost core version number](https://github.com/ZcashFoundation/frost/blob/main/frost-core/Cargo.toml#L7) - 2. [Frost ed25519 version number](https://github.com/ZcashFoundation/frost/blob/main/frost-ed25519/Cargo.toml#L8) - 3. [Frost ed448 version number](https://github.com/ZcashFoundation/frost/blob/main/frost-ed448/Cargo.toml#L7) - 4. [Frost p256 version number](https://github.com/ZcashFoundation/frost/blob/main/frost-p256/Cargo.toml#L8) - 5. [Frost re randomized version number](https://github.com/ZcashFoundation/frost/blob/main/frost-rerandomized/Cargo.toml#L8) - 6. [Frost ristretto255 version number](https://github.com/ZcashFoundation/frost/blob/main/frost-ristretto255/Cargo.toml#L8) - 7. [Frost secp256k1 version number](https://github.com/ZcashFoundation/frost/blob/main/frost-secp256k1/Cargo.toml#L7) - 8. [Frost secp256k1 tr version number](https://github.com/ZcashFoundation/frost/blob/main/frost-secp256k1-tr/Cargo.toml#L7) +- Currently all crates share the same version number, in the root Cargo.toml + file. Take note of that version. (If we ever decide to have specific + versions, update those separately as required.) -5. Decide which version to tag the release with (e.g. v0.3.0). Currently we always use the same release number for all crates, but it's possible for them to get out of sync in the future. +- Decide which version to tag the release with (e.g. v0.3.0), considering + [SemVer](https://doc.rust-lang.org/cargo/reference/semver.html). -6. Create new issue. E.g. [Release v0.4.0](https://github.com/ZcashFoundation/frost/issues/377) +- Create new issue. E.g. [Release v0.4.0](https://github.com/ZcashFoundation/frost/issues/377) ## Make changes -7. Bump the version of the crates in the root Cargo.toml file. (If they ever +- Bump the version of the crates in the root Cargo.toml file. (If they ever get out of sync, you will need to bump in each crate Cargo.toml file.) -8. Bump the version used in the tutorial (importing.md) +- Run `cargo semver-checks` to check if there are no API changes that break + SemVer compatibility. ([Installation + instructions](https://crates.io/crates/cargo-semver-checks)) Fix issues if + any (i.e. change the version, or revert/adapt the API change). + +- Bump the version used in the tutorial (importing.md) -9. Check if the [changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md) is up to date and update if required (we’re only keeping the one in frost-core for now). Double check using [FROST releases](https://github.com/ZcashFoundation/frost/releases) which will have a list of all the PRs that have been closed since the last release. Things to include in the changelog will be anything that impacts production code and big documentation changes. I.e. script and test changes should not be included. NOTE: Please add to the changelog whenever you make changes to the library as this will make things simpler for the person in charge of the release. +- Check if the [changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md) is up to date and update if required (we’re only keeping the one in frost-core for now). Double check using [FROST releases](https://github.com/ZcashFoundation/frost/releases) which will have a list of all the PRs that have been closed since the last release. Things to include in the changelog will be anything that impacts production code and big documentation changes. I.e. script and test changes should not be included. NOTE: Please add to the changelog whenever you make changes to the library as this will make things simpler for the person in charge of the release. - 1. Move version in changelog to Released - 2. Create a new version in “unreleased” in changelog + - Move version in changelog to Released + - Create a new version in “unreleased” in changelog -10. Update the version number for frost-core and frost-rerandomized in the Ciphersuite crates, e.g. in `frost-core = { path = "../frost-core", version = "0.4.0", features = ["test-impl"] }`. You'll need to do this for dependencies and dev-dependencies +- Update the version number for frost-core and frost-rerandomized in the Ciphersuite crates, e.g. in `frost-core = { path = "../frost-core", version = "0.4.0", features = ["test-impl"] }`. You'll need to do this for dependencies and dev-dependencies -11. Create a PR with subject `Release \` containing all these changes +- Create a PR with subject `Release \` containing all these changes -12. You’ll need someone to review and approve it +- You’ll need someone to review and approve it -13. Wait for it to pass CI checks +- Wait for it to pass CI checks ## Publish -14. Checkout main branch, **in the commit of the previously merged PR** (in case other stuff got merged after that) +- Checkout main branch, **in the commit of the previously merged PR** (in case other stuff got merged after that) -15. Run `cargo publish -p frost-core --dry-run` to check if it’s ready to publish. Fix issues if any. +- Run `cargo publish -p frost-core --dry-run` to check if it’s ready to publish. Fix issues if any. -16. [Draft and publish a new release](https://github.com/ZcashFoundation/frost/releases/new) for frost-core. +- [Draft and publish a new release](https://github.com/ZcashFoundation/frost/releases/new) for frost-core. - 1. In “Choose a tag” type `/` e.g. “frost-core/v0.2.0” and click “Create new tag” - 2. In “Target” select “main” as long as other PRs haven’t been merged after the version bump PR. Otherwise, **select the commit matching the PR that was merged above**. - 3. In “Release title” use ` ` e.g. “frost-core v0.2.0” - 4. Paste the (raw Markdown) changelog for this version into the description box. - 5. Leave “Set as pre-release” **unchecked** (we should have checked it in earlier versions but the ship has sailed. It doesn’t matter much) - 6. **Check** “Set as the latest release” + - In “Choose a tag” type `/` e.g. “frost-core/v0.2.0” and click “Create new tag” + - In “Target” select “main” as long as other PRs haven’t been merged after the version bump PR. Otherwise, **select the commit matching the PR that was merged above**. + - In “Release title” use ` ` e.g. “frost-core v0.2.0” + - Paste the (raw Markdown) changelog for this version into the description box. + - Leave “Set as pre-release” **unchecked** (we should have checked it in earlier versions but the ship has sailed. It doesn’t matter much) + - **Check** “Set as the latest release” -17. Publish it with `cargo publish -p frost-core` +- Publish it with `cargo publish -p frost-core` -18. Check if frost-rerandomized is ready to be published: `cargo publish -p frost-rerandomized --dry-run`. Fix any errors if needed. +- Check if frost-rerandomized is ready to be published: `cargo publish -p frost-rerandomized --dry-run`. Fix any errors if needed. -19. Draft and publish a frost-rerandomized release +- Draft and publish a frost-rerandomized release: - 1. Use the same process as described for frost-core above, but you can leave the changelog empty and **uncheck** “Set as the latest release” + - Run `gh release create "frost-rerandomized/v2.1.0" -n '' -t "frost-rerandomized v2.1.0" --latest=false` + (replace both instances of the version) -20. Publish it with `cargo publish -p frost-rerandomized` +- Publish it with `cargo publish -p frost-rerandomized` -21. Check if other crates are ready to be published: `for cs in ristretto255 ed25519 secp256k1 secp256k1-tr p256 ed448; do cargo publish -p frost-$cs --dry-run; done`. Fix any issues if needed. +- Check if other crates are ready to be published: `for cs in ristretto255 ed25519 secp256k1 secp256k1-tr p256 ed448; do cargo publish -p frost-$cs --dry-run; done`. Fix any issues if needed. - 1. If you get an error like this: + - If you get an error like this: “error: failed to verify package tarball Caused by: failed to select a version for the requirement `frost-core = "^0.3.0"` candidate versions found which didn't match: 0.2.0, 0.1.0 location searched: crates.io index required by package `frost-ed25519 v0.3.0 (frost/target/package/frost-ed25519-0.3.0)`” This is because the ciphersuite crates aren’t pointing at the new frost-core package. This is because you need to publish frost-core before you can publish the others otherwise they will not have the expected version to point to. -22. Draft and publish releases for each of those crates (sorry, that will be boring) +- Draft and publish releases for each of those crates: - 1. Use the same process as described for frost-core above (actions 1 - 3), but you can leave the changelog empty and **uncheck** “Set as the latest release” + - Run `for cs in ristretto255 ed25519 secp256k1 secp256k1-tr p256 ed448; do gh release create "frost-$cs/v2.1.0" -n '' -t "frost-$cs v2.1.0" --latest=false; done` (replace both instances of the version) -23. Publish those crates: `for cs in ristretto255 ed25519 secp256k1 secp256k1-tr p256 ed448; do cargo publish -p frost-$cs; done` +- Publish those crates: `for cs in ristretto255 ed25519 secp256k1 secp256k1-tr p256 ed448; do cargo publish -p frost-$cs; done` ## Confirm -24. Check versions in the crates to confirm everything worked: +- Check versions in the crates to confirm everything worked: - 1. [Frost core](https://crates.io/crates/frost-core/versions) - 2. [Frost ed25519](https://crates.io/crates/frost-ed25519/versions) - 3. [Frost ed448](https://crates.io/crates/frost-ed448/versions) - 4. [Frost p256](https://crates.io/crates/frost-p256/versions) - 5. [Frost ristretto255](https://crates.io/crates/frost-ristretto255/versions) - 6. [Frost secp256k1](https://crates.io/crates/frost-secp256k1/versions) - 7. [Frost secp256k1 tr](https://crates.io/crates/frost-secp256k1-tr/versions) - 8. [Frost rerandomized](https://crates.io/crates/frost-rerandomized/versions) + - [Frost core](https://crates.io/crates/frost-core/versions) + - [Frost ed25519](https://crates.io/crates/frost-ed25519/versions) + - [Frost ed448](https://crates.io/crates/frost-ed448/versions) + - [Frost p256](https://crates.io/crates/frost-p256/versions) + - [Frost ristretto255](https://crates.io/crates/frost-ristretto255/versions) + - [Frost secp256k1](https://crates.io/crates/frost-secp256k1/versions) + - [Frost secp256k1 tr](https://crates.io/crates/frost-secp256k1-tr/versions) + - [Frost rerandomized](https://crates.io/crates/frost-rerandomized/versions) -25. Let the team know in the #frost slack channel that the release is complete and successful +- Let the team know in the #frost slack channel that the release is complete and successful ## In the case of an unsuccessful release diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 2d4016d65..46eaca68d 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -8,9 +8,9 @@ Entries are listed in reverse chronological order. * It is now possible to identify the culprit in `frost_core::keys::dkg::part3()` if an invalid secret share was sent by one of the participants (by calling - frost_core::Error::culprit()`) (#728) + `frost_core::Error::culprit()`) (#728) * Added frost-secp256k1-tr crate, allowing to generate Bitcoin Taproot - (BIP340/BIP341) compatible signatures(#730). + (BIP340/BIP341) compatible signatures (#730). * Support refreshing shares using the DKG approach using the `frost_core::keys::refresh::refresh_dkg_{part1,part2,shares}()` functions (#766). diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index ef57c607e..b12c01e64 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -1,8 +1,8 @@ //! Refresh Shares //! -//! Implements the functionality to refresh a share. This requires the participation -//! of all the remaining signers. This can be done using a Trusted Dealer or -//! DKG (not yet implemented) +//! Implements the functionality to refresh a share. This requires the +//! participation of all the remaining signers. This can be done using a Trusted +//! Dealer or DKG. use alloc::collections::BTreeMap; use alloc::vec::Vec; From 64a4be6c6dbc10760af7c43add263ee019cd250b Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:49:09 +0300 Subject: [PATCH 100/175] chore(ci): update some github action dependencies (#873) --- .github/workflows/coverage.yaml | 7 ++-- .github/workflows/docs.yml | 6 +--- .github/workflows/main.yml | 61 +++++++-------------------------- book/src/dev/developer-guide.md | 2 +- 4 files changed, 17 insertions(+), 59 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index dac117b03..dece19699 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -27,12 +27,9 @@ jobs: with: persist-credentials: false - - uses: actions-rs/toolchain@v1.0.7 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - override: true - profile: minimal - components: llvm-tools-preview + components: llvm-tools - name: Install cargo-llvm-cov cargo command run: cargo install cargo-llvm-cov diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index aebf2fa88..d587aa240 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -41,11 +41,7 @@ jobs: persist-credentials: false - name: Install latest beta - uses: actions-rs/toolchain@v1 - with: - toolchain: beta - components: rust-docs - override: true + uses: dtolnay/rust-toolchain@beta - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b422c09f7..7c6d0e40f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,20 +9,14 @@ on: - main jobs: - build_default: name: build with default features runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.2.2 - - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: beta - override: true - - uses: actions-rs/cargo@v1.0.3 - with: - command: build + - uses: dtolnay/rust-toolchain@beta + - run: cargo build build_msrv: name: build with MSRV (1.66.1) @@ -85,14 +79,8 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: beta - override: true - - uses: actions-rs/cargo@v1.0.3 - with: - command: test - args: --release --all-features + - uses: dtolnay/rust-toolchain@beta + - run: cargo test --release --all-features clippy: name: Clippy @@ -103,10 +91,7 @@ jobs: with: persist-credentials: false - - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: stable - override: true + - uses: dtolnay/rust-toolchain@stable - name: Check workflow permissions id: check_permissions @@ -117,12 +102,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run clippy action to produce annotations - uses: actions-rs/clippy-check@v1.0.7 + uses: clechasseur/rs-clippy-check@v4 if: ${{ steps.check_permissions.outputs.has-permission }} with: - # GitHub displays the clippy job and its results as separate entries - name: Clippy (stable) Results - token: ${{ secrets.GITHUB_TOKEN }} args: --all-features --all-targets -- -D warnings - name: Run clippy manually without annotations @@ -138,18 +120,13 @@ jobs: with: persist-credentials: false - - uses: actions-rs/toolchain@v1.0.7 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable components: rustfmt - override: true - uses: Swatinem/rust-cache@v2 - - uses: actions-rs/cargo@v1.0.3 - with: - command: fmt - args: --all -- --check + - run: cargo fmt --all -- --check gencode: name: Check if automatically generated code is up to date @@ -160,18 +137,13 @@ jobs: with: persist-credentials: false - - uses: actions-rs/toolchain@v1.0.7 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable components: rustfmt - override: true - uses: Swatinem/rust-cache@v2 - - uses: actions-rs/cargo@v1.0.3 - with: - command: run - args: --bin gencode -- --check + - run: cargo run --bin gencode -- --check docs: name: Check Rust doc @@ -184,16 +156,9 @@ jobs: with: persist-credentials: false - - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: stable - profile: minimal - override: true + - uses: dtolnay/rust-toolchain@stable - - uses: actions-rs/cargo@v1.0.3 - with: - command: doc - args: --no-deps --document-private-items --all-features + - run: cargo doc --no-deps --document-private-items --all-features actionlint: runs-on: ubuntu-latest @@ -203,4 +168,4 @@ jobs: - uses: reviewdog/action-actionlint@v1.64.1 with: level: warning - fail_on_error: false + fail_level: none diff --git a/book/src/dev/developer-guide.md b/book/src/dev/developer-guide.md index 27ffb5809..7c31eec67 100644 --- a/book/src/dev/developer-guide.md +++ b/book/src/dev/developer-guide.md @@ -11,4 +11,4 @@ Test coverage checks are performed in the pipeline. This is configured here: `.github/workflows/coverage.yaml` To run these locally: 1. Install coverage tool by running `cargo install cargo-llvm-cov` -2. Run `cargo llvm-cov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs'` (you may be asked if you want to install `llvm-tools-preview`, if so type `Y`) +2. Run `cargo llvm-cov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs'` (you may be asked if you want to install `llvm-tools`, if so type `Y`) From fc87f59cc08e8721e9c543fbd90aed7da4bf0d67 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:10:41 +0300 Subject: [PATCH 101/175] chore(deps): improve organization of workspace (root Cargo.toml) (#874) --- Cargo.toml | 6 ++++-- book/src/dev/release-checklist.md | 2 +- frost-core/Cargo.toml | 4 ++-- frost-ed25519/Cargo.toml | 9 ++++----- frost-ed448/Cargo.toml | 8 ++++---- frost-p256/Cargo.toml | 10 +++++----- frost-rerandomized/Cargo.toml | 4 +--- frost-ristretto255/Cargo.toml | 10 +++++----- frost-secp256k1-tr/Cargo.toml | 10 +++++----- frost-secp256k1/Cargo.toml | 10 +++++----- gencode/Cargo.toml | 2 -- 11 files changed, 36 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d2615d4fa..c940a763f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = [ "gencode" ] - [workspace.package] edition = "2021" version = "2.1.0" @@ -38,5 +37,8 @@ rand_chacha = "0.3" rand_core = "0.6" serde_json = "1.0" +frost-core = { path = "frost-core", version = "2.1.0", default-features = false } +frost-rerandomized = { path = "frost-rerandomized", version = "2.1.0", default-features = false } + [profile.test.package."*"] -opt-level = 3 \ No newline at end of file +opt-level = 3 diff --git a/book/src/dev/release-checklist.md b/book/src/dev/release-checklist.md index 5cfb4b44c..79aac8659 100644 --- a/book/src/dev/release-checklist.md +++ b/book/src/dev/release-checklist.md @@ -46,7 +46,7 @@ releases easier. - Move version in changelog to Released - Create a new version in “unreleased” in changelog -- Update the version number for frost-core and frost-rerandomized in the Ciphersuite crates, e.g. in `frost-core = { path = "../frost-core", version = "0.4.0", features = ["test-impl"] }`. You'll need to do this for dependencies and dev-dependencies +- Update the version number for frost-core and frost-rerandomized in the root Cargo.toml file, e.g. in `frost-core = { path = "frost-core", version = "0.4.0", default-features = false }` - Create a PR with subject `Release \` containing all these changes diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index a013a9c42..0907e8b26 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -34,10 +34,10 @@ itertools = { version = "0.14.0", default-features = false } # Test dependencies used with the test-impl feature proptest = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true } -criterion = { version = "0.5", optional = true } +criterion = { workspace = true, optional = true } [dev-dependencies] -criterion = { version = "0.5" } +criterion.workspace = true lazy_static.workspace = true proptest.workspace = true rand.workspace = true diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index b6d26d599..36523fd97 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "frost-ed25519" edition.workspace = true - version.workspace = true authors.workspace = true readme = "README.md" @@ -18,15 +17,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +frost-core.workspace = true +frost-rerandomized.workspace = true rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +frost-core = { workspace = true, features = ["std", "test-impl"] } +frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } ed25519-dalek = "2.1.0" insta.workspace = true hex.workspace = true diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 16282a97f..bc7d85bb8 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -17,15 +17,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features.workspace = true ed448-goldilocks = { version = "0.9.0" } -frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +frost-core.workspace = true +frost-rerandomized.workspace = true rand_core.workspace = true sha3 = { version = "0.10.6", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +frost-core = { workspace = true, features = ["std", "test-impl"] } +frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } lazy_static.workspace = true insta.workspace = true hex.workspace = true diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index dc5e3edd9..1376e4def 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" license.workspace = true repository.workspace = true categories.workspace = true -keywords = ["cryptography", "crypto", "threshold", "signature"] +keywords = ["cryptography", "crypto", "p256", "threshold", "signature"] description = "A Schnorr signature scheme over the NIST P-256 curve that supports FROST." [package.metadata.docs.rs] @@ -17,15 +17,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features.workspace = true p256 = { version = "0.13.0", features = ["hash2curve"], default-features = false } -frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +frost-core.workspace = true +frost-rerandomized.workspace = true rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +frost-core = { workspace = true, features = ["std", "test-impl"] } +frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } insta.workspace = true hex.workspace = true lazy_static.workspace = true diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 878f710c6..cc3a1d376 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -17,9 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] derive-getters = "0.5.0" document-features.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", features = [ - "internals" -], default-features = false } +frost-core = { workspace = true, features = ["internals"] } hex.workspace = true rand_core.workspace = true diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index ff67d8917..06e88037b 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -17,15 +17,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } document-features.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +frost-core.workspace = true +frost-rerandomized.workspace = true rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] -criterion = { version = "0.5", features = ["html_reports"] } -frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +criterion = { workspace = true, features = ["html_reports"] } +frost-core = { workspace = true, features = ["std", "test-impl"] } +frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } insta.workspace = true hex.workspace = true lazy_static.workspace = true diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index 3a2292ca7..1f9d16e0a 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" license.workspace = true repository.workspace = true categories.workspace = true -keywords = ["cryptography", "crypto", "threshold", "signature"] +keywords = ["cryptography", "crypto", "secp256k1", "threshold", "signature"] description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot." [package.metadata.docs.rs] @@ -16,16 +16,16 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +frost-core.workspace = true +frost-rerandomized.workspace = true k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +frost-core = { workspace = true, features = ["std", "test-impl"] } +frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } insta.workspace = true hex.workspace = true lazy_static.workspace = true diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index 4a6ac8e46..c6d693513 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -7,7 +7,7 @@ readme = "README.md" license.workspace = true repository.workspace = true categories.workspace = true -keywords = ["cryptography", "crypto", "threshold", "signature"] +keywords = ["cryptography", "crypto", "secp256k1", "threshold", "signature"] description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST." [package.metadata.docs.rs] @@ -16,16 +16,16 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] document-features.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", default-features = false } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", default-features = false } +frost-core.workspace = true +frost-rerandomized.workspace = true k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } rand_core.workspace = true sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { path = "../frost-core", version = "2.1.0", features = ["test-impl"] } -frost-rerandomized = { path = "../frost-rerandomized", version = "2.1.0", features = ["test-impl"] } +frost-core = { workspace = true, features = ["std", "test-impl"] } +frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } insta.workspace = true hex.workspace = true lazy_static.workspace = true diff --git a/gencode/Cargo.toml b/gencode/Cargo.toml index 8487d9ea6..44dead9ba 100644 --- a/gencode/Cargo.toml +++ b/gencode/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" edition.workspace = true publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] regex = "1.6.0" serde_json.workspace = true From aed1ea885c29efe38f5df27fd0f8cd7b2ac380a5 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Mon, 24 Feb 2025 16:57:22 +0300 Subject: [PATCH 102/175] feat(frost-core): add (de)serialization for `VerifiableSecretSharingSharingSharingCommitment` (#878) * feat(frost-core): add (de)serialization for `VerifiableSecretSharingSharingSharingCommitment` * Update frost-core/src/keys.rs Co-authored-by: Conrado Gouvea * Update frost-core/src/keys.rs Co-authored-by: Conrado Gouvea * add tests * fix doc --------- Co-authored-by: Conrado Gouvea --- frost-core/src/keys.rs | 24 ++++ frost-core/src/tests/vss_commitment.rs | 124 +++++++++++++++++- frost-ed25519/src/tests/vss_commitment.rs | 25 ++++ frost-ed448/src/tests/vss_commitment.rs | 25 ++++ frost-p256/src/tests/vss_commitment.rs | 20 +++ .../src/tests/vss_commitment.rs | 26 ++++ .../src/tests/vss_commitment.rs | 25 ++++ frost-secp256k1/src/tests/vss_commitment.rs | 25 ++++ 8 files changed, 293 insertions(+), 1 deletion(-) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index d1461157b..e9c8f4bfd 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -327,6 +327,11 @@ where .collect::>>() } + /// Serialize the whole commitment vector as a single byte vector. + pub fn serialize_whole(&self) -> Result, Error> { + self.serialize().map(|v| v.concat()) + } + /// Returns VerifiableSecretSharingCommitment from an iterator of serialized /// CoefficientCommitments (e.g. a [`Vec>`]). pub fn deserialize(serialized_coefficient_commitments: I) -> Result> @@ -342,6 +347,25 @@ where Ok(Self::new(coefficient_commitments)) } + /// Deserialize a whole commitment vector from a single byte vector as returned by + /// [`VerifiableSecretSharingCommitment::serialize_whole()`]. + pub fn deserialize_whole(bytes: &[u8]) -> Result> { + // Get size from the size of the generator + let generator = ::generator(); + let len = ::serialize(&generator) + .expect("serializing the generator always works") + .as_ref() + .len(); + + let serialized_coefficient_commitments = bytes.chunks_exact(len); + + if !serialized_coefficient_commitments.remainder().is_empty() { + return Err(Error::InvalidCoefficient); + } + + Self::deserialize(serialized_coefficient_commitments) + } + /// Get the VerifyingKey matching this commitment vector (which is the first /// element in the vector), or an error if the vector is empty. pub(crate) fn verifying_key(&self) -> Result, Error> { diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 10465227f..25fe7a297 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -3,7 +3,7 @@ use crate::{ keys::{CoefficientCommitment, VerifiableSecretSharingCommitment}, tests::helpers::generate_element, - Group, + Error, Group, }; use debugless_unwrap::DebuglessUnwrap; use rand_core::{CryptoRng, RngCore}; @@ -46,6 +46,40 @@ pub fn check_serialize_vss_commitment(mu .all(|(e, c)| e.as_ref() == c)); } +/// Test serialize_whole VerifiableSecretSharingCommitment +pub fn check_serialize_whole_vss_commitment(mut rng: R) { + // Generate test CoefficientCommitments + + // --- + let input_1 = generate_element::(&mut rng); + let input_2 = generate_element::(&mut rng); + let input_3 = generate_element::(&mut rng); + + let coeff_comms = vec![ + CoefficientCommitment::::new(input_1), + CoefficientCommitment::new(input_2), + CoefficientCommitment::new(input_3), + ]; + + // --- + + let expected = [ + ::serialize(&input_1).unwrap(), + ::serialize(&input_2).unwrap(), + ::serialize(&input_3).unwrap(), + ] + .into_iter() + .map(|element| element.as_ref().to_vec()) + .collect::>() + .concat(); + + let vss_commitment = VerifiableSecretSharingCommitment(coeff_comms) + .serialize_whole() + .unwrap(); + + assert!(expected == vss_commitment); +} + /// Test deserialize VerifiableSecretSharingCommitment pub fn check_deserialize_vss_commitment(mut rng: R) { // Generate test CoefficientCommitments @@ -76,6 +110,40 @@ pub fn check_deserialize_vss_commitment( assert!(expected == vss_value.unwrap()); } +/// Test deserialize_whole VerifiableSecretSharingCommitment +pub fn check_deserialize_whole_vss_commitment(mut rng: R) { + // Generate test CoefficientCommitments + + // --- + let input_1 = generate_element::(&mut rng); + let input_2 = generate_element::(&mut rng); + let input_3 = generate_element::(&mut rng); + + let coeff_comms = vec![ + CoefficientCommitment::::new(input_1), + CoefficientCommitment::new(input_2), + CoefficientCommitment::new(input_3), + ]; + // --- + + let expected = VerifiableSecretSharingCommitment(coeff_comms); + + let data = vec![ + ::serialize(&input_1).unwrap(), + ::serialize(&input_2).unwrap(), + ::serialize(&input_3).unwrap(), + ] + .into_iter() + .map(|element| element.as_ref().to_vec()) + .collect::>() + .concat(); + + let vss_value = VerifiableSecretSharingCommitment::deserialize_whole(&data); + + assert!(vss_value.is_ok()); + assert!(expected == vss_value.unwrap()); +} + /// Test deserialize VerifiableSecretSharingCommitment error pub fn check_deserialize_vss_commitment_error( mut rng: R, @@ -109,6 +177,60 @@ pub fn check_deserialize_vss_commitment_error( + mut rng: R, + commitment_helpers: &Value, +) { + // Generate test CoefficientCommitments + + // --- + let values = &commitment_helpers["elements"]; + + let input_1 = generate_element::(&mut rng); + let input_2 = generate_element::(&mut rng); + let input_3 = generate_element::(&mut rng); + + let serialized: ::Serialization = + ::Serialization::try_from( + hex::decode(values["invalid_element"].as_str().unwrap()).unwrap(), + ) + .debugless_unwrap(); + // --- + + let data = vec![ + ::serialize(&input_1).unwrap(), + ::serialize(&input_2).unwrap(), + ::serialize(&input_3).unwrap(), + serialized, + ] + .into_iter() + .map(|element| element.as_ref().to_vec()) + .collect::>() + .concat(); + + let vss_value = VerifiableSecretSharingCommitment::::deserialize_whole(&data); + + assert!(vss_value.is_err()); + + // Generate test CoefficientCommitments with invalid length + + let mut data = vec![ + ::serialize(&input_1).unwrap(), + ::serialize(&input_2).unwrap(), + ::serialize(&input_3).unwrap(), + ] + .into_iter() + .map(|element| element.as_ref().to_vec()) + .collect::>() + .concat(); + data.append(&mut vec![0x00]); + + let vss_value = VerifiableSecretSharingCommitment::::deserialize_whole(&data); + + assert_eq!(vss_value, Err(Error::InvalidCoefficient)); +} + /// Test computing the public key package from a list of commitments. pub fn check_compute_public_key_package(mut rng: R) { let max_signers = 3; diff --git a/frost-ed25519/src/tests/vss_commitment.rs b/frost-ed25519/src/tests/vss_commitment.rs index c1c128fbf..3e0fcd26e 100644 --- a/frost-ed25519/src/tests/vss_commitment.rs +++ b/frost-ed25519/src/tests/vss_commitment.rs @@ -16,12 +16,28 @@ fn check_serialize_vss_commitment() { frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } +#[test] +fn check_serialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment() { let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } +#[test] +fn check_deserialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment_error() { let rng = rand::rngs::OsRng; @@ -30,6 +46,15 @@ fn check_deserialize_vss_commitment_error() { ); } +#[test] +fn check_deserialize_whole_vss_commitment_error() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< + Ed25519Sha512, + _, + >(rng, &ELEMENTS); +} + #[test] fn check_compute_public_key_package() { let rng = rand::rngs::OsRng; diff --git a/frost-ed448/src/tests/vss_commitment.rs b/frost-ed448/src/tests/vss_commitment.rs index c5beea6a2..7e1f83441 100644 --- a/frost-ed448/src/tests/vss_commitment.rs +++ b/frost-ed448/src/tests/vss_commitment.rs @@ -16,12 +16,28 @@ fn check_serialize_vss_commitment() { frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } +#[test] +fn check_serialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment() { let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } +#[test] +fn check_deserialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment_error() { let rng = rand::rngs::OsRng; @@ -30,6 +46,15 @@ fn check_deserialize_vss_commitment_error() { ); } +#[test] +fn check_deserialize_whole_vss_commitment_error() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< + Ed448Shake256, + _, + >(rng, &ELEMENTS); +} + #[test] fn check_compute_public_key_package() { let rng = rand::rngs::OsRng; diff --git a/frost-p256/src/tests/vss_commitment.rs b/frost-p256/src/tests/vss_commitment.rs index 24457911c..44c08c5be 100644 --- a/frost-p256/src/tests/vss_commitment.rs +++ b/frost-p256/src/tests/vss_commitment.rs @@ -16,12 +16,24 @@ fn check_serialize_vss_commitment() { frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } +#[test] +fn check_serialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::(rng); +} + #[test] fn check_deserialize_vss_commitment() { let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } +#[test] +fn check_deserialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::(rng); +} + #[test] fn check_deserialize_vss_commitment_error() { let rng = rand::rngs::OsRng; @@ -30,6 +42,14 @@ fn check_deserialize_vss_commitment_error() { ); } +#[test] +fn check_deserialize_whole_vss_commitment_error() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::( + rng, &ELEMENTS, + ); +} + #[test] fn check_compute_public_key_package() { let rng = rand::rngs::OsRng; diff --git a/frost-ristretto255/src/tests/vss_commitment.rs b/frost-ristretto255/src/tests/vss_commitment.rs index 66c6d37a8..06b16e422 100644 --- a/frost-ristretto255/src/tests/vss_commitment.rs +++ b/frost-ristretto255/src/tests/vss_commitment.rs @@ -16,6 +16,14 @@ fn check_serialize_vss_commitment() { frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } +#[test] +fn check_serialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment() { let rng = rand::rngs::OsRng; @@ -24,6 +32,15 @@ fn check_deserialize_vss_commitment() { ); } +#[test] +fn check_deserialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::< + Ristretto255Sha512, + _, + >(rng); +} + #[test] fn check_deserialize_vss_commitment_error() { let rng = rand::rngs::OsRng; @@ -33,6 +50,15 @@ fn check_deserialize_vss_commitment_error() { >(rng, &ELEMENTS); } +#[test] +fn check_deserialize_whole_vss_commitment_error() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< + Ristretto255Sha512, + _, + >(rng, &ELEMENTS); +} + #[test] fn check_compute_public_key_package() { let rng = rand::rngs::OsRng; diff --git a/frost-secp256k1-tr/src/tests/vss_commitment.rs b/frost-secp256k1-tr/src/tests/vss_commitment.rs index 0a0906853..f264c3328 100644 --- a/frost-secp256k1-tr/src/tests/vss_commitment.rs +++ b/frost-secp256k1-tr/src/tests/vss_commitment.rs @@ -16,6 +16,14 @@ fn check_serialize_vss_commitment() { frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } +#[test] +fn check_serialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment() { let rng = rand::rngs::OsRng; @@ -24,6 +32,14 @@ fn check_deserialize_vss_commitment() { ); } +#[test] +fn check_deserialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment_error() { let rng = rand::rngs::OsRng; @@ -32,6 +48,15 @@ fn check_deserialize_vss_commitment_error() { ); } +#[test] +fn check_deserialize_whole_vss_commitment_error() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< + Secp256K1Sha256TR, + _, + >(rng, &ELEMENTS); +} + #[test] fn check_compute_public_key_package() { let rng = rand::rngs::OsRng; diff --git a/frost-secp256k1/src/tests/vss_commitment.rs b/frost-secp256k1/src/tests/vss_commitment.rs index 5574f5918..79aa89a65 100644 --- a/frost-secp256k1/src/tests/vss_commitment.rs +++ b/frost-secp256k1/src/tests/vss_commitment.rs @@ -16,12 +16,28 @@ fn check_serialize_vss_commitment() { frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } +#[test] +fn check_serialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment() { let rng = rand::rngs::OsRng; frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } +#[test] +fn check_deserialize_whole_vss_commitment() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::( + rng, + ); +} + #[test] fn check_deserialize_vss_commitment_error() { let rng = rand::rngs::OsRng; @@ -30,6 +46,15 @@ fn check_deserialize_vss_commitment_error() { ); } +#[test] +fn check_deserialize_whole_vss_commitment_error() { + let rng = rand::rngs::OsRng; + frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< + Secp256K1Sha256, + _, + >(rng, &ELEMENTS); +} + #[test] fn check_compute_public_key_package() { let rng = rand::rngs::OsRng; From 2a6673fdf576a65f7e24407ba3e8486e56cb585d Mon Sep 17 00:00:00 2001 From: crStiv Date: Thu, 10 Apr 2025 16:33:37 +0300 Subject: [PATCH 103/175] docs: Add network topologies section with diagrams (#883) * Update frost.md * Update SUMMARY.md * Update frost.md --- book/src/SUMMARY.md | 1 + book/src/frost.md | 75 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 004ef64de..4d75f2d7d 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -2,6 +2,7 @@ [ZF FROST](index.md) - [Understanding FROST](frost.md) + - [Network Topologies](frost.md#network-topologies) - [Tutorial](tutorial.md) - [Importing and General Information](tutorial/importing.md) - [Trusted Dealer Key Generation](tutorial/trusted-dealer.md) diff --git a/book/src/frost.md b/book/src/frost.md index 7c34073ca..9bf566862 100644 --- a/book/src/frost.md +++ b/book/src/frost.md @@ -78,6 +78,81 @@ be able to produce the final signature. Of course, the Coordinator is still free to start the process with only 2 participants if they wish. ``` +## Network Topologies + +FROST supports different network topologies for both signing and DKG (Distributed Key Generation) processes. Understanding these topologies is crucial for implementing FROST in a way that best suits your application's needs. + +### Signing Topologies + +#### 1. Centralized Coordinator + +```ascii + Coordinator + / | \ + / | \ + / | \ + Signer1 Signer2 Signer3 +``` + +This is the default topology where: +- A single coordinator (which may or may not be a signer) manages the signing process +- Signers only communicate with the coordinator +- Pros: Simple to implement, clear communication flow +- Cons: Single point of failure, potential bottleneck + +#### 2. Distributed Coordination + +```ascii + Signer1 -------- Signer2 + \ / + \ / + \ / + Signer3 +``` + +In this topology: +- Each signer acts as their own coordinator +- All signers communicate directly with each other +- Pros: No single point of failure +- Cons: More complex implementation, requires full mesh networking + +### DKG Topologies + +#### 1. Full Mesh (Recommended) + +```ascii + Node1 --------- Node2 + | \ / | + | \ / | + | \ / | + | \ / | + | \ / | + Node4 --- Node3 +``` + +For DKG: +- All participants need to communicate directly with each other +- Requires authenticated and confidential channels between all pairs +- Requires a broadcast channel for public values +- Most secure but requires more complex networking setup + +#### 2. Star with Broadcast Hub + +```ascii + Hub + / | \ + / | \ + Node1 | Node3 + | + Node2 +``` + +Alternative DKG setup: +- A central hub relays messages between participants +- Simpler networking requirements +- Hub must be trusted for message delivery (but cannot learn secrets) +- May be suitable for controlled environments + ## Verifying Signatures Signature verification is carried out as normal with single-party signatures, From ca1c98c99bfd903ac0b34f543ad91475fe6488c3 Mon Sep 17 00:00:00 2001 From: shigeyuki azuchi Date: Sat, 12 Apr 2025 00:23:03 +0900 Subject: [PATCH 104/175] Add tests for all test vectors in DKG (#875) * Modify DKG key generation test to include additional testing of all participant data * Fix wrong signing share * Fix wrong proof of knowledge and add missing data for participant 2,3 * Fix format --- frost-core/src/tests/vectors_dkg.rs | 211 ++++++++++-------- frost-ed25519/tests/helpers/vectors_dkg.json | 4 +- frost-ed448/tests/helpers/vectors_dkg.json | 2 +- .../tests/helpers/vectors_dkg.json | 16 +- 4 files changed, 129 insertions(+), 104 deletions(-) diff --git a/frost-core/src/tests/vectors_dkg.rs b/frost-core/src/tests/vectors_dkg.rs index 894eda218..ddd10f296 100644 --- a/frost-core/src/tests/vectors_dkg.rs +++ b/frost-core/src/tests/vectors_dkg.rs @@ -1,6 +1,5 @@ //! Helper function for testing with test vectors. use alloc::{collections::BTreeMap, string::ToString, vec::Vec}; - use debugless_unwrap::DebuglessUnwrap; use hex::{self}; use serde_json::Value; @@ -44,76 +43,92 @@ fn json_to_element(vector: &Value) -> ::Seria /// Parse test vectors for a given ciphersuite. #[allow(clippy::type_complexity)] -pub fn parse_test_vectors_dkg(json_vectors: &Value) -> DKGTestVectors { +pub fn parse_test_vectors_dkg(json_vectors: &Value) -> Vec> { + let mut vectors: Vec> = Vec::new(); let inputs = &json_vectors["inputs"]; - let participant = &inputs["1"]; - - let participant_1_id: Identifier = (participant["identifier"].as_u64().unwrap() as u16) - .try_into() - .unwrap(); - let participant_2_id: Identifier = (inputs["2"]["identifier"].as_u64().unwrap() as u16) - .try_into() - .unwrap(); - let participant_3_id: Identifier = (inputs["3"]["identifier"].as_u64().unwrap() as u16) - .try_into() - .unwrap(); - - let mut round1_packages = BTreeMap::new(); - round1_packages.insert(participant_2_id, build_round_1_package(json_vectors, 2)); - round1_packages.insert(participant_3_id, build_round_1_package(json_vectors, 3)); - - let mut round2_packages = BTreeMap::new(); - round2_packages.insert(participant_2_id, build_round_2_package(json_vectors, 2)); - round2_packages.insert(participant_3_id, build_round_2_package(json_vectors, 3)); + let max_participants = json_vectors["config"]["MAX_PARTICIPANTS"].as_u64().unwrap() as u16; + let min_signers = json_vectors["config"]["MIN_PARTICIPANTS"].as_u64().unwrap() as u16; - let secret = - SigningKey::deserialize(json_to_scalar::(&participant["signing_key"]).as_ref()).unwrap(); + for i in 1..=max_participants { + let participant_id_str = &i.to_string(); + let participant_data = &inputs[participant_id_str]; + let participant_id: Identifier = (participant_data["identifier"].as_u64().unwrap() + as u16) + .try_into() + .unwrap(); - let coefficient = <::Field as Field>::deserialize(&json_to_scalar::( - &participant["coefficient"], - )) - .unwrap(); + let mut round1_packages = BTreeMap::new(); + let mut round2_packages = BTreeMap::new(); + for (other_participant_id_str, other_participant_data) in inputs.as_object().unwrap() { + if participant_id_str == other_participant_id_str { + continue; + } + match other_participant_id_str.parse::() { + Ok(id) => id, + Err(_) => continue, + }; + let other_participant_id: Identifier = + (other_participant_data["identifier"].as_u64().unwrap() as u16) + .try_into() + .unwrap(); + round1_packages.insert( + other_participant_id, + build_round_1_package(other_participant_data), + ); + round2_packages.insert( + other_participant_id, + build_round_2_package(participant_data, other_participant_id_str), + ); + } + + let secret = + SigningKey::deserialize(json_to_scalar::(&participant_data["signing_key"]).as_ref()) + .unwrap(); + + let coefficient = <::Field as Field>::deserialize(&json_to_scalar::( + &participant_data["coefficient"], + )) + .unwrap(); - let public_key_package = build_public_key_package(json_vectors); + let public_key_package = build_public_key_package(json_vectors); - let verifying_share = - VerifyingShare::deserialize(json_to_element::(&participant["verifying_share"]).as_ref()) - .unwrap(); + let verifying_share = VerifyingShare::deserialize( + json_to_element::(&participant_data["verifying_share"]).as_ref(), + ) + .unwrap(); - let verifying_key = - VerifyingKey::deserialize(json_to_element::(&inputs["verifying_key"]).as_ref()).unwrap(); + let verifying_key = + VerifyingKey::deserialize(json_to_element::(&inputs["verifying_key"]).as_ref()) + .unwrap(); - let signing_share = - SigningShare::deserialize(json_to_scalar::(&participant["signing_share"]).as_ref()) - .unwrap(); + let signing_share = SigningShare::deserialize( + json_to_scalar::(&participant_data["signing_share"]).as_ref(), + ) + .unwrap(); - let key_package = KeyPackage { - header: Header::default(), - identifier: participant_1_id, - signing_share, - verifying_share, - verifying_key, - min_signers: 2, - }; - - DKGTestVectors { - secret, - coefficient, - round1_packages, - round2_packages, - public_key_package, - key_package, - participant_id: participant_1_id, + let key_package = KeyPackage { + header: Header::default(), + identifier: participant_id, + signing_share, + verifying_share, + verifying_key, + min_signers, + }; + vectors.push(DKGTestVectors { + secret, + coefficient, + round1_packages, + round2_packages, + public_key_package, + key_package, + participant_id, + }) } + vectors } -fn build_round_1_package( - json_vectors: &Value, - participant_num: usize, -) -> Round1Package { - let inputs = &json_vectors["inputs"]; - let participant = &inputs[participant_num.to_string()]; - let vss_commitment = participant["vss_commitments"] +fn build_round_1_package(json_vectors: &Value) -> Round1Package { + let vss_commitment = json_vectors["vss_commitments"] .as_array() .unwrap() .iter() @@ -123,7 +138,7 @@ fn build_round_1_package( let commitment = VerifiableSecretSharingCommitment::deserialize(vss_commitment).unwrap(); let proof_of_knowledge = Signature::deserialize( - &hex::decode(participant["proof_of_knowledge"].as_str().unwrap()).unwrap(), + &hex::decode(json_vectors["proof_of_knowledge"].as_str().unwrap()).unwrap(), ) .debugless_unwrap(); @@ -136,12 +151,10 @@ fn build_round_1_package( fn build_round_2_package( json_vectors: &Value, - sender_num: usize, + sender_num: &String, ) -> Round2Package { - let inputs = &json_vectors["inputs"]; - let signing_share = SigningShare::deserialize( - json_to_scalar::(&inputs["1"]["signing_shares"][sender_num.to_string()]).as_ref(), + json_to_scalar::(&json_vectors["signing_shares"][sender_num]).as_ref(), ) .unwrap(); @@ -182,41 +195,43 @@ fn build_public_key_package(json_vectors: &Value) -> PublicKeyPa /// Test DKG with the given test vectors for a ciphersuite pub fn check_dkg_keygen(json_vectors: &Value) { - let DKGTestVectors { - secret, - coefficient, - round1_packages, - round2_packages, - public_key_package, - key_package, - participant_id, - } = parse_test_vectors_dkg(json_vectors); - - let min_signers = 2; - let max_signers = 3; - - let (coefficients, commitment) = generate_secret_polynomial( - &secret as &SigningKey, - max_signers, - min_signers, - vec![coefficient], - ) - .unwrap(); + for dkg_vectors in parse_test_vectors_dkg(json_vectors) { + let DKGTestVectors { + secret, + coefficient, + round1_packages, + round2_packages, + public_key_package, + key_package, + participant_id, + } = dkg_vectors; + + let min_signers = 2; + let max_signers = 3; + + let (coefficients, commitment) = generate_secret_polynomial( + &secret as &SigningKey, + max_signers, + min_signers, + vec![coefficient], + ) + .unwrap(); - let round1_secret_package = SecretPackage::new( - participant_id, - coefficients, - commitment.clone(), - min_signers, - max_signers, - ); + let round1_secret_package = SecretPackage::new( + participant_id, + coefficients, + commitment.clone(), + min_signers, + max_signers, + ); - let (round2_secret_package, _round2_packages_1) = - part2(round1_secret_package, &round1_packages).unwrap(); + let (round2_secret_package, _round2_packages_1) = + part2(round1_secret_package, &round1_packages).unwrap(); - let (expected_key_package, expected_public_key_package) = - part3(&round2_secret_package, &round1_packages, &round2_packages).unwrap(); + let (expected_key_package, expected_public_key_package) = + part3(&round2_secret_package, &round1_packages, &round2_packages).unwrap(); - assert_eq!(public_key_package, expected_public_key_package); - assert_eq!(key_package, expected_key_package); + assert_eq!(public_key_package, expected_public_key_package); + assert_eq!(key_package, expected_key_package); + } } diff --git a/frost-ed25519/tests/helpers/vectors_dkg.json b/frost-ed25519/tests/helpers/vectors_dkg.json index bcad1c9c2..4284fe4af 100644 --- a/frost-ed25519/tests/helpers/vectors_dkg.json +++ b/frost-ed25519/tests/helpers/vectors_dkg.json @@ -35,7 +35,7 @@ "3": "7fde55b354d5d8dddc940fe932de5d1a6110b9bc4edeba2db7b32c34074d3a0a" }, "verifying_share": "f326b756ed38b43a94bdac698e044d9e3f3a08a40e7c9d2e5346dd5bfaadf2f5", - "signing_share": "80a16ad6cf78be27995d5e312722ff2d42a5b08380b49dbc2e7e65c124220e0a" + "signing_share": "b9ed88cb30a9ddbc00dbdf7c40d6c76f791989cf9f3119bad899287c5f6c2303" }, "3": { "identifier": 3, @@ -49,7 +49,7 @@ "2": "6b3e57fab1e74e61aacfd93d4926172a7e878a48540fe97367721e38485b3a00" }, "verifying_share": "6bc91a2755902d955ce220ad0df6fbf57162260949d40bcf5a69cfffec9c085a", - "signing_share": "8bd411475cdc423c1ba94cf11b80e776592d5f675419398800d17173d673f40a" + "signing_share": "35b2b8fad8352f6e6a7c8fee082f45a339a37f0085c1f662c44c3f10903d970d" } } } diff --git a/frost-ed448/tests/helpers/vectors_dkg.json b/frost-ed448/tests/helpers/vectors_dkg.json index a0943e87c..09a2297f5 100644 --- a/frost-ed448/tests/helpers/vectors_dkg.json +++ b/frost-ed448/tests/helpers/vectors_dkg.json @@ -42,7 +42,7 @@ "proof_of_knowledge": "eb47491f2461792114d357d02102c1a806451cfa88f1297f7a671a87a04de0ffde478ec1c2b91e743379254fe84eb2e0d170c69aec88bc1980bf8009ffc93d6ee0c3713681aa303cf85595bd975953318ab07be9e56dc6ba22465793ee337e383562fafc7525c05b36732b93f5f4fee22300", "signing_shares": { "1": "77c1831adb90cd94d0404ec2a8730af57d4e29e10ad02e26ee61328c95f1258a17bad98617632d92ee5aa8224793231db3a599d322b82d2300", - "2": "e29c8b642abfa741710945aedadf34ac73ef6863c3e56d599cc3c58039d45b7382674cbd2c8e064c8bae33851c9166536181b83fe34ce02200" + "2": "6aa7e48f3d7f4de3b1a0bb95a5085705e5618b8f67d89cf43dd8d649057d295ff0c837e4a918634a94a773606ed156c9db543c3bcd1e2a0e00" }, "verifying_share": "3a1b5a9945fc64b088174c34e16dbced81f824fe8f9f12d1ec98afd4ea593a6ec75a74f70b77522c66681bd468080b525963dbcc2785d53a00", "signing_share": "da7c3a1048da9e6b8e480a72d48479ab4724f9804e96a44c7f7e04691ffbfdd7a56c2a9644e1ad1075baeae746ce8317f63104e87363652900" diff --git a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json index 9fcbc839e..55755fbad 100644 --- a/frost-secp256k1-tr/tests/helpers/vectors_dkg.json +++ b/frost-secp256k1-tr/tests/helpers/vectors_dkg.json @@ -13,7 +13,7 @@ "signing_key": "68e3f6904c6043973515a36bf7801a71597da35733f21305d75a5234f06e4529", "coefficient": "25d2d840a3e2718a431ec69e14ee8a015b000d43c7a9868060f01d5aa52a19d1", "vss_commitments": ["03e7ba4acb164d2bd5eba4f47b3a788109ddb3f88f1181792424fa332123a25ea8", "037495e920a1f032916193aa80ea97a4c3a611dec9ab47ccc969deb664f5f88bbe"], - "proof_of_knowledge": "6689a8d414eb4961308e21f8caa1045236efded4f3de9209dc07547e88be3b42e192de9bed27fb78a7a4d4e35a0422f11f52631b8e66d69e609398eaff2770b8", + "proof_of_knowledge": "191a4ef1851286e2fd6cebd483385452cbb12f43386241854939252c4ed8846b8631f9e69be37ccbd3a0b4593a8f63738747e165a22d0b5786eeb74e59a17837", "signing_shares": { "2": "1dd3cb3e2370e6af22917415f0ad584514807b58b3cc40d2230a26e115f02771", "3": "dd25ee86acd01f996618aa0d1153f5e8fbc929a8e8a18b8f0a15f91d087217e2" @@ -27,7 +27,12 @@ "coefficient": "f7ba0cbbffbea8aaceb3ade54bdbf35bafb1cda15b65ad490e0c63dd069a7c9f", "vss_commitments": ["03ef10370a008cd95e179dc51e2cb7828f30b72d254e5166484f927c84ab326582", "022ce0dac0db217ba326fbbe3e6132d45e2a4bfa0a0c3790d91eacce9a1c2d6a10"], "proof_of_knowledge": "a319dd51cf64b3896c22f54154812d4ae76cfa95f46f53ef69241fd702456fef32da76cc93d3a541ca495b723e793ee90c32440da5f314e2e58a2dc30550314a", - "verifying_share": "029ecb3a4db28a82e7b8d600d42711b02790dde3f063f0ecec6f812c1c5d7dcefc" + "signing_shares": { + "1": "b489a711942526abbb5330a8215d2e740f7dbddec3452006993a8cea3ac278cb", + "3": "20255dc07b1fb78bdf90bd85fd2389c988c8250faee11826656a09142fa9fc97" + }, + "verifying_share": "029ecb3a4db28a82e7b8d600d42711b02790dde3f063f0ecec6f812c1c5d7dcefc", + "signing_share": "9131f1241cd8f95f6439b0f5edc2ecb969a2d3db9c85fe5added77116d41d0ce" }, "3": { "identifier": 3, @@ -35,7 +40,12 @@ "coefficient": "42ff6f39ce4f97f279781378ebcf93df47add84d75882cd31b266e83f76e25f6", "vss_commitments": ["02da186c3863c5600b471a2799cb6f15ae4d8315a2f225c177798880e75ac820a0", "03e6a36e7fa4b117c1aa428886672e3a35d926bb4c585a9b07d8ee9a3387420067"], "proof_of_knowledge": "6e115d9e63fd15d432b380ccf1ec4ed03340fcf96caeae8985aedb5f905b1a65dc422ffe5878988fbbc55454857736c7755d9c8f5ee6822c8833ea21d54dba36", - "verifying_share": "02c98b3c2e9f4bde4cf90dc9c7be639e5adda6ea09fc605239880a22cb836f7145" + "signing_shares": { + "1": "da5c7f5238079835fe71f746364bb8756a7dcb228aeea686fa2aaa44dfec929c", + "2": "0d47e4b622ee3804bff8cfe088653efefe865cce0c065aecbf7e318182b89e2d" + }, + "verifying_share": "02c98b3c2e9f4bde4cf90dc9c7be639e5adda6ea09fc605239880a22cb836f7145", + "signing_share": "30a59cedaae84737d8ef28f9a128db7bd1f1fd8fb3373dfa139ce5e29a4555a9" } } } From d7cd2a299187cabdde5dba07e9147f14c95ee1e0 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Mon, 14 Apr 2025 07:36:59 -0300 Subject: [PATCH 105/175] docs: add warning about encrypted channel for DKG (#888) --- book/src/SUMMARY.md | 1 - book/src/frost.md | 114 ++++++++++++++++----------------- book/src/tutorial/dkg.md | 6 ++ book/src/zcash/ywallet-demo.md | 6 +- 4 files changed, 66 insertions(+), 61 deletions(-) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 4d75f2d7d..004ef64de 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -2,7 +2,6 @@ [ZF FROST](index.md) - [Understanding FROST](frost.md) - - [Network Topologies](frost.md#network-topologies) - [Tutorial](tutorial.md) - [Importing and General Information](tutorial/importing.md) - [Trusted Dealer Key Generation](tutorial/trusted-dealer.md) diff --git a/book/src/frost.md b/book/src/frost.md index 9bf566862..4b03a3a1f 100644 --- a/book/src/frost.md +++ b/book/src/frost.md @@ -78,6 +78,62 @@ be able to produce the final signature. Of course, the Coordinator is still free to start the process with only 2 participants if they wish. ``` +## Verifying Signatures + +Signature verification is carried out as normal with single-party signatures, +along with the signed message and the group verifying key as inputs. + +## Repairing Shares + +Repairing shares allow participants to help another participant recover their +share if they have lost it, or also issue a new share to a new participant +(while keeping the same threshold). + +The repair share functionality requires a threshold of participants to work. +For example, in a 2-of-3 scenario, two participants can help the third recover +their share, or they could issue a new share to move to a 2-of-4 group. + +The functionality works in such a way that each participant running the repair +share function is not able to obtain the share that is being recovered or +issued. + +## Refreshing Shares + +Refreshing shares allow participants (or a subset of them) to update their +shares in a way that maintains the same group public key. Some applications are: + +- Make it harder for attackers to compromise the shares. For example, in a + 2-of-3 threshold scenario, if an attacker steals one participant's device and + all participants refresh their shares, the attacker will need to start over + and steal two shares instead of just one more. +- Remove a participant from the group. For example, in a 2-of-3 threshold + scenario, if two participants decide to remove the third they can both refresh + their shares and the third participant would no longer be able to participate + in signing sessions with the others. (They can also then use the repair share + functionality to issue a new share and move from 2-of-2 back to 2-of-3.) + +```admonish danger +It is critically important to keep in mind that the **Refresh Shares +functionality does not "restore full security" to a group**. While the group +evolves and participants are removed and new participants are added, the +security of the group does not depend only on the threshold of the current +participants being honest, but also **on the threshold of all previous set of +participants being honest**! For example, if Alice, Mallory and Eve form a group +and Mallory is eventually excluded from the group and replaced with Bob, it is +not enough to trust 2 out of 3 between Alice, Bob and Eve. **You also need to +trust that Mallory won't collude with, say, Eve which could have kept her +original pre-refresh share and they could both together recompute the original +key and compromise the group.** If that's an unacceptable risk to your use case, +you will need to migrate to a new group if that makes sense to your application. +``` + +## Ciphersuites + +FROST is a generic protocol that works with any adequate prime-order group, +which in practice are constructed from elliptic curves. The spec specifies +five ciphersuites with the Ristretto255, Ed25519, Ed448, P-256 and secp256k1 +groups. It's possible (though not recommended) to use your own ciphersuite. + ## Network Topologies FROST supports different network topologies for both signing and DKG (Distributed Key Generation) processes. Understanding these topologies is crucial for implementing FROST in a way that best suits your application's needs. @@ -151,60 +207,4 @@ Alternative DKG setup: - A central hub relays messages between participants - Simpler networking requirements - Hub must be trusted for message delivery (but cannot learn secrets) -- May be suitable for controlled environments - -## Verifying Signatures - -Signature verification is carried out as normal with single-party signatures, -along with the signed message and the group verifying key as inputs. - -## Repairing Shares - -Repairing shares allow participants to help another participant recover their -share if they have lost it, or also issue a new share to a new participant -(while keeping the same threshold). - -The repair share functionality requires a threshold of participants to work. -For example, in a 2-of-3 scenario, two participants can help the third recover -their share, or they could issue a new share to move to a 2-of-4 group. - -The functionality works in such a way that each participant running the repair -share function is not able to obtain the share that is being recovered or -issued. - -## Refreshing Shares - -Refreshing shares allow participants (or a subset of them) to update their -shares in a way that maintains the same group public key. Some applications are: - -- Make it harder for attackers to compromise the shares. For example, in a - 2-of-3 threshold scenario, if an attacker steals one participant's device and - all participants refresh their shares, the attacker will need to start over - and steal two shares instead of just one more. -- Remove a participant from the group. For example, in a 2-of-3 threshold - scenario, if two participants decide to remove the third they can both refresh - their shares and the third participant would no longer be able to participate - in signing sessions with the others. (They can also then use the repair share - functionality to issue a new share and move from 2-of-2 back to 2-of-3.) - -```admonish danger -It is critically important to keep in mind that the **Refresh Shares -functionality does not "restore full security" to a group**. While the group -evolves and participants are removed and new participants are added, the -security of the group does not depend only on the threshold of the current -participants being honest, but also **on the threshold of all previous set of -participants being honest**! For example, if Alice, Mallory and Eve form a group -and Mallory is eventually excluded from the group and replaced with Bob, it is -not enough to trust 2 out of 3 between Alice, Bob and Eve. **You also need to -trust that Mallory won't collude with, say, Eve which could have kept her -original pre-refresh share and they could both together recompute the original -key and compromise the group.** If that's an unacceptable risk to your use case, -you will need to migrate to a new group if that makes sense to your application. -``` - -## Ciphersuites - -FROST is a generic protocol that works with any adequate prime-order group, -which in practice are constructed from elliptic curves. The spec specifies -five ciphersuites with the Ristretto255, Ed25519, Ed448, P-256 and secp256k1 -groups. It's possible (though not recommended) to use your own ciphersuite. +- May be suitable for controlled environments \ No newline at end of file diff --git a/book/src/tutorial/dkg.md b/book/src/tutorial/dkg.md index 5bab3dd6f..4bbd54355 100644 --- a/book/src/tutorial/dkg.md +++ b/book/src/tutorial/dkg.md @@ -75,6 +75,12 @@ The `round2::Package`s must be sent to their respective participants with the given `Identifier`s, using an [authenticated and confidential communication channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). +```admonish danger +The `round2::Package`s MUST be encrypted, otherwise an attacker who can read +the content of the packages will be able to recreate the secret being +generated. +``` + ## Part 3 Finally, upon receiving the other participant's `round2::Package`, the DKG is diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index c36513ee0..1972695ac 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -31,7 +31,7 @@ Install the `zcash-sign` tool: cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked zcash-sign ``` -Switch to an empty folder which will store the files generate in the demo. +Switch to an empty folder which will store the files generated in the demo. For example: ``` @@ -154,7 +154,7 @@ strings with the public keys of the contacts which will participate (along with the user running the command): ``` -frost-client dkg -d 'Alice, Bob and Eve's group' -s localhost:2744 -S , -t 2 -C redpallas -c alice.toml +frost-client dkg -d "Alice, Bob and Eve's group" -s localhost:2744 -S , -t 2 -C redpallas -c alice.toml ``` The user should then notify the others that a signing session has started (e.g. @@ -163,7 +163,7 @@ They should then run the following, replacing the name of the group if they wish and the threshold number with the one given by the first participant. ``` -frost-client dkg -d 'Alice, Bob and Eve's group' -s localhost:2744 -t 2 -C redpallas +frost-client dkg -d "Alice, Bob and Eve's group" -s localhost:2744 -t 2 -C redpallas ``` ```admonish note From a8c8b6d197bd818e23fc8fcfc457144a909872e0 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 24 Apr 2025 07:11:14 -0300 Subject: [PATCH 106/175] clippy fixes (#889) --- frost-core/src/benches.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frost-core/src/benches.rs b/frost-core/src/benches.rs index f89ce4af0..fd4ea7313 100644 --- a/frost-core/src/benches.rs +++ b/frost-core/src/benches.rs @@ -89,6 +89,8 @@ pub fn bench_sign( let mut group = c.benchmark_group(format!("FROST Signing {name}")); for &n in [3u16, 10, 100, 1000].iter() { let max_signers = n; + // div_ceil is in 1.73.0 which is larger than the current MSRV + #[allow(clippy::manual_div_ceil)] let min_signers = (n * 2 + 2) / 3; group.bench_with_input( From 78c70b4bea25ec118f9f062e98cfa0dc7a1195c3 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Fri, 6 Jun 2025 00:15:47 +0300 Subject: [PATCH 107/175] chore(frost-core): import `String` from `alloc` (#899) --- frost-core/src/tests/vectors_dkg.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frost-core/src/tests/vectors_dkg.rs b/frost-core/src/tests/vectors_dkg.rs index ddd10f296..be268d288 100644 --- a/frost-core/src/tests/vectors_dkg.rs +++ b/frost-core/src/tests/vectors_dkg.rs @@ -1,5 +1,9 @@ //! Helper function for testing with test vectors. -use alloc::{collections::BTreeMap, string::ToString, vec::Vec}; +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, + vec::Vec, +}; use debugless_unwrap::DebuglessUnwrap; use hex::{self}; use serde_json::Value; From 412f04937582d31f089ea8489a91ba0ddd956a8c Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:53:45 +0300 Subject: [PATCH 108/175] feat: bump MSRV to Rust 1.73 (#900) --- .github/workflows/main.yml | 4 ++-- Cargo.toml | 1 + frost-core/CHANGELOG.md | 2 ++ frost-core/Cargo.toml | 1 + frost-core/src/scalar_mul.rs | 23 +---------------------- frost-core/src/tests/vss_commitment.rs | 1 + frost-ed25519/Cargo.toml | 1 + frost-ed448/Cargo.toml | 1 + frost-ed448/src/lib.rs | 2 +- frost-p256/Cargo.toml | 1 + frost-rerandomized/Cargo.toml | 1 + frost-ristretto255/Cargo.toml | 1 + frost-secp256k1-tr/Cargo.toml | 1 + frost-secp256k1/Cargo.toml | 1 + 14 files changed, 16 insertions(+), 25 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7c6d0e40f..708ca1968 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - run: cargo build build_msrv: - name: build with MSRV (1.66.1) + name: build with MSRV (1.73) runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: - run: cargo update -Z minimal-versions # Now check that `cargo build` works with respect to the oldest possible # deps and the stated MSRV - - uses: dtolnay/rust-toolchain@1.66.1 + - uses: dtolnay/rust-toolchain@1.73 - run: cargo build --all-features # TODO: this is filling up the disk space in CI. See if there is a way to diff --git a/Cargo.toml b/Cargo.toml index c940a763f..57c5733d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ [workspace.package] edition = "2021" +rust-version = "1.73" version = "2.1.0" authors = [ "Deirdre Connolly ", diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 46eaca68d..a0a44652f 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,6 +4,8 @@ Entries are listed in reverse chronological order. ## Unreleased +* MSRV has been bumped to Rust 1.73 + ## 2.1.0 * It is now possible to identify the culprit in `frost_core::keys::dkg::part3()` diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 0907e8b26..ecd3266e3 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "frost-core" edition.workspace = true +rust-version.workspace = true version.workspace = true authors.workspace = true readme = "README.md" diff --git a/frost-core/src/scalar_mul.rs b/frost-core/src/scalar_mul.rs index e8215c443..3d82b28d0 100644 --- a/frost-core/src/scalar_mul.rs +++ b/frost-core/src/scalar_mul.rs @@ -14,27 +14,6 @@ use alloc::vec::Vec; use crate::{Ciphersuite, Element, Field, Group, Scalar}; -/// Calculates the quotient of `self` and `rhs`, rounding the result towards positive infinity. -/// -/// # Panics -/// -/// This function will panic if `rhs` is 0 or the division results in overflow. -/// -/// This function is similar to `div_ceil` that is [available on -/// Nightly](https://github.com/rust-lang/rust/issues/88581). -/// -// TODO: remove this function and use `div_ceil()` instead when `int_roundings` -// is stabilized. -const fn div_ceil(lhs: usize, rhs: usize) -> usize { - let d = lhs / rhs; - let r = lhs % rhs; - if r > 0 && rhs > 0 { - d + 1 - } else { - d - } -} - /// A trait for transforming a scalar generic over a ciphersuite to a non-adjacent form (NAF). pub trait NonAdjacentForm { fn non_adjacent_form(&self, w: usize) -> Vec; @@ -81,7 +60,7 @@ where let mut naf = vec![0; naf_length]; // Get the number of 64-bit limbs we need. - let num_limbs: usize = div_ceil(naf_length, u64::BITS as usize); + let num_limbs: usize = naf_length.div_ceil(u64::BITS as usize); let mut x_u64 = vec![0u64; num_limbs]; diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 25fe7a297..7b32b5ed5 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -5,6 +5,7 @@ use crate::{ tests::helpers::generate_element, Error, Group, }; +use alloc::vec::Vec; use debugless_unwrap::DebuglessUnwrap; use rand_core::{CryptoRng, RngCore}; use serde_json::Value; diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 36523fd97..b0e1a4125 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "frost-ed25519" edition.workspace = true +rust-version.workspace = true version.workspace = true authors.workspace = true readme = "README.md" diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index bc7d85bb8..81f7be32b 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "frost-ed448" edition.workspace = true +rust-version.workspace = true version.workspace = true authors.workspace = true readme = "README.md" diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 56523561d..f8d75704f 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -7,7 +7,7 @@ extern crate alloc; -use std::collections::BTreeMap; +use alloc::collections::BTreeMap; use ed448_goldilocks::{ curve::{edwards::CompressedEdwardsY, ExtendedPoint}, diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index 1376e4def..d8ca78cb0 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "frost-p256" edition.workspace = true +rust-version.workspace = true version.workspace = true authors.workspace = true readme = "README.md" diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index cc3a1d376..0045aab99 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "frost-rerandomized" edition.workspace = true +rust-version.workspace = true version.workspace = true authors.workspace = true readme = "README.md" diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 06e88037b..53f9f0b7f 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "frost-ristretto255" edition.workspace = true +rust-version.workspace = true version.workspace = true authors.workspace = true readme = "README.md" diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index 1f9d16e0a..17ba3805f 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "frost-secp256k1-tr" edition.workspace = true +rust-version.workspace = true version.workspace = true authors.workspace = true readme = "README.md" diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index c6d693513..ce2a63eea 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "frost-secp256k1" edition.workspace = true +rust-version.workspace = true version.workspace = true authors.workspace = true readme = "README.md" From 43787272e2d424bf329f2d07e9f4e20bdb1f0550 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Fri, 6 Jun 2025 19:50:57 +0300 Subject: [PATCH 109/175] feat: bump MSRV to Rust 1.81 (#901) --- .github/workflows/main.yml | 4 ++-- Cargo.toml | 4 ++-- frost-core/CHANGELOG.md | 3 ++- frost-core/Cargo.toml | 7 ++----- frost-core/src/error.rs | 7 +------ frost-core/src/lib.rs | 2 +- frost-ed25519/Cargo.toml | 9 +++------ frost-ed25519/src/lib.rs | 2 +- frost-ed448/Cargo.toml | 9 +++------ frost-ed448/src/lib.rs | 1 - frost-p256/Cargo.toml | 9 +++------ frost-p256/src/lib.rs | 2 +- frost-rerandomized/Cargo.toml | 3 --- frost-rerandomized/src/lib.rs | 2 +- frost-ristretto255/Cargo.toml | 9 +++------ frost-ristretto255/src/lib.rs | 2 +- frost-secp256k1-tr/Cargo.toml | 11 ++++------- frost-secp256k1-tr/src/lib.rs | 2 +- frost-secp256k1-tr/tests/helpers/mod.rs | 2 +- frost-secp256k1/Cargo.toml | 9 +++------ frost-secp256k1/src/lib.rs | 2 +- 21 files changed, 36 insertions(+), 65 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 708ca1968..20cbcd7dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - run: cargo build build_msrv: - name: build with MSRV (1.73) + name: build with MSRV (1.81) runs-on: ubuntu-latest steps: @@ -34,7 +34,7 @@ jobs: - run: cargo update -Z minimal-versions # Now check that `cargo build` works with respect to the oldest possible # deps and the stated MSRV - - uses: dtolnay/rust-toolchain@1.73 + - uses: dtolnay/rust-toolchain@1.81 - run: cargo build --all-features # TODO: this is filling up the disk space in CI. See if there is a way to diff --git a/Cargo.toml b/Cargo.toml index 57c5733d6..1ebcf7b9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ [workspace.package] edition = "2021" -rust-version = "1.73" +rust-version = "1.81" version = "2.1.0" authors = [ "Deirdre Connolly ", @@ -27,7 +27,7 @@ repository = "https://github.com/ZcashFoundation/frost" categories = ["cryptography"] [workspace.dependencies] -criterion = "0.5" +criterion = "0.6" document-features = "0.2.7" hex = { version = "0.4.3", default-features = false, features = ["alloc"] } insta = { version = "1.31.0", features = ["yaml"] } diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index a0a44652f..d6530d1c4 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -4,7 +4,8 @@ Entries are listed in reverse chronological order. ## Unreleased -* MSRV has been bumped to Rust 1.73 +* MSRV has been bumped to Rust 1.81, making all crates no-std (except + `frost-ed448`). The `std` and `nightly` features were removed from all crates ## 2.1.0 diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index ecd3266e3..b3eab931e 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -26,8 +26,7 @@ postcard = { version = "1.0.0", features = ["alloc"], optional = true } rand_core = { version = "0.6", default-features = false } serde = { version = "1.0.160", default-features = false, features = ["derive"], optional = true } serdect = { version = "0.2.0", optional = true } -thiserror-nostd-notrait = { version = "1.0.29", default-features = false } -thiserror = { version = "2.0.3", default-features = false, optional = true } +thiserror = { version = "2.0.3", default-features = false } visibility = "0.1.0" zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } itertools = { version = "0.14.0", default-features = false } @@ -46,10 +45,8 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "cheater-detection"] #! ## Features -## Enable standard library support. -std = ["dep:thiserror"] ## Expose internal types, which do not have SemVer guarantees. This is an advanced ## feature which can be useful if you need to build a modified version of FROST. ## The docs won't list them, you will need to check the source code. diff --git a/frost-core/src/error.rs b/frost-core/src/error.rs index 78662df7e..c32fb0c93 100644 --- a/frost-core/src/error.rs +++ b/frost-core/src/error.rs @@ -1,12 +1,7 @@ //! FROST Error types -#[cfg(feature = "std")] -use thiserror::Error; - -#[cfg(not(feature = "std"))] -use thiserror_nostd_notrait::Error; - use crate::{Ciphersuite, Identifier}; +use thiserror::Error; #[derive(Error, Debug, Clone, Copy, Eq, PartialEq)] pub struct ParticipantError(Identifier); diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index a290984e1..a0e232be2 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(non_snake_case)] // It's emitting false positives; see https://github.com/rust-lang/rust-clippy/issues/9413 #![allow(clippy::derive_partial_eq_without_eq)] diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index b0e1a4125..89adf485a 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -25,8 +25,8 @@ sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { workspace = true, features = ["std", "test-impl"] } -frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } +frost-core = { workspace = true, features = ["test-impl"] } +frost-rerandomized = { workspace = true, features = ["test-impl"] } ed25519-dalek = "2.1.0" insta.workspace = true hex.workspace = true @@ -37,11 +37,8 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -nightly = [] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "cheater-detection"] #! ## Features -## Enable standard library support. -std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 9c4bce8e8..334922d00 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 81f7be32b..1d47056a7 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -25,8 +25,8 @@ sha3 = { version = "0.10.6", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { workspace = true, features = ["std", "test-impl"] } -frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } +frost-core = { workspace = true, features = ["test-impl"] } +frost-rerandomized = { workspace = true, features = ["test-impl"] } lazy_static.workspace = true insta.workspace = true hex.workspace = true @@ -36,11 +36,8 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -nightly = [] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "cheater-detection"] #! ## Features -## Enable standard library support. -std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index f8d75704f..2acc59001 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -230,7 +230,6 @@ pub type Identifier = frost::Identifier; /// FROST(Ed448, SHAKE256) keys, key generation, key shares. pub mod keys { use super::*; - use std::collections::BTreeMap; /// The identifier list to use when generating key shares. pub type IdentifierList<'a> = frost::keys::IdentifierList<'a, E>; diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index d8ca78cb0..e69ca4552 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -25,8 +25,8 @@ sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { workspace = true, features = ["std", "test-impl"] } -frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } +frost-core = { workspace = true, features = ["test-impl"] } +frost-rerandomized = { workspace = true, features = ["test-impl"] } insta.workspace = true hex.workspace = true lazy_static.workspace = true @@ -36,11 +36,8 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -nightly = [] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "cheater-detection"] #! ## Features -## Enable standard library support. -std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 35c2620f7..b21cb8dbe 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 0045aab99..a1b054816 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -25,11 +25,8 @@ rand_core.workspace = true [dev-dependencies] [features] -nightly = [] default = ["serialization", "cheater-detection"] #! ## Features -## Enable standard library support. -std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index a309d6f1a..78ee354ba 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -9,7 +9,7 @@ //! - Each participant should call [`sign`] and send the resulting //! [`frost::round2::SignatureShare`] back to the Coordinator; //! - The Coordinator should then call [`aggregate`]. -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(non_snake_case)] extern crate alloc; diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 53f9f0b7f..355d5b430 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -25,8 +25,8 @@ sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion = { workspace = true, features = ["html_reports"] } -frost-core = { workspace = true, features = ["std", "test-impl"] } -frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } +frost-core = { workspace = true, features = ["test-impl"] } +frost-rerandomized = { workspace = true, features = ["test-impl"] } insta.workspace = true hex.workspace = true lazy_static.workspace = true @@ -37,11 +37,8 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -nightly = [] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "cheater-detection"] #! ## Features -## Enable standard library support. -std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index 0929c228e..cd376b6b7 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] #![doc = include_str!("../README.md")] diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index 17ba3805f..8171d070e 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -25,23 +25,20 @@ sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { workspace = true, features = ["std", "test-impl"] } -frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } +frost-core = { workspace = true, features = ["test-impl"] } +frost-rerandomized = { workspace = true, features = ["test-impl"] } insta.workspace = true hex.workspace = true lazy_static.workspace = true proptest.workspace = true rand.workspace = true rand_chacha.workspace = true -secp256k1 = "0.30.0" +secp256k1 = "0.31.0" serde_json.workspace = true [features] -nightly = [] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "cheater-detection"] #! ## Features -## Enable standard library support. -std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index eee93fd6b..cc012d019 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] diff --git a/frost-secp256k1-tr/tests/helpers/mod.rs b/frost-secp256k1-tr/tests/helpers/mod.rs index 0de6147cc..299e33f82 100644 --- a/frost-secp256k1-tr/tests/helpers/mod.rs +++ b/frost-secp256k1-tr/tests/helpers/mod.rs @@ -17,7 +17,7 @@ pub fn verify_signature( group_signature.serialize().unwrap().try_into().unwrap(), ); let pubkey = secp256k1::XOnlyPublicKey::from_byte_array( - &group_pubkey.serialize().unwrap()[1..33].try_into().unwrap(), + group_pubkey.serialize().unwrap()[1..33].try_into().unwrap(), ) .unwrap(); secp.verify_schnorr(&sig, msg, &pubkey).unwrap(); diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index ce2a63eea..a02795803 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -25,8 +25,8 @@ sha2 = { version = "0.10.2", default-features = false } [dev-dependencies] criterion.workspace = true -frost-core = { workspace = true, features = ["std", "test-impl"] } -frost-rerandomized = { workspace = true, features = ["std", "test-impl"] } +frost-core = { workspace = true, features = ["test-impl"] } +frost-rerandomized = { workspace = true, features = ["test-impl"] } insta.workspace = true hex.workspace = true lazy_static.workspace = true @@ -36,11 +36,8 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -nightly = [] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "cheater-detection"] #! ## Features -## Enable standard library support. -std = ["frost-core/std"] ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports ## `serde` (e.g. JSON with `serde_json`). diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index eec2c8ee8..9485a2317 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_auto_cfg))] From 81a091d93a17b358161fc0028d15cfc5e06944af Mon Sep 17 00:00:00 2001 From: VolodymyrBg Date: Fri, 6 Jun 2025 22:08:01 +0300 Subject: [PATCH 110/175] Add post_generate to Ciphersuite trait (#884) * Add post_generate to Ciphersuite trait * Add post_generate to Ciphersuite trait * Update keys.rs * Update keys.rs * Update keys.rs * make post_generate take a map of SecretShare * cargo fmt --------- Co-authored-by: Conrado Gouvea --- frost-core/src/keys.rs | 20 +++++++++++--------- frost-core/src/traits.rs | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index e9c8f4bfd..c9f02f2f2 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -548,7 +548,6 @@ pub fn split( } }; let mut verifying_shares: BTreeMap, VerifyingShare> = BTreeMap::new(); - let mut secret_shares_by_id: BTreeMap, SecretShare> = BTreeMap::new(); for secret_share in secret_shares { @@ -558,14 +557,17 @@ pub fn split( secret_shares_by_id.insert(secret_share.identifier, secret_share); } - Ok(( - secret_shares_by_id, - PublicKeyPackage { - header: Header::default(), - verifying_shares, - verifying_key, - }, - )) + let public_key_package = PublicKeyPackage { + header: Header::default(), + verifying_shares, + verifying_key, + }; + + // Apply post-processing + let (processed_secret_shares, processed_public_key_package) = + C::post_generate(secret_shares_by_id, public_key_package)?; + + Ok((processed_secret_shares, processed_public_key_package)) } /// Evaluate the polynomial with the given coefficients (constant term first) diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 91a5056af..9714015e1 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -10,7 +10,7 @@ use rand_core::{CryptoRng, RngCore}; use crate::{ challenge, - keys::{KeyPackage, PublicKeyPackage, VerifyingShare}, + keys::{KeyPackage, PublicKeyPackage, SecretShare, VerifyingShare}, random_nonzero, round1::{self}, round2::{self, SignatureShare}, @@ -422,4 +422,19 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { ) -> Result<(KeyPackage, PublicKeyPackage), Error> { Ok((key_package, public_key_package)) } + + /// Post-process the output of the key generation for a participant. + #[allow(clippy::type_complexity)] + fn post_generate( + secret_shares: BTreeMap, SecretShare>, + public_key_package: PublicKeyPackage, + ) -> Result< + ( + BTreeMap, SecretShare>, + PublicKeyPackage, + ), + Error, + > { + Ok((secret_shares, public_key_package)) + } } From 6ff59d1d7dcdece583b60df577934dab5f762831 Mon Sep 17 00:00:00 2001 From: Pili Guerra <1311133+mpguerra@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:17:51 +0200 Subject: [PATCH 111/175] Add CONTRIBUTING.md (#902) * Add CONTRIBUTING.md * Update link to point to #frost channel on discord --- CONTRIBUTING.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5ab53de20 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +* [Running and Debugging](#running-and-debugging) +* [Bug Reports](#bug-reports) +* [Pull Requests](#pull-requests) + +## Running and Debugging +[running-and-debugging]: #running-and-debugging + +See the [user documentation](https://frost.zfnd.org/user.html) for details on +how to build, run, and use the FROST Rust reference implementation. + +## Bug Reports +[bug-reports]: #bug-reports + +Please [create an issue](https://github.com/ZcashFoundation/frost/issues/new) on the FROST issue tracker. + +## Pull Requests +[pull-requests]: #pull-requests + +PRs are welcome for small and large changes, but please don't make large PRs +without coordinating with us via the [issue tracker](https://github.com/ZcashFoundation/frost/issues) or [Discord](https://discord.gg/muKwd2F83D). This helps +increase development coordination and makes PRs easier to merge. Low-effort PRs, including but not limited to fixing typos and grammatical corrections, will generally be redone by us to dissuade metric farming. + +Check out the [help wanted][hw] label if you're looking for a place to get started! + +FROST follows the [conventional commits][conventional] standard for the commits +merged to main. Since PRs are squashed before merging to main, the PR titles +should follow the conventional commits standard so that the merged commits +are conformant. + +[hw]: https://github.com/ZcashFoundation/frost/labels/E-help-wanted +[conventional]: https://www.conventionalcommits.org/en/v1.0.0/#specification \ No newline at end of file From 99565594a3ebb25b9625db4be1628f4b64731e80 Mon Sep 17 00:00:00 2001 From: Pili Guerra <1311133+mpguerra@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:46:22 +0200 Subject: [PATCH 112/175] Remove reference to private repo (#905) --- book/src/dev/frost-dependencies-for-audit.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/dev/frost-dependencies-for-audit.md b/book/src/dev/frost-dependencies-for-audit.md index cc8c13c55..98f8853c5 100644 --- a/book/src/dev/frost-dependencies-for-audit.md +++ b/book/src/dev/frost-dependencies-for-audit.md @@ -20,8 +20,8 @@ This is a list of production Rust code that is in scope and out of scope for FRO | Name | Version | Notes |------| ------- | ----- -| redjubjub | v0.6.0 | This library is being partially audited as part of the [Zebra audit](https://github.com/ZcashFoundation/zebra-private/blob/d4137908385be7e6df0a935b91bfc83b532261a2/book/src/dev/zebra-dependencies-for-audit.md#zcashzf-dependencies-1). -| reddsa | v0.5.0 | This library is being partially audited as part of the [Zebra audit](https://github.com/ZcashFoundation/zebra-private/blob/d4137908385be7e6df0a935b91bfc83b532261a2/book/src/dev/zebra-dependencies-for-audit.md#zcashzf-dependencies-1). +| redjubjub | v0.6.0 | This library is being partially audited as part of the [Zebra audit](https://github.com/ZcashFoundation/zebra/blob/main/book/src/dev/zebra-dependencies-for-audit.md#zcashzf-dependencies-1). +| reddsa | v0.5.0 | This library is being partially audited as part of the [Zebra audit](https://github.com/ZcashFoundation/zebra/blob/main/book/src/dev/zebra-dependencies-for-audit.md#zcashzf-dependencies-1). --- ## Partial Audit From f62ee736bcc604e6034b56ae3fe962b476525ab5 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 25 Aug 2025 12:28:12 -0300 Subject: [PATCH 113/175] core: remove unused error (#909) --- frost-core/src/error.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/frost-core/src/error.rs b/frost-core/src/error.rs index c32fb0c93..28d74f4d7 100644 --- a/frost-core/src/error.rs +++ b/frost-core/src/error.rs @@ -3,9 +3,6 @@ use crate::{Ciphersuite, Identifier}; use thiserror::Error; -#[derive(Error, Debug, Clone, Copy, Eq, PartialEq)] -pub struct ParticipantError(Identifier); - /// An error related to FROST. #[non_exhaustive] #[derive(Error, Debug, Copy, Clone, Eq, PartialEq)] From 379ef689c733b3d9c80fd409071d4f3af4dafed2 Mon Sep 17 00:00:00 2001 From: Conrado Date: Wed, 27 Aug 2025 07:11:48 -0300 Subject: [PATCH 114/175] refresh: validate min_signers (#908) * refresh: validate min_signers * add std back so we can make a non-breaking release --- frost-core/CHANGELOG.md | 3 + frost-core/Cargo.toml | 4 +- frost-core/src/keys/dkg.rs | 4 +- frost-core/src/keys/refresh.rs | 115 +++++++-- frost-core/src/tests/refresh.rs | 225 ++++++++++++++++++ frost-ed25519/Cargo.toml | 4 +- frost-ed25519/src/keys/refresh.rs | 51 +++- frost-ed25519/tests/integration_tests.rs | 19 ++ frost-ed448/Cargo.toml | 4 +- frost-ed448/src/keys/refresh.rs | 51 +++- frost-ed448/tests/integration_tests.rs | 19 ++ frost-p256/Cargo.toml | 4 +- frost-p256/src/keys/refresh.rs | 51 +++- frost-p256/tests/integration_tests.rs | 19 ++ frost-rerandomized/Cargo.toml | 4 +- frost-ristretto255/Cargo.toml | 4 +- frost-ristretto255/src/keys/refresh.rs | 51 +++- frost-ristretto255/tests/integration_tests.rs | 20 ++ frost-secp256k1-tr/Cargo.toml | 4 +- frost-secp256k1-tr/src/keys/refresh.rs | 51 +++- frost-secp256k1-tr/tests/integration_tests.rs | 20 ++ frost-secp256k1/Cargo.toml | 4 +- frost-secp256k1/src/keys/refresh.rs | 51 +++- frost-secp256k1/tests/integration_tests.rs | 19 ++ 24 files changed, 734 insertions(+), 67 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index d6530d1c4..74bb6ba22 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -6,6 +6,9 @@ Entries are listed in reverse chronological order. * MSRV has been bumped to Rust 1.81, making all crates no-std (except `frost-ed448`). The `std` and `nightly` features were removed from all crates +* Added validation for the `min_signers` parameter in the + `frost_core::keys::refresh` functions. +* Added DKG refresh functions to the crate-specific `refresh` modules. ## 2.1.0 diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index b3eab931e..64d6088f4 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -45,7 +45,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Expose internal types, which do not have SemVer guarantees. This is an advanced ## feature which can be useful if you need to build a modified version of FROST. diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 541c05398..0eac153ed 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -484,7 +484,7 @@ pub(crate) fn verify_proof_of_knowledge( /// `round1_packages` maps the identifier of each other participant to the /// [`round1::Package`] they sent to the current participant (the owner of /// `secret_package`). These identifiers must come from whatever mapping the -/// coordinator has between communication channels and participants, i.e. they +/// participant has between communication channels and participants, i.e. they /// must have assurance that the [`round1::Package`] came from the participant /// with that identifier. /// @@ -561,7 +561,7 @@ pub fn part2( /// `round2_packages` maps the identifier of each other participant to the /// [`round2::Package`] they sent to the current participant (the owner of /// `secret_package`). These identifiers must come from whatever mapping the -/// coordinator has between communication channels and participants, i.e. they +/// participant has between communication channels and participants, i.e. they /// must have assurance that the [`round2::Package`] came from the participant /// with that identifier. /// diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index b12c01e64..3815aec5a 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -1,8 +1,27 @@ //! Refresh Shares //! -//! Implements the functionality to refresh a share. This requires the -//! participation of all the remaining signers. This can be done using a Trusted -//! Dealer or DKG. +//! Refreshing shares has two purposes: +//! +//! - Mitigate against share compromise. +//! - Remove participants from a group. +//! +//! Refer to the [FROST +//! book](https://frost.zfnd.org/frost.html#refreshing-shares) for important +//! details. +//! +//! This modules supports refreshing shares using a Trusted Dealer or DKG. You +//! probably want to use the same approach as the original share generation. +//! +//! For the Trusted Dealer approach, the trusted dealer should call +//! [`compute_refreshing_shares()`] and send the returned refreshing shares to +//! the participants. Each participant should then call [`refresh_share()`]. +//! +//! For the DKG approach, the flow is very similar to [DKG +//! itself](`https://frost.zfnd.org/tutorial/dkg.html`). Each participant calls +//! [`refresh_dkg_part_1()`], keeps the returned secret package and sends the +//! returned package to other participants. Then each participants calls +//! [`refresh_dkg_part2()`] and sends the returned packages to the other +//! participants. Finally each participant calls [`refresh_dkg_shares()`]. use alloc::collections::BTreeMap; use alloc::vec::Vec; @@ -21,10 +40,20 @@ use core::iter; use super::{dkg::round1::Package, KeyPackage, SecretShare, VerifiableSecretSharingCommitment}; -/// Generates new zero key shares and a public key package using a trusted -/// dealer Building a new public key package is done by taking the verifying -/// shares from the new public key package and adding them to the original -/// verifying shares +/// Compute refreshing shares for the Trusted Dealer refresh procedure. +/// +/// - `pub_key_package`: the current public key package. +/// - `max_signers`: the number of participants that are refreshing their +/// shares. It can be smaller than the original value, but still equal to or +/// greater than `min_signers`. +/// - `min_signers`: the threshold needed to sign. It must be equal to the +/// original value for the group (i.e. the refresh process can't reduce +/// the threshold). +/// - `identifiers`: The identifiers of all participants that want to refresh +/// their shares. Must be the same length as `max_signers`. +/// +/// It returns a vectors of [`SecretShare`] that must be sent to the participants +/// in the same order as `identifiers`, and the refreshed [`PublicKeyPackage`]. pub fn compute_refreshing_shares( pub_key_package: PublicKeyPackage, max_signers: u16, @@ -86,9 +115,11 @@ pub fn compute_refreshing_shares( Ok((refreshing_shares_minus_identity, refreshed_pub_key_package)) } -/// Each participant refreshes their shares This is done by taking the -/// `refreshing_share` received from the trusted dealer and adding it to the -/// original share +/// Refresh a share in the Trusted Dealer refresh procedure. +/// +/// Must be called by each participant refreshing the shares, with the +/// `refreshing_share` received from the trusted dealer and the +/// `current_key_package` of the participant. pub fn refresh_share( mut refreshing_share: SecretShare, current_key_package: &KeyPackage, @@ -108,6 +139,10 @@ pub fn refresh_share( // Verify refreshing_share secret share let refreshed_share_package = KeyPackage::::try_from(refreshing_share)?; + if refreshed_share_package.min_signers() != current_key_package.min_signers() { + return Err(Error::InvalidMinSigners); + } + let signing_share: SigningShare = SigningShare::new( refreshed_share_package.signing_share.to_scalar() + current_key_package.signing_share.to_scalar(), @@ -119,8 +154,20 @@ pub fn refresh_share( Ok(new_key_package) } -/// Part 1 of refresh share with DKG. A refreshing_key is generated and a new package and secret_package are generated. -/// The identity commitment is removed from the packages. +/// Part 1 of refresh share with DKG. +/// +/// - `identifier`: The identifier of the participant that wants to refresh +/// their share. +/// - `max_signers`: the number of participants that are refreshing their +/// shares. It can be smaller than the original value, but still equal to or +/// greater than `min_signers`. +/// - `min_signers`: the threshold needed to sign. It must be equal to the +/// original value for the group (i.e. the refresh process can't reduce +/// the threshold). +/// +/// It returns the [`round1::SecretPackage`] that must be kept in memory +/// by the participant for the other steps, and the [`round1::Package`] that +/// must be sent to each other participant in the refresh run. pub fn refresh_dkg_part_1( identifier: Identifier, max_signers: u16, @@ -164,7 +211,21 @@ pub fn refresh_dkg_part_1( Ok((secret_package, package)) } -/// Part 2 of refresh share with DKG. The identity commitment needs to be added back into the secret package. +/// Performs the second part of the refresh procedure for the +/// participant holding the given [`round1::SecretPackage`], given the received +/// [`round1::Package`]s received from the other participants. +/// +/// `round1_packages` maps the identifier of each other participant to the +/// [`round1::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// participant has between communication channels and participants, i.e. they +/// must have assurance that the [`round1::Package`] came from the participant +/// with that identifier. +/// +/// It returns the [`round2::SecretPackage`] that must be kept in memory by the +/// participant for the final step, and the map of [`round2::Package`]s that +/// must be sent to each other participant who has the given identifier in the +/// map key. pub fn refresh_dkg_part2( mut secret_package: round1::SecretPackage, round1_packages: &BTreeMap, round1::Package>, @@ -241,8 +302,28 @@ pub fn refresh_dkg_part2( )) } -/// This is the step that actually refreshes the shares. New public key packages -/// and key packages are created. +/// Performs the third and final part of the refresh procedure for the +/// participant holding the given [`round2::SecretPackage`], given the received +/// [`round1::Package`]s and [`round2::Package`]s received from the other +/// participants. +/// +/// `round1_packages` must be the same used in [`refresh_dkg_part2()`]. +/// +/// `round2_packages` maps the identifier of each other participant to the +/// [`round2::Package`] they sent to the current participant (the owner of +/// `secret_package`). These identifiers must come from whatever mapping the +/// participant has between communication channels and participants, i.e. they +/// must have assurance that the [`round2::Package`] came from the participant +/// with that identifier. +/// +/// `old_pub_key_package` and `old_key_package` are the old values from the +/// participant, which are being refreshed. +/// +/// It returns the refreshed [`KeyPackage`] that has the long-lived key share +/// for the participant, and the refreshed [`PublicKeyPackage`]s that has public +/// information about all participants; both of which are required to compute +/// FROST signatures. Note that while the verifying (group) key of the +/// [`PublicKeyPackage`] will stay the same, the verifying shares will change. pub fn refresh_dkg_shares( round2_secret_package: &round2::SecretPackage, round1_packages: &BTreeMap, round1::Package>, @@ -250,6 +331,10 @@ pub fn refresh_dkg_shares( old_pub_key_package: PublicKeyPackage, old_key_package: KeyPackage, ) -> Result<(KeyPackage, PublicKeyPackage), Error> { + if round2_secret_package.min_signers() != old_key_package.min_signers() { + return Err(Error::InvalidMinSigners); + } + // Add identity commitment back into round1_packages let mut new_round_1_packages = BTreeMap::new(); for (sender_identifier, round1_package) in round1_packages { diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 0d7d617b8..cd20ec0da 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -245,6 +245,83 @@ pub fn check_refresh_shares_with_dealer_serialisation( + mut rng: R, +) { + // Compute shares + + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + const MAX_SIGNERS: u16 = 5; + const MIN_SIGNERS: u16 = 3; + let (old_shares, pub_key_package) = generate_with_dealer( + MAX_SIGNERS, + MIN_SIGNERS, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + let mut old_key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); + + for (k, v) in old_shares { + let key_package = KeyPackage::try_from(v).unwrap(); + old_key_packages.insert(k, key_package); + } + + //////////////////////////////////////////////////////////////////////////// + // New Key generation + //////////////////////////////////////////////////////////////////////////// + + // Signer 2 will be removed and Signers 1, 3, 4 & 5 will remain + + let remaining_ids = vec![ + Identifier::try_from(1).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(4).unwrap(), + Identifier::try_from(5).unwrap(), + ]; + + const NEW_MAX_SIGNERS: u16 = 4; + const NEW_MIN_SIGNERS: u16 = 2; + + // Trusted Dealer generates zero keys and new public key package + + let (zero_shares, _new_pub_key_package) = compute_refreshing_shares( + pub_key_package, + NEW_MAX_SIGNERS, + NEW_MIN_SIGNERS, + &remaining_ids, + &mut rng, + ) + .unwrap(); + + // Each participant refreshes their share + + let mut new_shares = BTreeMap::new(); + + for i in 0..remaining_ids.len() { + let identifier = remaining_ids[i]; + let current_share = &old_key_packages[&identifier]; + let new_share = refresh_share(zero_shares[i].clone(), current_share); + new_shares.insert(identifier, new_share); + } + + assert!( + new_shares + .iter() + .all(|(_, v)| v.is_err() && matches!(v, Err(Error::InvalidMinSigners))), + "{:?}", + new_shares + ); +} + /// Test FROST signing with DKG with a Ciphersuite. pub fn check_refresh_shares_with_dkg( mut rng: R, @@ -429,3 +506,151 @@ where // Proceed with the signing test. check_sign(min_signers, key_packages, rng, pubkeys).unwrap() } + +/// Test FROST signing with DKG with a Ciphersuite, using a smaller +/// threshold than the original one. +pub fn check_refresh_shares_with_dkg_smaller_threshold< + C: Ciphersuite + PartialEq, + R: RngCore + CryptoRng, +>( + mut rng: R, +) where + C::Group: core::cmp::PartialEq, +{ + //////////////////////////////////////////////////////////////////////////// + // Old Key generation + //////////////////////////////////////////////////////////////////////////// + + let old_max_signers = 5; + let min_signers = 3; + let (old_shares, pub_key_package) = generate_with_dealer( + old_max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + let mut old_key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); + + for (k, v) in old_shares { + let key_package = KeyPackage::try_from(v).unwrap(); + old_key_packages.insert(k, key_package); + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, Round 1 + //////////////////////////////////////////////////////////////////////////// + + let max_signers = 4; + // Use a smaller threshold than the original + let min_signers = 2; + + let remaining_ids = vec![ + Identifier::try_from(4).unwrap(), + Identifier::try_from(2).unwrap(), + Identifier::try_from(3).unwrap(), + Identifier::try_from(1).unwrap(), + ]; + + // Keep track of each participant's round 1 secret package. + // In practice each participant will keep its copy; no one + // will have all the participant's packages. + let mut round1_secret_packages: BTreeMap< + frost::Identifier, + frost::keys::dkg::round1::SecretPackage, + > = BTreeMap::new(); + + // Keep track of all round 1 packages sent to the given participant. + // This is used to simulate the broadcast; in practice the packages + // will be sent through some communication channel. + let mut received_round1_packages: BTreeMap< + frost::Identifier, + BTreeMap, frost::keys::dkg::round1::Package>, + > = BTreeMap::new(); + + // For each participant, perform the first part of the DKG protocol. + // In practice, each participant will perform this on their own environments. + for participant_identifier in remaining_ids.clone() { + let (round1_secret_package, round1_package) = + refresh_dkg_part_1(participant_identifier, max_signers, min_signers, &mut rng).unwrap(); + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round1_secret_packages.insert(participant_identifier, round1_secret_package); + + // "Send" the round 1 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + for receiver_participant_identifier in remaining_ids.clone() { + if receiver_participant_identifier == participant_identifier { + continue; + } + received_round1_packages + .entry(receiver_participant_identifier) + .or_default() + .insert(participant_identifier, round1_package.clone()); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, Round 2 + //////////////////////////////////////////////////////////////////////////// + // Keep track of each participant's round 2 secret package. + // In practice each participant will keep its copy; no one + // will have all the participant's packages. + let mut round2_secret_packages = BTreeMap::new(); + + // Keep track of all round 2 packages sent to the given participant. + // This is used to simulate the broadcast; in practice the packages + // will be sent through some communication channel. + let mut received_round2_packages = BTreeMap::new(); + + // For each participant, perform the second part of the DKG protocol. + // In practice, each participant will perform this on their own environments. + for participant_identifier in remaining_ids.clone() { + let round1_secret_package = round1_secret_packages + .remove(&participant_identifier) + .unwrap(); + let round1_packages = &received_round1_packages[&participant_identifier]; + let (round2_secret_package, round2_packages) = + refresh_dkg_part2(round1_secret_package, round1_packages).expect("should work"); + + // Store the participant's secret package for later use. + // In practice each participant will store it in their own environment. + round2_secret_packages.insert(participant_identifier, round2_secret_package); + + // "Send" the round 2 package to all other participants. In this + // test this is simulated using a BTreeMap; in practice this will be + // sent through some communication channel. + // Note that, in contrast to the previous part, here each other participant + // gets its own specific package. + for (receiver_identifier, round2_package) in round2_packages { + received_round2_packages + .entry(receiver_identifier) + .or_insert_with(BTreeMap::new) + .insert(participant_identifier, round2_package); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Key generation, final computation + //////////////////////////////////////////////////////////////////////////// + + // For each participant, this is where they refresh their shares + // In practice, each participant will perform this on their own environments. + let mut results = Vec::new(); + for participant_identifier in remaining_ids.clone() { + results.push(frost::keys::refresh::refresh_dkg_shares( + &round2_secret_packages[&participant_identifier], + &received_round1_packages[&participant_identifier], + &received_round2_packages[&participant_identifier], + pub_key_package.clone(), + old_key_packages[&participant_identifier].clone(), + )); + } + + assert!(results + .iter() + .all(|r| matches!(r, Err(Error::InvalidMinSigners)))); +} diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 89adf485a..0f86b6161 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -37,7 +37,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-ed25519/src/keys/refresh.rs b/frost-ed25519/src/keys/refresh.rs index c270fc202..3e26bcb76 100644 --- a/frost-ed25519/src/keys/refresh.rs +++ b/frost-ed25519/src/keys/refresh.rs @@ -1,15 +1,17 @@ //! Refresh Shares //! -//! Implements the functionality to refresh a share. This requires the participation -//! of all the remaining signers. This can be done using a Trusted Dealer or -//! DKG (not yet implemented) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, +) -> Result<(round2::SecretPackage, BTreeMap), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, + round2_packages: &BTreeMap, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +} diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index d7b5d16bf..830f6457d 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -179,6 +179,16 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Ed25519Sha512, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; @@ -186,6 +196,15 @@ fn check_refresh_shares_with_dkg() { frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::( + rng, + ); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng; diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 1d47056a7..4d834fe86 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -36,7 +36,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-ed448/src/keys/refresh.rs b/frost-ed448/src/keys/refresh.rs index c270fc202..3e26bcb76 100644 --- a/frost-ed448/src/keys/refresh.rs +++ b/frost-ed448/src/keys/refresh.rs @@ -1,15 +1,17 @@ //! Refresh Shares //! -//! Implements the functionality to refresh a share. This requires the participation -//! of all the remaining signers. This can be done using a Trusted Dealer or -//! DKG (not yet implemented) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, +) -> Result<(round2::SecretPackage, BTreeMap), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, + round2_packages: &BTreeMap, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +} diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 872051aa1..0dfb79144 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -179,6 +179,16 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Ed448Shake256, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; @@ -186,6 +196,15 @@ fn check_refresh_shares_with_dkg() { frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::( + rng, + ); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng; diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index e69ca4552..d047e7b43 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -36,7 +36,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-p256/src/keys/refresh.rs b/frost-p256/src/keys/refresh.rs index c270fc202..3e26bcb76 100644 --- a/frost-p256/src/keys/refresh.rs +++ b/frost-p256/src/keys/refresh.rs @@ -1,15 +1,17 @@ //! Refresh Shares //! -//! Implements the functionality to refresh a share. This requires the participation -//! of all the remaining signers. This can be done using a Trusted Dealer or -//! DKG (not yet implemented) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, +) -> Result<(round2::SecretPackage, BTreeMap), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, + round2_packages: &BTreeMap, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +} diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index 33e4c2048..868cc4d68 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -179,6 +179,16 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + P256Sha256, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; @@ -186,6 +196,15 @@ fn check_refresh_shares_with_dkg() { frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::( + rng, + ); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng; diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index a1b054816..6470cf9d1 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -25,7 +25,9 @@ rand_core.workspace = true [dev-dependencies] [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 355d5b430..c9e6254ec 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -37,7 +37,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-ristretto255/src/keys/refresh.rs b/frost-ristretto255/src/keys/refresh.rs index c270fc202..3e26bcb76 100644 --- a/frost-ristretto255/src/keys/refresh.rs +++ b/frost-ristretto255/src/keys/refresh.rs @@ -1,15 +1,17 @@ //! Refresh Shares //! -//! Implements the functionality to refresh a share. This requires the participation -//! of all the remaining signers. This can be done using a Trusted Dealer or -//! DKG (not yet implemented) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, +) -> Result<(round2::SecretPackage, BTreeMap), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, + round2_packages: &BTreeMap, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +} diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index 93a5574ee..7179fabef 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -180,6 +180,16 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Ristretto255Sha512, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; @@ -187,6 +197,16 @@ fn check_refresh_shares_with_dkg() { frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::< + Ristretto255Sha512, + _, + >(rng); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng; diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index 8171d070e..c9227fb3b 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -37,7 +37,9 @@ secp256k1 = "0.31.0" serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-secp256k1-tr/src/keys/refresh.rs b/frost-secp256k1-tr/src/keys/refresh.rs index c270fc202..3e26bcb76 100644 --- a/frost-secp256k1-tr/src/keys/refresh.rs +++ b/frost-secp256k1-tr/src/keys/refresh.rs @@ -1,15 +1,17 @@ //! Refresh Shares //! -//! Implements the functionality to refresh a share. This requires the participation -//! of all the remaining signers. This can be done using a Trusted Dealer or -//! DKG (not yet implemented) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, +) -> Result<(round2::SecretPackage, BTreeMap), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, + round2_packages: &BTreeMap, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +} diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs index 2d5db0201..4933040a3 100644 --- a/frost-secp256k1-tr/tests/integration_tests.rs +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -180,6 +180,16 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Secp256K1Sha256TR, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; @@ -187,6 +197,16 @@ fn check_refresh_shares_with_dkg() { frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::< + Secp256K1Sha256TR, + _, + >(rng); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng; diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index a02795803..30e20d578 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -36,7 +36,9 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection"] +default = ["serialization", "cheater-detection", "std"] +# No longer needed. Kept for retrocompatibility until 3.0.0 +std = [] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-secp256k1/src/keys/refresh.rs b/frost-secp256k1/src/keys/refresh.rs index c270fc202..3e26bcb76 100644 --- a/frost-secp256k1/src/keys/refresh.rs +++ b/frost-secp256k1/src/keys/refresh.rs @@ -1,15 +1,17 @@ //! Refresh Shares //! -//! Implements the functionality to refresh a share. This requires the participation -//! of all the remaining signers. This can be done using a Trusted Dealer or -//! DKG (not yet implemented) +//! Refer to [`frost_core::keys::refresh`] for more details. -use crate::{frost, Ciphersuite, CryptoRng, Error, Identifier, RngCore}; -use alloc::vec::Vec; +use crate::{ + frost, + keys::dkg::{round1, round2}, + Ciphersuite, CryptoRng, Error, Identifier, RngCore, +}; +use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; -/// Refreshes shares using a trusted dealer +/// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, @@ -26,10 +28,45 @@ pub fn compute_refreshing_shares( ) } -/// Each participant refreshed their shares +/// Refer to [`frost_core::keys::refresh::refresh_share`]. pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +pub fn refresh_dkg_part1( + identifier: Identifier, + max_signers: u16, + min_signers: u16, + mut rng: R, +) -> Result<(round1::SecretPackage, round1::Package), Error> { + frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. +pub fn refresh_dkg_part2( + secret_package: round1::SecretPackage, + round1_packages: &BTreeMap, +) -> Result<(round2::SecretPackage, BTreeMap), Error> { + frost::keys::refresh::refresh_dkg_part2(secret_package, round1_packages) +} + +/// Refer to [`frost_core::keys::refresh::refresh_dkg_shares`]. +pub fn refresh_dkg_shares( + round2_secret_package: &round2::SecretPackage, + round1_packages: &BTreeMap, + round2_packages: &BTreeMap, + old_pub_key_package: PublicKeyPackage, + old_key_package: KeyPackage, +) -> Result<(KeyPackage, PublicKeyPackage), Error> { + frost::keys::refresh::refresh_dkg_shares( + round2_secret_package, + round1_packages, + round2_packages, + old_pub_key_package, + old_key_package, + ) +} diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 0676f6181..0356b0a7c 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -179,6 +179,16 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { >(max_signers, min_signers, &identifiers, error, rng); } +#[test] +fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< + Secp256K1Sha256, + _, + >(rng); +} + #[test] fn check_refresh_shares_with_dkg() { let rng = rand::rngs::OsRng; @@ -186,6 +196,15 @@ fn check_refresh_shares_with_dkg() { frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } +#[test] +fn check_refresh_shares_with_dkg_smaller_threshold() { + let rng = rand::rngs::OsRng; + + frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::( + rng, + ); +} + #[test] fn check_sign_with_dealer() { let rng = rand::rngs::OsRng; From 3ffc19d8f473d5bc4e07ed41bc884bdb42d6c29f Mon Sep 17 00:00:00 2001 From: Conrado Date: Wed, 27 Aug 2025 11:50:02 -0300 Subject: [PATCH 115/175] bump to 2.2.0 (#914) --- Cargo.toml | 6 +++--- book/src/tutorial/importing.md | 4 ++-- book/src/tutorial/signing.md | 2 +- frost-core/CHANGELOG.md | 27 +++++++++++++++++++++++---- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1ebcf7b9f..ae89d5768 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ members = [ [workspace.package] edition = "2021" rust-version = "1.81" -version = "2.1.0" +version = "2.2.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -38,8 +38,8 @@ rand_chacha = "0.3" rand_core = "0.6" serde_json = "1.0" -frost-core = { path = "frost-core", version = "2.1.0", default-features = false } -frost-rerandomized = { path = "frost-rerandomized", version = "2.1.0", default-features = false } +frost-core = { path = "frost-core", version = "2.2.0", default-features = false } +frost-rerandomized = { path = "frost-rerandomized", version = "2.2.0", default-features = false } [profile.test.package."*"] opt-level = 3 diff --git a/book/src/tutorial/importing.md b/book/src/tutorial/importing.md index 2c2dd4139..23be0455b 100644 --- a/book/src/tutorial/importing.md +++ b/book/src/tutorial/importing.md @@ -6,7 +6,7 @@ Add to your `Cargo.toml` file: ``` [dependencies] -frost-ristretto255 = "2.1.0" +frost-ristretto255 = "2.2.0" ``` ## Handling errors @@ -38,7 +38,7 @@ needs to be transmitted. The importing would look like: ``` [dependencies] -frost-ristretto255 = { version = "2.1.0", features = ["serde"] } +frost-ristretto255 = { version = "2.2.0", features = ["serde"] } ``` Note that serde usage is optional. Applications can use different encodings, and diff --git a/book/src/tutorial/signing.md b/book/src/tutorial/signing.md index e978fa42b..4a385c2af 100644 --- a/book/src/tutorial/signing.md +++ b/book/src/tutorial/signing.md @@ -9,7 +9,7 @@ channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). ## Coordinator, Round 1 To sign, the -[Coordinator](file:///home/conrado/zfnd/frost/book/book/frost.html#signing) must +[Coordinator](../frost.md#signing) must select which participants are going to generate the signature, and must signal to start the process. This needs to be implemented by users of the ZF FROST library and will depend on the communication channel being used. diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 74bb6ba22..a5f2e4218 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -2,13 +2,32 @@ Entries are listed in reverse chronological order. -## Unreleased +## 2.2.0 + +### Security Fixes -* MSRV has been bumped to Rust 1.81, making all crates no-std (except - `frost-ed448`). The `std` and `nightly` features were removed from all crates * Added validation for the `min_signers` parameter in the - `frost_core::keys::refresh` functions. + `frost_core::keys::refresh` functions. It was not clear that it is not + possible to change `min_signers` with the refresh procedure. Using a smaller + value would not decrease the threshold, and attempts to sign using a smaller + threshold would fail. Additionally, after refreshing the shares with a smaller + threshold, it would still be possible to sign with the original threshold; + however, this could cause a security loss to the participant's shares. We have + not determined the exact security implications of doing so and judged simpler + to just validate `min_signers`. If for some reason you have done a refresh + share procedure with a smaller `min_signers` we strongly recommend migrating + to a new key. Thank you [BlockSec](https://blocksec.com/) for reporting the + finding. + +### Other Changes + +* MSRV has been bumped to Rust 1.81, making all crates no-std (except + `frost-ed448`). * Added DKG refresh functions to the crate-specific `refresh` modules. +* Added `VerifiableSecretSharingCommitment::{serialize,deserialize}_whole()` + methods. +* Added `Ciphersuite::post_generate()` method to allow more ciphersuite + customization. ## 2.1.0 From 60343168626d2de6f157392457f09643064a9941 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 29 Sep 2025 13:11:25 -0300 Subject: [PATCH 116/175] core: add aggregate_custom (#911) --- frost-core/src/error.rs | 27 ++--- frost-core/src/lib.rs | 111 ++++++++++++++++---- frost-core/src/round2.rs | 2 +- frost-core/src/tests/ciphersuite_generic.rs | 77 +++++++++++--- frost-ed25519/src/lib.rs | 19 ++++ frost-ed448/src/lib.rs | 19 ++++ frost-p256/src/lib.rs | 19 ++++ frost-ristretto255/src/lib.rs | 19 ++++ frost-secp256k1/src/lib.rs | 19 ++++ 9 files changed, 260 insertions(+), 52 deletions(-) diff --git a/frost-core/src/error.rs b/frost-core/src/error.rs index 28d74f4d7..e11d9fd54 100644 --- a/frost-core/src/error.rs +++ b/frost-core/src/error.rs @@ -1,11 +1,12 @@ //! FROST Error types use crate::{Ciphersuite, Identifier}; +use alloc::vec::Vec; use thiserror::Error; /// An error related to FROST. #[non_exhaustive] -#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Error, Debug, Clone, Eq, PartialEq)] pub enum Error { /// min_signers is invalid #[error("min_signers must be at least 2 and not larger than max_signers")] @@ -62,7 +63,7 @@ pub enum Error { #[error("Invalid signature share.")] InvalidSignatureShare { /// The identifier of the signer whose share validation failed. - culprit: Identifier, + culprits: Vec>, }, /// Secret share verification failed. #[error("Invalid secret share.")] @@ -113,24 +114,18 @@ impl Error where C: Ciphersuite, { - /// Return the identifier of the participant that caused the error. - /// Returns None if not applicable for the error. + /// Return the identifiers of the participants that caused the error. + /// Returns an empty vector if not applicable for the error. /// - /// This can be used to penalize a participant that does not follow the + /// This can be used to penalize participants that do not follow the /// protocol correctly, e.g. removing them from further signings. - pub fn culprit(&self) -> Option> { + pub fn culprits(&self) -> Vec> { // Use an exhaustive match to make sure that if we add new enum items // then we will explicitly check if they should be added here. match self { - Error::InvalidSignatureShare { - culprit: identifier, - } - | Error::InvalidProofOfKnowledge { - culprit: identifier, - } => Some(*identifier), - Error::InvalidSecretShare { - culprit: identifier, - } => *identifier, + Error::InvalidSignatureShare { culprits } => culprits.clone(), + Error::InvalidProofOfKnowledge { culprit } => vec![*culprit], + Error::InvalidSecretShare { culprit } => culprit.map(|i| vec![i]).unwrap_or_default(), Error::InvalidMinSigners | Error::InvalidMaxSigners | Error::InvalidCoefficients @@ -157,7 +152,7 @@ where | Error::IncorrectNumberOfCommitments | Error::SerializationError | Error::DeserializationError - | Error::IdentifierDerivationNotSupported => None, + | Error::IdentifierDerivationNotSupported => vec![], } } } diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index a0e232be2..653ed4f2d 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -566,6 +566,50 @@ pub fn aggregate( signature_shares: &BTreeMap, round2::SignatureShare>, pubkeys: &keys::PublicKeyPackage, ) -> Result, Error> +where + C: Ciphersuite, +{ + #[cfg(feature = "cheater-detection")] + { + aggregate_custom( + signing_package, + signature_shares, + pubkeys, + CheaterDetection::FirstCheater, + ) + } + #[cfg(not(feature = "cheater-detection"))] + { + aggregate_custom( + signing_package, + signature_shares, + pubkeys, + CheaterDetection::Disabled, + ) + } +} + +/// The type of cheater detection to use. +pub enum CheaterDetection { + /// Disable cheater detection. Fast in case there are invalid + /// shares. + Disabled, + /// Detect the first cheater and stop. Performance will depend on where + /// the cheater's share is in the list. + FirstCheater, + /// Detect all cheaters. Slower since all shares must be verified. + /// Performance will be proportional on the size of participants. + AllCheaters, +} + +/// Like [`aggregate()`], but allow specifying a specific cheater detection +/// strategy. +pub fn aggregate_custom( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, round2::SignatureShare>, + pubkeys: &keys::PublicKeyPackage, + cheater_detection: CheaterDetection, +) -> Result, Error> where C: Ciphersuite, { @@ -575,12 +619,16 @@ where return Err(Error::UnknownIdentifier); } - if !signing_package.signing_commitments().keys().all(|id| { - #[cfg(feature = "cheater-detection")] - return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id); - #[cfg(not(feature = "cheater-detection"))] - return signature_shares.contains_key(id); - }) { + if !signing_package + .signing_commitments() + .keys() + .all(|id| match cheater_detection { + CheaterDetection::Disabled => signature_shares.contains_key(id), + CheaterDetection::FirstCheater | CheaterDetection::AllCheaters => { + signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id) + } + }) + { return Err(Error::UnknownIdentifier); } @@ -619,32 +667,36 @@ where // Only if the verification of the aggregate signature failed; verify each share to find the cheater. // This approach is more efficient since we don't need to verify all shares // if the aggregate signature is valid (which should be the common case). - #[cfg(feature = "cheater-detection")] - if verification_result.is_err() { - detect_cheater( - &group_commitment, - &pubkeys, - &signing_package, - &signature_shares, - &binding_factor_list, - )?; + match cheater_detection { + CheaterDetection::Disabled => { + verification_result?; + } + CheaterDetection::FirstCheater | CheaterDetection::AllCheaters => { + if verification_result.is_err() { + detect_cheater( + &group_commitment, + &pubkeys, + &signing_package, + &signature_shares, + &binding_factor_list, + cheater_detection, + )?; + } + } } - #[cfg(not(feature = "cheater-detection"))] - verification_result?; - Ok(signature) } /// Optional cheater detection feature /// Each share is verified to find the cheater -#[cfg(feature = "cheater-detection")] fn detect_cheater( group_commitment: &GroupCommitment, pubkeys: &keys::PublicKeyPackage, signing_package: &SigningPackage, signature_shares: &BTreeMap, round2::SignatureShare>, binding_factor_list: &BindingFactorList, + cheater_detection: CheaterDetection, ) -> Result<(), Error> { // Compute the per-message challenge. let challenge = ::challenge( @@ -653,6 +705,8 @@ fn detect_cheater( signing_package.message(), )?; + let mut all_culprits = Vec::new(); + // Verify the signature shares. for (identifier, signature_share) in signature_shares { // Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_, @@ -662,7 +716,7 @@ fn detect_cheater( .get(identifier) .ok_or(Error::UnknownIdentifier)?; - verify_signature_share_precomputed( + let r = verify_signature_share_precomputed( *identifier, signing_package, binding_factor_list, @@ -670,7 +724,22 @@ fn detect_cheater( signature_share, verifying_share, challenge, - )?; + ); + match r { + Ok(_) => {} + Err(Error::InvalidSignatureShare { culprits }) => { + all_culprits.extend(culprits); + if let CheaterDetection::FirstCheater = cheater_detection { + break; + } + } + Err(e) => return Err(e), + } + } + if !all_culprits.is_empty() { + return Err(Error::InvalidSignatureShare { + culprits: all_culprits, + }); } // We should never reach here; but we return an error to be safe. diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index d3147dbfa..39c7cf447 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -74,7 +74,7 @@ where + (verifying_share.to_element() * challenge.0 * lambda_i)) { return Err(Error::InvalidSignatureShare { - culprit: identifier, + culprits: vec![identifier], }); } diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 220bfb516..195c55f9d 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -342,16 +342,65 @@ fn check_aggregate_corrupted_share( mut signature_shares: BTreeMap, frost::round2::SignatureShare>, pubkey_package: frost::keys::PublicKeyPackage, ) { - use crate::round2::SignatureShare; + use crate::{round2::SignatureShare, CheaterDetection}; let one = <::Group as Group>::Field::one(); - // Corrupt a share - let id = *signature_shares.keys().next().unwrap(); - *signature_shares.get_mut(&id).unwrap() = - SignatureShare::new(signature_shares[&id].to_scalar() + one); + // Corrupt two shares + let id1 = *signature_shares.keys().next().unwrap(); + *signature_shares.get_mut(&id1).unwrap() = + SignatureShare::new(signature_shares[&id1].to_scalar() + one); + let id2 = *signature_shares.keys().nth(1).unwrap(); + *signature_shares.get_mut(&id2).unwrap() = + SignatureShare::new(signature_shares[&id2].to_scalar() + one); + let e = frost::aggregate(&signing_package, &signature_shares, &pubkey_package).unwrap_err(); - assert_eq!(e.culprit(), Some(id)); - assert_eq!(e, Error::InvalidSignatureShare { culprit: id }); + assert_eq!(e.culprits(), vec![id1]); + assert_eq!( + e, + Error::InvalidSignatureShare { + culprits: vec![id1] + } + ); + + let e = frost::aggregate_custom( + &signing_package, + &signature_shares, + &pubkey_package, + crate::CheaterDetection::Disabled, + ) + .unwrap_err(); + assert_eq!(e.culprits(), vec![]); + assert_eq!(e, Error::InvalidSignature); + + let e = frost::aggregate_custom( + &signing_package, + &signature_shares, + &pubkey_package, + crate::CheaterDetection::FirstCheater, + ) + .unwrap_err(); + assert_eq!(e.culprits(), vec![id1]); + assert_eq!( + e, + Error::InvalidSignatureShare { + culprits: vec![id1] + } + ); + + let e = frost::aggregate_custom( + &signing_package, + &signature_shares, + &pubkey_package, + CheaterDetection::AllCheaters, + ) + .unwrap_err(); + assert_eq!(e.culprits(), vec![id1, id2]); + assert_eq!( + e, + Error::InvalidSignatureShare { + culprits: vec![id1, id2] + } + ); } /// Test NCC-E008263-4VP audit finding (PublicKeyPackage). @@ -729,7 +778,7 @@ fn check_part2_error( round1_packages.get_mut(&id).unwrap().proof_of_knowledge.z = round1_packages[&id].proof_of_knowledge.z + one; let e = frost::keys::dkg::part2(round1_secret_package, &round1_packages).unwrap_err(); - assert_eq!(e.culprit(), Some(id)); + assert_eq!(e.culprits(), vec![id]); assert_eq!(e, Error::InvalidProofOfKnowledge { culprit: id }); } @@ -738,17 +787,17 @@ pub fn check_error_culprit() { let identifier: frost::Identifier = 42u16.try_into().unwrap(); let e = Error::InvalidSignatureShare { - culprit: identifier, + culprits: vec![identifier], }; - assert_eq!(e.culprit(), Some(identifier)); + assert_eq!(e.culprits(), vec![identifier]); let e = Error::InvalidProofOfKnowledge { culprit: identifier, }; - assert_eq!(e.culprit(), Some(identifier)); + assert_eq!(e.culprits(), vec![identifier]); let e: Error = Error::InvalidSignature; - assert_eq!(e.culprit(), None); + assert_eq!(e.culprits(), vec![]); } /// Test identifier derivation with a Ciphersuite @@ -930,8 +979,8 @@ fn check_verifying_shares( SignatureShare::new(signature_shares[&id].to_scalar() + one); let e = frost::aggregate(&signing_package, &signature_shares, &pubkeys).unwrap_err(); - assert_eq!(e.culprit(), Some(id)); - assert_eq!(e, Error::InvalidSignatureShare { culprit: id }); + assert_eq!(e.culprits(), vec![id]); + assert_eq!(e, Error::InvalidSignatureShare { culprits: vec![id] }); } // Checks if `verify_signature_share()` works correctly. diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 334922d00..6d756028a 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -419,6 +419,25 @@ pub fn aggregate( frost::aggregate(signing_package, signature_shares, pubkeys) } +/// The type of cheater detection to use. +pub type CheaterDetection = frost::CheaterDetection; + +/// Like [`aggregate()`], but allow specifying a specific cheater detection +/// strategy. +pub fn aggregate_custom( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &keys::PublicKeyPackage, + cheater_detection: CheaterDetection, +) -> Result { + frost::aggregate_custom( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + ) +} + /// A signing key for a Schnorr signature on FROST(Ed25519, SHA-512). pub type SigningKey = frost_core::SigningKey; diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 2acc59001..3c45775aa 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -413,6 +413,25 @@ pub fn aggregate( frost::aggregate(signing_package, signature_shares, pubkeys) } +/// The type of cheater detection to use. +pub type CheaterDetection = frost::CheaterDetection; + +/// Like [`aggregate()`], but allow specifying a specific cheater detection +/// strategy. +pub fn aggregate_custom( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &keys::PublicKeyPackage, + cheater_detection: CheaterDetection, +) -> Result { + frost::aggregate_custom( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + ) +} + /// A signing key for a Schnorr signature on FROST(Ed448, SHAKE256). pub type SigningKey = frost_core::SigningKey; diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index b21cb8dbe..c99a26fb5 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -429,6 +429,25 @@ pub fn aggregate( frost::aggregate(signing_package, signature_shares, pubkeys) } +/// The type of cheater detection to use. +pub type CheaterDetection = frost::CheaterDetection; + +/// Like [`aggregate()`], but allow specifying a specific cheater detection +/// strategy. +pub fn aggregate_custom( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &keys::PublicKeyPackage, + cheater_detection: CheaterDetection, +) -> Result { + frost::aggregate_custom( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + ) +} + /// A signing key for a Schnorr signature on FROST(P-256, SHA-256). pub type SigningKey = frost_core::SigningKey

; diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index cd376b6b7..9951aad5b 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -405,6 +405,25 @@ pub fn aggregate( frost::aggregate(signing_package, signature_shares, pubkeys) } +/// The type of cheater detection to use. +pub type CheaterDetection = frost::CheaterDetection; + +/// Like [`aggregate()`], but allow specifying a specific cheater detection +/// strategy. +pub fn aggregate_custom( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &keys::PublicKeyPackage, + cheater_detection: CheaterDetection, +) -> Result { + frost::aggregate_custom( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + ) +} + /// A signing key for a Schnorr signature on FROST(ristretto255, SHA-512). pub type SigningKey = frost_core::SigningKey; diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 9485a2317..ee9b87a77 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -429,6 +429,25 @@ pub fn aggregate( frost::aggregate(signing_package, signature_shares, pubkeys) } +/// The type of cheater detection to use. +pub type CheaterDetection = frost::CheaterDetection; + +/// Like [`aggregate()`], but allow specifying a specific cheater detection +/// strategy. +pub fn aggregate_custom( + signing_package: &SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &keys::PublicKeyPackage, + cheater_detection: CheaterDetection, +) -> Result { + frost::aggregate_custom( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + ) +} + /// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256). pub type SigningKey = frost_core::SigningKey; From 29fa065e352671aa173e58a5c6177bfa5e974bd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 16:22:35 +0000 Subject: [PATCH 117/175] build(deps): bump reviewdog/action-actionlint from 1.64.1 to 1.65.2 (#885) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.64.1 to 1.65.2. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.64.1...v1.65.2) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-version: 1.65.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 20cbcd7dc..413f49e05 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -165,7 +165,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.2.2 - - uses: reviewdog/action-actionlint@v1.64.1 + - uses: reviewdog/action-actionlint@v1.65.2 with: level: warning fail_level: none From e0d2176da19596fe4d3e6ee4092d6dc697f45105 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Fri, 3 Oct 2025 03:33:01 +0500 Subject: [PATCH 118/175] feat(frost-core): simplify trait bounds, don't use the allocator in some places (#810) * feat(frost-core): simplify trait bounds, don't use the allocator in some places * use as_slice() * Copy -> Clone * fix build --- frost-core/src/lib.rs | 5 +- frost-core/src/serialization.rs | 48 +++++++------------ frost-core/src/signature.rs | 37 +++++++------- .../src/tests/coefficient_commitment.rs | 2 +- frost-core/src/tests/repairable.rs | 10 ++-- frost-core/src/tests/vectors.rs | 3 +- frost-core/src/tests/vectors_dkg.rs | 2 + frost-core/src/tests/vss_commitment.rs | 4 +- frost-core/src/traits.rs | 16 ++++--- 9 files changed, 63 insertions(+), 64 deletions(-) diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 653ed4f2d..66b9a2e55 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -272,12 +272,13 @@ where fn from_hex>(hex: T) -> Result { let v: Vec = FromHex::from_hex(hex).map_err(|_| "invalid hex")?; - match v.try_into() { + let ret = match v.as_slice().try_into() { Ok(bytes) => <::Field>::deserialize(&bytes) .map(|scalar| Self(scalar)) .map_err(|_| "malformed scalar encoding"), Err(_) => Err("malformed scalar encoding"), - } + }; + ret } } diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index fe1df7b86..73195c16c 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -27,10 +27,8 @@ where /// Deserialize a Scalar from a serialized buffer. pub fn deserialize(bytes: &[u8]) -> Result> { - let serialized: <::Field as Field>::Serialization = bytes - .to_vec() - .try_into() - .map_err(|_| FieldError::MalformedScalar)?; + let serialized: <::Field as Field>::Serialization = + bytes.try_into().map_err(|_| FieldError::MalformedScalar)?; let scalar = <::Field>::deserialize(&serialized)?; Ok(Self(scalar)) } @@ -59,18 +57,13 @@ where where D: serde::Deserializer<'de>, { - // Get size from the size of the zero scalar + // Get serialization buffer from the zero scalar let zero = <::Field as Field>::zero(); - let len = <::Field as Field>::serialize(&zero) - .as_ref() - .len(); - - let mut bytes = vec![0u8; len]; - serdect::array::deserialize_hex_or_bin(&mut bytes[..], deserializer)?; - let array = bytes - .try_into() - .map_err(|_| serde::de::Error::custom("invalid byte length"))?; - <::Group as Group>::Field::deserialize(&array) + let mut serialization = <::Field as Field>::serialize(&zero); + + serdect::array::deserialize_hex_or_bin(serialization.as_mut(), deserializer)?; + + <::Group as Group>::Field::deserialize(&serialization) .map(|scalar| Self(scalar)) .map_err(serde::de::Error::custom) } @@ -91,10 +84,8 @@ where /// Deserialize an Element. Returns an error if it's malformed or is the /// identity. pub fn deserialize(bytes: &[u8]) -> Result> { - let serialized: ::Serialization = bytes - .to_vec() - .try_into() - .map_err(|_| FieldError::MalformedScalar)?; + let serialized: ::Serialization = + bytes.try_into().map_err(|_| FieldError::MalformedScalar)?; let scalar = ::deserialize(&serialized)?; Ok(Self(scalar)) } @@ -124,19 +115,14 @@ where where D: serde::Deserializer<'de>, { - // Get size from the size of the generator + // Get serialization buffer from the generator let generator = ::generator(); - let len = ::serialize(&generator) - .expect("serializing the generator always works") - .as_ref() - .len(); - - let mut bytes = vec![0u8; len]; - serdect::array::deserialize_hex_or_bin(&mut bytes[..], deserializer)?; - let array = bytes - .try_into() - .map_err(|_| serde::de::Error::custom("invalid byte length"))?; - ::deserialize(&array) + let mut serialization = + ::serialize(&generator).expect("serializing the generator always works"); + + serdect::array::deserialize_hex_or_bin(serialization.as_mut(), deserializer)?; + + ::deserialize(&serialization) .map(|element| Self(element)) .map_err(serde::de::Error::custom) } diff --git a/frost-core/src/signature.rs b/frost-core/src/signature.rs index 915276955..02ccaac0d 100644 --- a/frost-core/src/signature.rs +++ b/frost-core/src/signature.rs @@ -38,34 +38,31 @@ where // and get its length. Note that we can't use the identity because it can be encoded // shorter in some cases (e.g. P-256, which uses SEC1 encoding). let generator = ::generator(); - let mut R_bytes = Vec::from(::serialize(&generator)?.as_ref()); - let R_bytes_len = R_bytes.len(); + let mut R_serialization = ::serialize(&generator)?; + let R_bytes_len = R_serialization.as_ref().len(); - let one = <::Field as Field>::zero(); - let mut z_bytes = - Vec::from(<::Field as Field>::serialize(&one).as_ref()); - let z_bytes_len = z_bytes.len(); + let zero = <::Field as Field>::zero(); + let mut z_serialization = <::Field as Field>::serialize(&zero); + let z_bytes_len = z_serialization.as_ref().len(); if bytes.len() != R_bytes_len + z_bytes_len { return Err(Error::MalformedSignature); } - R_bytes[..].copy_from_slice(bytes.get(0..R_bytes_len).ok_or(Error::MalformedSignature)?); - - let R_serialization = &R_bytes.try_into().map_err(|_| Error::MalformedSignature)?; + R_serialization + .as_mut() + .copy_from_slice(bytes.get(0..R_bytes_len).ok_or(Error::MalformedSignature)?); // We extract the exact length of bytes we expect, not just the remaining bytes with `bytes[R_bytes_len..]` - z_bytes[..].copy_from_slice( + z_serialization.as_mut().copy_from_slice( bytes .get(R_bytes_len..R_bytes_len + z_bytes_len) .ok_or(Error::MalformedSignature)?, ); - let z_serialization = &z_bytes.try_into().map_err(|_| Error::MalformedSignature)?; - Ok(Self { - R: ::deserialize(R_serialization)?, - z: <::Field>::deserialize(z_serialization)?, + R: ::deserialize(&R_serialization)?, + z: <::Field>::deserialize(&z_serialization)?, }) } @@ -77,10 +74,16 @@ where /// Converts this signature to its default byte serialization. #[cfg_attr(feature = "internals", visibility::make(pub))] pub(crate) fn default_serialize(&self) -> Result, Error> { - let mut bytes = Vec::::new(); + let R_serialization = ::serialize(&self.R)?; + let z_serialization = <::Field>::serialize(&self.z); + + let R_bytes = R_serialization.as_ref(); + let z_bytes = z_serialization.as_ref(); + + let mut bytes = Vec::with_capacity(R_bytes.len() + z_bytes.len()); - bytes.extend(::serialize(&self.R)?.as_ref()); - bytes.extend(<::Field>::serialize(&self.z).as_ref()); + bytes.extend(R_bytes); + bytes.extend(z_bytes); Ok(bytes) } diff --git a/frost-core/src/tests/coefficient_commitment.rs b/frost-core/src/tests/coefficient_commitment.rs index b3392ca42..533bf5433 100644 --- a/frost-core/src/tests/coefficient_commitment.rs +++ b/frost-core/src/tests/coefficient_commitment.rs @@ -44,7 +44,7 @@ pub fn check_create_coefficient_commitment_error( let values = &commitment_helpers["elements"]; let serialized: ::Serialization = ::Serialization::try_from( - hex::decode(values["invalid_element"].as_str().unwrap()).unwrap(), + &hex::decode(values["invalid_element"].as_str().unwrap()).unwrap(), ) .debugless_unwrap(); diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index a922e320c..27136f885 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -92,9 +92,11 @@ pub fn check_rts(mut rng: R) { fn generate_scalar_from_byte_string( bs: &str, -) -> <<::Group as Group>::Field as Field>::Scalar { +) -> <::Field as Field>::Scalar { let decoded = hex::decode(bs).unwrap(); - let out = <::Field>::deserialize(&decoded.try_into().debugless_unwrap()); + let out = <::Field>::deserialize( + &decoded.as_slice().try_into().debugless_unwrap(), + ); out.unwrap() } @@ -160,7 +162,7 @@ pub fn check_repair_share_step_2(repair_share_helpers: &Value) { let expected: Scalar = repair_share_step_2::(&[value_1, value_2, value_3]); - let actual: <<::Group as Group>::Field as Field>::Scalar = + let actual: <::Field as Field>::Scalar = generate_scalar_from_byte_string::(values["random_scalar_sum"].as_str().unwrap()); assert!(actual == expected); @@ -198,7 +200,7 @@ pub fn check_repair_share_step_3( &commitment, ); - let actual_sigma: <<::Group as Group>::Field as Field>::Scalar = + let actual_sigma: <::Field as Field>::Scalar = generate_scalar_from_byte_string::(sigmas["sigma_sum"].as_str().unwrap()); let actual: SecretShare = SecretShare::new( Identifier::try_from(2).unwrap(), diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index c11b14414..950fe9a94 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -45,7 +45,8 @@ pub fn parse_test_vectors(json_vectors: &Value) -> TestVectors::Field>::deserialize(&vec.try_into().debugless_unwrap()).unwrap() + <::Field>::deserialize(&vec.as_slice().try_into().debugless_unwrap()) + .unwrap() }) .collect(); diff --git a/frost-core/src/tests/vectors_dkg.rs b/frost-core/src/tests/vectors_dkg.rs index be268d288..02d0c85ad 100644 --- a/frost-core/src/tests/vectors_dkg.rs +++ b/frost-core/src/tests/vectors_dkg.rs @@ -35,12 +35,14 @@ fn json_to_scalar( vector: &Value, ) -> <::Field as Field>::Serialization { (hex::decode(vector.as_str().unwrap()).unwrap()) + .as_slice() .try_into() .debugless_unwrap() } fn json_to_element(vector: &Value) -> ::Serialization { (hex::decode(vector.as_str().unwrap()).unwrap()) + .as_slice() .try_into() .debugless_unwrap() } diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 7b32b5ed5..6949581db 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -161,7 +161,7 @@ pub fn check_deserialize_vss_commitment_error::Serialization = ::Serialization::try_from( - hex::decode(values["invalid_element"].as_str().unwrap()).unwrap(), + &hex::decode(values["invalid_element"].as_str().unwrap()).unwrap(), ) .debugless_unwrap(); // --- @@ -194,7 +194,7 @@ pub fn check_deserialize_whole_vss_commitment_error::Serialization = ::Serialization::try_from( - hex::decode(values["invalid_element"].as_str().unwrap()).unwrap(), + &hex::decode(values["invalid_element"].as_str().unwrap()).unwrap(), ) .debugless_unwrap(); // --- diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 9714015e1..c65d7d043 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -25,7 +25,7 @@ use crate::{ /// pass-through, implemented for a type just for the ciphersuite, and calls through to another /// implementation underneath, so that this trait does not have to be implemented for types you /// don't own. -pub trait Field: Copy + Clone { +pub trait Field: Copy { /// An element of the scalar field GF(p). /// The Eq/PartialEq implementation MUST be constant-time. type Scalar: Add @@ -37,7 +37,7 @@ pub trait Field: Copy + Clone { + Sub; /// A unique byte array buf of fixed length N. - type Serialization: AsRef<[u8]> + Debug + TryFrom>; + type Serialization: Clone + AsRef<[u8]> + AsMut<[u8]> + for<'a> TryFrom<&'a [u8]> + Debug; /// Returns the zero element of the field, the additive identity. fn zero() -> Self::Scalar; @@ -85,7 +85,7 @@ pub type Scalar = <<::Group as Group>::Field as Field>::Sca /// pass-through, implemented for a type just for the ciphersuite, and calls through to another /// implementation underneath, so that this trait does not have to be implemented for types you /// don't own. -pub trait Group: Copy + Clone + PartialEq { +pub trait Group: Copy + PartialEq { /// A prime order finite field GF(q) over which all scalar values for our prime order group can /// be multiplied are defined. type Field: Field; @@ -102,7 +102,7 @@ pub trait Group: Copy + Clone + PartialEq { /// A unique byte array buf of fixed length N. /// /// Little-endian! - type Serialization: AsRef<[u8]> + Debug + TryFrom>; + type Serialization: Clone + AsRef<[u8]> + AsMut<[u8]> + for<'a> TryFrom<&'a [u8]> + Debug; /// The order of the the quotient group when the prime order subgroup divides the order of the /// full curve group. @@ -147,7 +147,7 @@ pub type Element = <::Group as Group>::Element; /// /// [FROST ciphersuite]: https://datatracker.ietf.org/doc/html/rfc9591#name-ciphersuites // See https://github.com/ZcashFoundation/frost/issues/693 for reasoning about the 'static bound. -pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { +pub trait Ciphersuite: Copy + PartialEq + Debug + 'static { /// The ciphersuite ID string. It should be equal to the contextString in /// the spec. For new ciphersuites, this should be a string that identifies /// the ciphersuite; it's recommended to use a similar format to the @@ -162,7 +162,11 @@ pub trait Ciphersuite: Copy + Clone + PartialEq + Debug + 'static { /// A unique byte array of fixed length that is the `Group::ElementSerialization` + /// `Group::ScalarSerialization` - type SignatureSerialization: AsRef<[u8]> + TryFrom>; + type SignatureSerialization: Clone + + AsRef<[u8]> + + AsMut<[u8]> + + for<'a> TryFrom<&'a [u8]> + + Debug; /// [H1] for a FROST ciphersuite. /// From 80fd87eb0db11d58432b5c5f6eaee6816d3cbdd8 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 6 Oct 2025 09:38:23 -0300 Subject: [PATCH 119/175] ci: install clippy (#923) --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 413f49e05..219d4c7ff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -92,6 +92,8 @@ jobs: persist-credentials: false - uses: dtolnay/rust-toolchain@stable + with: + components: clippy - name: Check workflow permissions id: check_permissions From 76dc3a2efb459e82cc246bd63af3c0327145b0ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 13:59:34 -0300 Subject: [PATCH 120/175] build(deps): bump reviewdog/action-actionlint from 1.65.2 to 1.68.0 (#938) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.65.2 to 1.68.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.65.2...v1.68.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-version: 1.68.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 219d4c7ff..17aad0ad4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -167,7 +167,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v4.2.2 - - uses: reviewdog/action-actionlint@v1.65.2 + - uses: reviewdog/action-actionlint@v1.68.0 with: level: warning fail_level: none From dbaa19007ad1d8f8466773c7fa2c08260479bc90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:00:54 -0300 Subject: [PATCH 121/175] build(deps): bump clechasseur/rs-clippy-check from 4 to 5 (#928) Bumps [clechasseur/rs-clippy-check](https://github.com/clechasseur/rs-clippy-check) from 4 to 5. - [Release notes](https://github.com/clechasseur/rs-clippy-check/releases) - [Commits](https://github.com/clechasseur/rs-clippy-check/compare/v4...v5) --- updated-dependencies: - dependency-name: clechasseur/rs-clippy-check dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 17aad0ad4..9a00ee4b8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,7 +104,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run clippy action to produce annotations - uses: clechasseur/rs-clippy-check@v4 + uses: clechasseur/rs-clippy-check@v5 if: ${{ steps.check_permissions.outputs.has-permission }} with: args: --all-features --all-targets -- -D warnings From aad8b8ac34d23b16046e72028cf2dbac7d959ed6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:04:38 -0300 Subject: [PATCH 122/175] build(deps): bump codecov/codecov-action from 5.3.1 to 5.5.1 (#924) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.3.1 to 5.5.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.3.1...v5.5.1) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index dece19699..1cf1a1cdc 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -41,4 +41,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v5.3.1 + uses: codecov/codecov-action@v5.5.1 From 72c48478f619d3e49da9f2693c83f782d15ad198 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:13:54 -0300 Subject: [PATCH 123/175] build(deps): bump actions/checkout from 4.2.2 to 5.0.0 (#925) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 5.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.2.2...v5.0.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 1cf1a1cdc..200f08835 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -23,7 +23,7 @@ jobs: RUST_BACKTRACE: full steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 with: persist-credentials: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d587aa240..eb62d5d8b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the source code - uses: actions/checkout@v4.2.2 + uses: actions/checkout@v6.0.0 with: persist-credentials: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a00ee4b8..94893ede5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 - uses: dtolnay/rust-toolchain@beta - run: cargo build @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 # Re-resolve Cargo.lock with minimal versions. # This only works with nightly. We pin to a specific version because # newer versions use lock file version 4, but the MSRV cargo does not @@ -45,7 +45,7 @@ jobs: # runs-on: ubuntu-latest # steps: - # - uses: actions/checkout@v4.2.2 + # - uses: actions/checkout@v6.0.0 # - uses: dtolnay/rust-toolchain@stable # - run: cargo install cargo-all-features # # We check and then test because some test dependencies could help @@ -65,7 +65,7 @@ jobs: matrix: crate: [ristretto255, ed25519, p256, secp256k1, secp256k1-tr, rerandomized] steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -78,7 +78,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 - uses: dtolnay/rust-toolchain@beta - run: cargo test --release --all-features @@ -87,7 +87,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 with: persist-credentials: false @@ -118,7 +118,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 with: persist-credentials: false @@ -135,7 +135,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 with: persist-credentials: false @@ -154,7 +154,7 @@ jobs: RUSTDOCFLAGS: -D warnings steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 with: persist-credentials: false @@ -166,7 +166,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6.0.0 - uses: reviewdog/action-actionlint@v1.68.0 with: level: warning From 40edb6e351760cb72b523a9ea180eba011675dc1 Mon Sep 17 00:00:00 2001 From: Conrado Date: Thu, 27 Nov 2025 18:31:07 -0300 Subject: [PATCH 124/175] ci: commit Cargo.lock, update crypto-common to avoid generic-array deprecation warning (#939) --- .gitignore | 1 - Cargo.lock | 1714 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1714 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 5d3d773b8..bbb076267 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ /target **/*.rs.bk -Cargo.lock *~ **/.DS_Store .vscode/* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..2ca16f4bb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1714 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "const-crc32-nostd" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808ac43170e95b11dd23d78aa9eaac5bea45776a602955552c4e833f3f0f823d" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "criterion" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto 0.2.9", + "rand_core 0.6.4", + "rustc_version", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "debugless-unwrap" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f400d0750c0c069e8493f2256cb4da6f604b6d2eeb69a0ca8863acde352f8400" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derive-getters" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "ed448-goldilocks" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88322282bccdc6fa7ab65b0c30cb877fba541547653436d08bb775fa4a4307b4" +dependencies = [ + "fiat-crypto 0.1.20", + "hex", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "frost-core" +version = "2.2.0" +dependencies = [ + "byteorder", + "const-crc32-nostd", + "criterion", + "debugless-unwrap", + "derive-getters", + "document-features", + "hex", + "itertools 0.14.0", + "lazy_static", + "postcard", + "proptest", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "serde_json", + "serdect", + "thiserror", + "visibility", + "zeroize", +] + +[[package]] +name = "frost-ed25519" +version = "2.2.0" +dependencies = [ + "criterion", + "curve25519-dalek", + "document-features", + "ed25519-dalek", + "frost-core", + "frost-rerandomized", + "hex", + "insta", + "lazy_static", + "proptest", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde_json", + "sha2", +] + +[[package]] +name = "frost-ed448" +version = "2.2.0" +dependencies = [ + "criterion", + "document-features", + "ed448-goldilocks", + "frost-core", + "frost-rerandomized", + "hex", + "insta", + "lazy_static", + "proptest", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde_json", + "sha3", +] + +[[package]] +name = "frost-p256" +version = "2.2.0" +dependencies = [ + "criterion", + "document-features", + "frost-core", + "frost-rerandomized", + "hex", + "insta", + "lazy_static", + "p256", + "proptest", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde_json", + "sha2", +] + +[[package]] +name = "frost-rerandomized" +version = "2.2.0" +dependencies = [ + "derive-getters", + "document-features", + "frost-core", + "hex", + "rand_core 0.6.4", +] + +[[package]] +name = "frost-ristretto255" +version = "2.2.0" +dependencies = [ + "criterion", + "curve25519-dalek", + "document-features", + "frost-core", + "frost-rerandomized", + "hex", + "insta", + "lazy_static", + "postcard", + "proptest", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde_json", + "sha2", +] + +[[package]] +name = "frost-secp256k1" +version = "2.2.0" +dependencies = [ + "criterion", + "document-features", + "frost-core", + "frost-rerandomized", + "hex", + "insta", + "k256", + "lazy_static", + "proptest", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde_json", + "sha2", +] + +[[package]] +name = "frost-secp256k1-tr" +version = "2.2.0" +dependencies = [ + "criterion", + "document-features", + "frost-core", + "frost-rerandomized", + "hex", + "insta", + "k256", + "lazy_static", + "proptest", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "secp256k1", + "serde_json", + "sha2", +] + +[[package]] +name = "gencode" +version = "0.1.0" +dependencies = [ + "regex", + "serde_json", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "insta" +version = "1.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b246c455fbf8e7bdda56a226b525b24b601c0bbe15458beb72412678319cda5a" +dependencies = [ + "console", + "once_cell", + "serde", + "similar", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "elliptic-curve", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "elliptic-curve", + "primeorder", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.3", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.2", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] From 1882937ed8bae5810b8a49e5f442e9afbacc0607 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 20:57:36 +0000 Subject: [PATCH 125/175] build(deps): bump reviewdog/action-actionlint from 1.68.0 to 1.69.0 (#944) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.68.0 to 1.69.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.68.0...v1.69.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-version: 1.69.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94893ede5..4db655793 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -167,7 +167,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v6.0.0 - - uses: reviewdog/action-actionlint@v1.68.0 + - uses: reviewdog/action-actionlint@v1.69.0 with: level: warning fail_level: none From 4431ca12c40d1c26059a32c62c8f55d25f0a2f06 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:50:53 +0000 Subject: [PATCH 126/175] ci(mergify): upgrade configuration to current format (#934) Co-authored-by: Mergify <37929162+mergify[bot]@users.noreply.github.com> --- .mergify.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.mergify.yml b/.mergify.yml index 491837eb1..7a64a0f3d 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -9,7 +9,6 @@ queue_rules: # which are the same as the GitHub main branch protection rules # https://docs.mergify.com/conditions/#about-branch-protection - base=main - allow_inplace_checks: true batch_size: 2 # Wait for a few minutes to embark 2 tickets together in a merge train batch_max_wait_time: "3 minutes" From 43c88274e4e000a58e85f0e0805604ee7d1843f7 Mon Sep 17 00:00:00 2001 From: Conrado Date: Tue, 9 Dec 2025 07:30:44 -0300 Subject: [PATCH 127/175] all: remove cheater-detection feature (#958) --- frost-core/CHANGELOG.md | 11 ++++++++++ frost-core/Cargo.toml | 4 +--- frost-core/src/lib.rs | 24 ++++++--------------- frost-core/src/tests/ciphersuite_generic.rs | 9 -------- frost-ed25519/Cargo.toml | 4 +--- frost-ed448/Cargo.toml | 4 +--- frost-p256/Cargo.toml | 4 +--- frost-rerandomized/Cargo.toml | 4 +--- frost-ristretto255/Cargo.toml | 4 +--- frost-secp256k1-tr/Cargo.toml | 4 +--- frost-secp256k1/Cargo.toml | 4 +--- 11 files changed, 25 insertions(+), 51 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index a5f2e4218..80d2fc84c 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -2,6 +2,17 @@ Entries are listed in reverse chronological order. +## Unreleases + +### Breaking Changes + +* The `cheater-detection` feature was removed. If you relied on it (either by + using the default features, or by explicitly enabling it), then you don't have + to do anything (other than not enabling it explicitly if you were doing so); + the default behaviour is now as if `cheater-detection` was enabled. If you + explicitly *did not enable* it, you can avoid cheater detection by calling + `aggregate_custom()` with `CheaterDetection::Disabled`. + ## 2.2.0 ### Security Fixes diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 64d6088f4..9e7eb0c7b 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -45,7 +45,7 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 std = [] #! ## Features @@ -60,8 +60,6 @@ serde = ["dep:serde", "dep:serdect"] serialization = ["serde", "dep:postcard"] # Exposes ciphersuite-generic tests for other crates to use test-impl = ["dep:proptest", "dep:serde_json", "dep:criterion"] -# Enable cheater detection -cheater-detection = [] [lib] bench = false diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 66b9a2e55..2d6fb29ac 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -570,24 +570,12 @@ pub fn aggregate( where C: Ciphersuite, { - #[cfg(feature = "cheater-detection")] - { - aggregate_custom( - signing_package, - signature_shares, - pubkeys, - CheaterDetection::FirstCheater, - ) - } - #[cfg(not(feature = "cheater-detection"))] - { - aggregate_custom( - signing_package, - signature_shares, - pubkeys, - CheaterDetection::Disabled, - ) - } + aggregate_custom( + signing_package, + signature_shares, + pubkeys, + CheaterDetection::FirstCheater, + ) } /// The type of cheater detection to use. diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 195c55f9d..ce462133c 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -315,14 +315,6 @@ fn check_aggregate_errors( signature_shares: BTreeMap, frost::round2::SignatureShare>, pubkey_package: frost::keys::PublicKeyPackage, ) { - #[cfg(not(feature = "cheater-detection"))] - let pubkey_package = PublicKeyPackage { - header: pubkey_package.header, - verifying_shares: BTreeMap::new(), - verifying_key: pubkey_package.verifying_key, - }; - - #[cfg(feature = "cheater-detection")] check_aggregate_corrupted_share( signing_package.clone(), signature_shares.clone(), @@ -336,7 +328,6 @@ fn check_aggregate_errors( ); } -#[cfg(feature = "cheater-detection")] fn check_aggregate_corrupted_share( signing_package: frost::SigningPackage, mut signature_shares: BTreeMap, frost::round2::SignatureShare>, diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 0f86b6161..eb456a1e0 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -37,7 +37,7 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 std = [] #! ## Features @@ -47,8 +47,6 @@ std = [] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 4d834fe86..79a1d56df 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -36,7 +36,7 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 std = [] #! ## Features @@ -46,8 +46,6 @@ std = [] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index d047e7b43..029977790 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -36,7 +36,7 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 std = [] #! ## Features @@ -46,8 +46,6 @@ std = [] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 6470cf9d1..c255253e7 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -25,7 +25,7 @@ rand_core.workspace = true [dev-dependencies] [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 std = [] #! ## Features @@ -35,7 +35,5 @@ std = [] serde = ["frost-core/serde"] # Exposes ciphersuite-generic tests for other crates to use test-impl = ["frost-core/test-impl", "serialization"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization"] diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index c9e6254ec..98c670884 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -37,7 +37,7 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 std = [] #! ## Features @@ -47,8 +47,6 @@ std = [] serde = ["frost-core/serde", "curve25519-dalek/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index c9227fb3b..9d116b342 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -37,7 +37,7 @@ secp256k1 = "0.31.0" serde_json.workspace = true [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 std = [] #! ## Features @@ -47,8 +47,6 @@ std = [] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index 30e20d578..113a6c8a6 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -36,7 +36,7 @@ rand_chacha.workspace = true serde_json.workspace = true [features] -default = ["serialization", "cheater-detection", "std"] +default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 std = [] #! ## Features @@ -46,8 +46,6 @@ std = [] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] -## Enable cheater detection -cheater-detection = ["frost-core/cheater-detection", "frost-rerandomized/cheater-detection"] [lib] # Disables non-criterion benchmark which is not used; prevents errors From 18d4ed14606a76524e48c5a4034b8f0127d0f62f Mon Sep 17 00:00:00 2001 From: Conrado Date: Tue, 9 Dec 2025 07:30:47 -0300 Subject: [PATCH 128/175] dependabot: update lockfile only (#959) --- .github/dependabot.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 75dc8828b..7c0e7e5c2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,6 +8,9 @@ updates: open-pull-requests-limit: 10 - package-ecosystem: cargo directory: "/" + # Update only the lockfile. We shouldn't update Cargo.toml unless it's for + # a security issue, or if we need a new feature of the dependency. + versioning-strategy: lockfile-only schedule: interval: monthly timezone: America/New_York From ca1df5f0c464d36e2047cd91e03ae27df0d167df Mon Sep 17 00:00:00 2001 From: Conrado Date: Tue, 9 Dec 2025 07:35:08 -0300 Subject: [PATCH 129/175] ci: test with latest versions of dependencies (#961) --- .github/workflows/main.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4db655793..edf48e528 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,6 +18,15 @@ jobs: - uses: dtolnay/rust-toolchain@beta - run: cargo build + build_latest: + name: build with latest versions of dependencies + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6.0.0 + - uses: dtolnay/rust-toolchain@stable + - run: cargo update && cargo build --all-features + build_msrv: name: build with MSRV (1.81) runs-on: ubuntu-latest From ee450bda85e165dbe2e873546ae08ab0f855bafa Mon Sep 17 00:00:00 2001 From: Conrado Date: Thu, 18 Dec 2025 12:01:51 -0300 Subject: [PATCH 130/175] book: add devtool demo section (#968) --- book/src/SUMMARY.md | 1 + book/src/zcash/devtool-demo.md | 333 +++++++++++++++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 book/src/zcash/devtool-demo.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 004ef64de..1b2584b44 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -12,6 +12,7 @@ - [Serialization Format](user/serialization.md) - [FROST with Zcash](zcash.md) - [Technical Details](zcash/technical-details.md) + - [zcash-devtool Demo](zcash/devtool-demo.md) - [Ywallet Demo](zcash/ywallet-demo.md) - [FROST Server](zcash/server.md) - [Terminology](terminology.md) diff --git a/book/src/zcash/devtool-demo.md b/book/src/zcash/devtool-demo.md new file mode 100644 index 000000000..e8e713ae9 --- /dev/null +++ b/book/src/zcash/devtool-demo.md @@ -0,0 +1,333 @@ +# zcash-devtool Tutorial + +This tutorial explaining how to use FROST to sign a Zcash transaction using +[zcash-devtool](https://github.com/zcash/zcash-devtool). + + +## Setting up + +Install `cargo` and `git`. + +Install the `zcash-devtool`: + +``` +cargo install --git https://github.com/zcash/zcash-devtool.git --locked +``` + +Install the `frost-client` tool: + +``` +cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked frost-client +``` + +Install the `zcash-sign` tool: + +``` +cargo install --git https://github.com/ZcashFoundation/frost-zcash-demo.git --locked zcash-sign +``` + +Switch to an empty folder which will store the files generated in the demo. +For example: + +``` +mkdir frost-demo +cd frost-demo/ +``` + + +### Running the server + +This demo uses the ZF FROST server (frostd) to help participants communicate. +While in practice users would use an existing online server, for the demo you +can run a local server by following [these instructions](./server.md) (the +"Compiling, Running and Deploying" and "Local Testing" sections). + +The rest of the tutorial assumes the server is up and running. + + +### Initializing the users + +Run the following command to initialize three users (in practice, each user +would run a similar command, but for demo purposes we're assuming +you will simulate all of them in the same machine, so run these +commands in your machine): + +``` +frost-client init -c alice.toml +frost-client init -c bob.toml +frost-client init -c eve.toml +``` + +This will create a config file for three users; Alice, Bob and Eve. + +```admonish note +If you really want to run the demo in separate machines, then you can omit the +`-c alice.toml` part of the command (i.e. run `frost-client init`); it will +save to a default location in the user's home directory. +``` + + +## Generating FROST key shares + +First we will generate the FROST key shares. For simplicity we'll use trusted +dealer; if you want to use Distributed Key Generation, skip to the next section. + +In a new terminal (in case the previous terminal is running the server), run the +following: + +``` +frost-client trusted-dealer -d "Alice, Bob and Eve's group" --names Alice,Bob,Eve -c alice.toml -c bob.toml -c eve.toml -C redpallas +``` + +This will by default generate a 2-of-3 key shares. The key shares will be +written into each participant's config file. You can change the threhsold, +number of shares and file names using the command line; append `-h` to the +commend above for the command line help. + + +## Generating FROST key shares using DKG + +For real-word usage we commend generating key shares using Distributed Key +Generation. If you did the previous section, skip to "Generating the Full +Viewing Key for the wallet". + + +```admonish note +This section assumes each participant is running the commands in their own +machine. If you want to simulate all of them in a single machine, +specify the config file for the user (e.g. `-c alice.toml`) accordingly. +``` + + +### Initializing config files + +If they haven't yet, each participant should run: + +``` +frost-client init +``` + + +### Sharing contacts + +Each participant must now generate a contact string that they will need to share +with the other participants. This contact string will include a name, which they +can choose when exporting and will be shown to whoever they send the contact to. + +Run the following, substituting the name accordingly: + +``` +frost-client export --name 'Alice' +``` + +The command will print an encoded contact string such as +`zffrost1qyqq2stvd93k2g84hudcr98zp67a9rnx9v00euw9e5424hjathvre7ymy344fynjdvxmwxfg`. +Send it to the other participants using some trusted communication channel +(instant messaging, etc.). + +The other participants will send you their contacts. Import them by running the +following command for each contact (replace `` with the contact +string accordingly): + +``` +frost-client import +``` + + +### Generating shares + +Finally, to generate the shares, one of the participants will need to initiate +the process. They will need to public key of each participant, so they need to +first list them with the following command: + +``` +frost-client contacts +``` + +Then run the following command, replacing the `` and `` hex +strings with the public keys of the contacts which will participate (along with +the user running the command): + +``` +frost-client dkg -d "Alice, Bob and Eve's group" -s localhost:2744 -S , -t 2 -C redpallas -c alice.toml +``` + +The user should then notify the others that a signing session has started (e.g. +via instant messaging again), and also share the threshold number that was used. +They should then run the following, replacing the name of the group if they wish +and the threshold number with the one given by the first participant. + +``` +frost-client dkg -d "Alice, Bob and Eve's group" -s localhost:2744 -t 2 -C redpallas +``` + +```admonish note +A future version might not require specifying the threshold and group name. +``` + + +## Generating the Full Viewing Key for the wallet + +Next, we will need to generate a Zcash Full Viewing Key from the FROST group +material we have just generated; this address will then be imported into a wallet +so that we'll be able to create Zcash transactions for it. + +Run the following command: + +``` +frost-client groups +``` + +It will list all groups you're in - at this point it should list the only one +you have just created. Copy the Public Key it shows (it will look like e.g. +`79d6bcee79c88ad9ba259067772b97f5de12f1435b474d03bc98f255be08a610`) + +The run the following command, replacing `` with the value you copied, +and `test` with `main` if you're using Mainnet. + +``` +zcash-sign generate --net test --ak +``` + +It will print an Orchard address, and a Unified Full Viewing Key. Copy and +paste both somewhere to use them later. + + +## Importing the Full Viewing Key into zcash-devtool + +In the zcash-devtool folder, run the following, replacing `` with the +UFVK printed in the last step: + +``` +zcash-devtool wallet -w ./.frost.view/ init-fvk --name FROST_wallet --fvk --birthday 3720000 -s zecrocks +``` + +(Change `./.frost-view` or `FROST_wallet` if you want to change the folder or +name of the wallet.) + + +## Funding the wallet + +Now you will need to fund this wallet with some ZEC. Send ZEC to that address +using another account (or try [ZecFaucet](https://zecfaucet.com/)). + +## Creating the transaction + +Run the following, replacing `` with the address you want to ZEC to, +`` with the value you want to send in Zatoshis, `` with the +memo you want to send: + +``` +zcash-devtool pczt -w ./.frost.view/ create --address --value --memo > frost_pczt.created +``` + + +## Signing the transaction + +Now you will need to simulate two participants and a Coordinator to sign the +transaction, and you should still have the FROST server running which will +handle communications between them. It's probably easier to open three new +terminals. + +Go back to the signer terminal and run the following, replacing `` +with the path to the file you saved in the previous step, and `` +with the path where you want to write the signed transaction (e.g. +`frost_pczt.signed`). + +``` +zcash-sign sign -n test --tx-plan frost_pczt.created -o frost_pczt.signed +``` + +(Replace `test` with `main` if you're using Mainnet.) + +The program will print a SIGHASH and a Randomizer, and will prompt for a +signature. This is what you will get after running FROST, so let's do that; +leave the prompt there without typing anything. + + +### Coordinator + +In the second terminal, the Coordinator, run (in the same folder where you +initialized the users and ran the key generation) the following: + +``` +frost-client groups -c alice.toml +``` + +This will list the groups Alice is in; it should only list the one you created +earlier. You will need to copy some values in the command. Run the following, +replacing the value after `` with the "Public key" listed for the group; +replacing `` and `` with the public keys of Alice and Bob (the +hexadecimal values printed next to their names; Alice's name will be empty to +indicate it's her own). + +``` +frost-client coordinator -c alice.toml --server-url localhost:2744 --group -S , -m - -r - +``` + +It will prompt you for a message. Paste the SIGHASH generated with the +`zcash-sign` tool and press enter. It will then prompt for a randomizer. Paste +the one generated with the `zcash-sign` tool and press enter. + +The tool will connect to the server and wait for the other participants. + +```admonish warning +If you prefer to pass the message (SIGHASH) or randomizer as files by using +the `-m` and `-r` arguments, you will need to convert them to binary format. +``` + + +### Participant 1 (Alice) + +In the third terminal, Participant 1, run the following (replacing `` +with the same group public key used in the previous command): + +``` +frost-client participant -c alice.toml --server-url localhost:2744 --group +``` + +(We are using "Alice" again. There's nothing stopping a Coordinator from being a +Partcipant too!) + + +### Participant 2 (Bob) + +In the fourth terminal, for Participant 2, run the following (replacing `` +again): + +``` +frost-client participant -c bob.toml --server-url localhost:2744 --group +``` + + +### Coordinator + +Go back to the Coordinator CLI. The protocol should run and complete +successfully. It will print the final FROST-generated signature. Hurrah! Copy it +(just the hex value). + +Go back to the signer and paste the signature. It will write the signed +transaction to the file you specified. + +## Proving the transaction + +You will need to prove the transaction separately: + +``` +cargo run -p zcash-sign -- sign -n test --tx-plan frost_pczt.created -o frost_pczt.signed +``` + +And then combine signed and proven into a final transaction: + +``` +zcash-devtool pczt combine -i frost_pczt.signed -i frost_pczt.proven > frost_pczt.combined +``` + + +## Broadcasting the transaction + +Run: + +``` +zcash-devtool pczt -w ./.frost.view/ send -s zecrocks < test_pczt.combined +``` From 705acde7d07263dc63dad0f3adc04303d50aa26d Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 09:21:24 -0300 Subject: [PATCH 131/175] core: fix unused warning due to compiler bug (#971) --- frost-core/src/keys.rs | 2 ++ frost-core/src/round1.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index c9f02f2f2..7785452a2 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -1,5 +1,7 @@ //! FROST keys, keygen, key shares #![allow(clippy::type_complexity)] +// Remove after https://github.com/rust-lang/rust/issues/147648 is fixed +#![allow(unused_assignments)] use core::iter; diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 88cdeac1f..7c3b10c08 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -1,4 +1,6 @@ //! FROST Round 1 functionality and types +// Remove after https://github.com/rust-lang/rust/issues/147648 is fixed +#![allow(unused_assignments)] use alloc::{ collections::BTreeMap, From a454d0233fe2709c1cd2dd0f9f14713d63770cd2 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 09:21:26 -0300 Subject: [PATCH 132/175] docs: explain origin of identifiers in aggregate() (#956) --- book/src/tutorial/signing.md | 13 +++++++++++++ frost-core/src/lib.rs | 7 +++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/book/src/tutorial/signing.md b/book/src/tutorial/signing.md index 4a385c2af..ecd249ef2 100644 --- a/book/src/tutorial/signing.md +++ b/book/src/tutorial/signing.md @@ -100,6 +100,19 @@ What should be done in that case is up to the application. The misbehaving parti could be excluded from future signing sessions, for example. ``` +```admonish danger +In `aggregate()` you need to provide a map from `Identifier` to +`SignatureShare`. If you need cheater detection, then it is important that these +identifiers come from a mapping between authenticated channels and identifiers; +i.e. you should not simply send the `Identifier` along with the +`SignatureShare`; otherwise the cheater could simply lie about their identifier. + +For example, if you authenticate the communication channels with TLS, then you +will need to create a public key -> identifier mapping, and use that mapping +to get the identifier for the connection where the `SignatureShare` was read +from. +``` + ## Verifying signatures diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 2d6fb29ac..b5b1620c6 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -551,7 +551,8 @@ where /// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping /// the coordinator has between communication channels and participants, i.e. /// they must have assurance that the [`round2::SignatureShare`] came from -/// the participant with that identifier. +/// the participant with that identifier. (This means that you *MUST NOT* send +/// the identifier along with the [`round2::SignatureShare`].) /// /// This operation is performed by a coordinator that can communicate with all /// the signing participants before publishing the final signature. The @@ -592,7 +593,9 @@ pub enum CheaterDetection { } /// Like [`aggregate()`], but allow specifying a specific cheater detection -/// strategy. +/// strategy. If you are disabling cheater detection, then the identifiers +/// in `signature_shares` do not need to correspond to the senders (i.e. +/// you don't need to authenticate the origin of the shares). pub fn aggregate_custom( signing_package: &SigningPackage, signature_shares: &BTreeMap, round2::SignatureShare>, From 4827fd28e866c7cc554a65b32a854cd871bfe94c Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 09:49:30 -0300 Subject: [PATCH 133/175] core: fix `dkg::round2::SecretPackage` serialization (#937) * add test to reproduce the bug * core: remove identity of frost_core::keys::dkg::round2::SecretPackage before returning it; add it back when necessary * additional roundtrip test --- frost-core/src/keys/refresh.rs | 13 +++++++++ frost-core/src/tests/ciphersuite_generic.rs | 27 +++++++++++++++--- frost-core/src/tests/refresh.rs | 31 +++++++++++++++++---- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index 3815aec5a..57691ea08 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -290,6 +290,8 @@ pub fn refresh_dkg_part2( } let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients()); + // We remove the identity again to make it serializable + secret_package.commitment.0.remove(0); Ok(( round2::SecretPackage::new( secret_package.identifier, @@ -335,6 +337,17 @@ pub fn refresh_dkg_shares( return Err(Error::InvalidMinSigners); } + // Add identity commitment back into the round2_secret_package + let mut commitment = round2_secret_package.commitment.0.clone(); + commitment.insert(0, CoefficientCommitment::new(C::Group::identity())); + let round2_secret_package = round2::SecretPackage::new( + round2_secret_package.identifier, + VerifiableSecretSharingCommitment::::new(commitment), + round2_secret_package.secret_share.0, + round2_secret_package.min_signers, + round2_secret_package.max_signers, + ); + // Add identity commitment back into round1_packages let mut new_round_1_packages = BTreeMap::new(); for (sender_identifier, round1_package) in round1_packages { diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index ce462133c..86021b595 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -5,6 +5,7 @@ use alloc::{borrow::ToOwned, collections::BTreeMap, vec::Vec}; use rand_core::{CryptoRng, RngCore}; use crate as frost; +use crate::keys::dkg::{round1, round2}; use crate::keys::SigningShare; use crate::round2::SignatureShare; use crate::{ @@ -454,7 +455,12 @@ where // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. - round1_secret_packages.insert(participant_identifier, round1_secret_package); + round1_secret_packages.insert( + participant_identifier, + // Serialization roundtrip to simulate storage for later + round1::SecretPackage::deserialize(&round1_secret_package.serialize().unwrap()) + .unwrap(), + ); // "Send" the round 1 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be @@ -469,7 +475,11 @@ where received_round1_packages .entry(receiver_participant_identifier) .or_default() - .insert(participant_identifier, round1_package.clone()); + .insert( + participant_identifier, + // Serialization roundtrip to simulate communication + round1::Package::deserialize(&round1_package.serialize().unwrap()).unwrap(), + ); } } @@ -501,7 +511,12 @@ where // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. - round2_secret_packages.insert(participant_identifier, round2_secret_package); + round2_secret_packages.insert( + participant_identifier, + // Serialization roundtrip to simulate storage for later + round2::SecretPackage::deserialize(&round2_secret_package.serialize().unwrap()) + .unwrap(), + ); // "Send" the round 2 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be @@ -512,7 +527,11 @@ where received_round2_packages .entry(receiver_identifier) .or_insert_with(BTreeMap::new) - .insert(participant_identifier, round2_package); + .insert( + participant_identifier, + // Serialization roundtrip to simulate communication + round2::Package::deserialize(&round2_package.serialize().unwrap()).unwrap(), + ); } } diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index cd20ec0da..83b67b5fa 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -2,6 +2,7 @@ use rand_core::{CryptoRng, RngCore}; +use crate::keys::dkg::{round1, round2}; use crate::keys::generate_with_dealer; use crate::keys::refresh::{ compute_refreshing_shares, refresh_dkg_part2, refresh_dkg_part_1, refresh_share, @@ -76,7 +77,9 @@ pub fn check_refresh_shares_with_dealer( for i in 0..remaining_ids.len() { let identifier = remaining_ids[i]; let current_share = &old_key_packages[&identifier]; - let new_share = refresh_share(zero_shares[i].clone(), current_share); + // Do a serialization roundtrip to simulate real usage + let zero_share = SecretShare::deserialize(&zero_shares[i].serialize().unwrap()).unwrap(); + let new_share = refresh_share(zero_share, current_share); new_shares.insert(identifier, new_share); } @@ -388,7 +391,12 @@ where // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. - round1_secret_packages.insert(participant_identifier, round1_secret_package); + round1_secret_packages.insert( + participant_identifier, + // Serialization roundtrip to simulate storage for later + round1::SecretPackage::deserialize(&round1_secret_package.serialize().unwrap()) + .unwrap(), + ); // "Send" the round 1 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be @@ -400,7 +408,11 @@ where received_round1_packages .entry(receiver_participant_identifier) .or_default() - .insert(participant_identifier, round1_package.clone()); + .insert( + participant_identifier, + // Serialization roundtrip to simulate communication + round1::Package::deserialize(&round1_package.serialize().unwrap()).unwrap(), + ); } } @@ -429,7 +441,12 @@ where // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. - round2_secret_packages.insert(participant_identifier, round2_secret_package); + round2_secret_packages.insert( + participant_identifier, + // Serialization roundtrip to simulate storage for later + round2::SecretPackage::deserialize(&round2_secret_package.serialize().unwrap()) + .unwrap(), + ); // "Send" the round 2 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be @@ -440,7 +457,11 @@ where received_round2_packages .entry(receiver_identifier) .or_insert_with(BTreeMap::new) - .insert(participant_identifier, round2_package); + .insert( + participant_identifier, + // Serialization roundtrip to simulate communication + round2::Package::deserialize(&round2_package.serialize().unwrap()).unwrap(), + ); } } From 85c16bc032a7c9c9d9b73e3263981b0b3dc5df02 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 09:53:12 -0300 Subject: [PATCH 134/175] core: expose NonceCommitment getter/new under internals (#947) --- frost-core/src/round1.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 7c3b10c08..33b7814df 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -133,10 +133,15 @@ where C: Ciphersuite, { /// Create a new [`NonceCommitment`] from an [`Element`] + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn new(value: Element) -> Self { Self(SerializableElement(value)) } + /// Get the inner [`Element`] of the [`NonceCommitment`] + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(crate) fn value(&self) -> Element { self.0 .0 } From 0f2116217ce3ab7591ffb33152674cb4389c20bf Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 10:07:30 -0300 Subject: [PATCH 135/175] core: add pre-commitment computation hooks (#950) --- frost-core/src/lib.rs | 2 ++ frost-core/src/round2.rs | 2 ++ frost-core/src/traits.rs | 29 ++++++++++++++++++++++++++--- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index b5b1620c6..926c11c06 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -631,7 +631,9 @@ where // binding factor. let binding_factor_list: BindingFactorList = compute_binding_factor_list(&signing_package, &pubkeys.verifying_key, &[])?; + // Compute the group commitment from signing commitments produced in round one. + let signing_package = ::pre_commitment_aggregate(&signing_package, &binding_factor_list)?; let group_commitment = compute_group_commitment(&signing_package, &binding_factor_list)?; // The aggregation of the signature shares by summing them up, resulting in diff --git a/frost-core/src/round2.rs b/frost-core/src/round2.rs index 39c7cf447..b7a1e909d 100644 --- a/frost-core/src/round2.rs +++ b/frost-core/src/round2.rs @@ -155,6 +155,8 @@ pub fn sign( .clone(); // Compute the group commitment from signing commitments produced in round one. + let (signing_package, signer_nonces) = + ::pre_commitment_sign(&signing_package, &signer_nonces, &binding_factor_list)?; let group_commitment = compute_group_commitment(&signing_package, &binding_factor_list)?; // Compute Lagrange coefficient. diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index c65d7d043..44c1c436c 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -12,10 +12,10 @@ use crate::{ challenge, keys::{KeyPackage, PublicKeyPackage, SecretShare, VerifyingShare}, random_nonzero, - round1::{self}, + round1::{self, SigningNonces}, round2::{self, SignatureShare}, - BindingFactor, Challenge, Error, FieldError, GroupCommitment, GroupError, Identifier, - Signature, SigningKey, SigningPackage, VerifyingKey, + BindingFactor, BindingFactorList, Challenge, Error, FieldError, GroupCommitment, GroupError, + Identifier, Signature, SigningKey, SigningPackage, VerifyingKey, }; /// A prime order finite field GF(q) over which all scalar values for our prime order group can be @@ -337,6 +337,29 @@ pub trait Ciphersuite: Copy + PartialEq + Debug + 'static { )) } + /// Optional. Pre-process [`crate::compute_group_commitment()`] inputs in + /// [`round2::sign()`]. + #[allow(clippy::type_complexity)] + fn pre_commitment_sign<'a>( + signing_package: &'a SigningPackage, + signing_nonces: &'a SigningNonces, + _binding_factor_list: &'a BindingFactorList, + ) -> Result<(Cow<'a, SigningPackage>, Cow<'a, SigningNonces>), Error> { + Ok(( + Cow::Borrowed(signing_package), + Cow::Borrowed(signing_nonces), + )) + } + + /// Optional. Pre-process [`crate::compute_group_commitment()`] inputs in + /// [`crate::aggregate()`]. + fn pre_commitment_aggregate<'a>( + signing_package: &'a SigningPackage, + _binding_factor_list: &'a BindingFactorList, + ) -> Result>, Error> { + Ok(Cow::Borrowed(signing_package)) + } + /// Optional. Generate a nonce and a commitment to it. Used by /// [`SigningKey`] for regular (non-FROST) signing and internally by the DKG /// to generate proof-of-knowledge signatures. From 2bc191f0348d9b3361fffeb9711762b763c9b41f Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 10:37:43 -0300 Subject: [PATCH 136/175] core: improve zeroization (#945) * core: improve zeroization * fix minimal-versions build * Update book/src/user/zeroization.md Co-authored-by: natalie --------- Co-authored-by: natalie --- Cargo.lock | 1 + book/src/SUMMARY.md | 1 + book/src/user/zeroization.md | 15 +++++++++++++++ frost-core/Cargo.toml | 5 ++++- frost-core/src/keys.rs | 6 +++--- frost-core/src/keys/dkg.rs | 34 +++++++++------------------------ frost-core/src/keys/refresh.rs | 2 +- frost-core/src/serialization.rs | 10 ++++++++++ 8 files changed, 44 insertions(+), 30 deletions(-) create mode 100644 book/src/user/zeroization.md diff --git a/Cargo.lock b/Cargo.lock index 2ca16f4bb..a2cff93df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -549,6 +549,7 @@ dependencies = [ "thiserror", "visibility", "zeroize", + "zeroize_derive", ] [[package]] diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 1b2584b44..4ffa00e4c 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -9,6 +9,7 @@ - [Distributed Key Generation](tutorial/dkg.md) - [Refreshing Shares](tutorial/refreshing-shares.md) - [User Documentation](user.md) + - [Zeroization](user/zeroization.md) - [Serialization Format](user/serialization.md) - [FROST with Zcash](zcash.md) - [Technical Details](zcash/technical-details.md) diff --git a/book/src/user/zeroization.md b/book/src/user/zeroization.md new file mode 100644 index 000000000..a8647fdb3 --- /dev/null +++ b/book/src/user/zeroization.md @@ -0,0 +1,15 @@ +# Zeroization + +The ZF FROST crates have limited best-effort support at zeroization. The +top-level structs (`KeyPackage`, `SecretShare`, etc.) implement the +`Zeroize` and `ZeroizeOnDrop` from the `zeroize` crate. This means that +when they are dropped they are cleared from memory. + +However, be advised that the user is responsible for everything else. For +example, if you serialize the structs, then you will be responsible for +zeroizing the serialized buffers, which _will_ contain secrets. + +Additionally, if you extract the secret fields (e.g. `KeyPackage::signing_share()`) +they you are also responsible for zeroizing them if you make a copy, since +the inner types do not implement `ZeroizeOnDrop` (though most of them do +implement `Zeroize` so you can call `zeroize()` manually). diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 9e7eb0c7b..ca03f3359 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -28,7 +28,10 @@ serde = { version = "1.0.160", default-features = false, features = ["derive"], serdect = { version = "0.2.0", optional = true } thiserror = { version = "2.0.3", default-features = false } visibility = "0.1.0" -zeroize = { version = "1.5.4", default-features = false, features = ["derive"] } +zeroize = { version = "1.5.4", default-features = false, features = ["derive", "alloc"] } +# We indirectly depend on this via `zeroize` but the minimal version enforced by +# `zeroize` does not work for us, so we specify it here. +zeroize_derive = { version = "1.4.2" } itertools = { version = "0.14.0", default-features = false } # Test dependencies used with the test-impl feature diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 7785452a2..7219143c5 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -17,7 +17,7 @@ use derive_getters::Getters; use hex::FromHex; use rand_core::{CryptoRng, RngCore}; -use zeroize::{DefaultIsZeroes, Zeroize}; +use zeroize::{DefaultIsZeroes, Zeroize, ZeroizeOnDrop}; use crate::{ serialization::{SerializableElement, SerializableScalar}, @@ -394,7 +394,7 @@ where /// /// To derive a FROST keypair, the receiver of the [`SecretShare`] *must* call /// .into(), which under the hood also performs validation. -#[derive(Clone, Debug, Zeroize, PartialEq, Eq, Getters)] +#[derive(Clone, Debug, Zeroize, PartialEq, Eq, Getters, ZeroizeOnDrop)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] @@ -622,7 +622,7 @@ fn evaluate_vss( /// When using a central dealer, [`SecretShare`]s are distributed to /// participants, who then perform verification, before deriving /// [`KeyPackage`]s, which they store to later use during signing. -#[derive(Clone, Debug, PartialEq, Eq, Getters, Zeroize)] +#[derive(Clone, Debug, PartialEq, Eq, Getters, Zeroize, ZeroizeOnDrop)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 0eac153ed..6f48c3e80 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -54,7 +54,7 @@ use super::{ pub mod round1 { use alloc::vec::Vec; use derive_getters::Getters; - use zeroize::Zeroize; + use zeroize::{Zeroize, ZeroizeOnDrop}; use super::*; @@ -117,18 +117,20 @@ pub mod round1 { /// # Security /// /// This package MUST NOT be sent to other participants! - #[derive(Clone, PartialEq, Eq, Getters)] + #[derive(Clone, PartialEq, Eq, Getters, Zeroize, ZeroizeOnDrop)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SecretPackage { /// The identifier of the participant holding the secret. + #[zeroize(skip)] pub(crate) identifier: Identifier, /// Coefficients of the temporary secret polynomial for the participant. /// These are (a_{i0}, ..., a_{i(t−1)})) which define the polynomial f_i(x) #[getter(skip)] pub(crate) coefficients: Vec>, /// The public commitment for the participant (C_i) + #[zeroize(skip)] pub(crate) commitment: VerifiableSecretSharingCommitment, /// The minimum number of signers. pub(crate) min_signers: u16, @@ -196,23 +198,12 @@ pub mod round1 { .finish() } } - - impl Zeroize for SecretPackage - where - C: Ciphersuite, - { - fn zeroize(&mut self) { - for c in self.coefficients.iter_mut() { - *c = SerializableScalar(<::Field>::zero()); - } - } - } } /// DKG Round 2 structures. pub mod round2 { use derive_getters::Getters; - use zeroize::Zeroize; + use zeroize::{Zeroize, ZeroizeOnDrop}; #[cfg(feature = "serialization")] use alloc::vec::Vec; @@ -275,14 +266,16 @@ pub mod round2 { /// # Security /// /// This package MUST NOT be sent to other participants! - #[derive(Clone, PartialEq, Eq, Getters)] + #[derive(Clone, PartialEq, Eq, Getters, Zeroize, ZeroizeOnDrop)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SecretPackage { /// The identifier of the participant holding the secret. + #[zeroize(skip)] pub(crate) identifier: Identifier, /// The public commitment from the participant (C_i) + #[zeroize(skip)] pub(crate) commitment: VerifiableSecretSharingCommitment, /// The participant's own secret share (f_i(i)). #[getter(skip)] @@ -351,15 +344,6 @@ pub mod round2 { .finish() } } - - impl Zeroize for SecretPackage - where - C: Ciphersuite, - { - fn zeroize(&mut self) { - self.secret_share = SerializableScalar(<::Field>::zero()); - } - } } /// Performs the first part of the distributed key generation protocol @@ -542,7 +526,7 @@ pub fn part2( Ok(( round2::SecretPackage::new( secret_package.identifier, - secret_package.commitment, + secret_package.commitment.clone(), fii, secret_package.min_signers, secret_package.max_signers, diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index 57691ea08..07495163c 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -295,7 +295,7 @@ pub fn refresh_dkg_part2( Ok(( round2::SecretPackage::new( secret_package.identifier, - secret_package.commitment, + secret_package.commitment.clone(), fii, secret_package.min_signers, secret_package.max_signers, diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index 73195c16c..5fc642f95 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -1,6 +1,7 @@ //! Serialization support. use alloc::vec::Vec; +use zeroize::Zeroize; use crate::{Ciphersuite, FieldError}; @@ -34,6 +35,15 @@ where } } +impl Zeroize for SerializableScalar +where + C: Ciphersuite, +{ + fn zeroize(&mut self) { + self.0 = <::Field as Field>::zero(); + } +} + #[cfg(feature = "serde")] impl serde::Serialize for SerializableScalar where From 99aca96f5f467dff6d8eca59537d0985b0c43426 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 10:37:45 -0300 Subject: [PATCH 137/175] docs: add warning about secp256k1; clean up READMEs (#954) * docs: add warning about secp256k1; clean up READMEs * Update frost-rerandomized/README.md Co-authored-by: natalie * fix Agreement -> Generation --------- Co-authored-by: natalie --- README.md | 31 +++++++++++++++++++------------ book/src/index.md | 8 +------- book/src/tutorial.md | 6 ++++++ frost-core/README.md | 25 +++---------------------- frost-ed25519/README.md | 7 +++++++ frost-ed448/README.md | 7 +++++++ frost-p256/README.md | 7 +++++++ frost-rerandomized/README.md | 21 +++++++++------------ frost-ristretto255/README.md | 7 +++++++ frost-secp256k1-tr/README.md | 7 +++++++ frost-secp256k1/README.md | 7 +++++++ gencode/src/main.rs | 6 ++++++ performance.md | 2 +- 13 files changed, 87 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 019b60d3d..02948166a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ | secp256k1 ciphersuite (Taproot) | [`frost-secp256k1-tr`] | [![crates.io](https://img.shields.io/crates/v/frost-secp256k1-tr.svg)](https://crates.io/crates/frost-secp256k1-tr) | [![Documentation](https://docs.rs/frost-secp256k1-tr/badge.svg)](https://docs.rs/frost-secp256k1-tr) | | Generic Re-randomized FROST | [`frost-rerandomized`] | [![crates.io](https://img.shields.io/crates/v/frost-rerandomized.svg)](https://crates.io/crates/frost-rerandomized) | [![Documentation](https://docs.rs/frost-rerandomized/badge.svg)](https://docs.rs/frost-rerandomized) | -Rust implementations of ['Two-Round Threshold Schnorr Signatures with FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/). +Rust implementations of ['RFC 9591: Two-Round Threshold Schnorr Signatures with FROST'](https://datatracker.ietf.org/doc/rfc9591/). Unlike signatures in a single-party setting, threshold signatures require cooperation among a threshold number of signers, each holding a share of a common private key. The security of threshold @@ -21,7 +21,7 @@ schemes in general assume that an adversary can corrupt strictly fewer than a th participants. ['Two-Round Threshold Schnorr Signatures with -FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/) presents a variant of a Flexible +FROST'](https://datatracker.ietf.org/doc/rfc9591/) presents a variant of a Flexible Round-Optimized Schnorr Threshold (FROST) signature scheme originally defined in [FROST20](https://eprint.iacr.org/2020/852.pdf). FROST reduces network overhead during threshold signing operations while employing a novel technique to protect against forgery attacks applicable @@ -29,22 +29,29 @@ to prior Schnorr-based threshold signature constructions. Besides FROST itself, this repository also provides: -- Trusted dealer key generation as specified in the appendix of ['Two-Round Threshold Schnorr Signatures with FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/); +- Trusted dealer key generation as specified in the appendix of ['Two-Round Threshold Schnorr Signatures with FROST'](https://datatracker.ietf.org/doc/rfc9591/); - Distributed key generation as specified in the original paper [FROST20](https://eprint.iacr.org/2020/852.pdf); - Repairable Threshold Scheme (RTS) from ['A Survey and Refinement of Repairable Threshold Schemes'](https://eprint.iacr.org/2017/1155) which allows a participant to recover a lost share with the help of a threshold of other participants; -- Rerandomized FROST (paper under review). -- Refresh Share functionality using a Trusted Dealer. This can be used to refresh the shares of the participants or to remove a participant. +- [Re-Randomized FROST](https://eprint.iacr.org/2024/436). +- Refresh Share functionality using a Trusted Dealer or Distributed Key + Generation. This can be used to refresh the shares of the participants or to + remove a participant. ## Getting Started -Refer to the [ZF FROST book](https://frost.zfnd.org/). +If you're not familiar with FROST, first read [Understanding FROST](frost.md). -## Status ⚠ +Then read the [Tutorial](tutorial.md), and use the [Rust docs](user.md) as +reference. -The FROST specification is not yet finalized, though no significant changes are -expected at this point. This code base has been partially audited by NCC, see -below for details. The APIs and types in the crates contained in this repository -follow SemVer guarantees. +## Status + +The crates are considered stable and feature complete, though eventual API +cleanups and additional functionality might be included in future releases. + +This code base has been partially audited by NCC, see below for details. The +APIs and types in the crates contained in this repository follow SemVer +guarantees. ### NCC Audit @@ -74,7 +81,7 @@ All issues identified in the audit were addressed by us and reviewed by NCC. `frost-core` implements the base traits and types in a generic manner, to enable top-level implementations for different ciphersuites / curves without having to implement all of FROST from -scratch. End-users should not use `frost-core` if they want to sign and verify signatures, they +scratch. End-users should not use `frost-core` if they want to just sign and verify signatures for a specific ciphersuite; they should use the crate specific to their ciphersuite/curve parameters that uses `frost-core` as a dependency. diff --git a/book/src/index.md b/book/src/index.md index 66fad0a2c..39ee232b7 100644 --- a/book/src/index.md +++ b/book/src/index.md @@ -2,10 +2,4 @@ This is a guide-level reference for the [ZF FROST library](https://github.com/ZcashFoundation/frost/). -## Getting Started - -If you're not familiar with FROST, first read [Understanding FROST](frost.md). - -Then read the [Tutorial](tutorial.md), and use the [Rust -docs](user.md) as -reference. \ No newline at end of file +{{#include ../../README.md}} \ No newline at end of file diff --git a/book/src/tutorial.md b/book/src/tutorial.md index 15df64c92..15d0a2514 100644 --- a/book/src/tutorial.md +++ b/book/src/tutorial.md @@ -15,3 +15,9 @@ If you need to support multiple ciphersuites then feel free to use This tutorial will use the `frost-ristretto255` crate, but changing to another ciphersuite should be a matter of simply changing the import. +```admonish note +"The `frost-secp256k1` crate is not compatible with Bitcoin BIP-340 (Taproot) +signatures. Use +[frost-secp256k1-tr](https://crates.io/crates/frost-secp256k1-tr) instead +if you want to support it. +``` diff --git a/frost-core/README.md b/frost-core/README.md index aac4cf268..e54be2f5c 100644 --- a/frost-core/README.md +++ b/frost-core/README.md @@ -1,30 +1,11 @@ # FROST (Flexible Round-Optimised Schnorr Threshold signatures) Core Base traits and types in Rust that implement ['Two-Round Threshold Schnorr Signatures with -FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/) generically for +FROST'](https://datatracker.ietf.org/doc/rfc9591/) generically for [`Ciphersuite`] implementations. -For key generation, refer to the [`keys`] module. For round-specific -types and functions, refer to the [`round1`] and [`round2`] modules. This module -contains types and functions not directly related to key generation and the -FROST rounds. - - -## Status ⚠ - -The FROST specification is not yet finalized, though no significant changes are -expected at this point. This code base has been audited by NCC. The APIs and -types in `frost-core` are subject to change during the release candidate phase, -and will follow SemVer guarantees after 1.0.0. - -## Usage - -`frost-core` implements the base traits and types in a generic manner, to enable top-level -implementations for different ciphersuites / curves without having to implement all of FROST from -scratch. End-users should not use `frost-core` if they want to sign and verify signatures, they -should use the crate specific to their ciphersuite/curve parameters that uses `frost-core` as a -dependency, such as [`frost_ristretto255`](../frost_ristretto255). +For more details, refer to [The ZF FROST Book](https://frost.zfnd.org/). ## Example -See ciphersuite-specific crates, e.g. [`frost_ristretto255`](../frost_ristretto255). +See ciphersuite-specific crates, e.g. [`frost_ristretto255`]([../frost_ristretto255](https://crates.io/crates/frost-ristretto255)). diff --git a/frost-ed25519/README.md b/frost-ed25519/README.md index 0b9a2c9a6..500f8f449 100644 --- a/frost-ed25519/README.md +++ b/frost-ed25519/README.md @@ -1,6 +1,13 @@ An implementation of Schnorr signatures on the Ed25519 curve for both single and threshold numbers of signers (FROST). +This crate is a re-export of the ciphersuite-generic +[frost-core](https://crates.io/crates/frost-core) crate, parametrized with the +Ed25519 curve. For more details, refer to [The ZF FROST +Book](https://frost.zfnd.org/). + + + ## Example: key generation with trusted dealer and FROST signing Creating a key with a trusted dealer and splitting into shares; then signing a message diff --git a/frost-ed448/README.md b/frost-ed448/README.md index 34d4e537b..77f1eb8c7 100644 --- a/frost-ed448/README.md +++ b/frost-ed448/README.md @@ -1,6 +1,13 @@ An implementation of Schnorr signatures on the Ed448 curve for both single and threshold numbers of signers (FROST). +This crate is a re-export of the ciphersuite-generic +[frost-core](https://crates.io/crates/frost-core) crate, parametrized with the +Ed448 curve. For more details, refer to [The ZF FROST +Book](https://frost.zfnd.org/). + + + ## Example: key generation with trusted dealer and FROST signing Creating a key with a trusted dealer and splitting into shares; then signing a message diff --git a/frost-p256/README.md b/frost-p256/README.md index ce0aa3d5c..47f391fb2 100644 --- a/frost-p256/README.md +++ b/frost-p256/README.md @@ -1,6 +1,13 @@ An implementation of Schnorr signatures on the P-256 curve for both single and threshold numbers of signers (FROST). +This crate is a re-export of the ciphersuite-generic +[frost-core](https://crates.io/crates/frost-core) crate, parametrized with the +P-256 curve. For more details, refer to [The ZF FROST +Book](https://frost.zfnd.org/). + + + ## Example: key generation with trusted dealer and FROST signing Creating a key with a trusted dealer and splitting into shares; then signing a message diff --git a/frost-rerandomized/README.md b/frost-rerandomized/README.md index 733b6e4be..543f13b1b 100644 --- a/frost-rerandomized/README.md +++ b/frost-rerandomized/README.md @@ -1,22 +1,19 @@ # FROST (Flexible Round-Optimised Schnorr Threshold signatures) Rerandomized -Base traits and types in Rust that implement ['Two-Round Threshold Schnorr Signatures with -FROST'](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/) generically for -`frost-core::Ciphersuite` implementations, with support for Zcash-compatible -RedDSA re-randomized signatures. - -## Status ⚠ - -The FROST specification is not yet finalized, and this codebase has not yet been audited or -released. The APIs and types in `frost-rerandomized` are subject to change. +A ciphersuite-generic implementation of [Re-Randomized +FROST](https://eprint.iacr.org/2024/436), which allows creating signatures using +FROST under re-randomized keys. ## Usage `frost-rerandomized` is similar to `frost-core`, but provides different `sign()` and `aggregate()` functions adding support for re-randomized signatures. -End-users should not use `frost-rerandomized` if they want to sign and verify signatures, they -should use the crate specific to their ciphersuite/curve parameters that uses `frost-rerandomized` as a -dependency, such as [`reddsa`](https://github.com/ZcashFoundation/reddsa/). + +Currently, the main ciphersuite crates do not re-expose the rerandomization +functions; if you want to use this functionality, you will need to use this +crate parametrized with the chosen ciphersuite. The exceptions are the Zcash +ciphersuites in [`reddsa`](https://github.com/ZcashFoundation/reddsa/) which +do expose the randomized functionality. ## Example diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index 4ba16fe16..0f061bd95 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -1,6 +1,13 @@ An implementation of Schnorr signatures on the Ristretto group for both single and threshold numbers of signers (FROST). +This crate is a re-export of the ciphersuite-generic +[frost-core](https://crates.io/crates/frost-core) crate, parametrized with the +Ristretto group. For more details, refer to [The ZF FROST +Book](https://frost.zfnd.org/). + + + ## Example: key generation with trusted dealer and FROST signing Creating a key with a trusted dealer and splitting into shares; then signing a message diff --git a/frost-secp256k1-tr/README.md b/frost-secp256k1-tr/README.md index 26e287012..bc74f3ef2 100644 --- a/frost-secp256k1-tr/README.md +++ b/frost-secp256k1-tr/README.md @@ -1,6 +1,13 @@ An implementation of Schnorr signatures on the secp256k1 curve (Taproot) for both single and threshold numbers of signers (FROST). +This crate is a re-export of the ciphersuite-generic +[frost-core](https://crates.io/crates/frost-core) crate, parametrized with the +secp256k1 curve (Taproot). For more details, refer to [The ZF FROST +Book](https://frost.zfnd.org/). + + + ## Example: key generation with trusted dealer and FROST signing Creating a key with a trusted dealer and splitting into shares; then signing a message diff --git a/frost-secp256k1/README.md b/frost-secp256k1/README.md index bdec1acb7..c65e5789d 100644 --- a/frost-secp256k1/README.md +++ b/frost-secp256k1/README.md @@ -1,6 +1,13 @@ An implementation of Schnorr signatures on the secp256k1 curve for both single and threshold numbers of signers (FROST). +This crate is a re-export of the ciphersuite-generic +[frost-core](https://crates.io/crates/frost-core) crate, parametrized with the +secp256k1 curve. For more details, refer to [The ZF FROST +Book](https://frost.zfnd.org/). + +*This crate is not compatible with Bitcoin BIP-340 (Taproot) signatures. Use [frost-secp256k1-tr](https://crates.io/crates/frost-secp256k1-tr) instead* + ## Example: key generation with trusted dealer and FROST signing Creating a key with a trusted dealer and splitting into shares; then signing a message diff --git a/gencode/src/main.rs b/gencode/src/main.rs index 8a420fca9..4d0e0a90b 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -212,6 +212,7 @@ fn main() -> ExitCode { "ristretto255_sha512", "ristretto255", "", + "", ] .iter() .map(|x| x.to_string()) @@ -252,6 +253,7 @@ fn main() -> ExitCode { "p256_sha256", "p256", "

", + "", ], ), ( @@ -265,6 +267,7 @@ fn main() -> ExitCode { "ed25519_sha512", "ed25519", "", + "", ], ), ( @@ -278,6 +281,7 @@ fn main() -> ExitCode { "ed448_shake256", "ed448", "", + "", ], ), ( @@ -291,6 +295,7 @@ fn main() -> ExitCode { "secp256k1_sha256", "secp256k1", "", + "*This crate is not compatible with Bitcoin BIP-340 (Taproot) signatures. Use [frost-secp256k1-tr](https://crates.io/crates/frost-secp256k1-tr) instead*", ], ), ( @@ -304,6 +309,7 @@ fn main() -> ExitCode { "secp256k1_tr_sha256", "secp256k1_tr", "", + "", ], ), ] { diff --git a/performance.md b/performance.md index 6b21dd433..029632de5 100644 --- a/performance.md +++ b/performance.md @@ -6,7 +6,7 @@ FROST is a threshold Schnorr signature scheme [invented](https://eprint.iacr.org/2020/852) by Chelsea Komlo (researcher at the Zcash Foundation) and Ian Goldberg, and in the process of becoming an [IETF -RFC](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/). Threshold +RFC](https://datatracker.ietf.org/doc/rfc9591/). Threshold signatures allow a private key being split into shares given to multiple participants, allowing a subgroup of them (e.g. 3 out of 5, or whatever threshold specified at key generation) to generate a signature that can be From a473fc8c982dc6471f73372dd06de7b976e9d3c4 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 10:42:16 -0300 Subject: [PATCH 138/175] book: make it clear that auth channel is not needed for signing (#953) * book: make it clear that auth channel is not needed for signing * Update book/src/tutorial/signing.md Co-authored-by: natalie --------- Co-authored-by: natalie --- book/src/tutorial/signing.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/book/src/tutorial/signing.md b/book/src/tutorial/signing.md index ecd249ef2..18b120c46 100644 --- a/book/src/tutorial/signing.md +++ b/book/src/tutorial/signing.md @@ -25,8 +25,13 @@ their commitments (a `SigningCommitments`) by calling ``` The `SigningNonces` must be kept by the participant to use in Round 2, while the -`SigningCommitments` must be sent to the Coordinator using an [authenticated -channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). +`SigningCommitments` must be sent to the Coordinator. + +```admonish info +FROST does not require using an [authenticated nor encrypted +channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) +during the **signing** process. +``` ## Coordinator, Round 2 @@ -38,10 +43,9 @@ message to be signed, and then build a `SigningPackage` by calling {{#include ../../../frost-ristretto255/README.md:round2_package}} ``` -The `SigningPackage` must then be sent to all the participants using an -[authenticated -channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). (Of course, -if the message is confidential, then the channel must also be confidential.) +The `SigningPackage` must then be sent to all the participants. (If the message +is confidential, then the channel must also be confidential, since the message +is included in the `SigningPackage`.) ```admonish warning In all of the main FROST ciphersuites, the entire message must @@ -63,9 +67,7 @@ their `SigningNonces` from Round 1, by calling {{#include ../../../frost-ristretto255/README.md:round2_sign}} ``` -The resulting `SignatureShare` must then be sent back to the Coordinator using -an [authenticated -channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). +The resulting `SignatureShare` must then be sent back to the Coordinator. ```admonish important In most applications, it is important that the participant must be aware of what @@ -94,7 +96,8 @@ in the `SigningPackage` in Round 2 for the group verifying key in the `PublicKey FROST supports identifiable abort: if a participant misbehaves and produces an invalid signature share, then aggregation will fail and the returned error will have the identifier of the misbehaving participant. (If multiple participants -misbehave, only the first one detected will be returned.) +misbehave, only the first one detected will be returned. If you need to detect +all cheaters, use [`aggregate_custom()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/fn.aggregate_custom.html)) What should be done in that case is up to the application. The misbehaving participant could be excluded from future signing sessions, for example. From 94cef54b9cd86b5b3b89a7b68400b829795c0779 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 11:07:15 -0300 Subject: [PATCH 139/175] core: bind traits to Send + Sync (#946) --- Cargo.lock | 34 +++++++ Cargo.toml | 3 + frost-core/Cargo.toml | 4 +- frost-core/src/tests/ciphersuite_generic.rs | 96 +++++++++++++++++-- frost-core/src/traits.rs | 10 +- frost-ed25519/Cargo.toml | 1 + frost-ed25519/tests/integration_tests.rs | 10 ++ frost-ed448/Cargo.toml | 1 + frost-ed448/tests/integration_tests.rs | 10 ++ frost-p256/Cargo.toml | 1 + frost-p256/tests/integration_tests.rs | 10 ++ frost-ristretto255/Cargo.toml | 1 + frost-ristretto255/tests/integration_tests.rs | 11 +++ frost-secp256k1-tr/Cargo.toml | 1 + frost-secp256k1-tr/tests/integration_tests.rs | 10 ++ frost-secp256k1/Cargo.toml | 1 + frost-secp256k1/tests/integration_tests.rs | 10 ++ gencode/src/main.rs | 7 ++ 18 files changed, 211 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2cff93df..3b63dc00b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -547,6 +547,7 @@ dependencies = [ "serde_json", "serdect", "thiserror", + "tokio", "visibility", "zeroize", "zeroize_derive", @@ -571,6 +572,7 @@ dependencies = [ "rand_core 0.6.4", "serde_json", "sha2", + "tokio", ] [[package]] @@ -591,6 +593,7 @@ dependencies = [ "rand_core 0.6.4", "serde_json", "sha3", + "tokio", ] [[package]] @@ -611,6 +614,7 @@ dependencies = [ "rand_core 0.6.4", "serde_json", "sha2", + "tokio", ] [[package]] @@ -643,6 +647,7 @@ dependencies = [ "rand_core 0.6.4", "serde_json", "sha2", + "tokio", ] [[package]] @@ -663,6 +668,7 @@ dependencies = [ "rand_core 0.6.4", "serde_json", "sha2", + "tokio", ] [[package]] @@ -684,6 +690,7 @@ dependencies = [ "secp256k1", "serde_json", "sha2", + "tokio", ] [[package]] @@ -932,6 +939,12 @@ dependencies = [ "primeorder", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "pkcs8" version = "0.10.2" @@ -1447,6 +1460,27 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typenum" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index ae89d5768..6b89170f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ members = [ ] [workspace.package] +# If you update the edition, make sure to also update the argument to rustfmt +# in gencode/src/main.rs. edition = "2021" rust-version = "1.81" version = "2.2.0" @@ -37,6 +39,7 @@ rand = "0.8" rand_chacha = "0.3" rand_core = "0.6" serde_json = "1.0" +tokio = { version = "1.0", features = ["rt", "time", "macros"] } frost-core = { path = "frost-core", version = "2.2.0", default-features = false } frost-rerandomized = { path = "frost-rerandomized", version = "2.2.0", default-features = false } diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index ca03f3359..7e3c5d334 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -38,6 +38,7 @@ itertools = { version = "0.14.0", default-features = false } proptest = { version = "1.0", optional = true } serde_json = { version = "1.0", optional = true } criterion = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } [dev-dependencies] criterion.workspace = true @@ -47,6 +48,7 @@ rand.workspace = true rand_chacha.workspace = true serde_json.workspace = true + [features] default = ["serialization", "std"] # No longer needed. Kept for retrocompatibility until 3.0.0 @@ -62,7 +64,7 @@ internals = [] serde = ["dep:serde", "dep:serdect"] serialization = ["serde", "dep:postcard"] # Exposes ciphersuite-generic tests for other crates to use -test-impl = ["dep:proptest", "dep:serde_json", "dep:criterion"] +test-impl = ["dep:proptest", "dep:serde_json", "dep:criterion", "dep:tokio"] [lib] bench = false diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 86021b595..5b40e4204 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -119,13 +119,11 @@ pub fn check_sign_with_dealer( .unwrap(); // Verifies the secret shares from the dealer - let mut key_packages: BTreeMap, frost::keys::KeyPackage> = - BTreeMap::new(); + let key_packages: BTreeMap, frost::keys::KeyPackage> = shares + .into_iter() + .map(|(k, v)| (k, frost::keys::KeyPackage::try_from(v).unwrap())) + .collect(); - for (k, v) in shares { - let key_package = frost::keys::KeyPackage::try_from(v).unwrap(); - key_packages.insert(k, key_package); - } // Check if it fails with not enough signers. Usually this would return an // error before even running the signing procedure, because `KeyPackage` // contains the correct `min_signers` value and the signing procedure checks @@ -1025,3 +1023,89 @@ fn check_verify_signature_share( .expect_err("should have failed"); } } + +/// Test FROST signing in an async context. +/// The ultimate goal of the test is to ensure that types are Send + Sync. +pub async fn async_check_sign( + mut rng: R, +) { + tokio::spawn(async move { + let max_signers = 5; + let min_signers = 3; + let (shares, pubkey_package) = frost::keys::generate_with_dealer( + max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + + // The test is sprinkled with await points to ensure that types that + // cross them are Send + Sync. + tokio::time::sleep(core::time::Duration::from_millis(1)).await; + + // Verifies the secret shares from the dealer + let key_packages: BTreeMap, frost::keys::KeyPackage> = shares + .into_iter() + .map(|(k, v)| (k, frost::keys::KeyPackage::try_from(v).unwrap())) + .collect(); + + tokio::time::sleep(core::time::Duration::from_millis(1)).await; + + let mut nonces_map: BTreeMap, frost::round1::SigningNonces> = + BTreeMap::new(); + let mut commitments_map: BTreeMap< + frost::Identifier, + frost::round1::SigningCommitments, + > = BTreeMap::new(); + + for participant_identifier in key_packages.keys().take(min_signers as usize).cloned() { + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _min_signers_. + let (nonces, commitments) = frost::round1::commit( + key_packages + .get(&participant_identifier) + .unwrap() + .signing_share(), + &mut rng, + ); + tokio::time::sleep(core::time::Duration::from_millis(1)).await; + nonces_map.insert(participant_identifier, nonces); + commitments_map.insert(participant_identifier, commitments); + } + + let mut signature_shares = BTreeMap::new(); + let message = "message to sign".as_bytes(); + let signing_package = SigningPackage::new(commitments_map, message); + + for participant_identifier in nonces_map.keys() { + let key_package = key_packages.get(participant_identifier).unwrap(); + let nonces_to_use = nonces_map.get(participant_identifier).unwrap(); + let signature_share = + frost::round2::sign(&signing_package, nonces_to_use, key_package).unwrap(); + tokio::time::sleep(core::time::Duration::from_millis(1)).await; + signature_shares.insert(*participant_identifier, signature_share); + } + + let group_signature = + frost::aggregate(&signing_package, &signature_shares, &pubkey_package).unwrap(); + tokio::time::sleep(core::time::Duration::from_millis(1)).await; + + pubkey_package + .verifying_key + .verify(message, &group_signature) + .unwrap(); + tokio::time::sleep(core::time::Duration::from_millis(1)).await; + + for (participant_identifier, _) in nonces_map.clone() { + let key_package = key_packages.get(&participant_identifier).unwrap(); + key_package + .verifying_key + .verify(message, &group_signature) + .unwrap(); + tokio::time::sleep(core::time::Duration::from_millis(1)).await; + } + }) + .await + .unwrap(); +} diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 44c1c436c..321d64f1c 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -34,7 +34,9 @@ pub trait Field: Copy { + Eq + Mul + PartialEq - + Sub; + + Sub + + Send + + Sync; /// A unique byte array buf of fixed length N. type Serialization: Clone + AsRef<[u8]> + AsMut<[u8]> + for<'a> TryFrom<&'a [u8]> + Debug; @@ -97,7 +99,9 @@ pub trait Group: Copy + PartialEq { + Eq + Mul<::Scalar, Output = Self::Element> + PartialEq - + Sub; + + Sub + + Send + + Sync; /// A unique byte array buf of fixed length N. /// @@ -147,7 +151,7 @@ pub type Element = <::Group as Group>::Element; /// /// [FROST ciphersuite]: https://datatracker.ietf.org/doc/html/rfc9591#name-ciphersuites // See https://github.com/ZcashFoundation/frost/issues/693 for reasoning about the 'static bound. -pub trait Ciphersuite: Copy + PartialEq + Debug + 'static { +pub trait Ciphersuite: Copy + PartialEq + Debug + 'static + Send + Sync { /// The ciphersuite ID string. It should be equal to the contextString in /// the spec. For new ciphersuites, this should be a string that identifies /// the ciphersuite; it's recommended to use a similar format to the diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index eb456a1e0..e16cda6f4 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -35,6 +35,7 @@ proptest.workspace = true rand.workspace = true rand_chacha.workspace = true serde_json.workspace = true +tokio.workspace = true [features] default = ["serialization", "std"] diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 830f6457d..649576c24 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -379,3 +379,13 @@ fn check_sign_with_incorrect_commitments() { rng, ); } + +#[tokio::test] +async fn check_async_sign_with_dealer() { + tokio::spawn(async { + let rng = rand::rngs::OsRng; + frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; + }) + .await + .unwrap(); +} diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 79a1d56df..d9a0bedc9 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -34,6 +34,7 @@ proptest.workspace = true rand.workspace = true rand_chacha.workspace = true serde_json.workspace = true +tokio.workspace = true [features] default = ["serialization", "std"] diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 0dfb79144..601228dce 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -379,3 +379,13 @@ fn check_sign_with_incorrect_commitments() { rng, ); } + +#[tokio::test] +async fn check_async_sign_with_dealer() { + tokio::spawn(async { + let rng = rand::rngs::OsRng; + frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; + }) + .await + .unwrap(); +} diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index 029977790..42c429bfd 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -34,6 +34,7 @@ proptest.workspace = true rand.workspace = true rand_chacha.workspace = true serde_json.workspace = true +tokio.workspace = true [features] default = ["serialization", "std"] diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index 868cc4d68..1fd354b0d 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -376,3 +376,13 @@ fn check_sign_with_incorrect_commitments() { rng, ); } + +#[tokio::test] +async fn check_async_sign_with_dealer() { + tokio::spawn(async { + let rng = rand::rngs::OsRng; + frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; + }) + .await + .unwrap(); +} diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 98c670884..d02077cb6 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -35,6 +35,7 @@ proptest.workspace = true rand.workspace = true rand_chacha.workspace = true serde_json.workspace = true +tokio.workspace = true [features] default = ["serialization", "std"] diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index 7179fabef..d16f682da 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -383,3 +383,14 @@ fn check_sign_with_incorrect_commitments() { _, >(rng); } + +#[tokio::test] +async fn check_async_sign_with_dealer() { + tokio::spawn(async { + let rng = rand::rngs::OsRng; + frost_core::tests::ciphersuite_generic::async_check_sign::(rng) + .await; + }) + .await + .unwrap(); +} diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index 9d116b342..55f83eeef 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -35,6 +35,7 @@ rand.workspace = true rand_chacha.workspace = true secp256k1 = "0.31.0" serde_json.workspace = true +tokio.workspace = true [features] default = ["serialization", "std"] diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs index 4933040a3..fe5007dad 100644 --- a/frost-secp256k1-tr/tests/integration_tests.rs +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -383,3 +383,13 @@ fn check_sign_with_incorrect_commitments() { _, >(rng); } + +#[tokio::test] +async fn check_async_sign_with_dealer() { + tokio::spawn(async { + let rng = rand::rngs::OsRng; + frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; + }) + .await + .unwrap(); +} diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index 113a6c8a6..ab20daba1 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -34,6 +34,7 @@ proptest.workspace = true rand.workspace = true rand_chacha.workspace = true serde_json.workspace = true +tokio.workspace = true [features] default = ["serialization", "std"] diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 0356b0a7c..63ba125f5 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -380,3 +380,13 @@ fn check_sign_with_incorrect_commitments() { _, >(rng); } + +#[tokio::test] +async fn check_async_sign_with_dealer() { + tokio::spawn(async { + let rng = rand::rngs::OsRng; + frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; + }) + .await + .unwrap(); +} diff --git a/gencode/src/main.rs b/gencode/src/main.rs index 4d0e0a90b..5f92f3882 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -167,6 +167,8 @@ fn copy_and_replace( pub fn rustfmt(source: String) -> String { let mut child = Command::new("rustfmt") + .arg("--edition") + .arg("2021") .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) @@ -181,6 +183,11 @@ pub fn rustfmt(source: String) -> String { }); let output = child.wait_with_output().expect("Failed to read stdout"); + assert!( + output.status.success(), + "rustfmt failed: {}", + String::from_utf8_lossy(&output.stderr) + ); String::from_utf8_lossy(&output.stdout).to_string() } From 5a270164993055c61652d40ff303e1ccc34afded Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 11:26:29 -0300 Subject: [PATCH 140/175] fix function name and ciphersuite refresh modules (#922) * fix function name and ciphersuite refresh modules * core: add min_signers to PublicKeyPackage (#912) * core: add min_signers to PublicKeyPackage * fix JSON deserialization when min_signers is missing * fix warnings, restore wrongly commented out line --- frost-core/CHANGELOG.md | 9 +- frost-core/src/keys.rs | 37 ++- frost-core/src/keys/dkg.rs | 2 +- frost-core/src/keys/refresh.rs | 14 +- frost-core/src/lib.rs | 10 +- frost-core/src/serialization.rs | 249 ++++++++++++++++++ frost-core/src/tests/ciphersuite_generic.rs | 14 +- frost-core/src/tests/refresh.rs | 44 ++-- frost-core/src/tests/vectors.rs | 3 +- frost-core/src/tests/vectors_dkg.rs | 2 + frost-core/src/traits.rs | 6 +- frost-ed25519/src/keys/refresh.rs | 10 +- frost-ed25519/tests/helpers/samples.rs | 14 +- frost-ed25519/tests/integration_tests.rs | 4 +- frost-ed25519/tests/recreation_tests.rs | 19 +- frost-ed25519/tests/serde_tests.rs | 20 +- frost-ed25519/tests/serialization_tests.rs | 11 + ...ey_package_new_postcard_serialization.snap | 5 + frost-ed448/src/keys/refresh.rs | 10 +- frost-ed448/tests/helpers/samples.rs | 14 +- frost-ed448/tests/integration_tests.rs | 4 +- frost-ed448/tests/recreation_tests.rs | 19 +- frost-ed448/tests/serde_tests.rs | 20 +- frost-ed448/tests/serialization_tests.rs | 11 + ...ey_package_new_postcard_serialization.snap | 5 + frost-p256/src/keys/refresh.rs | 10 +- frost-p256/tests/helpers/samples.rs | 14 +- frost-p256/tests/integration_tests.rs | 4 +- frost-p256/tests/recreation_tests.rs | 19 +- frost-p256/tests/serde_tests.rs | 20 +- frost-p256/tests/serialization_tests.rs | 11 + ...ey_package_new_postcard_serialization.snap | 5 + frost-rerandomized/src/lib.rs | 3 +- frost-ristretto255/src/keys/refresh.rs | 10 +- frost-ristretto255/tests/helpers/samples.rs | 14 +- frost-ristretto255/tests/integration_tests.rs | 4 +- frost-ristretto255/tests/recreation_tests.rs | 19 +- frost-ristretto255/tests/serde_tests.rs | 20 +- .../tests/serialization_tests.rs | 11 + ...ey_package_new_postcard_serialization.snap | 5 + frost-secp256k1-tr/src/keys/refresh.rs | 10 +- frost-secp256k1-tr/src/lib.rs | 8 +- frost-secp256k1-tr/tests/helpers/samples.rs | 14 +- frost-secp256k1-tr/tests/integration_tests.rs | 4 +- frost-secp256k1-tr/tests/recreation_tests.rs | 19 +- frost-secp256k1-tr/tests/serde_tests.rs | 20 +- .../tests/serialization_tests.rs | 11 + ...ey_package_new_postcard_serialization.snap | 5 + frost-secp256k1/src/keys/refresh.rs | 10 +- frost-secp256k1/tests/helpers/samples.rs | 14 +- frost-secp256k1/tests/integration_tests.rs | 4 +- frost-secp256k1/tests/recreation_tests.rs | 19 +- frost-secp256k1/tests/serde_tests.rs | 20 +- frost-secp256k1/tests/serialization_tests.rs | 11 + ...ey_package_new_postcard_serialization.snap | 5 + 55 files changed, 785 insertions(+), 114 deletions(-) create mode 100644 frost-ed25519/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap create mode 100644 frost-ed448/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap create mode 100644 frost-p256/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap create mode 100644 frost-ristretto255/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap create mode 100644 frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap create mode 100644 frost-secp256k1/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 80d2fc84c..ca1fbfb4f 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -2,7 +2,7 @@ Entries are listed in reverse chronological order. -## Unreleases +## Unreleased ### Breaking Changes @@ -12,6 +12,13 @@ Entries are listed in reverse chronological order. the default behaviour is now as if `cheater-detection` was enabled. If you explicitly *did not enable* it, you can avoid cheater detection by calling `aggregate_custom()` with `CheaterDetection::Disabled`. +* The `std` and `nightly` features were removed from all crates +* Renamed `frost_core::keys::refresh::refresh_dkg_part_1` to `refresh_dkg_part1`. +* Fixed the crate-specific versions of the `refresh` module to be non-generic. + +### Additional changes + +* Added DKG refresh functions to the crate-specific `refresh` modules. ## 2.2.0 diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 7219143c5..cc6fe7af4 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -381,6 +381,11 @@ where pub(crate) fn coefficients(&self) -> &[CoefficientCommitment] { &self.0 } + + /// Return the threshold associated with this commitment. + pub(crate) fn min_signers(&self) -> u16 { + self.0.len() as u16 + } } /// A secret share generated by performing a (t-out-of-n) secret sharing scheme, @@ -563,6 +568,7 @@ pub fn split( header: Header::default(), verifying_shares, verifying_key, + min_signers: Some(min_signers), }; // Apply post-processing @@ -706,7 +712,7 @@ where signing_share: secret_share.signing_share, verifying_share, verifying_key, - min_signers: secret_share.commitment.0.len() as u16, + min_signers: secret_share.commitment.min_signers(), }) } } @@ -716,7 +722,7 @@ where /// /// Used for verification purposes before publishing a signature. #[derive(Clone, Debug, PartialEq, Eq, Getters)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct PublicKeyPackage { @@ -728,6 +734,12 @@ pub struct PublicKeyPackage { pub(crate) verifying_shares: BTreeMap, VerifyingShare>, /// The joint public key for the entire group. pub(crate) verifying_key: VerifyingKey, + /// The minimum number of signers (threshold) required for the group. + /// This can be None in packages created with `frost_core` prior to 3.0.0. + #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] + #[cfg_attr(feature = "serde", serde(default))] + #[getter(copy)] + pub(crate) min_signers: Option, } impl PublicKeyPackage @@ -738,11 +750,26 @@ where pub fn new( verifying_shares: BTreeMap, VerifyingShare>, verifying_key: VerifyingKey, + min_signers: u16, + ) -> Self { + Self::new_internal(verifying_shares, verifying_key, Some(min_signers)) + } + + /// Create a new [`PublicKeyPackage`] instance, allowing not specifying the + /// number of signers. This is used internally, in particular for testing + /// old [`PublicKeyPackage`]s that do not encode the `min_signers`. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn new_internal( + verifying_shares: BTreeMap, VerifyingShare>, + verifying_key: VerifyingKey, + min_signers: Option, ) -> Self { Self { header: Header::default(), verifying_shares, verifying_key, + min_signers, } } @@ -761,6 +788,7 @@ where Ok(PublicKeyPackage::new( verifying_keys, VerifyingKey::from_commitment(commitment)?, + commitment.min_signers(), )) } @@ -777,6 +805,11 @@ where let group_commitment = sum_commitments(&commitments)?; Self::from_commitment(&identifiers, &group_commitment) } + + /// Return the maximum number of signers. + pub fn max_signers(&self) -> u16 { + self.verifying_shares.len() as u16 + } } #[cfg(feature = "serialization")] diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 6f48c3e80..5f56256e2 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -491,7 +491,7 @@ pub fn part2( } for package in round1_packages.values() { - if package.commitment.0.len() != secret_package.min_signers as usize { + if package.commitment.min_signers() != secret_package.min_signers { return Err(Error::IncorrectNumberOfCommitments); } } diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index 07495163c..4df4e6a47 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -18,7 +18,7 @@ //! //! For the DKG approach, the flow is very similar to [DKG //! itself](`https://frost.zfnd.org/tutorial/dkg.html`). Each participant calls -//! [`refresh_dkg_part_1()`], keeps the returned secret package and sends the +//! [`refresh_dkg_part1()`], keeps the returned secret package and sends the //! returned package to other participants. Then each participants calls //! [`refresh_dkg_part2()`] and sends the returned packages to the other //! participants. Finally each participant calls [`refresh_dkg_shares()`]. @@ -61,6 +61,14 @@ pub fn compute_refreshing_shares( identifiers: &[Identifier], rng: &mut R, ) -> Result<(Vec>, PublicKeyPackage), Error> { + // Validate min_signers. It's OK if the min_signers is missing, because + // we validate it again in `refresh_share()`. + if let Some(package_min_signers) = pub_key_package.min_signers { + if min_signers != package_min_signers { + return Err(Error::InvalidMinSigners); + } + } + // Validate inputs if identifiers.len() != max_signers as usize { return Err(Error::IncorrectNumberOfIdentifiers); @@ -110,6 +118,7 @@ pub fn compute_refreshing_shares( header: pub_key_package.header, verifying_shares: refreshed_verifying_shares, verifying_key: pub_key_package.verifying_key, + min_signers: Some(pub_key_package.min_signers.unwrap_or(min_signers)), }; Ok((refreshing_shares_minus_identity, refreshed_pub_key_package)) @@ -168,7 +177,7 @@ pub fn refresh_share( /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the refresh run. -pub fn refresh_dkg_part_1( +pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, @@ -460,6 +469,7 @@ pub fn refresh_dkg_shares( header: old_pub_key_package.header, verifying_shares: new_verifying_shares, verifying_key: old_pub_key_package.verifying_key, + min_signers: Some(round2_secret_package.min_signers), }; let key_package = KeyPackage { diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 926c11c06..8c1b54f03 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -758,7 +758,15 @@ pub fn verify_signature_share( // In order to reuse `pre_aggregate()`, we need to create some "dummy" containers let signature_shares = BTreeMap::from([(identifier, *signature_share)]); let verifying_shares = BTreeMap::from([(identifier, *verifying_share)]); - let public_key_package = PublicKeyPackage::new(verifying_shares, *verifying_key); + let public_key_package = PublicKeyPackage { + verifying_shares, + verifying_key: *verifying_key, + // Use None since we don't have the min_signers value here. This + // can only cause problems if the `pre_aggregate` function relies on it. + // This has been documented in `pre_aggregate()`. + min_signers: None, + header: Header::default(), + }; let (signing_package, signature_shares, pubkeys) = ::pre_aggregate(signing_package, &signature_shares, &public_key_package)?; diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index 5fc642f95..01ca843c7 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -1,9 +1,22 @@ //! Serialization support. +#[cfg(feature = "serde")] +use alloc::collections::BTreeMap; +use alloc::string::String; use alloc::vec::Vec; +#[cfg(feature = "serde")] +use core::fmt::Formatter; +#[cfg(feature = "serde")] +use core::marker::PhantomData; use zeroize::Zeroize; +#[cfg(feature = "serde")] +use crate::keys::PublicKeyPackage; +#[cfg(feature = "serde")] +use crate::keys::VerifyingShare; use crate::{Ciphersuite, FieldError}; +#[cfg(feature = "serde")] +use crate::{Header, Identifier, VerifyingKey}; use crate::{Element, Error, Field, Group}; @@ -240,3 +253,239 @@ impl serde::Deserialize<'de>, C: Ciphersuite> Deserialize for T { postcard::from_bytes(bytes).map_err(|_| Error::DeserializationError) } } + +/// Custom deserializer for PublicKeyPackage, which allows a non-existing +/// `min_signers` field for the `postcard` encoding. +#[cfg(feature = "serde")] +impl<'de, C: Ciphersuite> serde::Deserialize<'de> for PublicKeyPackage +where + C: Ciphersuite, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use core::fmt; + + // The following are copied from the `serde::Deserialize` derive, and + // are required to support `visit_map()` which in turn is required for + // `serde_json`. + + enum Field { + Field0, + Field1, + Field2, + Field3, + } + + struct FieldVisitor; + + impl<'de> serde::de::Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting(&self, __formatter: &mut Formatter) -> fmt::Result { + Formatter::write_str(__formatter, "field identifier") + } + + fn visit_u64<__E>(self, __value: u64) -> Result + where + __E: serde::de::Error, + { + match __value { + 0u64 => Ok(Field::Field0), + 1u64 => Ok(Field::Field1), + 2u64 => Ok(Field::Field2), + 3u64 => Ok(Field::Field3), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Unsigned(__value), + &"field index 0 <= i < 4", + )), + } + } + + fn visit_str<__E>(self, __value: &str) -> Result + where + __E: serde::de::Error, + { + match __value { + "header" => Ok(Field::Field0), + "verifying_shares" => Ok(Field::Field1), + "verifying_key" => Ok(Field::Field2), + "min_signers" => Ok(Field::Field3), + _ => Err(serde::de::Error::unknown_field(__value, FIELDS)), + } + } + + fn visit_bytes<__E>(self, __value: &[u8]) -> Result + where + __E: serde::de::Error, + { + match __value { + b"header" => Ok(Field::Field0), + b"verifying_shares" => Ok(Field::Field1), + b"verifying_key" => Ok(Field::Field2), + b"min_signers" => Ok(Field::Field3), + _ => { + let __value = &String::from_utf8_lossy(__value); + Err(serde::de::Error::unknown_field(__value, FIELDS)) + } + } + } + } + + impl<'de> serde::Deserialize<'de> for Field { + #[inline] + fn deserialize<__D>(__deserializer: __D) -> Result + where + __D: serde::Deserializer<'de>, + { + serde::Deserializer::deserialize_identifier(__deserializer, FieldVisitor) + } + } + + struct Visitor { + marker: PhantomData, + } + + impl<'de, C: Ciphersuite> serde::de::Visitor<'de> for Visitor + where + C: Ciphersuite, + { + type Value = PublicKeyPackage; + + fn expecting(&self, fmt: &mut Formatter) -> core::fmt::Result { + Formatter::write_str(fmt, "struct PublicKeyPackage") + } + + // Postcard serializes structs as sequences, so we override + // `visit_seq` to deserialize the struct from a sequence of elements. + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + // Read the first three fields as usual. + + let header = seq.next_element::>()?.ok_or_else(|| { + serde::de::Error::invalid_length( + 0usize, + &"struct PublicKeyPackage with 4 elements", + ) + })?; + let verifying_shares = seq + .next_element::, VerifyingShare>>()? + .ok_or_else(|| { + serde::de::Error::invalid_length( + 1usize, + &"struct PublicKeyPackage with 4 elements", + ) + })?; + let verifying_key = seq.next_element::>()?.ok_or_else(|| { + serde::de::Error::invalid_length( + 2usize, + &"struct PublicKeyPackage with 4 elements", + ) + })?; + + // For the `min_signers` field, fill it with None if + // `next_element()` fails (i.e. there are no other elements) + let min_signers = match seq.next_element::>() { + Ok(Some(min_signers)) => min_signers, + _ => None, + }; + + Ok(PublicKeyPackage { + header, + verifying_shares, + verifying_key, + min_signers, + }) + } + + // Again this is copied from the `serde::Deserialize` derive; + // the only change is not requiring `min_signers` to be present. + fn visit_map<__A>(self, mut __map: __A) -> Result + where + __A: serde::de::MapAccess<'de>, + { + let mut __field0: Option> = None; + let mut __field1: Option, VerifyingShare>> = None; + let mut __field2: Option> = None; + let mut __field3: Option> = None; + while let Some(__key) = serde::de::MapAccess::next_key::(&mut __map)? { + match __key { + Field::Field0 => { + if Option::is_some(&__field0) { + return Err(<__A::Error as serde::de::Error>::duplicate_field( + "header", + )); + } + __field0 = + Some(serde::de::MapAccess::next_value::>(&mut __map)?); + } + Field::Field1 => { + if Option::is_some(&__field1) { + return Err(<__A::Error as serde::de::Error>::duplicate_field( + "verifying_shares", + )); + } + __field1 = Some(serde::de::MapAccess::next_value::< + BTreeMap, VerifyingShare>, + >(&mut __map)?); + } + Field::Field2 => { + if Option::is_some(&__field2) { + return Err(<__A::Error as serde::de::Error>::duplicate_field( + "verifying_key", + )); + } + __field2 = Some(serde::de::MapAccess::next_value::>( + &mut __map, + )?); + } + Field::Field3 => { + if Option::is_some(&__field3) { + return Err(<__A::Error as serde::de::Error>::duplicate_field( + "min_signers", + )); + } + __field3 = + Some(serde::de::MapAccess::next_value::>(&mut __map)?); + } + } + } + let __field0 = match __field0 { + Some(__field0) => __field0, + None => Err(<__A::Error as serde::de::Error>::missing_field("header"))?, + }; + let __field1 = match __field1 { + Some(__field1) => __field1, + None => Err(<__A::Error as serde::de::Error>::missing_field( + "verifying_shares", + ))?, + }; + let __field2 = match __field2 { + Some(__field2) => __field2, + None => Err(<__A::Error as serde::de::Error>::missing_field( + "verifying_key", + ))?, + }; + let __field3 = __field3.unwrap_or_default(); + Ok(PublicKeyPackage { + header: __field0, + verifying_shares: __field1, + verifying_key: __field2, + min_signers: __field3, + }) + } + } + + const FIELDS: &[&str] = &["header", "verifying_shares", "verifying_key", "min_signers"]; + deserializer.deserialize_struct( + "PublicKeyPackage", + FIELDS, + Visitor { + marker: PhantomData::, + }, + ) + } +} diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 5b40e4204..a2c0df8c1 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -542,9 +542,9 @@ where // will have all the participant's packages. let mut key_packages = BTreeMap::new(); - // Map of the verifying key of each participant. + // Map of the verifying share of each participant. // Used by the signing test that follows. - let mut verifying_keys = BTreeMap::new(); + let mut verifying_shares = BTreeMap::new(); // The group public key, used by the signing test that follows. let mut verifying_key = None; // For each participant, store the set of verifying keys they have computed. @@ -572,7 +572,7 @@ where &received_round2_packages[&participant_identifier], ) .unwrap(); - verifying_keys.insert(participant_identifier, key_package.verifying_share); + verifying_shares.insert(participant_identifier, key_package.verifying_share); // Test if all verifying_key are equal if let Some(previous_verifying_key) = verifying_key { assert_eq!(previous_verifying_key, key_package.verifying_key) @@ -585,10 +585,14 @@ where // Test if the set of verifying keys is correct for all participants. for verifying_keys_for_participant in pubkey_packages_by_participant.values() { - assert!(verifying_keys_for_participant.verifying_shares == verifying_keys); + assert!(verifying_keys_for_participant.verifying_shares == verifying_shares); } - let pubkeys = frost::keys::PublicKeyPackage::new(verifying_keys, verifying_key.unwrap()); + let pubkeys = pubkey_packages_by_participant + .first_key_value() + .unwrap() + .1 + .clone(); // Proceed with the signing test. check_sign(min_signers, key_packages, rng, pubkeys).unwrap() diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 83b67b5fa..f9b58dbe2 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -5,7 +5,7 @@ use rand_core::{CryptoRng, RngCore}; use crate::keys::dkg::{round1, round2}; use crate::keys::generate_with_dealer; use crate::keys::refresh::{ - compute_refreshing_shares, refresh_dkg_part2, refresh_dkg_part_1, refresh_share, + compute_refreshing_shares, refresh_dkg_part1, refresh_dkg_part2, refresh_share, }; #[cfg(feature = "serialization")] use crate::keys::{PublicKeyPackage, SecretShare}; @@ -296,33 +296,15 @@ pub fn check_refresh_shares_with_dealer_fails_with_different_min_signers< // Trusted Dealer generates zero keys and new public key package - let (zero_shares, _new_pub_key_package) = compute_refreshing_shares( + let r = compute_refreshing_shares( pub_key_package, NEW_MAX_SIGNERS, NEW_MIN_SIGNERS, &remaining_ids, &mut rng, - ) - .unwrap(); - - // Each participant refreshes their share - - let mut new_shares = BTreeMap::new(); - - for i in 0..remaining_ids.len() { - let identifier = remaining_ids[i]; - let current_share = &old_key_packages[&identifier]; - let new_share = refresh_share(zero_shares[i].clone(), current_share); - new_shares.insert(identifier, new_share); - } - - assert!( - new_shares - .iter() - .all(|(_, v)| v.is_err() && matches!(v, Err(Error::InvalidMinSigners))), - "{:?}", - new_shares ); + + assert_eq!(r, Err(Error::InvalidMinSigners)); } /// Test FROST signing with DKG with a Ciphersuite. @@ -387,7 +369,7 @@ where // In practice, each participant will perform this on their own environments. for participant_identifier in remaining_ids.clone() { let (round1_secret_package, round1_package) = - refresh_dkg_part_1(participant_identifier, max_signers, min_signers, &mut rng).unwrap(); + refresh_dkg_part1(participant_identifier, max_signers, min_signers, &mut rng).unwrap(); // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. @@ -474,9 +456,9 @@ where // will have all the participant's packages. let mut key_packages = BTreeMap::new(); - // Map of the verifying key of each participant. + // Map of the verifying share of each participant. // Used by the signing test that follows. - let mut verifying_keys = BTreeMap::new(); + let mut verifying_shares = BTreeMap::new(); // The group public key, used by the signing test that follows. let mut verifying_key = None; // For each participant, store the set of verifying keys they have computed. @@ -506,7 +488,7 @@ where old_key_packages[&participant_identifier].clone(), ) .unwrap(); - verifying_keys.insert(participant_identifier, key_package.verifying_share); + verifying_shares.insert(participant_identifier, key_package.verifying_share); // Test if all verifying_key are equal if let Some(previous_verifying_key) = verifying_key { assert_eq!(previous_verifying_key, key_package.verifying_key) @@ -519,10 +501,14 @@ where // Test if the set of verifying keys is correct for all participants. for verifying_keys_for_participant in pubkey_packages_by_participant.values() { - assert!(verifying_keys_for_participant.verifying_shares == verifying_keys); + assert!(verifying_keys_for_participant.verifying_shares == verifying_shares); } - let pubkeys = frost::keys::PublicKeyPackage::new(verifying_keys, verifying_key.unwrap()); + let pubkeys = pubkey_packages_by_participant + .first_key_value() + .unwrap() + .1 + .clone(); // Proceed with the signing test. check_sign(min_signers, key_packages, rng, pubkeys).unwrap() @@ -594,7 +580,7 @@ pub fn check_refresh_shares_with_dkg_smaller_threshold< // In practice, each participant will perform this on their own environments. for participant_identifier in remaining_ids.clone() { let (round1_secret_package, round1_package) = - refresh_dkg_part_1(participant_identifier, max_signers, min_signers, &mut rng).unwrap(); + refresh_dkg_part1(participant_identifier, max_signers, min_signers, &mut rng).unwrap(); // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index 950fe9a94..e278d0838 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -293,7 +293,8 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { .map(|(i, key_package)| (i, *key_package.verifying_share())) .collect(); - let pubkey_package = frost::keys::PublicKeyPackage::new(verifying_shares, verifying_key); + let pubkey_package = + frost::keys::PublicKeyPackage::new(verifying_shares, verifying_key, min_signers as u16); //////////////////////////////////////////////////////////////////////////// // Aggregation: collects the signing shares from all participants, diff --git a/frost-core/src/tests/vectors_dkg.rs b/frost-core/src/tests/vectors_dkg.rs index 02d0c85ad..90fafef26 100644 --- a/frost-core/src/tests/vectors_dkg.rs +++ b/frost-core/src/tests/vectors_dkg.rs @@ -176,6 +176,7 @@ fn build_public_key_package(json_vectors: &Value) -> PublicKeyPa let mut verifying_shares = BTreeMap::new(); let max_participants = json_vectors["config"]["MAX_PARTICIPANTS"].as_u64().unwrap() as u8; + let min_participants = json_vectors["config"]["MIN_PARTICIPANTS"].as_u64().unwrap() as u8; for i in 1..=max_participants { let participant_id: Identifier = (inputs[i.to_string()]["identifier"].as_u64().unwrap() @@ -196,6 +197,7 @@ fn build_public_key_package(json_vectors: &Value) -> PublicKeyPa header: Header::default(), verifying_shares, verifying_key, + min_signers: Some(min_participants as u16), } } diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 321d64f1c..9ae94319f 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -295,9 +295,9 @@ pub trait Ciphersuite: Copy + PartialEq + Debug + 'static + Send + Sync { /// Optional. Pre-process [`crate::aggregate()`] and /// [`crate::verify_signature_share()`] inputs. In the latter case, "dummy" /// container BTreeMap and PublicKeyPackage are passed with the relevant - /// values. The default implementation returns them as-is. [`Cow`] is used - /// so implementations can choose to return the same passed reference or a - /// modified clone. + /// values (PublicKeyPackage.min_signers will be None). The default + /// implementation returns them as-is. [`Cow`] is used so implementations + /// can choose to return the same passed reference or a modified clone. #[allow(clippy::type_complexity)] fn pre_aggregate<'a>( signing_package: &'a SigningPackage, diff --git a/frost-ed25519/src/keys/refresh.rs b/frost-ed25519/src/keys/refresh.rs index 3e26bcb76..36797d959 100644 --- a/frost-ed25519/src/keys/refresh.rs +++ b/frost-ed25519/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - Ciphersuite, CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, RngCore, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, min_signers: u16, @@ -29,21 +29,21 @@ pub fn compute_refreshing_shares( } /// Refer to [`frost_core::keys::refresh::refresh_share`]. -pub fn refresh_share( +pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } -/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, mut rng: R, ) -> Result<(round1::SecretPackage, round1::Package), Error> { - frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) + frost::keys::refresh::refresh_dkg_part1(identifier, max_signers, min_signers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. diff --git a/frost-ed25519/tests/helpers/samples.rs b/frost-ed25519/tests/helpers/samples.rs index e1d87e169..3ce2f8af6 100644 --- a/frost-ed25519/tests/helpers/samples.rs +++ b/frost-ed25519/tests/helpers/samples.rs @@ -104,7 +104,19 @@ pub fn public_key_package() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key) + PublicKeyPackage::new_internal(verifying_shares, verifying_key, None) +} + +/// Generate a sample PublicKeyPackage with `min_signers`. +pub fn public_key_package_new() -> PublicKeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); + + PublicKeyPackage::new(verifying_shares, verifying_key, 2) } /// Generate a sample round1::SecretPackage. diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 649576c24..d43664a62 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -117,7 +117,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s Identifier::try_from(4).unwrap(), Identifier::try_from(5).unwrap(), ]; - let min_signers = 3; + let min_signers = 2; let max_signers = 3; let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; @@ -150,7 +150,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 3; + let min_signers = 2; let max_signers = 1; let error = Error::InvalidMaxSigners; diff --git a/frost-ed25519/tests/recreation_tests.rs b/frost-ed25519/tests/recreation_tests.rs index 5ae8964e4..4261cb458 100644 --- a/frost-ed25519/tests/recreation_tests.rs +++ b/frost-ed25519/tests/recreation_tests.rs @@ -101,8 +101,25 @@ fn check_public_key_package_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers(); - let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key); + let new_public_key_package = + PublicKeyPackage::new_internal(verifying_shares.clone(), *verifying_key, min_signers); + + assert!(public_key_package == new_public_key_package); +} + +/// Check if PublicKeyPackage can be recreated. +#[test] +fn check_public_key_package_new_recreation() { + let public_key_package = samples::public_key_package_new(); + + let verifying_shares = public_key_package.verifying_shares(); + let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers().unwrap(); + + let new_public_key_package = + PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); assert!(public_key_package == new_public_key_package); } diff --git a/frost-ed25519/tests/serde_tests.rs b/frost-ed25519/tests/serde_tests.rs index 9f722797b..a36c278b9 100644 --- a/frost-ed25519/tests/serde_tests.rs +++ b/frost-ed25519/tests/serde_tests.rs @@ -434,7 +434,7 @@ fn check_key_package_serialization() { #[test] fn check_public_key_package_serialization() { - let public_key_package = samples::public_key_package(); + let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); println!("{}", json); @@ -450,11 +450,27 @@ fn check_public_key_package_serialization() { "verifying_shares": { "2a00000000000000000000000000000000000000000000000000000000000000": "5866666666666666666666666666666666666666666666666666666666666666" }, - "verifying_key": "5866666666666666666666666666666666666666666666666666666666666666" + "verifying_key": "5866666666666666666666666666666666666666666666666666666666666666", + "min_signers": 2 }"#; let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); assert!(public_key_package == decoded_public_key_package); + // Old version without min_signers + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-ED25519-SHA512-v1" + }, + "verifying_shares": { + "2a00000000000000000000000000000000000000000000000000000000000000": "5866666666666666666666666666666666666666666666666666666666666666" + }, + "verifying_key": "5866666666666666666666666666666666666666666666666666666666666666" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package.verifying_key() == decoded_public_key_package.verifying_key()); + assert!(public_key_package.verifying_shares() == decoded_public_key_package.verifying_shares()); + let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); diff --git a/frost-ed25519/tests/serialization_tests.rs b/frost-ed25519/tests/serialization_tests.rs index 7fe52b912..6af901684 100644 --- a/frost-ed25519/tests/serialization_tests.rs +++ b/frost-ed25519/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_public_key_package_new_postcard_serialization() { + let public_key_package = samples::public_key_package_new(); + let bytes: Vec<_> = public_key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + public_key_package, + PublicKeyPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_secret_package_postcard_serialization() { let round1_secret_package = samples::round1_secret_package(); diff --git a/frost-ed25519/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap b/frost-ed25519/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap new file mode 100644 index 000000000..342de36f3 --- /dev/null +++ b/frost-ed25519/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ed25519/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00b169f0da012a00000000000000000000000000000000000000000000000000000000000000586666666666666666666666666666666666666666666666666666666666666658666666666666666666666666666666666666666666666666666666666666660102 diff --git a/frost-ed448/src/keys/refresh.rs b/frost-ed448/src/keys/refresh.rs index 3e26bcb76..36797d959 100644 --- a/frost-ed448/src/keys/refresh.rs +++ b/frost-ed448/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - Ciphersuite, CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, RngCore, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, min_signers: u16, @@ -29,21 +29,21 @@ pub fn compute_refreshing_shares( } /// Refer to [`frost_core::keys::refresh::refresh_share`]. -pub fn refresh_share( +pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } -/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, mut rng: R, ) -> Result<(round1::SecretPackage, round1::Package), Error> { - frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) + frost::keys::refresh::refresh_dkg_part1(identifier, max_signers, min_signers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. diff --git a/frost-ed448/tests/helpers/samples.rs b/frost-ed448/tests/helpers/samples.rs index 4994b515d..d4e045217 100644 --- a/frost-ed448/tests/helpers/samples.rs +++ b/frost-ed448/tests/helpers/samples.rs @@ -104,7 +104,19 @@ pub fn public_key_package() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key) + PublicKeyPackage::new_internal(verifying_shares, verifying_key, None) +} + +/// Generate a sample PublicKeyPackage with `min_signers`. +pub fn public_key_package_new() -> PublicKeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); + + PublicKeyPackage::new(verifying_shares, verifying_key, 2) } /// Generate a sample round1::SecretPackage. diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 601228dce..d22705c76 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -117,7 +117,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s Identifier::try_from(4).unwrap(), Identifier::try_from(5).unwrap(), ]; - let min_signers = 3; + let min_signers = 2; let max_signers = 3; let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; @@ -150,7 +150,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 3; + let min_signers = 2; let max_signers = 1; let error = Error::InvalidMaxSigners; diff --git a/frost-ed448/tests/recreation_tests.rs b/frost-ed448/tests/recreation_tests.rs index 44b7abfe8..a0a457aa5 100644 --- a/frost-ed448/tests/recreation_tests.rs +++ b/frost-ed448/tests/recreation_tests.rs @@ -101,8 +101,25 @@ fn check_public_key_package_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers(); - let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key); + let new_public_key_package = + PublicKeyPackage::new_internal(verifying_shares.clone(), *verifying_key, min_signers); + + assert!(public_key_package == new_public_key_package); +} + +/// Check if PublicKeyPackage can be recreated. +#[test] +fn check_public_key_package_new_recreation() { + let public_key_package = samples::public_key_package_new(); + + let verifying_shares = public_key_package.verifying_shares(); + let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers().unwrap(); + + let new_public_key_package = + PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); assert!(public_key_package == new_public_key_package); } diff --git a/frost-ed448/tests/serde_tests.rs b/frost-ed448/tests/serde_tests.rs index 3b5c667a4..db380eed1 100644 --- a/frost-ed448/tests/serde_tests.rs +++ b/frost-ed448/tests/serde_tests.rs @@ -434,7 +434,7 @@ fn check_key_package_serialization() { #[test] fn check_public_key_package_serialization() { - let public_key_package = samples::public_key_package(); + let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); println!("{}", json); @@ -450,11 +450,27 @@ fn check_public_key_package_serialization() { "verifying_shares": { "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" }, - "verifying_key": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" + "verifying_key": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900", + "min_signers": 2 }"#; let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); assert!(public_key_package == decoded_public_key_package); + // Old version without min_signers + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-ED448-SHAKE256-v1" + }, + "verifying_shares": { + "2a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" + }, + "verifying_key": "14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package.verifying_key() == decoded_public_key_package.verifying_key()); + assert!(public_key_package.verifying_shares() == decoded_public_key_package.verifying_shares()); + let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); diff --git a/frost-ed448/tests/serialization_tests.rs b/frost-ed448/tests/serialization_tests.rs index e6a177ec4..e9810a6b1 100644 --- a/frost-ed448/tests/serialization_tests.rs +++ b/frost-ed448/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_public_key_package_new_postcard_serialization() { + let public_key_package = samples::public_key_package_new(); + let bytes: Vec<_> = public_key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + public_key_package, + PublicKeyPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_secret_package_postcard_serialization() { let round1_secret_package = samples::round1_secret_package(); diff --git a/frost-ed448/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap b/frost-ed448/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap new file mode 100644 index 000000000..fcfcff41b --- /dev/null +++ b/frost-ed448/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ed448/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +005a064cfd012a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f690014fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f69000102 diff --git a/frost-p256/src/keys/refresh.rs b/frost-p256/src/keys/refresh.rs index 3e26bcb76..36797d959 100644 --- a/frost-p256/src/keys/refresh.rs +++ b/frost-p256/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - Ciphersuite, CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, RngCore, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, min_signers: u16, @@ -29,21 +29,21 @@ pub fn compute_refreshing_shares( } /// Refer to [`frost_core::keys::refresh::refresh_share`]. -pub fn refresh_share( +pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } -/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, mut rng: R, ) -> Result<(round1::SecretPackage, round1::Package), Error> { - frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) + frost::keys::refresh::refresh_dkg_part1(identifier, max_signers, min_signers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. diff --git a/frost-p256/tests/helpers/samples.rs b/frost-p256/tests/helpers/samples.rs index be57e472e..df9407b9d 100644 --- a/frost-p256/tests/helpers/samples.rs +++ b/frost-p256/tests/helpers/samples.rs @@ -104,7 +104,19 @@ pub fn public_key_package() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key) + PublicKeyPackage::new_internal(verifying_shares, verifying_key, None) +} + +/// Generate a sample PublicKeyPackage with `min_signers`. +pub fn public_key_package_new() -> PublicKeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); + + PublicKeyPackage::new(verifying_shares, verifying_key, 2) } /// Generate a sample round1::SecretPackage. diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index 1fd354b0d..8a9a92ed1 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -117,7 +117,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s Identifier::try_from(4).unwrap(), Identifier::try_from(5).unwrap(), ]; - let min_signers = 3; + let min_signers = 2; let max_signers = 3; let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; @@ -150,7 +150,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 3; + let min_signers = 2; let max_signers = 1; let error = Error::InvalidMaxSigners; diff --git a/frost-p256/tests/recreation_tests.rs b/frost-p256/tests/recreation_tests.rs index 0f4dbf7dd..e5816bfe2 100644 --- a/frost-p256/tests/recreation_tests.rs +++ b/frost-p256/tests/recreation_tests.rs @@ -101,8 +101,25 @@ fn check_public_key_package_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers(); - let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key); + let new_public_key_package = + PublicKeyPackage::new_internal(verifying_shares.clone(), *verifying_key, min_signers); + + assert!(public_key_package == new_public_key_package); +} + +/// Check if PublicKeyPackage can be recreated. +#[test] +fn check_public_key_package_new_recreation() { + let public_key_package = samples::public_key_package_new(); + + let verifying_shares = public_key_package.verifying_shares(); + let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers().unwrap(); + + let new_public_key_package = + PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); assert!(public_key_package == new_public_key_package); } diff --git a/frost-p256/tests/serde_tests.rs b/frost-p256/tests/serde_tests.rs index c14758146..7091bd7b3 100644 --- a/frost-p256/tests/serde_tests.rs +++ b/frost-p256/tests/serde_tests.rs @@ -434,7 +434,7 @@ fn check_key_package_serialization() { #[test] fn check_public_key_package_serialization() { - let public_key_package = samples::public_key_package(); + let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); println!("{}", json); @@ -450,11 +450,27 @@ fn check_public_key_package_serialization() { "verifying_shares": { "000000000000000000000000000000000000000000000000000000000000002a": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" }, - "verifying_key": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" + "verifying_key": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", + "min_signers": 2 }"#; let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); assert!(public_key_package == decoded_public_key_package); + // Old version without min_signers + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-P256-SHA256-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" + }, + "verifying_key": "036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package.verifying_key() == decoded_public_key_package.verifying_key()); + assert!(public_key_package.verifying_shares() == decoded_public_key_package.verifying_shares()); + let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); diff --git a/frost-p256/tests/serialization_tests.rs b/frost-p256/tests/serialization_tests.rs index b83b12ee7..dbd32cec3 100644 --- a/frost-p256/tests/serialization_tests.rs +++ b/frost-p256/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_public_key_package_new_postcard_serialization() { + let public_key_package = samples::public_key_package_new(); + let bytes: Vec<_> = public_key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + public_key_package, + PublicKeyPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_secret_package_postcard_serialization() { let round1_secret_package = samples::round1_secret_package(); diff --git a/frost-p256/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap b/frost-p256/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap new file mode 100644 index 000000000..158419f6e --- /dev/null +++ b/frost-p256/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-p256/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00a132f0c901000000000000000000000000000000000000000000000000000000000000002a036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296036b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2960102 diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 78ee354ba..4099893b4 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -111,9 +111,10 @@ impl Randomize for PublicKeyPackage { }) .collect(); - Ok(PublicKeyPackage::new( + Ok(PublicKeyPackage::new_internal( randomized_verifying_shares, randomized_params.randomized_verifying_key, + self.min_signers(), )) } } diff --git a/frost-ristretto255/src/keys/refresh.rs b/frost-ristretto255/src/keys/refresh.rs index 3e26bcb76..36797d959 100644 --- a/frost-ristretto255/src/keys/refresh.rs +++ b/frost-ristretto255/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - Ciphersuite, CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, RngCore, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, min_signers: u16, @@ -29,21 +29,21 @@ pub fn compute_refreshing_shares( } /// Refer to [`frost_core::keys::refresh::refresh_share`]. -pub fn refresh_share( +pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } -/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, mut rng: R, ) -> Result<(round1::SecretPackage, round1::Package), Error> { - frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) + frost::keys::refresh::refresh_dkg_part1(identifier, max_signers, min_signers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. diff --git a/frost-ristretto255/tests/helpers/samples.rs b/frost-ristretto255/tests/helpers/samples.rs index d6f58b90d..0b0a6e304 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -104,7 +104,19 @@ pub fn public_key_package() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key) + PublicKeyPackage::new_internal(verifying_shares, verifying_key, None) +} + +/// Generate a sample PublicKeyPackage with `min_signers`. +pub fn public_key_package_new() -> PublicKeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); + + PublicKeyPackage::new(verifying_shares, verifying_key, 2) } /// Generate a sample round1::SecretPackage. diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index d16f682da..8ff8af167 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -118,7 +118,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s Identifier::try_from(4).unwrap(), Identifier::try_from(5).unwrap(), ]; - let min_signers = 3; + let min_signers = 2; let max_signers = 3; let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; @@ -151,7 +151,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 3; + let min_signers = 2; let max_signers = 1; let error = Error::InvalidMaxSigners; diff --git a/frost-ristretto255/tests/recreation_tests.rs b/frost-ristretto255/tests/recreation_tests.rs index a5974a965..fc6947b29 100644 --- a/frost-ristretto255/tests/recreation_tests.rs +++ b/frost-ristretto255/tests/recreation_tests.rs @@ -101,8 +101,25 @@ fn check_public_key_package_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers(); - let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key); + let new_public_key_package = + PublicKeyPackage::new_internal(verifying_shares.clone(), *verifying_key, min_signers); + + assert!(public_key_package == new_public_key_package); +} + +/// Check if PublicKeyPackage can be recreated. +#[test] +fn check_public_key_package_new_recreation() { + let public_key_package = samples::public_key_package_new(); + + let verifying_shares = public_key_package.verifying_shares(); + let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers().unwrap(); + + let new_public_key_package = + PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); assert!(public_key_package == new_public_key_package); } diff --git a/frost-ristretto255/tests/serde_tests.rs b/frost-ristretto255/tests/serde_tests.rs index faf1769ae..d73e846ef 100644 --- a/frost-ristretto255/tests/serde_tests.rs +++ b/frost-ristretto255/tests/serde_tests.rs @@ -434,7 +434,7 @@ fn check_key_package_serialization() { #[test] fn check_public_key_package_serialization() { - let public_key_package = samples::public_key_package(); + let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); println!("{}", json); @@ -450,11 +450,27 @@ fn check_public_key_package_serialization() { "verifying_shares": { "2a00000000000000000000000000000000000000000000000000000000000000": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" }, - "verifying_key": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + "verifying_key": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76", + "min_signers": 2 }"#; let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); assert!(public_key_package == decoded_public_key_package); + // Old version without min_signers + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-RISTRETTO255-SHA512-v1" + }, + "verifying_shares": { + "2a00000000000000000000000000000000000000000000000000000000000000": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + }, + "verifying_key": "e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package.verifying_key() == decoded_public_key_package.verifying_key()); + assert!(public_key_package.verifying_shares() == decoded_public_key_package.verifying_shares()); + let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); diff --git a/frost-ristretto255/tests/serialization_tests.rs b/frost-ristretto255/tests/serialization_tests.rs index 1aa96a25a..641bb4746 100644 --- a/frost-ristretto255/tests/serialization_tests.rs +++ b/frost-ristretto255/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_public_key_package_new_postcard_serialization() { + let public_key_package = samples::public_key_package_new(); + let bytes: Vec<_> = public_key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + public_key_package, + PublicKeyPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_secret_package_postcard_serialization() { let round1_secret_package = samples::round1_secret_package(); diff --git a/frost-ristretto255/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap b/frost-ristretto255/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap new file mode 100644 index 000000000..8a5fd3195 --- /dev/null +++ b/frost-ristretto255/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-ristretto255/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00d76ecff5012a00000000000000000000000000000000000000000000000000000000000000e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d76e2f2ae0a6abc4e71a884a961c500515f58e30b6aa582dd8db6a65945e08d2d760102 diff --git a/frost-secp256k1-tr/src/keys/refresh.rs b/frost-secp256k1-tr/src/keys/refresh.rs index 3e26bcb76..36797d959 100644 --- a/frost-secp256k1-tr/src/keys/refresh.rs +++ b/frost-secp256k1-tr/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - Ciphersuite, CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, RngCore, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, min_signers: u16, @@ -29,21 +29,21 @@ pub fn compute_refreshing_shares( } /// Refer to [`frost_core::keys::refresh::refresh_share`]. -pub fn refresh_share( +pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } -/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, mut rng: R, ) -> Result<(round1::SecretPackage, round1::Package), Error> { - frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) + frost::keys::refresh::refresh_dkg_part1(identifier, max_signers, min_signers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index cc012d019..d4e5f7edc 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -645,7 +645,7 @@ pub mod keys { (*i, vs) }) .collect(); - PublicKeyPackage::new(verifying_shares, verifying_key) + PublicKeyPackage::new_internal(verifying_shares, verifying_key, self.min_signers()) } else { self } @@ -766,7 +766,11 @@ pub mod keys { (*i, vs) }) .collect(); - PublicKeyPackage::new(verifying_shares, verifying_key) + PublicKeyPackage::new_internal( + verifying_shares, + verifying_key, + public_key_package.min_signers(), + ) } } diff --git a/frost-secp256k1-tr/tests/helpers/samples.rs b/frost-secp256k1-tr/tests/helpers/samples.rs index 337ae709f..e94ae01ee 100644 --- a/frost-secp256k1-tr/tests/helpers/samples.rs +++ b/frost-secp256k1-tr/tests/helpers/samples.rs @@ -104,7 +104,19 @@ pub fn public_key_package() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key) + PublicKeyPackage::new_internal(verifying_shares, verifying_key, None) +} + +/// Generate a sample PublicKeyPackage with `min_signers`. +pub fn public_key_package_new() -> PublicKeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); + + PublicKeyPackage::new(verifying_shares, verifying_key, 2) } /// Generate a sample round1::SecretPackage. diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs index fe5007dad..11db8b2bb 100644 --- a/frost-secp256k1-tr/tests/integration_tests.rs +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -118,7 +118,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s Identifier::try_from(4).unwrap(), Identifier::try_from(5).unwrap(), ]; - let min_signers = 3; + let min_signers = 2; let max_signers = 3; let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; @@ -151,7 +151,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 3; + let min_signers = 2; let max_signers = 1; let error = Error::InvalidMaxSigners; diff --git a/frost-secp256k1-tr/tests/recreation_tests.rs b/frost-secp256k1-tr/tests/recreation_tests.rs index f6f71ea00..bcdd3df4b 100644 --- a/frost-secp256k1-tr/tests/recreation_tests.rs +++ b/frost-secp256k1-tr/tests/recreation_tests.rs @@ -101,8 +101,25 @@ fn check_public_key_package_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers(); - let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key); + let new_public_key_package = + PublicKeyPackage::new_internal(verifying_shares.clone(), *verifying_key, min_signers); + + assert!(public_key_package == new_public_key_package); +} + +/// Check if PublicKeyPackage can be recreated. +#[test] +fn check_public_key_package_new_recreation() { + let public_key_package = samples::public_key_package_new(); + + let verifying_shares = public_key_package.verifying_shares(); + let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers().unwrap(); + + let new_public_key_package = + PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); assert!(public_key_package == new_public_key_package); } diff --git a/frost-secp256k1-tr/tests/serde_tests.rs b/frost-secp256k1-tr/tests/serde_tests.rs index 62a70e70c..e13cf1d9a 100644 --- a/frost-secp256k1-tr/tests/serde_tests.rs +++ b/frost-secp256k1-tr/tests/serde_tests.rs @@ -434,7 +434,7 @@ fn check_key_package_serialization() { #[test] fn check_public_key_package_serialization() { - let public_key_package = samples::public_key_package(); + let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); println!("{}", json); @@ -450,11 +450,27 @@ fn check_public_key_package_serialization() { "verifying_shares": { "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" }, - "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "min_signers": 2 }"#; let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); assert!(public_key_package == decoded_public_key_package); + // Old version without min_signers + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-TR-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package.verifying_key() == decoded_public_key_package.verifying_key()); + assert!(public_key_package.verifying_shares() == decoded_public_key_package.verifying_shares()); + let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); diff --git a/frost-secp256k1-tr/tests/serialization_tests.rs b/frost-secp256k1-tr/tests/serialization_tests.rs index d38bc5ece..997501730 100644 --- a/frost-secp256k1-tr/tests/serialization_tests.rs +++ b/frost-secp256k1-tr/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_public_key_package_new_postcard_serialization() { + let public_key_package = samples::public_key_package_new(); + let bytes: Vec<_> = public_key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + public_key_package, + PublicKeyPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_secret_package_postcard_serialization() { let round1_secret_package = samples::round1_secret_package(); diff --git a/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap new file mode 100644 index 000000000..b147c34cf --- /dev/null +++ b/frost-secp256k1-tr/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1-tr/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00230f8ab301000000000000000000000000000000000000000000000000000000000000002a0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980102 diff --git a/frost-secp256k1/src/keys/refresh.rs b/frost-secp256k1/src/keys/refresh.rs index 3e26bcb76..36797d959 100644 --- a/frost-secp256k1/src/keys/refresh.rs +++ b/frost-secp256k1/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - Ciphersuite, CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, RngCore, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, max_signers: u16, min_signers: u16, @@ -29,21 +29,21 @@ pub fn compute_refreshing_shares( } /// Refer to [`frost_core::keys::refresh::refresh_share`]. -pub fn refresh_share( +pub fn refresh_share( zero_share: SecretShare, current_share: &KeyPackage, ) -> Result { frost::keys::refresh::refresh_share(zero_share, current_share) } -/// Refer to [`frost_core::keys::refresh::refresh_dkg_part_1`]. +/// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, mut rng: R, ) -> Result<(round1::SecretPackage, round1::Package), Error> { - frost::keys::refresh::refresh_dkg_part_1(identifier, max_signers, min_signers, &mut rng) + frost::keys::refresh::refresh_dkg_part1(identifier, max_signers, min_signers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part2`]. diff --git a/frost-secp256k1/tests/helpers/samples.rs b/frost-secp256k1/tests/helpers/samples.rs index a69632d29..2b1bb8ef5 100644 --- a/frost-secp256k1/tests/helpers/samples.rs +++ b/frost-secp256k1/tests/helpers/samples.rs @@ -104,7 +104,19 @@ pub fn public_key_package() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key) + PublicKeyPackage::new_internal(verifying_shares, verifying_key, None) +} + +/// Generate a sample PublicKeyPackage with `min_signers`. +pub fn public_key_package_new() -> PublicKeyPackage { + let identifier = 42u16.try_into().unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_share = VerifyingShare::deserialize(serialized_element.as_ref()).unwrap(); + let serialized_element = ::Group::serialize(&element1()).unwrap(); + let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); + let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); + + PublicKeyPackage::new(verifying_shares, verifying_key, 2) } /// Generate a sample round1::SecretPackage. diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 63ba125f5..e2c111c47 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -117,7 +117,7 @@ fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_s Identifier::try_from(4).unwrap(), Identifier::try_from(5).unwrap(), ]; - let min_signers = 3; + let min_signers = 2; let max_signers = 3; let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; @@ -150,7 +150,7 @@ fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { let rng = rand::rngs::OsRng; let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 3; + let min_signers = 2; let max_signers = 1; let error = Error::InvalidMaxSigners; diff --git a/frost-secp256k1/tests/recreation_tests.rs b/frost-secp256k1/tests/recreation_tests.rs index e61b30333..f72c154c1 100644 --- a/frost-secp256k1/tests/recreation_tests.rs +++ b/frost-secp256k1/tests/recreation_tests.rs @@ -101,8 +101,25 @@ fn check_public_key_package_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers(); - let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key); + let new_public_key_package = + PublicKeyPackage::new_internal(verifying_shares.clone(), *verifying_key, min_signers); + + assert!(public_key_package == new_public_key_package); +} + +/// Check if PublicKeyPackage can be recreated. +#[test] +fn check_public_key_package_new_recreation() { + let public_key_package = samples::public_key_package_new(); + + let verifying_shares = public_key_package.verifying_shares(); + let verifying_key = public_key_package.verifying_key(); + let min_signers = public_key_package.min_signers().unwrap(); + + let new_public_key_package = + PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); assert!(public_key_package == new_public_key_package); } diff --git a/frost-secp256k1/tests/serde_tests.rs b/frost-secp256k1/tests/serde_tests.rs index 82a0735d4..5b02fdaeb 100644 --- a/frost-secp256k1/tests/serde_tests.rs +++ b/frost-secp256k1/tests/serde_tests.rs @@ -434,7 +434,7 @@ fn check_key_package_serialization() { #[test] fn check_public_key_package_serialization() { - let public_key_package = samples::public_key_package(); + let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); println!("{}", json); @@ -450,11 +450,27 @@ fn check_public_key_package_serialization() { "verifying_shares": { "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" }, - "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "min_signers": 2 }"#; let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); assert!(public_key_package == decoded_public_key_package); + // Old version without min_signers + let json = r#"{ + "header": { + "version": 0, + "ciphersuite": "FROST-secp256k1-SHA256-v1" + }, + "verifying_shares": { + "000000000000000000000000000000000000000000000000000000000000002a": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }, + "verifying_key": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + }"#; + let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(json).unwrap(); + assert!(public_key_package.verifying_key() == decoded_public_key_package.verifying_key()); + assert!(public_key_package.verifying_shares() == decoded_public_key_package.verifying_shares()); + let invalid_json = "{}"; assert!(serde_json::from_str::(invalid_json).is_err()); diff --git a/frost-secp256k1/tests/serialization_tests.rs b/frost-secp256k1/tests/serialization_tests.rs index ec8d6f8ef..08e53d3e4 100644 --- a/frost-secp256k1/tests/serialization_tests.rs +++ b/frost-secp256k1/tests/serialization_tests.rs @@ -82,6 +82,17 @@ fn check_public_key_package_postcard_serialization() { ); } +#[test] +fn check_public_key_package_new_postcard_serialization() { + let public_key_package = samples::public_key_package_new(); + let bytes: Vec<_> = public_key_package.serialize().unwrap(); + assert_snapshot!(hex::encode(&bytes)); + assert_eq!( + public_key_package, + PublicKeyPackage::deserialize(&bytes).unwrap() + ); +} + #[test] fn check_round1_secret_package_postcard_serialization() { let round1_secret_package = samples::round1_secret_package(); diff --git a/frost-secp256k1/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap b/frost-secp256k1/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap new file mode 100644 index 000000000..0435fdaa1 --- /dev/null +++ b/frost-secp256k1/tests/snapshots/serialization_tests__check_public_key_package_new_postcard_serialization.snap @@ -0,0 +1,5 @@ +--- +source: frost-secp256k1/tests/serialization_tests.rs +expression: "hex::encode(&bytes)" +--- +00eed6b1b101000000000000000000000000000000000000000000000000000000000000002a0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980102 From 8f60e6da72d4d948f64c309bb70b16fc21c7cf37 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 11:53:28 -0300 Subject: [PATCH 141/175] core: add more serialization roundtrips to tests (#952) --- frost-core/src/tests/ciphersuite_generic.rs | 77 ++++++++++++++++++--- frost-core/src/tests/refresh.rs | 40 ++++++++++- 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index a2c0df8c1..a15bd2569 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -6,7 +6,8 @@ use rand_core::{CryptoRng, RngCore}; use crate as frost; use crate::keys::dkg::{round1, round2}; -use crate::keys::SigningShare; +use crate::keys::{SecretShare, SigningShare}; +use crate::round1::SigningNonces; use crate::round2::SignatureShare; use crate::{ keys::PublicKeyPackage, Error, Field, Group, Identifier, Signature, SigningKey, SigningPackage, @@ -26,6 +27,8 @@ pub fn check_zero_key_fails() { /// Test share generation with a Ciphersuite pub fn check_share_generation(mut rng: R) { let secret = crate::SigningKey::::new(&mut rng); + // Simulate serialization / deserialization to ensure it works + let secret = SigningKey::deserialize(&secret.serialize()).unwrap(); let max_signers = 5; let min_signers = 3; @@ -110,20 +113,30 @@ pub fn check_sign_with_dealer( let max_signers = 5; let min_signers = 3; - let (shares, pubkeys) = frost::keys::generate_with_dealer( + let (shares, pub_key_package) = frost::keys::generate_with_dealer( max_signers, min_signers, frost::keys::IdentifierList::Default, &mut rng, ) .unwrap(); + // Simulate serialization / deserialization to ensure it works + let pub_key_package = + PublicKeyPackage::deserialize(&pub_key_package.serialize().unwrap()).unwrap(); // Verifies the secret shares from the dealer - let key_packages: BTreeMap, frost::keys::KeyPackage> = shares - .into_iter() - .map(|(k, v)| (k, frost::keys::KeyPackage::try_from(v).unwrap())) - .collect(); + let mut key_packages: BTreeMap, frost::keys::KeyPackage> = + BTreeMap::new(); + for (k, v) in shares { + // Simulate serialization / deserialization to ensure it works + let v = SecretShare::::deserialize(&v.serialize().unwrap()).unwrap(); + let key_package = frost::keys::KeyPackage::try_from(v).unwrap(); + // Simulate serialization / deserialization to ensure it works + let key_package = + frost::keys::KeyPackage::deserialize(&key_package.serialize().unwrap()).unwrap(); + key_packages.insert(k, key_package); + } // Check if it fails with not enough signers. Usually this would return an // error before even running the signing procedure, because `KeyPackage` // contains the correct `min_signers` value and the signing procedure checks @@ -144,11 +157,11 @@ pub fn check_sign_with_dealer( }) .collect(), &mut rng, - pubkeys.clone(), + pub_key_package.clone(), ); assert_eq!(r, Err(Error::InvalidSignature)); - check_sign(min_signers, key_packages, rng, pubkeys).unwrap() + check_sign(min_signers, key_packages, rng, pub_key_package).unwrap() } /// Test FROST signing with trusted dealer fails with invalid numbers of signers. @@ -203,7 +216,10 @@ pub fn check_sign( // Round 1: generating nonces and signing commitments for each participant //////////////////////////////////////////////////////////////////////////// - for participant_identifier in key_packages.keys().take(min_signers as usize).cloned() { + for participant_identifier in key_packages.keys().take(min_signers as usize) { + // Simulate serialization / deserialization to ensure it works + let participant_identifier = + Identifier::deserialize(&participant_identifier.serialize()).unwrap(); // Generate one (1) nonce and one SigningCommitments instance for each // participant, up to _min_signers_. let (nonces, commitments) = frost::round1::commit( @@ -213,6 +229,11 @@ pub fn check_sign( .signing_share(), &mut rng, ); + // Simulate serialization / deserialization to ensure it works + let nonces = SigningNonces::deserialize(&nonces.serialize().unwrap()).unwrap(); + let commitments = + frost::round1::SigningCommitments::deserialize(&commitments.serialize().unwrap()) + .unwrap(); nonces_map.insert(participant_identifier, nonces); commitments_map.insert(participant_identifier, commitments); } @@ -223,6 +244,9 @@ pub fn check_sign( let mut signature_shares = BTreeMap::new(); let message = "message to sign".as_bytes(); let signing_package = SigningPackage::new(commitments_map, message); + // Simulate serialization / deserialization to ensure it works + let signing_package = + SigningPackage::deserialize(&signing_package.serialize().unwrap()).unwrap(); //////////////////////////////////////////////////////////////////////////// // Round 2: each participant generates their signature share @@ -241,6 +265,8 @@ pub fn check_sign( // Each participant generates their signature share. let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?; + // Simulate serialization / deserialization to ensure it works + let signature_share = SignatureShare::deserialize(&signature_share.serialize()).unwrap(); signature_shares.insert(*participant_identifier, signature_share); } @@ -265,6 +291,8 @@ pub fn check_sign( // Aggregate (also verifies the signature shares) let group_signature = frost::aggregate(&signing_package, &signature_shares, &pubkey_package)?; + // Simulate serialization / deserialization to ensure it works + let group_signature = Signature::deserialize(&group_signature.serialize().unwrap()).unwrap(); // Check that the threshold signature can be verified by the group public // key (the verification key). @@ -451,6 +479,16 @@ where frost::keys::dkg::part1(participant_identifier, max_signers, min_signers, &mut rng) .unwrap(); + // Simulate serialization / deserialization to ensure it works + let round1_secret_package = frost::keys::dkg::round1::SecretPackage::::deserialize( + &round1_secret_package.serialize().unwrap(), + ) + .unwrap(); + let round1_package = frost::keys::dkg::round1::Package::::deserialize( + &round1_package.serialize().unwrap(), + ) + .unwrap(); + // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. round1_secret_packages.insert( @@ -507,6 +545,12 @@ where let (round2_secret_package, round2_packages) = frost::keys::dkg::part2(round1_secret_package, round1_packages).expect("should work"); + // Simulate serialization / deserialization to ensure it works + let round2_secret_package = frost::keys::dkg::round2::SecretPackage::::deserialize( + &round2_secret_package.serialize().unwrap(), + ) + .unwrap(); + // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. round2_secret_packages.insert( @@ -522,6 +566,11 @@ where // Note that, in contrast to the previous part, here each other participant // gets its own specific package. for (receiver_identifier, round2_package) in round2_packages { + // Simulate serialization / deserialization to ensure it works + let round2_package = frost::keys::dkg::round2::Package::::deserialize( + &round2_package.serialize().unwrap(), + ) + .unwrap(); received_round2_packages .entry(receiver_identifier) .or_insert_with(BTreeMap::new) @@ -572,6 +621,13 @@ where &received_round2_packages[&participant_identifier], ) .unwrap(); + // Simulate serialization / deserialization to ensure it works + let key_package = + frost::keys::KeyPackage::deserialize(&key_package.serialize().unwrap()).unwrap(); + let pubkey_package_for_participant = frost::keys::PublicKeyPackage::deserialize( + &pubkey_package_for_participant.serialize().unwrap(), + ) + .unwrap(); verifying_shares.insert(participant_identifier, key_package.verifying_share); // Test if all verifying_key are equal if let Some(previous_verifying_key) = verifying_key { @@ -593,6 +649,9 @@ where .unwrap() .1 .clone(); + // Simulate serialization / deserialization to ensure it works + let pubkeys = + frost::keys::PublicKeyPackage::deserialize(&pubkeys.serialize().unwrap()).unwrap(); // Proceed with the signing test. check_sign(min_signers, key_packages, rng, pubkeys).unwrap() diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index f9b58dbe2..a8fdfb5bb 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -69,6 +69,10 @@ pub fn check_refresh_shares_with_dealer( &mut rng, ) .unwrap(); + // Simulate serialization / deserialization to ensure it works + let new_pub_key_package = + frost::keys::PublicKeyPackage::deserialize(&new_pub_key_package.serialize().unwrap()) + .unwrap(); // Each participant refreshes their share @@ -79,14 +83,16 @@ pub fn check_refresh_shares_with_dealer( let current_share = &old_key_packages[&identifier]; // Do a serialization roundtrip to simulate real usage let zero_share = SecretShare::deserialize(&zero_shares[i].serialize().unwrap()).unwrap(); - let new_share = refresh_share(zero_share, current_share); + let new_share = refresh_share(zero_share, current_share).unwrap(); new_shares.insert(identifier, new_share); } let mut key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); for (k, v) in new_shares { - key_packages.insert(k, v.unwrap()); + // Simulate serialization / deserialization to ensure it works + let v = KeyPackage::::deserialize(&v.serialize().unwrap()).unwrap(); + key_packages.insert(k, v); } check_sign(MIN_SIGNERS, key_packages, rng, new_pub_key_package).unwrap(); } @@ -371,6 +377,16 @@ where let (round1_secret_package, round1_package) = refresh_dkg_part1(participant_identifier, max_signers, min_signers, &mut rng).unwrap(); + // Simulate serialization / deserialization to ensure it works + let round1_secret_package = frost::keys::dkg::round1::SecretPackage::::deserialize( + &round1_secret_package.serialize().unwrap(), + ) + .unwrap(); + let round1_package = frost::keys::dkg::round1::Package::::deserialize( + &round1_package.serialize().unwrap(), + ) + .unwrap(); + // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. round1_secret_packages.insert( @@ -421,6 +437,12 @@ where let (round2_secret_package, round2_packages) = refresh_dkg_part2(round1_secret_package, round1_packages).expect("should work"); + // Simulate serialization / deserialization to ensure it works + let round2_secret_package = frost::keys::dkg::round2::SecretPackage::::deserialize( + &round2_secret_package.serialize().unwrap(), + ) + .unwrap(); + // Store the participant's secret package for later use. // In practice each participant will store it in their own environment. round2_secret_packages.insert( @@ -436,6 +458,11 @@ where // Note that, in contrast to the previous part, here each other participant // gets its own specific package. for (receiver_identifier, round2_package) in round2_packages { + // Simulate serialization / deserialization to ensure it works + let round2_package = frost::keys::dkg::round2::Package::::deserialize( + &round2_package.serialize().unwrap(), + ) + .unwrap(); received_round2_packages .entry(receiver_identifier) .or_insert_with(BTreeMap::new) @@ -488,6 +515,12 @@ where old_key_packages[&participant_identifier].clone(), ) .unwrap(); + // Simulate serialization / deserialization to ensure it works + let key_package = KeyPackage::deserialize(&key_package.serialize().unwrap()).unwrap(); + let pubkey_package_for_participant = frost::keys::PublicKeyPackage::deserialize( + &pubkey_package_for_participant.serialize().unwrap(), + ) + .unwrap(); verifying_shares.insert(participant_identifier, key_package.verifying_share); // Test if all verifying_key are equal if let Some(previous_verifying_key) = verifying_key { @@ -509,6 +542,9 @@ where .unwrap() .1 .clone(); + // Simulate serialization / deserialization to ensure it works + let pubkeys = + frost::keys::PublicKeyPackage::deserialize(&pubkeys.serialize().unwrap()).unwrap(); // Proceed with the signing test. check_sign(min_signers, key_packages, rng, pubkeys).unwrap() From 59b4dbd8be6f6914e7811dc0a555f940926562e9 Mon Sep 17 00:00:00 2001 From: Conrado Date: Mon, 22 Dec 2025 14:05:50 -0300 Subject: [PATCH 142/175] update debugless-unwrap (#962) --- Cargo.lock | 4 ++-- Cargo.toml | 2 ++ frost-core/Cargo.toml | 5 +++-- frost-core/src/tests/coefficient_commitment.rs | 2 +- frost-core/src/tests/repairable.rs | 2 +- frost-core/src/tests/vectors.rs | 2 +- frost-core/src/tests/vectors_dkg.rs | 2 +- frost-core/src/tests/vss_commitment.rs | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b63dc00b..d80823506 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -353,9 +353,9 @@ dependencies = [ [[package]] name = "debugless-unwrap" -version = "0.0.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f400d0750c0c069e8493f2256cb4da6f604b6d2eeb69a0ca8863acde352f8400" +checksum = "b93fdcfc175d53094fca1d23d171685621bb67f1658413514f2053d575b3b38c" [[package]] name = "der" diff --git a/Cargo.toml b/Cargo.toml index 6b89170f9..dc52fea2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,8 @@ repository = "https://github.com/ZcashFoundation/frost" categories = ["cryptography"] [workspace.dependencies] +# Currently holding back from updating due to MSRV 1.86 of criterion 0.8 +# (we don't want to raise our MSRV just for this) criterion = "0.6" document-features = "0.2.7" hex = { version = "0.4.3", default-features = false, features = ["alloc"] } diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 7e3c5d334..a5147822b 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -19,7 +19,7 @@ rustdoc-args = ["--cfg", "docsrs"] byteorder = { version = "1.4", default-features = false } const-crc32 = { version = "1.2.0", package = "const-crc32-nostd" } document-features.workspace = true -debugless-unwrap = "0.0.4" +debugless-unwrap = { version = "1.0.0", optional = true } derive-getters = "0.5.0" hex.workspace = true postcard = { version = "1.0.0", features = ["alloc"], optional = true } @@ -41,6 +41,7 @@ criterion = { workspace = true, optional = true } tokio = { workspace = true, optional = true } [dev-dependencies] +debugless-unwrap = "1.0.0" criterion.workspace = true lazy_static.workspace = true proptest.workspace = true @@ -64,7 +65,7 @@ internals = [] serde = ["dep:serde", "dep:serdect"] serialization = ["serde", "dep:postcard"] # Exposes ciphersuite-generic tests for other crates to use -test-impl = ["dep:proptest", "dep:serde_json", "dep:criterion", "dep:tokio"] +test-impl = ["dep:proptest", "dep:serde_json", "dep:criterion", "dep:tokio", "dep:debugless-unwrap"] [lib] bench = false diff --git a/frost-core/src/tests/coefficient_commitment.rs b/frost-core/src/tests/coefficient_commitment.rs index 533bf5433..b491108b9 100644 --- a/frost-core/src/tests/coefficient_commitment.rs +++ b/frost-core/src/tests/coefficient_commitment.rs @@ -2,7 +2,7 @@ use crate as frost; use crate::{keys::CoefficientCommitment, tests::helpers::generate_element, Group}; -use debugless_unwrap::DebuglessUnwrap; +use debugless_unwrap::DebuglessUnwrapExt; use rand_core::{CryptoRng, RngCore}; use serde_json::Value; diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index 27136f885..4c1aaf8ec 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -2,7 +2,7 @@ use alloc::collections::BTreeMap; -use debugless_unwrap::DebuglessUnwrap; +use debugless_unwrap::DebuglessUnwrapExt; use rand_core::{CryptoRng, RngCore}; use serde_json::Value; diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index e278d0838..b3bfd7f81 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -1,7 +1,7 @@ //! Helper function for testing with test vectors. use alloc::collections::BTreeMap; -use debugless_unwrap::DebuglessUnwrap; +use debugless_unwrap::DebuglessUnwrapExt; use hex::{self, FromHex}; use serde_json::Value; diff --git a/frost-core/src/tests/vectors_dkg.rs b/frost-core/src/tests/vectors_dkg.rs index 90fafef26..6f1ce5353 100644 --- a/frost-core/src/tests/vectors_dkg.rs +++ b/frost-core/src/tests/vectors_dkg.rs @@ -4,7 +4,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use debugless_unwrap::DebuglessUnwrap; +use debugless_unwrap::DebuglessUnwrapExt; use hex::{self}; use serde_json::Value; diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 6949581db..62a27556f 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -6,7 +6,7 @@ use crate::{ Error, Group, }; use alloc::vec::Vec; -use debugless_unwrap::DebuglessUnwrap; +use debugless_unwrap::DebuglessUnwrapExt; use rand_core::{CryptoRng, RngCore}; use serde_json::Value; From 504ef202a6297b173f962a3f7c31dcf1be1cee2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kyle=20=F0=9F=90=86?= Date: Tue, 6 Jan 2026 10:10:45 -0400 Subject: [PATCH 143/175] Implement ZeroizeOnDrop for SigningNonces (#987) --- frost-core/src/round1.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index 33b7814df..b6f204943 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -14,7 +14,7 @@ use derive_getters::Getters; use hex::FromHex; use rand_core::{CryptoRng, RngCore}; -use zeroize::Zeroize; +use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::{ serialization::{SerializableElement, SerializableScalar}, @@ -209,7 +209,7 @@ where /// Note that [`SigningNonces`] must be used *only once* for a signing /// operation; re-using nonces will result in leakage of a signer's long-lived /// signing key. -#[derive(Clone, Zeroize, PartialEq, Eq, Getters)] +#[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq, Getters)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] From 78066bffcffd40895ce836f45bf10ce18ff2a440 Mon Sep 17 00:00:00 2001 From: Conrado Date: Fri, 23 Jan 2026 07:58:20 -0300 Subject: [PATCH 144/175] all: cleanup repairable module (#957) * all: cleanup repairable module --- frost-core/CHANGELOG.md | 10 ++ frost-core/src/keys/repairable.rs | 195 ++++++++++++++++------ frost-core/src/tests/repairable.rs | 151 ++++++++++------- frost-ed25519/src/keys/repairable.rs | 52 +++--- frost-ed448/src/keys/repairable.rs | 52 +++--- frost-p256/src/keys/repairable.rs | 52 +++--- frost-ristretto255/src/keys/repairable.rs | 52 +++--- frost-secp256k1-tr/src/keys/repairable.rs | 52 +++--- frost-secp256k1/src/keys/repairable.rs | 52 +++--- 9 files changed, 412 insertions(+), 256 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index ca1fbfb4f..be59fdf1e 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -15,6 +15,16 @@ Entries are listed in reverse chronological order. * The `std` and `nightly` features were removed from all crates * Renamed `frost_core::keys::refresh::refresh_dkg_part_1` to `refresh_dkg_part1`. * Fixed the crate-specific versions of the `refresh` module to be non-generic. +* Refactored the `frost_core::keys::repairable` module: + * `repair_share_step_1()` now takes a `KeyPackage` and returns a map with + a new `Delta` type instead of a raw `Scalar` + * `repair_share_step_2()` now takes the `Delta` type and returns a new `Sigma` + type instead of a raw `Scalar` + * `repair_share_step_3()` now takes the `Sigma` type and a `PublicKeyPackage` + instead of `VerifiableSecretSharingCommitment`; and returns a `KeyPackage` + instead of `SecretShare`. + * These changes provide more type safety and are make it more useful since + `SecretPackage`s are not expected to be stored ### Additional changes diff --git a/frost-core/src/keys/repairable.rs b/frost-core/src/keys/repairable.rs index 24cd32b60..6acd52eaa 100644 --- a/frost-core/src/keys/repairable.rs +++ b/frost-core/src/keys/repairable.rs @@ -1,40 +1,128 @@ //! Repairable Threshold Scheme //! -//! Implements the Repairable Threshold Scheme (RTS) from . -//! The RTS is used to help a signer (participant) repair their lost share. This is achieved -//! using a subset of the other signers known here as `helpers`. +//! Implements the Repairable Threshold Scheme (RTS) from +//! . The RTS is used to help a signer +//! (participant) repair their lost share. This is achieved using a subset of +//! the other signers known here as `helpers`. +//! +//! The repair procedure should be run as follows: +//! +//! - Participants need to agree somehow on who are going to be the `helpers` +//! for the repair, and which participant is going to repair their share. +//! - Each helper runs `repair_share_step_1`, generating a set of `delta` values +//! to be sent to each helper (including themselves). +//! - Each helper runs `repair_share_step_2`, passing the received `delta` +//! values, generating a `sigma` value to be sent to the participant repairing +//! their share. +//! - The participant repairing their share runs `repair_share_step_3`, passing +//! all the received `sigma` values, recovering their lost `KeyPackage`. (They +//! will also need the `PublicKeyPackage` for this step which could be +//! provided by any of the helpers). use alloc::collections::{BTreeMap, BTreeSet}; use alloc::vec::Vec; +use crate::keys::{KeyPackage, PublicKeyPackage}; +use crate::serialization::SerializableScalar; use crate::{ - compute_lagrange_coefficient, Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier, - RngCore, Scalar, + compute_lagrange_coefficient, Ciphersuite, CryptoRng, Error, Field, Group, Identifier, RngCore, + Scalar, }; -use super::{generate_coefficients, SecretShare, SigningShare, VerifiableSecretSharingCommitment}; +use super::{generate_coefficients, SigningShare}; + +/// A delta value which is the output of step 1 of RTS. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct Delta(pub(crate) SerializableScalar); + +impl Delta +where + C: Ciphersuite, +{ + /// Create a new [`Delta`] from a scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn new(scalar: Scalar) -> Self { + Self(SerializableScalar(scalar)) + } + + /// Get the inner scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn to_scalar(&self) -> Scalar { + self.0 .0 + } + + /// Deserialize from bytes + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self(SerializableScalar::deserialize(bytes)?)) + } + + /// Serialize to bytes + pub fn serialize(&self) -> Vec { + self.0.serialize() + } +} + +/// A sigma value which is the output of step 2 of RTS. +#[derive(Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] +#[cfg_attr(feature = "serde", serde(transparent))] +pub struct Sigma(pub(crate) SerializableScalar); + +impl Sigma +where + C: Ciphersuite, +{ + /// Create a new [`Sigma`] from a scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn new(scalar: Scalar) -> Self { + Self(SerializableScalar(scalar)) + } + + /// Get the inner scalar. + #[cfg_attr(feature = "internals", visibility::make(pub))] + #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] + pub(crate) fn to_scalar(&self) -> Scalar { + self.0 .0 + } + + /// Deserialize from bytes + pub fn deserialize(bytes: &[u8]) -> Result> { + Ok(Self(SerializableScalar::deserialize(bytes)?)) + } + + /// Serialize to bytes + pub fn serialize(&self) -> Vec { + self.0.serialize() + } +} /// Step 1 of RTS. /// -/// Generates the "delta" values from `helper_i` to help `participant` recover their share -/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` -/// is the share of `helper_i`. +/// Generates the "delta" values from the helper with `key_package_i` to send to +/// `helpers` (which includes the helper with `key_package_i`), to help +/// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. pub fn repair_share_step_1( helpers: &[Identifier], - share_i: &SecretShare, + key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, -) -> Result, Scalar>, Error> { +) -> Result, Delta>, Error> { if helpers.len() < 2 { - return Err(Error::InvalidMinSigners); - } - - if helpers.is_empty() { return Err(Error::IncorrectNumberOfIdentifiers); } + if !helpers.contains(&key_package_i.identifier) { + return Err(Error::UnknownIdentifier); + } let xset: BTreeSet<_> = helpers.iter().cloned().collect(); if xset.len() != helpers.len() { return Err(Error::DuplicatedIdentifier); @@ -42,7 +130,7 @@ pub fn repair_share_step_1( let rand_val: Vec> = generate_coefficients::(helpers.len() - 1, rng); - compute_last_random_value(&xset, share_i, &rand_val, participant) + compute_last_random_value(&xset, key_package_i, &rand_val, participant) } /// Compute the last delta value given the (generated uniformly at random) remaining ones @@ -51,19 +139,20 @@ pub fn repair_share_step_1( /// Returns a BTreeMap mapping which value should be sent to which participant. fn compute_last_random_value( helpers: &BTreeSet>, - share_i: &SecretShare, + key_package_i: &KeyPackage, random_values: &Vec>, participant: Identifier, -) -> Result, Scalar>, Error> { +) -> Result, Delta>, Error> { // Calculate Lagrange Coefficient for helper_i - let zeta_i = compute_lagrange_coefficient(helpers, Some(participant), share_i.identifier)?; + let zeta_i = + compute_lagrange_coefficient(helpers, Some(participant), key_package_i.identifier)?; - let lhs = zeta_i * share_i.signing_share.to_scalar(); + let lhs = zeta_i * key_package_i.signing_share.to_scalar(); - let mut out: BTreeMap, Scalar> = helpers + let mut out: BTreeMap, Delta> = helpers .iter() .copied() - .zip(random_values.iter().copied()) + .zip(random_values.iter().map(|v| Delta::new(*v))) .collect(); let mut sum_i_deltas = <::Field>::zero(); @@ -74,58 +163,56 @@ fn compute_last_random_value( out.insert( *helpers.last().ok_or(Error::IncorrectNumberOfIdentifiers)?, - lhs - sum_i_deltas, + Delta::new(lhs - sum_i_deltas), ); Ok(out) } -// Communication round -// -// `helper_i` sends 1 `delta_j` to all other helpers (j) -// `helper_i` retains 1 `delta_j` - /// Step 2 of RTS. /// -/// Generates the `sigma` values from all `deltas` received from `helpers` -/// to help `participant` recover their share. -/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. -/// -/// Returns a scalar -pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { +/// Generates the "sigma" value from all `deltas` received from all helpers. +/// The "sigma" value must be sent to the participant repairing their share. +pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { let mut sigma_j = <::Field>::zero(); - for d in deltas_j { - sigma_j = sigma_j + *d; + for d in deltas { + sigma_j = sigma_j + d.to_scalar(); } - sigma_j + Sigma::new(sigma_j) } -// Communication round -// -// `helper_j` sends 1 `sigma_j` to the `participant` repairing their share. - -/// Step 3 of RTS +/// Step 3 of RTS. +/// +/// The participant with the given `identifier` recovers their `KeyPackage` +/// with the "sigma" values received from all helpers and the `PublicKeyPackage` +/// of the group (which can be sent by any of the helpers). /// -/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` -/// is made up of the `identifier`and `commitment` of the `participant` as well as the -/// `value` which is the `SigningShare`. +/// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. +/// This happens for `PublicKeyPackage`s created before the 3.0.0 release; +/// in that case, the user should set the `min_signers` field manually. pub fn repair_share_step_3( - sigmas: &[Scalar], + sigmas: &[Sigma], identifier: Identifier, - commitment: &VerifiableSecretSharingCommitment, -) -> SecretShare { + public_key_package: &PublicKeyPackage, +) -> Result, Error> { let mut share = <::Field>::zero(); for s in sigmas { - share = share + *s; + share = share + s.to_scalar(); } + let signing_share = SigningShare::new(share); + let verifying_share = signing_share.into(); - SecretShare { - header: Header::default(), + Ok(KeyPackage { + header: Default::default(), identifier, - signing_share: SigningShare::new(share), - commitment: commitment.clone(), - } + signing_share, + verifying_share, + verifying_key: *public_key_package.verifying_key(), + min_signers: public_key_package + .min_signers() + .ok_or(Error::InvalidMinSigners)?, + }) } diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index 4c1aaf8ec..4dd5986e3 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -7,13 +7,15 @@ use rand_core::{CryptoRng, RngCore}; use serde_json::Value; use crate as frost; +use crate::keys::repairable::{Delta, Sigma}; +use crate::keys::KeyPackage; use crate::{ compute_lagrange_coefficient, keys::{ repairable::{repair_share_step_1, repair_share_step_2, repair_share_step_3}, - PublicKeyPackage, SecretShare, SigningShare, + PublicKeyPackage, SecretShare, }, - Ciphersuite, Error, Field, Group, Identifier, Scalar, + Ciphersuite, Error, Field, Group, Identifier, }; /// We want to test that recovered share matches the original share @@ -26,24 +28,30 @@ pub fn check_rts(mut rng: R) { let max_signers = 5; let min_signers = 3; - let (shares, _pubkeys): (BTreeMap, SecretShare>, PublicKeyPackage) = - frost::keys::generate_with_dealer( - max_signers, - min_signers, - frost::keys::IdentifierList::Default, - &mut rng, - ) - .unwrap(); + let (shares, public_key_package): ( + BTreeMap, SecretShare>, + PublicKeyPackage, + ) = frost::keys::generate_with_dealer( + max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); + let key_packages = shares + .into_iter() + .map(|(id, share)| (id, share.try_into().unwrap())) + .collect::, KeyPackage>>(); // Try to recover a share // Signer 2 will lose their share // Signer 1, 4 and 5 will help signer 2 to recover their share - let helper_1 = &shares[&Identifier::try_from(1).unwrap()]; - let helper_4 = &shares[&Identifier::try_from(4).unwrap()]; - let helper_5 = &shares[&Identifier::try_from(5).unwrap()]; - let participant = &shares[&Identifier::try_from(2).unwrap()]; + let helper_1 = &key_packages[&Identifier::try_from(1).unwrap()]; + let helper_4 = &key_packages[&Identifier::try_from(4).unwrap()]; + let helper_5 = &key_packages[&Identifier::try_from(5).unwrap()]; + let participant = &key_packages[&Identifier::try_from(2).unwrap()]; let helpers: [Identifier; 3] = [ helper_1.identifier, @@ -62,17 +70,17 @@ pub fn check_rts(mut rng: R) { // Each helper calculates their sigma from the random values received from the other helpers - let helper_1_sigma: Scalar = repair_share_step_2::(&[ + let helper_1_sigma: Sigma = repair_share_step_2::(&[ helper_1_deltas[&helpers[0]], helper_4_deltas[&helpers[0]], helper_5_deltas[&helpers[0]], ]); - let helper_4_sigma: Scalar = repair_share_step_2::(&[ + let helper_4_sigma: Sigma = repair_share_step_2::(&[ helper_1_deltas[&helpers[1]], helper_4_deltas[&helpers[1]], helper_5_deltas[&helpers[1]], ]); - let helper_5_sigma: Scalar = repair_share_step_2::(&[ + let helper_5_sigma: Sigma = repair_share_step_2::(&[ helper_1_deltas[&helpers[2]], helper_4_deltas[&helpers[2]], helper_5_deltas[&helpers[2]], @@ -83,10 +91,10 @@ pub fn check_rts(mut rng: R) { let participant_recovered_share = repair_share_step_3( &[helper_1_sigma, helper_4_sigma, helper_5_sigma], participant.identifier, - &participant.commitment, - ); + &public_key_package, + ) + .unwrap(); - // TODO: assert on commitment equality as well once updates have been made to VerifiableSecretSharingCommitment assert!(participant.signing_share() == participant_recovered_share.signing_share()) } @@ -114,14 +122,18 @@ pub fn check_repair_share_step_1(mut rng &mut rng, ) .unwrap(); + let key_packages = shares + .into_iter() + .map(|(id, share)| (id, share.try_into().unwrap())) + .collect::, KeyPackage>>(); // Signer 2 will lose their share // Signers (helpers) 1, 4 and 5 will help signer 2 (participant) to recover their share - let helper_1 = &shares[&Identifier::try_from(1).unwrap()]; - let helper_4 = &shares[&Identifier::try_from(4).unwrap()]; - let helper_5 = &shares[&Identifier::try_from(5).unwrap()]; - let participant = &shares[&Identifier::try_from(2).unwrap()]; + let helper_1 = &key_packages[&Identifier::try_from(1).unwrap()]; + let helper_4 = &key_packages[&Identifier::try_from(4).unwrap()]; + let helper_5 = &key_packages[&Identifier::try_from(5).unwrap()]; + let participant = &key_packages[&Identifier::try_from(2).unwrap()]; let helpers: [Identifier; 3] = [ helper_1.identifier, @@ -141,7 +153,7 @@ pub fn check_repair_share_step_1(mut rng let mut rhs = <::Field>::zero(); for (_k, v) in deltas { - rhs = rhs + v; + rhs = rhs + v.to_scalar(); } let lhs = lagrange_coefficient * helper_4.signing_share.to_scalar(); @@ -153,17 +165,20 @@ pub fn check_repair_share_step_1(mut rng pub fn check_repair_share_step_2(repair_share_helpers: &Value) { let values = &repair_share_helpers["scalar_generation"]; - let value_1 = - generate_scalar_from_byte_string::(values["random_scalar_1"].as_str().unwrap()); - let value_2 = - generate_scalar_from_byte_string::(values["random_scalar_2"].as_str().unwrap()); - let value_3 = - generate_scalar_from_byte_string::(values["random_scalar_3"].as_str().unwrap()); - - let expected: Scalar = repair_share_step_2::(&[value_1, value_2, value_3]); - - let actual: <::Field as Field>::Scalar = - generate_scalar_from_byte_string::(values["random_scalar_sum"].as_str().unwrap()); + let value_1 = Delta::new(generate_scalar_from_byte_string::( + values["random_scalar_1"].as_str().unwrap(), + )); + let value_2 = Delta::new(generate_scalar_from_byte_string::( + values["random_scalar_2"].as_str().unwrap(), + )); + let value_3 = Delta::new(generate_scalar_from_byte_string::( + values["random_scalar_3"].as_str().unwrap(), + )); + let expected = repair_share_step_2::(&[value_1, value_2, value_3]); + + let actual = Sigma::new(generate_scalar_from_byte_string::( + values["random_scalar_sum"].as_str().unwrap(), + )); assert!(actual == expected); } @@ -173,42 +188,46 @@ pub fn check_repair_share_step_3( mut rng: R, repair_share_helpers: &Value, ) { - // Generate shares + // We need a dummy public key package to call the function let max_signers = 5; let min_signers = 3; - let (shares, _pubkeys): (BTreeMap, SecretShare>, PublicKeyPackage) = - frost::keys::generate_with_dealer( - max_signers, - min_signers, - frost::keys::IdentifierList::Default, - &mut rng, - ) - .unwrap(); + let (_shares, public_key_package): ( + BTreeMap, SecretShare>, + PublicKeyPackage, + ) = frost::keys::generate_with_dealer( + max_signers, + min_signers, + frost::keys::IdentifierList::Default, + &mut rng, + ) + .unwrap(); let sigmas: &Value = &repair_share_helpers["sigma_generation"]; - let sigma_1 = generate_scalar_from_byte_string::(sigmas["sigma_1"].as_str().unwrap()); - let sigma_2 = generate_scalar_from_byte_string::(sigmas["sigma_2"].as_str().unwrap()); - let sigma_3 = generate_scalar_from_byte_string::(sigmas["sigma_3"].as_str().unwrap()); - let sigma_4 = generate_scalar_from_byte_string::(sigmas["sigma_4"].as_str().unwrap()); - - let commitment = (shares[&Identifier::try_from(1).unwrap()].commitment).clone(); - - let expected = repair_share_step_3::( + let sigma_1 = Sigma::new(generate_scalar_from_byte_string::( + sigmas["sigma_1"].as_str().unwrap(), + )); + let sigma_2 = Sigma::new(generate_scalar_from_byte_string::( + sigmas["sigma_2"].as_str().unwrap(), + )); + let sigma_3 = Sigma::new(generate_scalar_from_byte_string::( + sigmas["sigma_3"].as_str().unwrap(), + )); + let sigma_4 = Sigma::new(generate_scalar_from_byte_string::( + sigmas["sigma_4"].as_str().unwrap(), + )); + + let actual = repair_share_step_3::( &[sigma_1, sigma_2, sigma_3, sigma_4], Identifier::try_from(2).unwrap(), - &commitment, - ); + &public_key_package, + ) + .unwrap(); - let actual_sigma: <::Field as Field>::Scalar = + let expected: <::Field as Field>::Scalar = generate_scalar_from_byte_string::(sigmas["sigma_sum"].as_str().unwrap()); - let actual: SecretShare = SecretShare::new( - Identifier::try_from(2).unwrap(), - SigningShare::new(actual_sigma), - commitment, - ); - assert!(actual.signing_share == expected.signing_share); + assert!(expected == actual.signing_share().to_scalar()); } /// Test repair share step 1 fails with invalid numbers of signers. @@ -229,16 +248,20 @@ pub fn check_repair_share_step_1_fails_with_invalid_min_signers< &mut rng, ) .unwrap(); + let key_packages = shares + .into_iter() + .map(|(id, share)| (id, share.try_into().unwrap())) + .collect::, KeyPackage>>(); let helper = Identifier::try_from(3).unwrap(); let out = repair_share_step_1( &[helper], - &shares[&helper], + &key_packages[&helper], &mut rng, Identifier::try_from(2).unwrap(), ); assert!(out.is_err()); - assert!(out == Err(Error::InvalidMinSigners)) + assert!(out == Err(Error::IncorrectNumberOfIdentifiers)) } diff --git a/frost-ed25519/src/keys/repairable.rs b/frost-ed25519/src/keys/repairable.rs index f0df91b75..0d9930f24 100644 --- a/frost-ed25519/src/keys/repairable.rs +++ b/frost-ed25519/src/keys/repairable.rs @@ -6,52 +6,58 @@ use alloc::collections::BTreeMap; +use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Ed25519Sha512, Error}; -use super::{SecretShare, VerifiableSecretSharingCommitment}; +/// A delta value which is the output of step 1 of RTS. +pub type Delta = frost::keys::repairable::Delta; + +/// A sigma value which is the output of step 2 of RTS. +pub type Sigma = frost::keys::repairable::Sigma; /// Step 1 of RTS. /// -/// Generates the "delta" values from `helper_i` to help `participant` recover their share -/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` -/// is the share of `helper_i`. +/// Generates the "delta" values from the helper with `key_package_i` to send to +/// `helpers` (which includes the helper with `key_package_i`), to help +/// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. pub fn repair_share_step_1( helpers: &[Identifier], - share_i: &SecretShare, + key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, -) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) +) -> Result, Error> { + frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) } /// Step 2 of RTS. /// -/// Generates the `sigma` values from all `deltas` received from `helpers` -/// to help `participant` recover their share. -/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. -/// -/// Returns a scalar -pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { - frost::keys::repairable::repair_share_step_2::(deltas_j) +/// Generates the "sigma" value from all `deltas` received from all helpers. +/// The "sigma" value must be sent to the participant repairing their share. +pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_step_2::(deltas) } -/// Step 3 of RTS +/// Step 3 of RTS. +/// +/// The participant with the given `identifier` recovers their `KeyPackage` +/// with the "sigma" values received from all helpers and the `PublicKeyPackage` +/// of the group (which can be sent by any of the helpers). /// -/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` -/// is made up of the `identifier`and `commitment` of the `participant` as well as the -/// `value` which is the `SigningShare`. +/// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. +/// This happens for `PublicKeyPackage`s created before the 3.0.0 release; +/// in that case, the user should set the `min_signers` field manually. pub fn repair_share_step_3( - sigmas: &[Scalar], + sigmas: &[Sigma], identifier: Identifier, - commitment: &VerifiableSecretSharingCommitment, -) -> SecretShare { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) + public_key_package: &PublicKeyPackage, +) -> Result { + frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) } #[cfg(test)] diff --git a/frost-ed448/src/keys/repairable.rs b/frost-ed448/src/keys/repairable.rs index 7bbdecaff..2aa936644 100644 --- a/frost-ed448/src/keys/repairable.rs +++ b/frost-ed448/src/keys/repairable.rs @@ -6,52 +6,58 @@ use alloc::collections::BTreeMap; +use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Ed448Shake256, Error}; -use super::{SecretShare, VerifiableSecretSharingCommitment}; +/// A delta value which is the output of step 1 of RTS. +pub type Delta = frost::keys::repairable::Delta; + +/// A sigma value which is the output of step 2 of RTS. +pub type Sigma = frost::keys::repairable::Sigma; /// Step 1 of RTS. /// -/// Generates the "delta" values from `helper_i` to help `participant` recover their share -/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` -/// is the share of `helper_i`. +/// Generates the "delta" values from the helper with `key_package_i` to send to +/// `helpers` (which includes the helper with `key_package_i`), to help +/// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. pub fn repair_share_step_1( helpers: &[Identifier], - share_i: &SecretShare, + key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, -) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) +) -> Result, Error> { + frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) } /// Step 2 of RTS. /// -/// Generates the `sigma` values from all `deltas` received from `helpers` -/// to help `participant` recover their share. -/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. -/// -/// Returns a scalar -pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { - frost::keys::repairable::repair_share_step_2::(deltas_j) +/// Generates the "sigma" value from all `deltas` received from all helpers. +/// The "sigma" value must be sent to the participant repairing their share. +pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_step_2::(deltas) } -/// Step 3 of RTS +/// Step 3 of RTS. +/// +/// The participant with the given `identifier` recovers their `KeyPackage` +/// with the "sigma" values received from all helpers and the `PublicKeyPackage` +/// of the group (which can be sent by any of the helpers). /// -/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` -/// is made up of the `identifier`and `commitment` of the `participant` as well as the -/// `value` which is the `SigningShare`. +/// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. +/// This happens for `PublicKeyPackage`s created before the 3.0.0 release; +/// in that case, the user should set the `min_signers` field manually. pub fn repair_share_step_3( - sigmas: &[Scalar], + sigmas: &[Sigma], identifier: Identifier, - commitment: &VerifiableSecretSharingCommitment, -) -> SecretShare { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) + public_key_package: &PublicKeyPackage, +) -> Result { + frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) } #[cfg(test)] diff --git a/frost-p256/src/keys/repairable.rs b/frost-p256/src/keys/repairable.rs index 484a9fcd6..c691b9469 100644 --- a/frost-p256/src/keys/repairable.rs +++ b/frost-p256/src/keys/repairable.rs @@ -6,52 +6,58 @@ use alloc::collections::BTreeMap; +use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Error, P256Sha256}; -use super::{SecretShare, VerifiableSecretSharingCommitment}; +/// A delta value which is the output of step 1 of RTS. +pub type Delta = frost::keys::repairable::Delta; + +/// A sigma value which is the output of step 2 of RTS. +pub type Sigma = frost::keys::repairable::Sigma; /// Step 1 of RTS. /// -/// Generates the "delta" values from `helper_i` to help `participant` recover their share -/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` -/// is the share of `helper_i`. +/// Generates the "delta" values from the helper with `key_package_i` to send to +/// `helpers` (which includes the helper with `key_package_i`), to help +/// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. pub fn repair_share_step_1( helpers: &[Identifier], - share_i: &SecretShare, + key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, -) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) +) -> Result, Error> { + frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) } /// Step 2 of RTS. /// -/// Generates the `sigma` values from all `deltas` received from `helpers` -/// to help `participant` recover their share. -/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. -/// -/// Returns a scalar -pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { - frost::keys::repairable::repair_share_step_2::(deltas_j) +/// Generates the "sigma" value from all `deltas` received from all helpers. +/// The "sigma" value must be sent to the participant repairing their share. +pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_step_2::(deltas) } -/// Step 3 of RTS +/// Step 3 of RTS. +/// +/// The participant with the given `identifier` recovers their `KeyPackage` +/// with the "sigma" values received from all helpers and the `PublicKeyPackage` +/// of the group (which can be sent by any of the helpers). /// -/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` -/// is made up of the `identifier`and `commitment` of the `participant` as well as the -/// `value` which is the `SigningShare`. +/// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. +/// This happens for `PublicKeyPackage`s created before the 3.0.0 release; +/// in that case, the user should set the `min_signers` field manually. pub fn repair_share_step_3( - sigmas: &[Scalar], + sigmas: &[Sigma], identifier: Identifier, - commitment: &VerifiableSecretSharingCommitment, -) -> SecretShare { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) + public_key_package: &PublicKeyPackage, +) -> Result { + frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) } #[cfg(test)] diff --git a/frost-ristretto255/src/keys/repairable.rs b/frost-ristretto255/src/keys/repairable.rs index e3d20a79e..45bc27e3b 100644 --- a/frost-ristretto255/src/keys/repairable.rs +++ b/frost-ristretto255/src/keys/repairable.rs @@ -6,52 +6,58 @@ use alloc::collections::BTreeMap; +use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Error, Ristretto255Sha512}; -use super::{SecretShare, VerifiableSecretSharingCommitment}; +/// A delta value which is the output of step 1 of RTS. +pub type Delta = frost::keys::repairable::Delta; + +/// A sigma value which is the output of step 2 of RTS. +pub type Sigma = frost::keys::repairable::Sigma; /// Step 1 of RTS. /// -/// Generates the "delta" values from `helper_i` to help `participant` recover their share -/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` -/// is the share of `helper_i`. +/// Generates the "delta" values from the helper with `key_package_i` to send to +/// `helpers` (which includes the helper with `key_package_i`), to help +/// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. pub fn repair_share_step_1( helpers: &[Identifier], - share_i: &SecretShare, + key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, -) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) +) -> Result, Error> { + frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) } /// Step 2 of RTS. /// -/// Generates the `sigma` values from all `deltas` received from `helpers` -/// to help `participant` recover their share. -/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. -/// -/// Returns a scalar -pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { - frost::keys::repairable::repair_share_step_2::(deltas_j) +/// Generates the "sigma" value from all `deltas` received from all helpers. +/// The "sigma" value must be sent to the participant repairing their share. +pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_step_2::(deltas) } -/// Step 3 of RTS +/// Step 3 of RTS. +/// +/// The participant with the given `identifier` recovers their `KeyPackage` +/// with the "sigma" values received from all helpers and the `PublicKeyPackage` +/// of the group (which can be sent by any of the helpers). /// -/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` -/// is made up of the `identifier`and `commitment` of the `participant` as well as the -/// `value` which is the `SigningShare`. +/// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. +/// This happens for `PublicKeyPackage`s created before the 3.0.0 release; +/// in that case, the user should set the `min_signers` field manually. pub fn repair_share_step_3( - sigmas: &[Scalar], + sigmas: &[Sigma], identifier: Identifier, - commitment: &VerifiableSecretSharingCommitment, -) -> SecretShare { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) + public_key_package: &PublicKeyPackage, +) -> Result { + frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) } #[cfg(test)] diff --git a/frost-secp256k1-tr/src/keys/repairable.rs b/frost-secp256k1-tr/src/keys/repairable.rs index 44543354c..0db86275c 100644 --- a/frost-secp256k1-tr/src/keys/repairable.rs +++ b/frost-secp256k1-tr/src/keys/repairable.rs @@ -6,52 +6,58 @@ use alloc::collections::BTreeMap; +use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Error, Secp256K1Sha256TR}; -use super::{SecretShare, VerifiableSecretSharingCommitment}; +/// A delta value which is the output of step 1 of RTS. +pub type Delta = frost::keys::repairable::Delta; + +/// A sigma value which is the output of step 2 of RTS. +pub type Sigma = frost::keys::repairable::Sigma; /// Step 1 of RTS. /// -/// Generates the "delta" values from `helper_i` to help `participant` recover their share -/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` -/// is the share of `helper_i`. +/// Generates the "delta" values from the helper with `key_package_i` to send to +/// `helpers` (which includes the helper with `key_package_i`), to help +/// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. pub fn repair_share_step_1( helpers: &[Identifier], - share_i: &SecretShare, + key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, -) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) +) -> Result, Error> { + frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) } /// Step 2 of RTS. /// -/// Generates the `sigma` values from all `deltas` received from `helpers` -/// to help `participant` recover their share. -/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. -/// -/// Returns a scalar -pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { - frost::keys::repairable::repair_share_step_2::(deltas_j) +/// Generates the "sigma" value from all `deltas` received from all helpers. +/// The "sigma" value must be sent to the participant repairing their share. +pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_step_2::(deltas) } -/// Step 3 of RTS +/// Step 3 of RTS. +/// +/// The participant with the given `identifier` recovers their `KeyPackage` +/// with the "sigma" values received from all helpers and the `PublicKeyPackage` +/// of the group (which can be sent by any of the helpers). /// -/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` -/// is made up of the `identifier`and `commitment` of the `participant` as well as the -/// `value` which is the `SigningShare`. +/// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. +/// This happens for `PublicKeyPackage`s created before the 3.0.0 release; +/// in that case, the user should set the `min_signers` field manually. pub fn repair_share_step_3( - sigmas: &[Scalar], + sigmas: &[Sigma], identifier: Identifier, - commitment: &VerifiableSecretSharingCommitment, -) -> SecretShare { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) + public_key_package: &PublicKeyPackage, +) -> Result { + frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) } #[cfg(test)] diff --git a/frost-secp256k1/src/keys/repairable.rs b/frost-secp256k1/src/keys/repairable.rs index 98a2e7c51..22ef03d71 100644 --- a/frost-secp256k1/src/keys/repairable.rs +++ b/frost-secp256k1/src/keys/repairable.rs @@ -6,52 +6,58 @@ use alloc::collections::BTreeMap; +use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore, Scalar}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Error, Secp256K1Sha256}; -use super::{SecretShare, VerifiableSecretSharingCommitment}; +/// A delta value which is the output of step 1 of RTS. +pub type Delta = frost::keys::repairable::Delta; + +/// A sigma value which is the output of step 2 of RTS. +pub type Sigma = frost::keys::repairable::Sigma; /// Step 1 of RTS. /// -/// Generates the "delta" values from `helper_i` to help `participant` recover their share -/// where `helpers` contains the identifiers of all the helpers (including `helper_i`), and `share_i` -/// is the share of `helper_i`. +/// Generates the "delta" values from the helper with `key_package_i` to send to +/// `helpers` (which includes the helper with `key_package_i`), to help +/// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. pub fn repair_share_step_1( helpers: &[Identifier], - share_i: &SecretShare, + key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, -) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, share_i, rng, participant) +) -> Result, Error> { + frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) } /// Step 2 of RTS. /// -/// Generates the `sigma` values from all `deltas` received from `helpers` -/// to help `participant` recover their share. -/// `sigma` is the sum of all received `delta` and the `delta_i` generated for `helper_i`. -/// -/// Returns a scalar -pub fn repair_share_step_2(deltas_j: &[Scalar]) -> Scalar { - frost::keys::repairable::repair_share_step_2::(deltas_j) +/// Generates the "sigma" value from all `deltas` received from all helpers. +/// The "sigma" value must be sent to the participant repairing their share. +pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_step_2::(deltas) } -/// Step 3 of RTS +/// Step 3 of RTS. +/// +/// The participant with the given `identifier` recovers their `KeyPackage` +/// with the "sigma" values received from all helpers and the `PublicKeyPackage` +/// of the group (which can be sent by any of the helpers). /// -/// The `participant` sums all `sigma_j` received to compute the `share`. The `SecretShare` -/// is made up of the `identifier`and `commitment` of the `participant` as well as the -/// `value` which is the `SigningShare`. +/// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. +/// This happens for `PublicKeyPackage`s created before the 3.0.0 release; +/// in that case, the user should set the `min_signers` field manually. pub fn repair_share_step_3( - sigmas: &[Scalar], + sigmas: &[Sigma], identifier: Identifier, - commitment: &VerifiableSecretSharingCommitment, -) -> SecretShare { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, commitment) + public_key_package: &PublicKeyPackage, +) -> Result { + frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) } #[cfg(test)] From 5cac0f44979a9913862469f16ee7c242b2dfb8d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:50:46 +0000 Subject: [PATCH 145/175] build(deps): bump reviewdog/action-actionlint from 1.69.0 to 1.69.1 (#965) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.69.0 to 1.69.1. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.69.0...v1.69.1) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-version: 1.69.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index edf48e528..6a6504753 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -176,7 +176,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v6.0.0 - - uses: reviewdog/action-actionlint@v1.69.0 + - uses: reviewdog/action-actionlint@v1.69.1 with: level: warning fail_level: none From 9637346d3911c67474ca2d75514fe7b1461c2f03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:50:50 +0000 Subject: [PATCH 146/175] build(deps): bump codecov/codecov-action from 5.5.1 to 5.5.2 (#982) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.1 to 5.5.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.5.1...v5.5.2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.5.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 200f08835..b3325cf3f 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -41,4 +41,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v5.5.1 + uses: codecov/codecov-action@v5.5.2 From e6a57b331a568c151cbc3ca1a710487f2bbd7f07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:26:49 +0000 Subject: [PATCH 147/175] build(deps): bump actions/checkout from 6.0.0 to 6.0.1 (#966) Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6.0.0...v6.0.1) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index b3325cf3f..8e2786fed 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -23,7 +23,7 @@ jobs: RUST_BACKTRACE: full steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index eb62d5d8b..d5345c23f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the source code - uses: actions/checkout@v6.0.0 + uses: actions/checkout@v6.0.2 with: persist-credentials: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a6504753..6d2659deb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@beta - run: cargo build @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@stable - run: cargo update && cargo build --all-features @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 # Re-resolve Cargo.lock with minimal versions. # This only works with nightly. We pin to a specific version because # newer versions use lock file version 4, but the MSRV cargo does not @@ -54,7 +54,7 @@ jobs: # runs-on: ubuntu-latest # steps: - # - uses: actions/checkout@v6.0.0 + # - uses: actions/checkout@v6.0.2 # - uses: dtolnay/rust-toolchain@stable # - run: cargo install cargo-all-features # # We check and then test because some test dependencies could help @@ -74,7 +74,7 @@ jobs: matrix: crate: [ristretto255, ed25519, p256, secp256k1, secp256k1-tr, rerandomized] steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -87,7 +87,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 - uses: dtolnay/rust-toolchain@beta - run: cargo test --release --all-features @@ -96,7 +96,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 with: persist-credentials: false @@ -127,7 +127,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 with: persist-credentials: false @@ -144,7 +144,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 with: persist-credentials: false @@ -163,7 +163,7 @@ jobs: RUSTDOCFLAGS: -D warnings steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 with: persist-credentials: false @@ -175,7 +175,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v6.0.0 + - uses: actions/checkout@v6.0.2 - uses: reviewdog/action-actionlint@v1.69.1 with: level: warning From bac329797305d7da05528925a5b73b6381eab7e1 Mon Sep 17 00:00:00 2001 From: Conrado Date: Fri, 23 Jan 2026 11:33:56 -0300 Subject: [PATCH 148/175] frost-rerandomized: change Randomizer generation (#762) * frost-rerandomized: change Randomizer generation to take SigningCommitments and not depend on serialization feature * ci: change doc warnings to errors * fix doc warnings * fix `one` variable naming --- frost-core/src/round1.rs | 3 + frost-rerandomized/src/lib.rs | 162 ++++++++++++++++++++++++++++++-- frost-rerandomized/src/tests.rs | 61 ++++++++++-- 3 files changed, 212 insertions(+), 14 deletions(-) diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index b6f204943..e37c24938 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -396,6 +396,9 @@ impl GroupCommitmentShare { /// commitment list. /// /// [`encode_group_commitment_list()`]: https://datatracker.ietf.org/doc/html/rfc9591#name-list-operations +#[cfg(feature = "internals")] +#[cfg_attr(feature = "internals", visibility::make(pub))] +#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(super) fn encode_group_commitments( signing_commitments: &BTreeMap, SigningCommitments>, ) -> Result, Error> { diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 4099893b4..a88e93c67 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -3,10 +3,13 @@ //! To sign with re-randomized FROST: //! //! - Do Round 1 the same way as regular FROST; -//! - The Coordinator should call [`RandomizedParams::new()`] and send -//! the [`RandomizedParams::randomizer`] to all participants, using a -//! confidential channel, along with the regular [`frost::SigningPackage`]; -//! - Each participant should call [`sign`] and send the resulting +//! - The Coordinator should call [`RandomizedParams::new_from_commitments()`] +//! and send the generate randomizer seed (the second returned value) to all +//! participants, using a confidential channel, along with the regular +//! [`frost::SigningPackage`]; +//! - Each participant should regenerate the RandomizerParams by calling +//! [`RandomizedParams::regenerate_from_seed_and_commitments()`], which they +//! should pass to [`sign_with_randomizer_seed()`] and send the resulting //! [`frost::round2::SignatureShare`] back to the Coordinator; //! - The Coordinator should then call [`aggregate`]. #![no_std] @@ -27,8 +30,10 @@ use frost_core::SigningPackage; use frost_core::{ self as frost, keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare}, + round1::encode_group_commitments, + round1::SigningCommitments, serialization::SerializableScalar, - Ciphersuite, Error, Field, Group, Scalar, VerifyingKey, + Ciphersuite, Error, Field, Group, Identifier, Scalar, VerifyingKey, }; #[cfg(feature = "serde")] @@ -36,7 +41,6 @@ use frost_core::serde; // When pulled into `reddsa`, that has its own sibling `rand_core` import. // For the time being, we do not re-export this `rand_core`. -#[cfg(feature = "serialization")] use rand_core::{CryptoRng, RngCore}; /// Randomize the given key type for usage in a FROST signing with re-randomized keys, @@ -123,6 +127,9 @@ impl Randomize for PublicKeyPackage { /// be sent from the Coordinator using a confidential channel. /// /// See [`frost::round2::sign`] for documentation on the other parameters. +#[deprecated( + note = "switch to sign_with_randomizer_seed(), passing a seed generated with RandomizedParams::new_from_commitments()" +)] pub fn sign( signing_package: &frost::SigningPackage, signer_nonces: &frost::round1::SigningNonces, @@ -135,6 +142,25 @@ pub fn sign( frost::round2::sign(signing_package, signer_nonces, &randomized_key_package) } +/// Re-randomized FROST signing using the given `randomizer_seed`, which should +/// be sent from the Coordinator using a confidential channel. +/// +/// See [`frost::round2::sign`] for documentation on the other parameters. +pub fn sign_with_randomizer_seed( + signing_package: &frost::SigningPackage, + signer_nonces: &frost::round1::SigningNonces, + key_package: &frost::keys::KeyPackage, + randomizer_seed: &[u8], +) -> Result, Error> { + let randomized_params = RandomizedParams::regenerate_from_seed_and_commitments( + key_package.verifying_key(), + randomizer_seed, + signing_package.signing_commitments(), + )?; + let randomized_key_package = key_package.randomize(&randomized_params)?; + frost::round2::sign(signing_package, signer_nonces, &randomized_key_package) +} + /// Re-randomized FROST signature share aggregation with the given [`RandomizedParams`], /// which can be computed from the previously generated randomizer using /// [`RandomizedParams::from_randomizer`]. @@ -178,12 +204,15 @@ impl Randomizer where C: RandomizedCiphersuite, { - /// Create a new random Randomizer. + /// Create a new random Randomizer using a SigningPackage for randomness. /// /// The [`SigningPackage`] must be the signing package being used in the /// current FROST signing run. It is hashed into the randomizer calculation, /// which binds it to that specific package. #[cfg(feature = "serialization")] + #[deprecated( + note = "switch to new_from_commitments(), passing the commitments from SigningPackage" + )] pub fn new( mut rng: R, signing_package: &SigningPackage, @@ -212,6 +241,65 @@ where .ok_or(Error::SerializationError)?; Ok(Self(SerializableScalar(randomizer))) } + + /// Create a new random Randomizer using SigningCommitments for randomness. + /// + /// The [`SigningCommitments`] map must be the one being used in the current + /// FROST signing run (built by the Coordinator after receiving from + /// Participants). It is hashed into the randomizer calculation, which binds + /// it to that specific commitments. + /// + /// Returns the Randomizer and the generate randomizer seed. Both can be + /// used to regenerate the Randomizer with + /// [`Self::regenerate_from_seed_and_commitments()`]. + pub fn new_from_commitments( + mut rng: R, + signing_commitments: &BTreeMap, SigningCommitments>, + ) -> Result<(Self, Vec), Error> { + // Generate a dummy scalar to get its encoded size + let zero = <::Field as Field>::zero(); + let ns = <::Field as Field>::serialize(&zero) + .as_ref() + .len(); + let mut randomizer_seed = alloc::vec![0; ns]; + rng.fill_bytes(&mut randomizer_seed); + Ok(( + Self::regenerate_from_seed_and_commitments(&randomizer_seed, signing_commitments)?, + randomizer_seed, + )) + } + + /// Regenerates a Randomizer generated with + /// [`Self::new_from_commitments()`]. This can be used by Participants after + /// receiving the randomizer seed and commitments in Round 2. This is better + /// than the Coordinator simply generating a Randomizer and sending it to + /// Participants, because in this approach the participants don't need to + /// fully trust the Coordinator's random number generator (i.e. even if the + /// randomizer seed was not randomly generated the randomizer will still + /// be). + /// + /// This should be used exclusively with the output of + /// [`Self::new_from_commitments()`]; it is strongly suggested to not + /// attempt generating the randomizer seed yourself (even if the point of + /// this approach is to hedge against issues in the randomizer seed + /// generation). + pub fn regenerate_from_seed_and_commitments( + randomizer_seed: &[u8], + signing_commitments: &BTreeMap, SigningCommitments>, + ) -> Result, Error> + where + C: RandomizedCiphersuite, + { + let randomizer = C::hash_randomizer( + &[ + randomizer_seed, + &encode_group_commitments(signing_commitments)?, + ] + .concat(), + ) + .ok_or(Error::SerializationError)?; + Ok(Self(SerializableScalar(randomizer))) + } } impl Randomizer @@ -267,18 +355,76 @@ where C: RandomizedCiphersuite, { /// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and - /// the given `participants`. + /// the given [`SigningPackage`]. #[cfg(feature = "serialization")] + #[deprecated( + note = "switch to new_from_commitments(), passing the commitments from SigningPackage" + )] pub fn new( group_verifying_key: &VerifyingKey, signing_package: &SigningPackage, rng: R, ) -> Result> { + #[allow(deprecated)] Ok(Self::from_randomizer( group_verifying_key, Randomizer::new(rng, signing_package)?, )) } + + /// Create a new [`RandomizedParams`] for the given [`VerifyingKey`] and the + /// given signing commitments. + /// + /// The [`SigningCommitments`] map must be the one being used in the current + /// FROST signing run (built by the Coordinator after receiving from + /// Participants). It is hashed into the randomizer calculation, which binds + /// it to that specific commitments. + /// + /// Returns the generated [`RandomizedParams`] and a randomizer seed. Both + /// can be used to regenerate the [`RandomizedParams`] with + /// [`Self::regenerate_from_seed_and_commitments()`]. + pub fn new_from_commitments( + group_verifying_key: &VerifyingKey, + signing_commitments: &BTreeMap, SigningCommitments>, + rng: R, + ) -> Result<(Self, Vec), Error> { + let (randomizer, randomizer_seed) = + Randomizer::new_from_commitments(rng, signing_commitments)?; + Ok(( + Self::from_randomizer(group_verifying_key, randomizer), + randomizer_seed, + )) + } + + /// Regenerate a [`RandomizedParams`] with the given [`VerifyingKey`] from + /// the given given signing commitments. + /// + /// Returns the generated [`RandomizedParams`] and a randomizer seed, which + /// can be used to regenerate the [`RandomizedParams`]. + /// + /// Regenerates a [`RandomizedParams`] generated with + /// [`Self::new_from_commitments()`]. This can be used by Participants after + /// receiving the randomizer seed and commitments in Round 2. This is better + /// than the Coordinator simply generating a [`Randomizer`] and sending it + /// to Participants, because in this approach the participants don't need to + /// fully trust the Coordinator's random number generator (i.e. even if the + /// randomizer seed was not randomly generated the randomizer will still + /// be). + /// + /// This should be used exclusively with the output of + /// [`Self::new_from_commitments()`]; it is strongly suggested to not + /// attempt generating the randomizer seed yourself (even if the point of + /// this approach is to hedge against issues in the randomizer seed + /// generation). + pub fn regenerate_from_seed_and_commitments( + group_verifying_key: &VerifyingKey, + randomizer_seed: &[u8], + signing_commitments: &BTreeMap, SigningCommitments>, + ) -> Result> { + let randomizer = + Randomizer::regenerate_from_seed_and_commitments(randomizer_seed, signing_commitments)?; + Ok(Self::from_randomizer(group_verifying_key, randomizer)) + } } impl RandomizedParams diff --git a/frost-rerandomized/src/tests.rs b/frost-rerandomized/src/tests.rs index f29c91c96..0b5264988 100644 --- a/frost-rerandomized/src/tests.rs +++ b/frost-rerandomized/src/tests.rs @@ -6,7 +6,9 @@ use alloc::collections::BTreeMap; use alloc::vec::Vec; use crate::{frost_core as frost, RandomizedCiphersuite, RandomizedParams, Randomizer}; -use frost_core::{Field, Group, Signature, SigningPackage, VerifyingKey}; +use frost_core::{ + round1::SigningCommitments, Field, Group, Identifier, Signature, SigningPackage, VerifyingKey, +}; use rand_core::{CryptoRng, RngCore}; /// Test re-randomized FROST signing with trusted dealer with a Ciphersuite. @@ -70,9 +72,12 @@ pub fn check_randomized_sign_with_dealer( check_from_randomizer(&mut rng, signing_package, pubkeys); check_from_randomizer_and_signing_package(&mut rng, signing_package); + + check_from_seed_and_signing_commitments(&mut rng, signing_package.signing_commitments()); } fn check_from_randomizer( @@ -138,6 +150,7 @@ fn check_from_randomizer( signing_package: &SigningPackage, pubkeys: &frost::keys::PublicKeyPackage, ) { + #[allow(deprecated)] let randomizer = Randomizer::new(rng, signing_package).unwrap(); let randomizer_params = RandomizedParams::from_randomizer(pubkeys.verifying_key(), randomizer); @@ -176,3 +189,39 @@ fn check_from_randomizer_and_signing_package( + mut rng: &mut R, + signing_commitments: &BTreeMap, SigningCommitments>, +) { + // Make sure regeneration returns the same Randomizer. + let (randomizer1, randomizer_seed1) = + Randomizer::new_from_commitments(&mut rng, signing_commitments).unwrap(); + let randomizer2 = + Randomizer::regenerate_from_seed_and_commitments(&randomizer_seed1, signing_commitments) + .unwrap(); + assert!(randomizer1 == randomizer2); + + let (randomizer2, randomizer_seed2) = + Randomizer::new_from_commitments(&mut rng, signing_commitments).unwrap(); + + // Make sure that different rng_randomizers lead to different randomizers + assert!(randomizer1 != randomizer2); + assert!(randomizer_seed1 != randomizer_seed2); + + // Modify the commitments map, by overwriting the first entry with the value + // of the last entry. + let mut modified_signing_commitments = signing_commitments.clone(); + modified_signing_commitments + .first_entry() + .unwrap() + .insert(*signing_commitments.last_key_value().unwrap().1); + let randomizer2 = Randomizer::regenerate_from_seed_and_commitments( + &randomizer_seed1, + &modified_signing_commitments, + ) + .unwrap(); + + // Make sure that different packages lead to different randomizers + assert!(randomizer1 != randomizer2); +} From 3ed40ad6e2278578a718e7b6053ce10f1c763c93 Mon Sep 17 00:00:00 2001 From: Conrado Date: Fri, 23 Jan 2026 12:06:04 -0300 Subject: [PATCH 149/175] core: simplify compute_refreshing_shares() (#940) --- frost-core/CHANGELOG.md | 6 + frost-core/src/keys/refresh.rs | 47 ++++---- frost-core/src/tests/refresh.rs | 105 ++---------------- frost-ed25519/src/keys/refresh.rs | 10 +- frost-ed25519/tests/integration_tests.rs | 85 +------------- frost-ed448/src/keys/refresh.rs | 10 +- frost-ed448/tests/integration_tests.rs | 85 +------------- frost-p256/src/keys/refresh.rs | 10 +- frost-p256/tests/integration_tests.rs | 85 +------------- frost-ristretto255/src/keys/refresh.rs | 10 +- frost-ristretto255/tests/integration_tests.rs | 85 +------------- frost-secp256k1-tr/src/keys/refresh.rs | 10 +- frost-secp256k1-tr/tests/integration_tests.rs | 85 +------------- frost-secp256k1/src/keys/refresh.rs | 10 +- frost-secp256k1/tests/integration_tests.rs | 85 +------------- 15 files changed, 48 insertions(+), 680 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index be59fdf1e..87f66e950 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -15,6 +15,12 @@ Entries are listed in reverse chronological order. * The `std` and `nightly` features were removed from all crates * Renamed `frost_core::keys::refresh::refresh_dkg_part_1` to `refresh_dkg_part1`. * Fixed the crate-specific versions of the `refresh` module to be non-generic. +* Removed the `min_signers` and `max_signers` arguments from + `frost_core::keys::refresh::compute_refreshing_shares()`. The former is now + read from the `pub_key_package`; if you pass a pre-3.0.0 generate package, + you will need to fills its `min_signers` field with the original threshold + before calling the function (recreate it with `PublicKeyPackage::new()`). + The latter was simply redundant. * Refactored the `frost_core::keys::repairable` module: * `repair_share_step_1()` now takes a `KeyPackage` and returns a map with a new `Delta` type instead of a raw `Scalar` diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index 4df4e6a47..cd80ad042 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -42,38 +42,37 @@ use super::{dkg::round1::Package, KeyPackage, SecretShare, VerifiableSecretShari /// Compute refreshing shares for the Trusted Dealer refresh procedure. /// -/// - `pub_key_package`: the current public key package. -/// - `max_signers`: the number of participants that are refreshing their -/// shares. It can be smaller than the original value, but still equal to or -/// greater than `min_signers`. -/// - `min_signers`: the threshold needed to sign. It must be equal to the -/// original value for the group (i.e. the refresh process can't reduce -/// the threshold). +/// - `pub_key_package`: the current public key package. Note: if a pre-3.0.0 +/// generate package is used, you will need to manually set the `min_signers` +/// field with the theshold that was used in the original share generation. +/// (You can't change the threshold when refreshing shares.) /// - `identifiers`: The identifiers of all participants that want to refresh -/// their shares. Must be the same length as `max_signers`. +/// their shares. Must be a subset of the identifiers in `pub_key_package`. If +/// not all identifiers are passed, the refresh procedure will effectively +/// remove the missing participants. The length must be equal to or greater +/// than the threshold of the group. /// -/// It returns a vectors of [`SecretShare`] that must be sent to the participants -/// in the same order as `identifiers`, and the refreshed [`PublicKeyPackage`]. +/// It returns a vectors of [`SecretShare`] that must be sent to the +/// participants in the same order as `identifiers`, and the refreshed +/// [`PublicKeyPackage`]. pub fn compute_refreshing_shares( pub_key_package: PublicKeyPackage, - max_signers: u16, - min_signers: u16, identifiers: &[Identifier], rng: &mut R, ) -> Result<(Vec>, PublicKeyPackage), Error> { - // Validate min_signers. It's OK if the min_signers is missing, because - // we validate it again in `refresh_share()`. - if let Some(package_min_signers) = pub_key_package.min_signers { - if min_signers != package_min_signers { - return Err(Error::InvalidMinSigners); - } - } + let min_signers = pub_key_package + .min_signers + .ok_or(Error::InvalidMinSigners)?; + + let signers = identifiers.len() as u16; + validate_num_of_signers(min_signers, signers)?; - // Validate inputs - if identifiers.len() != max_signers as usize { - return Err(Error::IncorrectNumberOfIdentifiers); + if identifiers + .iter() + .any(|i| !pub_key_package.verifying_shares().contains_key(i)) + { + return Err(Error::UnknownIdentifier); } - validate_num_of_signers(min_signers, max_signers)?; // Build refreshing shares let refreshing_key = SigningKey { @@ -83,7 +82,7 @@ pub fn compute_refreshing_shares( let coefficients = generate_coefficients::(min_signers as usize - 1, rng); let refreshing_shares = generate_secret_shares( &refreshing_key, - max_signers, + signers, min_signers, coefficients, identifiers, diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index a8fdfb5bb..3d50d5988 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -57,18 +57,10 @@ pub fn check_refresh_shares_with_dealer( Identifier::try_from(5).unwrap(), ]; - const NEW_MAX_SIGNERS: u16 = 4; - // Trusted Dealer generates zero keys and new public key package - let (zero_shares, new_pub_key_package) = compute_refreshing_shares( - pub_key_package, - NEW_MAX_SIGNERS, - MIN_SIGNERS, - &remaining_ids, - &mut rng, - ) - .unwrap(); + let (zero_shares, new_pub_key_package) = + compute_refreshing_shares(pub_key_package, &remaining_ids, &mut rng).unwrap(); // Simulate serialization / deserialization to ensure it works let new_pub_key_package = frost::keys::PublicKeyPackage::deserialize(&new_pub_key_package.serialize().unwrap()) @@ -102,21 +94,13 @@ pub fn check_refresh_shares_with_dealer_fails_with_invalid_signers< C: Ciphersuite, R: RngCore + CryptoRng, >( - new_max_signers: u16, - min_signers: u16, identifiers: &[Identifier], error: Error, mut rng: R, ) { let (_old_shares, pub_key_package) = generate_with_dealer::(5, 2, frost::keys::IdentifierList::Default, &mut rng).unwrap(); - let out = compute_refreshing_shares( - pub_key_package, - new_max_signers, - min_signers, - identifiers, - &mut rng, - ); + let out = compute_refreshing_shares(pub_key_package, identifiers, &mut rng); assert!(out.is_err()); assert!(out == Err(error)) @@ -166,18 +150,10 @@ pub fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package< Identifier::try_from(5).unwrap(), ]; - const NEW_MAX_SIGNERS: u16 = 4; - // Trusted Dealer generates zero keys and new public key package - let e = compute_refreshing_shares( - incorrect_pub_key_package, - NEW_MAX_SIGNERS, - MIN_SIGNERS, - &remaining_ids, - &mut rng, - ) - .unwrap_err(); + let e = + compute_refreshing_shares(incorrect_pub_key_package, &remaining_ids, &mut rng).unwrap_err(); assert_eq!(e, Error::UnknownIdentifier) } @@ -215,16 +191,8 @@ pub fn check_refresh_shares_with_dealer_serialisation( - mut rng: R, -) { - // Compute shares - - //////////////////////////////////////////////////////////////////////////// - // Old Key generation - //////////////////////////////////////////////////////////////////////////// - - const MAX_SIGNERS: u16 = 5; - const MIN_SIGNERS: u16 = 3; - let (old_shares, pub_key_package) = generate_with_dealer( - MAX_SIGNERS, - MIN_SIGNERS, - frost::keys::IdentifierList::Default, - &mut rng, - ) - .unwrap(); - - let mut old_key_packages: BTreeMap, KeyPackage> = BTreeMap::new(); - - for (k, v) in old_shares { - let key_package = KeyPackage::try_from(v).unwrap(); - old_key_packages.insert(k, key_package); - } - - //////////////////////////////////////////////////////////////////////////// - // New Key generation - //////////////////////////////////////////////////////////////////////////// - - // Signer 2 will be removed and Signers 1, 3, 4 & 5 will remain - - let remaining_ids = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - - const NEW_MAX_SIGNERS: u16 = 4; - const NEW_MIN_SIGNERS: u16 = 2; - - // Trusted Dealer generates zero keys and new public key package - - let r = compute_refreshing_shares( - pub_key_package, - NEW_MAX_SIGNERS, - NEW_MIN_SIGNERS, - &remaining_ids, - &mut rng, - ); - - assert_eq!(r, Err(Error::InvalidMinSigners)); -} - /// Test FROST signing with DKG with a Ciphersuite. pub fn check_refresh_shares_with_dkg( mut rng: R, diff --git a/frost-ed25519/src/keys/refresh.rs b/frost-ed25519/src/keys/refresh.rs index 36797d959..bb4b6dd64 100644 --- a/frost-ed25519/src/keys/refresh.rs +++ b/frost-ed25519/src/keys/refresh.rs @@ -14,18 +14,10 @@ use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, - max_signers: u16, - min_signers: u16, identifiers: &[Identifier], mut rng: &mut R, ) -> Result<(Vec, PublicKeyPackage), Error> { - frost::keys::refresh::compute_refreshing_shares( - old_pub_key_package, - max_signers, - min_signers, - identifiers, - &mut rng, - ) + frost::keys::refresh::compute_refreshing_shares(old_pub_key_package, identifiers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_share`]. diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index d43664a62..2cc6620a6 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -89,77 +89,6 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { >(rng); } -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 1; - let max_signers = 4; - let error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ed25519Sha512, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 2; - let max_signers = 3; - let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ed25519Sha512, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 6; - let max_signers = 4; - let error: frost_core::Error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ed25519Sha512, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 2; - let max_signers = 1; - let error = Error::InvalidMaxSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ed25519Sha512, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { let rng = rand::rngs::OsRng; @@ -169,24 +98,12 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { Identifier::try_from(4).unwrap(), Identifier::try_from(6).unwrap(), ]; - let min_signers = 2; - let max_signers = 4; let error = Error::UnknownIdentifier; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< Ed25519Sha512, _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { - let rng = rand::rngs::OsRng; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< - Ed25519Sha512, - _, - >(rng); + >(&identifiers, error, rng); } #[test] diff --git a/frost-ed448/src/keys/refresh.rs b/frost-ed448/src/keys/refresh.rs index 36797d959..bb4b6dd64 100644 --- a/frost-ed448/src/keys/refresh.rs +++ b/frost-ed448/src/keys/refresh.rs @@ -14,18 +14,10 @@ use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, - max_signers: u16, - min_signers: u16, identifiers: &[Identifier], mut rng: &mut R, ) -> Result<(Vec, PublicKeyPackage), Error> { - frost::keys::refresh::compute_refreshing_shares( - old_pub_key_package, - max_signers, - min_signers, - identifiers, - &mut rng, - ) + frost::keys::refresh::compute_refreshing_shares(old_pub_key_package, identifiers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_share`]. diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index d22705c76..28bd22ad2 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -89,77 +89,6 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { >(rng); } -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 1; - let max_signers = 4; - let error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ed448Shake256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 2; - let max_signers = 3; - let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ed448Shake256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 6; - let max_signers = 4; - let error: frost_core::Error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ed448Shake256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 2; - let max_signers = 1; - let error = Error::InvalidMaxSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ed448Shake256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { let rng = rand::rngs::OsRng; @@ -169,24 +98,12 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { Identifier::try_from(4).unwrap(), Identifier::try_from(6).unwrap(), ]; - let min_signers = 2; - let max_signers = 4; let error = Error::UnknownIdentifier; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< Ed448Shake256, _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { - let rng = rand::rngs::OsRng; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< - Ed448Shake256, - _, - >(rng); + >(&identifiers, error, rng); } #[test] diff --git a/frost-p256/src/keys/refresh.rs b/frost-p256/src/keys/refresh.rs index 36797d959..bb4b6dd64 100644 --- a/frost-p256/src/keys/refresh.rs +++ b/frost-p256/src/keys/refresh.rs @@ -14,18 +14,10 @@ use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, - max_signers: u16, - min_signers: u16, identifiers: &[Identifier], mut rng: &mut R, ) -> Result<(Vec, PublicKeyPackage), Error> { - frost::keys::refresh::compute_refreshing_shares( - old_pub_key_package, - max_signers, - min_signers, - identifiers, - &mut rng, - ) + frost::keys::refresh::compute_refreshing_shares(old_pub_key_package, identifiers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_share`]. diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index 8a9a92ed1..d1d27f440 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -89,77 +89,6 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { >(rng); } -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 1; - let max_signers = 4; - let error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - P256Sha256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 2; - let max_signers = 3; - let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - P256Sha256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 6; - let max_signers = 4; - let error: frost_core::Error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - P256Sha256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 2; - let max_signers = 1; - let error = Error::InvalidMaxSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - P256Sha256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { let rng = rand::rngs::OsRng; @@ -169,24 +98,12 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { Identifier::try_from(4).unwrap(), Identifier::try_from(6).unwrap(), ]; - let min_signers = 2; - let max_signers = 4; let error = Error::UnknownIdentifier; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< P256Sha256, _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { - let rng = rand::rngs::OsRng; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< - P256Sha256, - _, - >(rng); + >(&identifiers, error, rng); } #[test] diff --git a/frost-ristretto255/src/keys/refresh.rs b/frost-ristretto255/src/keys/refresh.rs index 36797d959..bb4b6dd64 100644 --- a/frost-ristretto255/src/keys/refresh.rs +++ b/frost-ristretto255/src/keys/refresh.rs @@ -14,18 +14,10 @@ use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, - max_signers: u16, - min_signers: u16, identifiers: &[Identifier], mut rng: &mut R, ) -> Result<(Vec, PublicKeyPackage), Error> { - frost::keys::refresh::compute_refreshing_shares( - old_pub_key_package, - max_signers, - min_signers, - identifiers, - &mut rng, - ) + frost::keys::refresh::compute_refreshing_shares(old_pub_key_package, identifiers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_share`]. diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index 8ff8af167..c4ef3be0e 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -90,77 +90,6 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { >(rng); } -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 1; - let max_signers = 4; - let error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ristretto255Sha512, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 2; - let max_signers = 3; - let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ristretto255Sha512, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 6; - let max_signers = 4; - let error: frost_core::Error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ristretto255Sha512, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 2; - let max_signers = 1; - let error = Error::InvalidMaxSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Ristretto255Sha512, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { let rng = rand::rngs::OsRng; @@ -170,24 +99,12 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { Identifier::try_from(4).unwrap(), Identifier::try_from(6).unwrap(), ]; - let min_signers = 2; - let max_signers = 4; let error = Error::UnknownIdentifier; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< Ristretto255Sha512, _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { - let rng = rand::rngs::OsRng; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< - Ristretto255Sha512, - _, - >(rng); + >(&identifiers, error, rng); } #[test] diff --git a/frost-secp256k1-tr/src/keys/refresh.rs b/frost-secp256k1-tr/src/keys/refresh.rs index 36797d959..bb4b6dd64 100644 --- a/frost-secp256k1-tr/src/keys/refresh.rs +++ b/frost-secp256k1-tr/src/keys/refresh.rs @@ -14,18 +14,10 @@ use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, - max_signers: u16, - min_signers: u16, identifiers: &[Identifier], mut rng: &mut R, ) -> Result<(Vec, PublicKeyPackage), Error> { - frost::keys::refresh::compute_refreshing_shares( - old_pub_key_package, - max_signers, - min_signers, - identifiers, - &mut rng, - ) + frost::keys::refresh::compute_refreshing_shares(old_pub_key_package, identifiers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_share`]. diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs index 11db8b2bb..682db377d 100644 --- a/frost-secp256k1-tr/tests/integration_tests.rs +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -90,77 +90,6 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { >(rng); } -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 1; - let max_signers = 4; - let error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256TR, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 2; - let max_signers = 3; - let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256TR, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 6; - let max_signers = 4; - let error: frost_core::Error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256TR, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 2; - let max_signers = 1; - let error = Error::InvalidMaxSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256TR, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { let rng = rand::rngs::OsRng; @@ -170,24 +99,12 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { Identifier::try_from(4).unwrap(), Identifier::try_from(6).unwrap(), ]; - let min_signers = 2; - let max_signers = 4; let error = Error::UnknownIdentifier; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< Secp256K1Sha256TR, _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { - let rng = rand::rngs::OsRng; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< - Secp256K1Sha256TR, - _, - >(rng); + >(&identifiers, error, rng); } #[test] diff --git a/frost-secp256k1/src/keys/refresh.rs b/frost-secp256k1/src/keys/refresh.rs index 36797d959..bb4b6dd64 100644 --- a/frost-secp256k1/src/keys/refresh.rs +++ b/frost-secp256k1/src/keys/refresh.rs @@ -14,18 +14,10 @@ use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, - max_signers: u16, - min_signers: u16, identifiers: &[Identifier], mut rng: &mut R, ) -> Result<(Vec, PublicKeyPackage), Error> { - frost::keys::refresh::compute_refreshing_shares( - old_pub_key_package, - max_signers, - min_signers, - identifiers, - &mut rng, - ) + frost::keys::refresh::compute_refreshing_shares(old_pub_key_package, identifiers, &mut rng) } /// Refer to [`frost_core::keys::refresh::refresh_share`]. diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index e2c111c47..343214e1b 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -89,77 +89,6 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { >(rng); } -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 1; - let max_signers = 4; - let error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_unequal_num_identifiers_and_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 2; - let max_signers = 3; - let error: frost_core::Error = Error::IncorrectNumberOfIdentifiers; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; - let identifiers = vec![ - Identifier::try_from(1).unwrap(), - Identifier::try_from(3).unwrap(), - Identifier::try_from(4).unwrap(), - Identifier::try_from(5).unwrap(), - ]; - let min_signers = 6; - let max_signers = 4; - let error: frost_core::Error = Error::InvalidMinSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; - let identifiers = vec![Identifier::try_from(1).unwrap()]; - let min_signers = 2; - let max_signers = 1; - let error = Error::InvalidMaxSigners; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< - Secp256K1Sha256, - _, - >(max_signers, min_signers, &identifiers, error, rng); -} - #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { let rng = rand::rngs::OsRng; @@ -169,24 +98,12 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { Identifier::try_from(4).unwrap(), Identifier::try_from(6).unwrap(), ]; - let min_signers = 2; - let max_signers = 4; let error = Error::UnknownIdentifier; frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_signers::< Secp256K1Sha256, _, - >(max_signers, min_signers, &identifiers, error, rng); -} - -#[test] -fn check_refresh_shares_with_dealer_fails_with_different_min_signers() { - let rng = rand::rngs::OsRng; - - frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_different_min_signers::< - Secp256K1Sha256, - _, - >(rng); + >(&identifiers, error, rng); } #[test] From 8f974985f3cb03ca0ccd00f864d93a46dee2fa01 Mon Sep 17 00:00:00 2001 From: Conrado Date: Tue, 27 Jan 2026 12:58:22 -0300 Subject: [PATCH 150/175] use partX() for repair share functions (#1001) --- frost-core/CHANGELOG.md | 16 +++++---- frost-core/src/keys/repairable.rs | 22 ++++++------- frost-core/src/tests/repairable.rs | 40 +++++++++++------------ frost-ed25519/src/keys/repairable.rs | 38 ++++++++++----------- frost-ed448/src/keys/repairable.rs | 38 ++++++++++----------- frost-p256/src/keys/repairable.rs | 38 ++++++++++----------- frost-ristretto255/src/keys/repairable.rs | 38 ++++++++++----------- frost-secp256k1-tr/src/keys/repairable.rs | 40 +++++++++++------------ frost-secp256k1/src/keys/repairable.rs | 38 ++++++++++----------- 9 files changed, 154 insertions(+), 154 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 87f66e950..057b40603 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -22,13 +22,15 @@ Entries are listed in reverse chronological order. before calling the function (recreate it with `PublicKeyPackage::new()`). The latter was simply redundant. * Refactored the `frost_core::keys::repairable` module: - * `repair_share_step_1()` now takes a `KeyPackage` and returns a map with - a new `Delta` type instead of a raw `Scalar` - * `repair_share_step_2()` now takes the `Delta` type and returns a new `Sigma` - type instead of a raw `Scalar` - * `repair_share_step_3()` now takes the `Sigma` type and a `PublicKeyPackage` - instead of `VerifiableSecretSharingCommitment`; and returns a `KeyPackage` - instead of `SecretShare`. + * `repair_share_step_1()` was renamed to `repair_share_part1()` and now takes + a `KeyPackage` and returns a map with a new `Delta` type instead of a raw + `Scalar` + * `repair_share_step_2()` was renamed to `repair_share_part2()` and now takes + the `Delta` type and returns a new `Sigma` type instead of a raw `Scalar` + * `repair_share_step_3()` was renamed to `repair_share_part3()` and now takes + the `Sigma` type and a `PublicKeyPackage` instead of + `VerifiableSecretSharingCommitment`; and returns a `KeyPackage` instead of + `SecretShare`. * These changes provide more type safety and are make it more useful since `SecretPackage`s are not expected to be stored diff --git a/frost-core/src/keys/repairable.rs b/frost-core/src/keys/repairable.rs index 6acd52eaa..da4f5f02a 100644 --- a/frost-core/src/keys/repairable.rs +++ b/frost-core/src/keys/repairable.rs @@ -9,12 +9,12 @@ //! //! - Participants need to agree somehow on who are going to be the `helpers` //! for the repair, and which participant is going to repair their share. -//! - Each helper runs `repair_share_step_1`, generating a set of `delta` values +//! - Each helper runs `repair_share_part1`, generating a set of `delta` values //! to be sent to each helper (including themselves). -//! - Each helper runs `repair_share_step_2`, passing the received `delta` +//! - Each helper runs `repair_share_part2`, passing the received `delta` //! values, generating a `sigma` value to be sent to the participant repairing //! their share. -//! - The participant repairing their share runs `repair_share_step_3`, passing +//! - The participant repairing their share runs `repair_share_part3`, passing //! all the received `sigma` values, recovering their lost `KeyPackage`. (They //! will also need the `PublicKeyPackage` for this step which could be //! provided by any of the helpers). @@ -32,7 +32,7 @@ use crate::{ use super::{generate_coefficients, SigningShare}; -/// A delta value which is the output of step 1 of RTS. +/// A delta value which is the output of part 1 of RTS. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] @@ -68,7 +68,7 @@ where } } -/// A sigma value which is the output of step 2 of RTS. +/// A sigma value which is the output of part 2 of RTS. #[derive(Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] @@ -104,14 +104,14 @@ where } } -/// Step 1 of RTS. +/// Part 1 of RTS. /// /// Generates the "delta" values from the helper with `key_package_i` to send to /// `helpers` (which includes the helper with `key_package_i`), to help /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_step_1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, @@ -169,11 +169,11 @@ fn compute_last_random_value( Ok(out) } -/// Step 2 of RTS. +/// Part 2 of RTS. /// /// Generates the "sigma" value from all `deltas` received from all helpers. /// The "sigma" value must be sent to the participant repairing their share. -pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { +pub fn repair_share_part2(deltas: &[Delta]) -> Sigma { let mut sigma_j = <::Field>::zero(); for d in deltas { @@ -183,7 +183,7 @@ pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { Sigma::new(sigma_j) } -/// Step 3 of RTS. +/// Part 3 of RTS. /// /// The participant with the given `identifier` recovers their `KeyPackage` /// with the "sigma" values received from all helpers and the `PublicKeyPackage` @@ -192,7 +192,7 @@ pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { /// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. /// This happens for `PublicKeyPackage`s created before the 3.0.0 release; /// in that case, the user should set the `min_signers` field manually. -pub fn repair_share_step_3( +pub fn repair_share_part3( sigmas: &[Sigma], identifier: Identifier, public_key_package: &PublicKeyPackage, diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index 4dd5986e3..e4379732e 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -12,7 +12,7 @@ use crate::keys::KeyPackage; use crate::{ compute_lagrange_coefficient, keys::{ - repairable::{repair_share_step_1, repair_share_step_2, repair_share_step_3}, + repairable::{repair_share_part1, repair_share_part2, repair_share_part3}, PublicKeyPackage, SecretShare, }, Ciphersuite, Error, Field, Group, Identifier, @@ -62,25 +62,25 @@ pub fn check_rts(mut rng: R) { // Each helper generates random values for each helper let helper_1_deltas = - repair_share_step_1(&helpers, helper_1, &mut rng, participant.identifier).unwrap(); + repair_share_part1(&helpers, helper_1, &mut rng, participant.identifier).unwrap(); let helper_4_deltas = - repair_share_step_1(&helpers, helper_4, &mut rng, participant.identifier).unwrap(); + repair_share_part1(&helpers, helper_4, &mut rng, participant.identifier).unwrap(); let helper_5_deltas = - repair_share_step_1(&helpers, helper_5, &mut rng, participant.identifier).unwrap(); + repair_share_part1(&helpers, helper_5, &mut rng, participant.identifier).unwrap(); // Each helper calculates their sigma from the random values received from the other helpers - let helper_1_sigma: Sigma = repair_share_step_2::(&[ + let helper_1_sigma: Sigma = repair_share_part2::(&[ helper_1_deltas[&helpers[0]], helper_4_deltas[&helpers[0]], helper_5_deltas[&helpers[0]], ]); - let helper_4_sigma: Sigma = repair_share_step_2::(&[ + let helper_4_sigma: Sigma = repair_share_part2::(&[ helper_1_deltas[&helpers[1]], helper_4_deltas[&helpers[1]], helper_5_deltas[&helpers[1]], ]); - let helper_5_sigma: Sigma = repair_share_step_2::(&[ + let helper_5_sigma: Sigma = repair_share_part2::(&[ helper_1_deltas[&helpers[2]], helper_4_deltas[&helpers[2]], helper_5_deltas[&helpers[2]], @@ -88,7 +88,7 @@ pub fn check_rts(mut rng: R) { // The participant wishing to recover their share sums the sigmas sent from all helpers - let participant_recovered_share = repair_share_step_3( + let participant_recovered_share = repair_share_part3( &[helper_1_sigma, helper_4_sigma, helper_5_sigma], participant.identifier, &public_key_package, @@ -108,8 +108,8 @@ fn generate_scalar_from_byte_string( out.unwrap() } -/// Test repair_share_step_1 -pub fn check_repair_share_step_1(mut rng: R) { +/// Test repair_share_part1 +pub fn check_repair_share_part1(mut rng: R) { // Compute shares let max_signers = 5; @@ -142,7 +142,7 @@ pub fn check_repair_share_step_1(mut rng ]; // Generate deltas for helper 4 - let deltas = repair_share_step_1(&helpers, helper_4, &mut rng, participant.identifier).unwrap(); + let deltas = repair_share_part1(&helpers, helper_4, &mut rng, participant.identifier).unwrap(); let lagrange_coefficient = compute_lagrange_coefficient( &helpers.iter().cloned().collect(), @@ -161,8 +161,8 @@ pub fn check_repair_share_step_1(mut rng assert!(lhs == rhs) } -/// Test repair_share_step_2 -pub fn check_repair_share_step_2(repair_share_helpers: &Value) { +/// Test repair_share_part2 +pub fn check_repair_share_part2(repair_share_helpers: &Value) { let values = &repair_share_helpers["scalar_generation"]; let value_1 = Delta::new(generate_scalar_from_byte_string::( @@ -174,7 +174,7 @@ pub fn check_repair_share_step_2(repair_share_helpers: &Value) { let value_3 = Delta::new(generate_scalar_from_byte_string::( values["random_scalar_3"].as_str().unwrap(), )); - let expected = repair_share_step_2::(&[value_1, value_2, value_3]); + let expected = repair_share_part2::(&[value_1, value_2, value_3]); let actual = Sigma::new(generate_scalar_from_byte_string::( values["random_scalar_sum"].as_str().unwrap(), @@ -183,8 +183,8 @@ pub fn check_repair_share_step_2(repair_share_helpers: &Value) { assert!(actual == expected); } -/// Test repair_share -pub fn check_repair_share_step_3( +/// Test repair_share_part3 +pub fn check_repair_share_part3( mut rng: R, repair_share_helpers: &Value, ) { @@ -217,7 +217,7 @@ pub fn check_repair_share_step_3( sigmas["sigma_4"].as_str().unwrap(), )); - let actual = repair_share_step_3::( + let actual = repair_share_part3::( &[sigma_1, sigma_2, sigma_3, sigma_4], Identifier::try_from(2).unwrap(), &public_key_package, @@ -230,8 +230,8 @@ pub fn check_repair_share_step_3( assert!(expected == actual.signing_share().to_scalar()); } -/// Test repair share step 1 fails with invalid numbers of signers. -pub fn check_repair_share_step_1_fails_with_invalid_min_signers< +/// Test repair share part 1 fails with invalid numbers of signers. +pub fn check_repair_share_part1_fails_with_invalid_min_signers< C: Ciphersuite, R: RngCore + CryptoRng, >( @@ -255,7 +255,7 @@ pub fn check_repair_share_step_1_fails_with_invalid_min_signers< let helper = Identifier::try_from(3).unwrap(); - let out = repair_share_step_1( + let out = repair_share_part1( &[helper], &key_packages[&helper], &mut rng, diff --git a/frost-ed25519/src/keys/repairable.rs b/frost-ed25519/src/keys/repairable.rs index 0d9930f24..9e809ed30 100644 --- a/frost-ed25519/src/keys/repairable.rs +++ b/frost-ed25519/src/keys/repairable.rs @@ -13,37 +13,37 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Ed25519Sha512, Error}; -/// A delta value which is the output of step 1 of RTS. +/// A delta value which is the output of part 1 of RTS. pub type Delta = frost::keys::repairable::Delta; -/// A sigma value which is the output of step 2 of RTS. +/// A sigma value which is the output of part 2 of RTS. pub type Sigma = frost::keys::repairable::Sigma; -/// Step 1 of RTS. +/// Part 1 of RTS. /// /// Generates the "delta" values from the helper with `key_package_i` to send to /// `helpers` (which includes the helper with `key_package_i`), to help /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_step_1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, ) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) + frost::keys::repairable::repair_share_part1(helpers, key_package_i, rng, participant) } -/// Step 2 of RTS. +/// Part 2 of RTS. /// /// Generates the "sigma" value from all `deltas` received from all helpers. /// The "sigma" value must be sent to the participant repairing their share. -pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { - frost::keys::repairable::repair_share_step_2::(deltas) +pub fn repair_share_part2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_part2::(deltas) } -/// Step 3 of RTS. +/// Part 3 of RTS. /// /// The participant with the given `identifier` recovers their `KeyPackage` /// with the "sigma" values received from all helpers and the `PublicKeyPackage` @@ -52,12 +52,12 @@ pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { /// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. /// This happens for `PublicKeyPackage`s created before the 3.0.0 release; /// in that case, the user should set the `min_signers` field manually. -pub fn repair_share_step_3( +pub fn repair_share_part3( sigmas: &[Sigma], identifier: Identifier, public_key_package: &PublicKeyPackage, ) -> Result { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) + frost::keys::repairable::repair_share_part3(sigmas, identifier, public_key_package) } #[cfg(test)] @@ -76,30 +76,30 @@ mod tests { } #[test] - fn check_repair_share_step_1() { + fn check_repair_share_part1() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1::(rng); + frost_core::tests::repairable::check_repair_share_part1::(rng); } #[test] - fn check_repair_share_step_2() { - frost_core::tests::repairable::check_repair_share_step_2::(&REPAIR_SHARE); + fn check_repair_share_part2() { + frost_core::tests::repairable::check_repair_share_part2::(&REPAIR_SHARE); } #[test] - fn check_repair_share_step_3() { + fn check_repair_share_part3() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_3::( + frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, ); } #[test] - fn check_repair_share_step_1_fails_with_invalid_min_signers() { + fn check_repair_share_part1_fails_with_invalid_min_signers() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< + frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Ed25519Sha512, _, >(rng); diff --git a/frost-ed448/src/keys/repairable.rs b/frost-ed448/src/keys/repairable.rs index 2aa936644..f1e21d479 100644 --- a/frost-ed448/src/keys/repairable.rs +++ b/frost-ed448/src/keys/repairable.rs @@ -13,37 +13,37 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Ed448Shake256, Error}; -/// A delta value which is the output of step 1 of RTS. +/// A delta value which is the output of part 1 of RTS. pub type Delta = frost::keys::repairable::Delta; -/// A sigma value which is the output of step 2 of RTS. +/// A sigma value which is the output of part 2 of RTS. pub type Sigma = frost::keys::repairable::Sigma; -/// Step 1 of RTS. +/// Part 1 of RTS. /// /// Generates the "delta" values from the helper with `key_package_i` to send to /// `helpers` (which includes the helper with `key_package_i`), to help /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_step_1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, ) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) + frost::keys::repairable::repair_share_part1(helpers, key_package_i, rng, participant) } -/// Step 2 of RTS. +/// Part 2 of RTS. /// /// Generates the "sigma" value from all `deltas` received from all helpers. /// The "sigma" value must be sent to the participant repairing their share. -pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { - frost::keys::repairable::repair_share_step_2::(deltas) +pub fn repair_share_part2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_part2::(deltas) } -/// Step 3 of RTS. +/// Part 3 of RTS. /// /// The participant with the given `identifier` recovers their `KeyPackage` /// with the "sigma" values received from all helpers and the `PublicKeyPackage` @@ -52,12 +52,12 @@ pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { /// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. /// This happens for `PublicKeyPackage`s created before the 3.0.0 release; /// in that case, the user should set the `min_signers` field manually. -pub fn repair_share_step_3( +pub fn repair_share_part3( sigmas: &[Sigma], identifier: Identifier, public_key_package: &PublicKeyPackage, ) -> Result { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) + frost::keys::repairable::repair_share_part3(sigmas, identifier, public_key_package) } #[cfg(test)] @@ -76,30 +76,30 @@ mod tests { } #[test] - fn check_repair_share_step_1() { + fn check_repair_share_part1() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1::(rng); + frost_core::tests::repairable::check_repair_share_part1::(rng); } #[test] - fn check_repair_share_step_2() { - frost_core::tests::repairable::check_repair_share_step_2::(&REPAIR_SHARE); + fn check_repair_share_part2() { + frost_core::tests::repairable::check_repair_share_part2::(&REPAIR_SHARE); } #[test] - fn check_repair_share_step_3() { + fn check_repair_share_part3() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_3::( + frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, ); } #[test] - fn check_repair_share_step_1_fails_with_invalid_min_signers() { + fn check_repair_share_part1_fails_with_invalid_min_signers() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< + frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Ed448Shake256, _, >(rng); diff --git a/frost-p256/src/keys/repairable.rs b/frost-p256/src/keys/repairable.rs index c691b9469..98db9d1aa 100644 --- a/frost-p256/src/keys/repairable.rs +++ b/frost-p256/src/keys/repairable.rs @@ -13,37 +13,37 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Error, P256Sha256}; -/// A delta value which is the output of step 1 of RTS. +/// A delta value which is the output of part 1 of RTS. pub type Delta = frost::keys::repairable::Delta; -/// A sigma value which is the output of step 2 of RTS. +/// A sigma value which is the output of part 2 of RTS. pub type Sigma = frost::keys::repairable::Sigma; -/// Step 1 of RTS. +/// Part 1 of RTS. /// /// Generates the "delta" values from the helper with `key_package_i` to send to /// `helpers` (which includes the helper with `key_package_i`), to help /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_step_1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, ) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) + frost::keys::repairable::repair_share_part1(helpers, key_package_i, rng, participant) } -/// Step 2 of RTS. +/// Part 2 of RTS. /// /// Generates the "sigma" value from all `deltas` received from all helpers. /// The "sigma" value must be sent to the participant repairing their share. -pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { - frost::keys::repairable::repair_share_step_2::(deltas) +pub fn repair_share_part2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_part2::(deltas) } -/// Step 3 of RTS. +/// Part 3 of RTS. /// /// The participant with the given `identifier` recovers their `KeyPackage` /// with the "sigma" values received from all helpers and the `PublicKeyPackage` @@ -52,12 +52,12 @@ pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { /// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. /// This happens for `PublicKeyPackage`s created before the 3.0.0 release; /// in that case, the user should set the `min_signers` field manually. -pub fn repair_share_step_3( +pub fn repair_share_part3( sigmas: &[Sigma], identifier: Identifier, public_key_package: &PublicKeyPackage, ) -> Result { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) + frost::keys::repairable::repair_share_part3(sigmas, identifier, public_key_package) } #[cfg(test)] @@ -76,30 +76,30 @@ mod tests { } #[test] - fn check_repair_share_step_1() { + fn check_repair_share_part1() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1::(rng); + frost_core::tests::repairable::check_repair_share_part1::(rng); } #[test] - fn check_repair_share_step_2() { - frost_core::tests::repairable::check_repair_share_step_2::(&REPAIR_SHARE); + fn check_repair_share_part2() { + frost_core::tests::repairable::check_repair_share_part2::(&REPAIR_SHARE); } #[test] - fn check_repair_share_step_3() { + fn check_repair_share_part3() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_3::( + frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, ); } #[test] - fn check_repair_share_step_1_fails_with_invalid_min_signers() { + fn check_repair_share_part1_fails_with_invalid_min_signers() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< + frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< P256Sha256, _, >(rng); diff --git a/frost-ristretto255/src/keys/repairable.rs b/frost-ristretto255/src/keys/repairable.rs index 45bc27e3b..3c316bcc3 100644 --- a/frost-ristretto255/src/keys/repairable.rs +++ b/frost-ristretto255/src/keys/repairable.rs @@ -13,37 +13,37 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Error, Ristretto255Sha512}; -/// A delta value which is the output of step 1 of RTS. +/// A delta value which is the output of part 1 of RTS. pub type Delta = frost::keys::repairable::Delta; -/// A sigma value which is the output of step 2 of RTS. +/// A sigma value which is the output of part 2 of RTS. pub type Sigma = frost::keys::repairable::Sigma; -/// Step 1 of RTS. +/// Part 1 of RTS. /// /// Generates the "delta" values from the helper with `key_package_i` to send to /// `helpers` (which includes the helper with `key_package_i`), to help /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_step_1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, ) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) + frost::keys::repairable::repair_share_part1(helpers, key_package_i, rng, participant) } -/// Step 2 of RTS. +/// Part 2 of RTS. /// /// Generates the "sigma" value from all `deltas` received from all helpers. /// The "sigma" value must be sent to the participant repairing their share. -pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { - frost::keys::repairable::repair_share_step_2::(deltas) +pub fn repair_share_part2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_part2::(deltas) } -/// Step 3 of RTS. +/// Part 3 of RTS. /// /// The participant with the given `identifier` recovers their `KeyPackage` /// with the "sigma" values received from all helpers and the `PublicKeyPackage` @@ -52,12 +52,12 @@ pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { /// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. /// This happens for `PublicKeyPackage`s created before the 3.0.0 release; /// in that case, the user should set the `min_signers` field manually. -pub fn repair_share_step_3( +pub fn repair_share_part3( sigmas: &[Sigma], identifier: Identifier, public_key_package: &PublicKeyPackage, ) -> Result { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) + frost::keys::repairable::repair_share_part3(sigmas, identifier, public_key_package) } #[cfg(test)] @@ -76,32 +76,32 @@ mod tests { } #[test] - fn check_repair_share_step_1() { + fn check_repair_share_part1() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1::(rng); + frost_core::tests::repairable::check_repair_share_part1::(rng); } #[test] - fn check_repair_share_step_2() { - frost_core::tests::repairable::check_repair_share_step_2::( + fn check_repair_share_part2() { + frost_core::tests::repairable::check_repair_share_part2::( &REPAIR_SHARE, ); } #[test] - fn check_repair_share_step_3() { + fn check_repair_share_part3() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_3::( + frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, ); } #[test] - fn check_repair_share_step_1_fails_with_invalid_min_signers() { + fn check_repair_share_part1_fails_with_invalid_min_signers() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< + frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Ristretto255Sha512, _, >(rng); diff --git a/frost-secp256k1-tr/src/keys/repairable.rs b/frost-secp256k1-tr/src/keys/repairable.rs index 0db86275c..3ae930d49 100644 --- a/frost-secp256k1-tr/src/keys/repairable.rs +++ b/frost-secp256k1-tr/src/keys/repairable.rs @@ -13,37 +13,37 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Error, Secp256K1Sha256TR}; -/// A delta value which is the output of step 1 of RTS. +/// A delta value which is the output of part 1 of RTS. pub type Delta = frost::keys::repairable::Delta; -/// A sigma value which is the output of step 2 of RTS. +/// A sigma value which is the output of part 2 of RTS. pub type Sigma = frost::keys::repairable::Sigma; -/// Step 1 of RTS. +/// Part 1 of RTS. /// /// Generates the "delta" values from the helper with `key_package_i` to send to /// `helpers` (which includes the helper with `key_package_i`), to help /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_step_1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, ) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) + frost::keys::repairable::repair_share_part1(helpers, key_package_i, rng, participant) } -/// Step 2 of RTS. +/// Part 2 of RTS. /// /// Generates the "sigma" value from all `deltas` received from all helpers. /// The "sigma" value must be sent to the participant repairing their share. -pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { - frost::keys::repairable::repair_share_step_2::(deltas) +pub fn repair_share_part2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_part2::(deltas) } -/// Step 3 of RTS. +/// Part 3 of RTS. /// /// The participant with the given `identifier` recovers their `KeyPackage` /// with the "sigma" values received from all helpers and the `PublicKeyPackage` @@ -52,12 +52,12 @@ pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { /// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. /// This happens for `PublicKeyPackage`s created before the 3.0.0 release; /// in that case, the user should set the `min_signers` field manually. -pub fn repair_share_step_3( +pub fn repair_share_part3( sigmas: &[Sigma], identifier: Identifier, public_key_package: &PublicKeyPackage, ) -> Result { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) + frost::keys::repairable::repair_share_part3(sigmas, identifier, public_key_package) } #[cfg(test)] @@ -76,32 +76,30 @@ mod tests { } #[test] - fn check_repair_share_step_1() { + fn check_repair_share_part1() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1::(rng); + frost_core::tests::repairable::check_repair_share_part1::(rng); } #[test] - fn check_repair_share_step_2() { - frost_core::tests::repairable::check_repair_share_step_2::( - &REPAIR_SHARE, - ); + fn check_repair_share_part2() { + frost_core::tests::repairable::check_repair_share_part2::(&REPAIR_SHARE); } #[test] - fn check_repair_share_step_3() { + fn check_repair_share_part3() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_3::( + frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, ); } #[test] - fn check_repair_share_step_1_fails_with_invalid_min_signers() { + fn check_repair_share_part1_fails_with_invalid_min_signers() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< + frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Secp256K1Sha256TR, _, >(rng); diff --git a/frost-secp256k1/src/keys/repairable.rs b/frost-secp256k1/src/keys/repairable.rs index 22ef03d71..48b1506aa 100644 --- a/frost-secp256k1/src/keys/repairable.rs +++ b/frost-secp256k1/src/keys/repairable.rs @@ -13,37 +13,37 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; use crate::{Error, Secp256K1Sha256}; -/// A delta value which is the output of step 1 of RTS. +/// A delta value which is the output of part 1 of RTS. pub type Delta = frost::keys::repairable::Delta; -/// A sigma value which is the output of step 2 of RTS. +/// A sigma value which is the output of part 2 of RTS. pub type Sigma = frost::keys::repairable::Sigma; -/// Step 1 of RTS. +/// Part 1 of RTS. /// /// Generates the "delta" values from the helper with `key_package_i` to send to /// `helpers` (which includes the helper with `key_package_i`), to help /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_step_1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, participant: Identifier, ) -> Result, Error> { - frost::keys::repairable::repair_share_step_1(helpers, key_package_i, rng, participant) + frost::keys::repairable::repair_share_part1(helpers, key_package_i, rng, participant) } -/// Step 2 of RTS. +/// Part 2 of RTS. /// /// Generates the "sigma" value from all `deltas` received from all helpers. /// The "sigma" value must be sent to the participant repairing their share. -pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { - frost::keys::repairable::repair_share_step_2::(deltas) +pub fn repair_share_part2(deltas: &[Delta]) -> Sigma { + frost::keys::repairable::repair_share_part2::(deltas) } -/// Step 3 of RTS. +/// Part 3 of RTS. /// /// The participant with the given `identifier` recovers their `KeyPackage` /// with the "sigma" values received from all helpers and the `PublicKeyPackage` @@ -52,12 +52,12 @@ pub fn repair_share_step_2(deltas: &[Delta]) -> Sigma { /// Returns an error if the `min_signers` field is not set in the `PublicKeyPackage`. /// This happens for `PublicKeyPackage`s created before the 3.0.0 release; /// in that case, the user should set the `min_signers` field manually. -pub fn repair_share_step_3( +pub fn repair_share_part3( sigmas: &[Sigma], identifier: Identifier, public_key_package: &PublicKeyPackage, ) -> Result { - frost::keys::repairable::repair_share_step_3(sigmas, identifier, public_key_package) + frost::keys::repairable::repair_share_part3(sigmas, identifier, public_key_package) } #[cfg(test)] @@ -76,30 +76,30 @@ mod tests { } #[test] - fn check_repair_share_step_1() { + fn check_repair_share_part1() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1::(rng); + frost_core::tests::repairable::check_repair_share_part1::(rng); } #[test] - fn check_repair_share_step_2() { - frost_core::tests::repairable::check_repair_share_step_2::(&REPAIR_SHARE); + fn check_repair_share_part2() { + frost_core::tests::repairable::check_repair_share_part2::(&REPAIR_SHARE); } #[test] - fn check_repair_share_step_3() { + fn check_repair_share_part3() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_3::( + frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, ); } #[test] - fn check_repair_share_step_1_fails_with_invalid_min_signers() { + fn check_repair_share_part1_fails_with_invalid_min_signers() { let rng = rand::rngs::OsRng; - frost_core::tests::repairable::check_repair_share_step_1_fails_with_invalid_min_signers::< + frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Secp256K1Sha256, _, >(rng); From 68b1f572e9cd4df5c01306af7edcf3217c249cb2 Mon Sep 17 00:00:00 2001 From: Conrado Date: Tue, 27 Jan 2026 12:58:26 -0300 Subject: [PATCH 151/175] reexport rerandomized API in ciphersuite crates (#998) --- frost-core/CHANGELOG.md | 2 + frost-ed25519/src/rerandomized.rs | 65 ++++++++++++++++++++++++++ frost-ed448/src/rerandomized.rs | 65 ++++++++++++++++++++++++++ frost-p256/src/rerandomized.rs | 65 ++++++++++++++++++++++++++ frost-rerandomized/src/lib.rs | 33 ++++++++++--- frost-ristretto255/src/lib.rs | 2 + frost-ristretto255/src/rerandomized.rs | 65 ++++++++++++++++++++++++++ frost-secp256k1-tr/src/rerandomized.rs | 65 ++++++++++++++++++++++++++ frost-secp256k1/src/rerandomized.rs | 65 ++++++++++++++++++++++++++ gencode/src/main.rs | 1 + 10 files changed, 422 insertions(+), 6 deletions(-) create mode 100644 frost-ed25519/src/rerandomized.rs create mode 100644 frost-ed448/src/rerandomized.rs create mode 100644 frost-p256/src/rerandomized.rs create mode 100644 frost-ristretto255/src/rerandomized.rs create mode 100644 frost-secp256k1-tr/src/rerandomized.rs create mode 100644 frost-secp256k1/src/rerandomized.rs diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 057b40603..3e85019dd 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -37,6 +37,8 @@ Entries are listed in reverse chronological order. ### Additional changes * Added DKG refresh functions to the crate-specific `refresh` modules. +* Re-exported the `frost-rerandomized` crate in the ciphersuite functions, e.g. + you can call `frost_ristretto255::rerandomized::sign_with_randomizer_seed()`. ## 2.2.0 diff --git a/frost-ed25519/src/rerandomized.rs b/frost-ed25519/src/rerandomized.rs new file mode 100644 index 000000000..7a90a0dd1 --- /dev/null +++ b/frost-ed25519/src/rerandomized.rs @@ -0,0 +1,65 @@ +//! FROST implementation supporting re-randomizable keys. + +use alloc::collections::btree_map::BTreeMap; + +/// Re-randomized FROST signing using the given `randomizer_seed`, which should +/// be sent from the Coordinator using a confidential channel. +/// +/// See [`crate::round2::sign`] for documentation on the other parameters. +pub fn sign_with_randomizer_seed( + signing_package: &crate::SigningPackage, + signer_nonces: &crate::round1::SigningNonces, + key_package: &crate::keys::KeyPackage, + randomizer_seed: &[u8], +) -> Result { + frost_rerandomized::sign_with_randomizer_seed::( + signing_package, + signer_nonces, + key_package, + randomizer_seed, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`]. +/// +/// See [`frost_core::aggregate`] for documentation on the other parameters. +pub fn aggregate( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate::( + signing_package, + signature_shares, + pubkeys, + randomized_params, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`] using the given cheater detection strategy. +/// +/// See [`frost_core::aggregate_custom`] for documentation on the other parameters. +pub fn aggregate_custom( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + cheater_detection: crate::CheaterDetection, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate_custom::( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + randomized_params, + ) +} + +/// A randomizer. A random scalar which is used to randomize the key. +pub type Randomizer = frost_rerandomized::Randomizer; + +/// Randomized parameters for a signing instance of randomized FROST. +pub type RandomizedParams = frost_rerandomized::RandomizedParams; diff --git a/frost-ed448/src/rerandomized.rs b/frost-ed448/src/rerandomized.rs new file mode 100644 index 000000000..10d5d013c --- /dev/null +++ b/frost-ed448/src/rerandomized.rs @@ -0,0 +1,65 @@ +//! FROST implementation supporting re-randomizable keys. + +use alloc::collections::btree_map::BTreeMap; + +/// Re-randomized FROST signing using the given `randomizer_seed`, which should +/// be sent from the Coordinator using a confidential channel. +/// +/// See [`crate::round2::sign`] for documentation on the other parameters. +pub fn sign_with_randomizer_seed( + signing_package: &crate::SigningPackage, + signer_nonces: &crate::round1::SigningNonces, + key_package: &crate::keys::KeyPackage, + randomizer_seed: &[u8], +) -> Result { + frost_rerandomized::sign_with_randomizer_seed::( + signing_package, + signer_nonces, + key_package, + randomizer_seed, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`]. +/// +/// See [`frost_core::aggregate`] for documentation on the other parameters. +pub fn aggregate( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate::( + signing_package, + signature_shares, + pubkeys, + randomized_params, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`] using the given cheater detection strategy. +/// +/// See [`frost_core::aggregate_custom`] for documentation on the other parameters. +pub fn aggregate_custom( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + cheater_detection: crate::CheaterDetection, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate_custom::( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + randomized_params, + ) +} + +/// A randomizer. A random scalar which is used to randomize the key. +pub type Randomizer = frost_rerandomized::Randomizer; + +/// Randomized parameters for a signing instance of randomized FROST. +pub type RandomizedParams = frost_rerandomized::RandomizedParams; diff --git a/frost-p256/src/rerandomized.rs b/frost-p256/src/rerandomized.rs new file mode 100644 index 000000000..e5e8b4411 --- /dev/null +++ b/frost-p256/src/rerandomized.rs @@ -0,0 +1,65 @@ +//! FROST implementation supporting re-randomizable keys. + +use alloc::collections::btree_map::BTreeMap; + +/// Re-randomized FROST signing using the given `randomizer_seed`, which should +/// be sent from the Coordinator using a confidential channel. +/// +/// See [`crate::round2::sign`] for documentation on the other parameters. +pub fn sign_with_randomizer_seed( + signing_package: &crate::SigningPackage, + signer_nonces: &crate::round1::SigningNonces, + key_package: &crate::keys::KeyPackage, + randomizer_seed: &[u8], +) -> Result { + frost_rerandomized::sign_with_randomizer_seed::( + signing_package, + signer_nonces, + key_package, + randomizer_seed, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`]. +/// +/// See [`frost_core::aggregate`] for documentation on the other parameters. +pub fn aggregate( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate::( + signing_package, + signature_shares, + pubkeys, + randomized_params, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`] using the given cheater detection strategy. +/// +/// See [`frost_core::aggregate_custom`] for documentation on the other parameters. +pub fn aggregate_custom( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + cheater_detection: crate::CheaterDetection, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate_custom::( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + randomized_params, + ) +} + +/// A randomizer. A random scalar which is used to randomize the key. +pub type Randomizer = frost_rerandomized::Randomizer; + +/// Randomized parameters for a signing instance of randomized FROST. +pub type RandomizedParams = frost_rerandomized::RandomizedParams; diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index a88e93c67..0b5e05715 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -30,10 +30,9 @@ use frost_core::SigningPackage; use frost_core::{ self as frost, keys::{KeyPackage, PublicKeyPackage, SigningShare, VerifyingShare}, - round1::encode_group_commitments, - round1::SigningCommitments, + round1::{encode_group_commitments, SigningCommitments}, serialization::SerializableScalar, - Ciphersuite, Error, Field, Group, Identifier, Scalar, VerifyingKey, + CheaterDetection, Ciphersuite, Error, Field, Group, Identifier, Scalar, VerifyingKey, }; #[cfg(feature = "serde")] @@ -161,9 +160,8 @@ pub fn sign_with_randomizer_seed( frost::round2::sign(signing_package, signer_nonces, &randomized_key_package) } -/// Re-randomized FROST signature share aggregation with the given [`RandomizedParams`], -/// which can be computed from the previously generated randomizer using -/// [`RandomizedParams::from_randomizer`]. +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`]. /// /// See [`frost::aggregate`] for documentation on the other parameters. pub fn aggregate( @@ -183,6 +181,29 @@ where ) } +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`] using the given cheater detection strategy. +/// +/// See [`frost::aggregate_custom`] for documentation on the other parameters. +pub fn aggregate_custom( + signing_package: &frost::SigningPackage, + signature_shares: &BTreeMap, frost::round2::SignatureShare>, + pubkeys: &frost::keys::PublicKeyPackage, + cheater_detection: CheaterDetection, + randomized_params: &RandomizedParams, +) -> Result, Error> +where + C: Ciphersuite, +{ + let randomized_public_key_package = pubkeys.randomize(randomized_params)?; + frost::aggregate_custom( + signing_package, + signature_shares, + &randomized_public_key_package, + cheater_detection, + ) +} + /// A randomizer. A random scalar which is used to randomize the key. #[derive(Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index 9951aad5b..bb137326e 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -22,6 +22,8 @@ use frost_core as frost; #[cfg(test)] mod tests; +pub mod rerandomized; + // Re-exports in our public API #[cfg(feature = "serde")] pub use frost_core::serde; diff --git a/frost-ristretto255/src/rerandomized.rs b/frost-ristretto255/src/rerandomized.rs new file mode 100644 index 000000000..21410f41b --- /dev/null +++ b/frost-ristretto255/src/rerandomized.rs @@ -0,0 +1,65 @@ +//! FROST implementation supporting re-randomizable keys. + +use alloc::collections::btree_map::BTreeMap; + +/// Re-randomized FROST signing using the given `randomizer_seed`, which should +/// be sent from the Coordinator using a confidential channel. +/// +/// See [`crate::round2::sign`] for documentation on the other parameters. +pub fn sign_with_randomizer_seed( + signing_package: &crate::SigningPackage, + signer_nonces: &crate::round1::SigningNonces, + key_package: &crate::keys::KeyPackage, + randomizer_seed: &[u8], +) -> Result { + frost_rerandomized::sign_with_randomizer_seed::( + signing_package, + signer_nonces, + key_package, + randomizer_seed, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`]. +/// +/// See [`frost_core::aggregate`] for documentation on the other parameters. +pub fn aggregate( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate::( + signing_package, + signature_shares, + pubkeys, + randomized_params, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`] using the given cheater detection strategy. +/// +/// See [`frost_core::aggregate_custom`] for documentation on the other parameters. +pub fn aggregate_custom( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + cheater_detection: crate::CheaterDetection, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate_custom::( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + randomized_params, + ) +} + +/// A randomizer. A random scalar which is used to randomize the key. +pub type Randomizer = frost_rerandomized::Randomizer; + +/// Randomized parameters for a signing instance of randomized FROST. +pub type RandomizedParams = frost_rerandomized::RandomizedParams; diff --git a/frost-secp256k1-tr/src/rerandomized.rs b/frost-secp256k1-tr/src/rerandomized.rs new file mode 100644 index 000000000..7135cdbd4 --- /dev/null +++ b/frost-secp256k1-tr/src/rerandomized.rs @@ -0,0 +1,65 @@ +//! FROST implementation supporting re-randomizable keys. + +use alloc::collections::btree_map::BTreeMap; + +/// Re-randomized FROST signing using the given `randomizer_seed`, which should +/// be sent from the Coordinator using a confidential channel. +/// +/// See [`crate::round2::sign`] for documentation on the other parameters. +pub fn sign_with_randomizer_seed( + signing_package: &crate::SigningPackage, + signer_nonces: &crate::round1::SigningNonces, + key_package: &crate::keys::KeyPackage, + randomizer_seed: &[u8], +) -> Result { + frost_rerandomized::sign_with_randomizer_seed::( + signing_package, + signer_nonces, + key_package, + randomizer_seed, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`]. +/// +/// See [`frost_core::aggregate`] for documentation on the other parameters. +pub fn aggregate( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate::( + signing_package, + signature_shares, + pubkeys, + randomized_params, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`] using the given cheater detection strategy. +/// +/// See [`frost_core::aggregate_custom`] for documentation on the other parameters. +pub fn aggregate_custom( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + cheater_detection: crate::CheaterDetection, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate_custom::( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + randomized_params, + ) +} + +/// A randomizer. A random scalar which is used to randomize the key. +pub type Randomizer = frost_rerandomized::Randomizer; + +/// Randomized parameters for a signing instance of randomized FROST. +pub type RandomizedParams = frost_rerandomized::RandomizedParams; diff --git a/frost-secp256k1/src/rerandomized.rs b/frost-secp256k1/src/rerandomized.rs new file mode 100644 index 000000000..7f7fc932c --- /dev/null +++ b/frost-secp256k1/src/rerandomized.rs @@ -0,0 +1,65 @@ +//! FROST implementation supporting re-randomizable keys. + +use alloc::collections::btree_map::BTreeMap; + +/// Re-randomized FROST signing using the given `randomizer_seed`, which should +/// be sent from the Coordinator using a confidential channel. +/// +/// See [`crate::round2::sign`] for documentation on the other parameters. +pub fn sign_with_randomizer_seed( + signing_package: &crate::SigningPackage, + signer_nonces: &crate::round1::SigningNonces, + key_package: &crate::keys::KeyPackage, + randomizer_seed: &[u8], +) -> Result { + frost_rerandomized::sign_with_randomizer_seed::( + signing_package, + signer_nonces, + key_package, + randomizer_seed, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`]. +/// +/// See [`frost_core::aggregate`] for documentation on the other parameters. +pub fn aggregate( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate::( + signing_package, + signature_shares, + pubkeys, + randomized_params, + ) +} + +/// Re-randomized FROST signature share aggregation with the given +/// [`RandomizedParams`] using the given cheater detection strategy. +/// +/// See [`frost_core::aggregate_custom`] for documentation on the other parameters. +pub fn aggregate_custom( + signing_package: &crate::SigningPackage, + signature_shares: &BTreeMap, + pubkeys: &crate::keys::PublicKeyPackage, + cheater_detection: crate::CheaterDetection, + randomized_params: &RandomizedParams, +) -> Result { + frost_rerandomized::aggregate_custom::( + signing_package, + signature_shares, + pubkeys, + cheater_detection, + randomized_params, + ) +} + +/// A randomizer. A random scalar which is used to randomize the key. +pub type Randomizer = frost_rerandomized::Randomizer; + +/// Randomized parameters for a signing instance of randomized FROST. +pub type RandomizedParams = frost_rerandomized::RandomizedParams; diff --git a/gencode/src/main.rs b/gencode/src/main.rs index 5f92f3882..f88cd7a7b 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -355,6 +355,7 @@ fn main() -> ExitCode { for filename in [ "README.md", "dkg.md", + "src/rerandomized.rs", "src/keys/dkg.rs", "src/keys/refresh.rs", "src/keys/repairable.rs", From ee89da3624d55ee1f8d4b08ab5bc98da92963d42 Mon Sep 17 00:00:00 2001 From: Conrado Date: Tue, 27 Jan 2026 13:02:51 -0300 Subject: [PATCH 152/175] change PublicKeyPackage::new() to take min_signers as an Option (#1000) --- frost-core/src/keys.rs | 9 ++++++--- frost-core/src/tests/vectors.rs | 7 +++++-- frost-ed25519/tests/helpers/samples.rs | 2 +- frost-ed25519/tests/recreation_tests.rs | 2 +- frost-ed448/tests/helpers/samples.rs | 2 +- frost-ed448/tests/recreation_tests.rs | 2 +- frost-p256/tests/helpers/samples.rs | 2 +- frost-p256/tests/recreation_tests.rs | 2 +- frost-ristretto255/tests/helpers/samples.rs | 2 +- frost-ristretto255/tests/recreation_tests.rs | 2 +- frost-secp256k1-tr/tests/helpers/samples.rs | 2 +- frost-secp256k1-tr/tests/recreation_tests.rs | 2 +- frost-secp256k1/tests/helpers/samples.rs | 2 +- frost-secp256k1/tests/recreation_tests.rs | 2 +- 14 files changed, 23 insertions(+), 17 deletions(-) diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index cc6fe7af4..5389f5ebc 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -747,12 +747,15 @@ where C: Ciphersuite, { /// Create a new [`PublicKeyPackage`] instance. + /// + /// `min_signers` is an `Option` for compatibility with pre-3.0.0 packages. + /// In normal usage, it should always be `Some(value)`. pub fn new( verifying_shares: BTreeMap, VerifyingShare>, verifying_key: VerifyingKey, - min_signers: u16, + min_signers: Option, ) -> Self { - Self::new_internal(verifying_shares, verifying_key, Some(min_signers)) + Self::new_internal(verifying_shares, verifying_key, min_signers) } /// Create a new [`PublicKeyPackage`] instance, allowing not specifying the @@ -788,7 +791,7 @@ where Ok(PublicKeyPackage::new( verifying_keys, VerifyingKey::from_commitment(commitment)?, - commitment.min_signers(), + Some(commitment.min_signers()), )) } diff --git a/frost-core/src/tests/vectors.rs b/frost-core/src/tests/vectors.rs index b3bfd7f81..1ca8a97bb 100644 --- a/frost-core/src/tests/vectors.rs +++ b/frost-core/src/tests/vectors.rs @@ -293,8 +293,11 @@ pub fn check_sign_with_test_vectors(json_vectors: &Value) { .map(|(i, key_package)| (i, *key_package.verifying_share())) .collect(); - let pubkey_package = - frost::keys::PublicKeyPackage::new(verifying_shares, verifying_key, min_signers as u16); + let pubkey_package = frost::keys::PublicKeyPackage::new( + verifying_shares, + verifying_key, + Some(min_signers as u16), + ); //////////////////////////////////////////////////////////////////////////// // Aggregation: collects the signing shares from all participants, diff --git a/frost-ed25519/tests/helpers/samples.rs b/frost-ed25519/tests/helpers/samples.rs index 3ce2f8af6..2c6c9c531 100644 --- a/frost-ed25519/tests/helpers/samples.rs +++ b/frost-ed25519/tests/helpers/samples.rs @@ -116,7 +116,7 @@ pub fn public_key_package_new() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key, 2) + PublicKeyPackage::new(verifying_shares, verifying_key, Some(2)) } /// Generate a sample round1::SecretPackage. diff --git a/frost-ed25519/tests/recreation_tests.rs b/frost-ed25519/tests/recreation_tests.rs index 4261cb458..479b7d93f 100644 --- a/frost-ed25519/tests/recreation_tests.rs +++ b/frost-ed25519/tests/recreation_tests.rs @@ -116,7 +116,7 @@ fn check_public_key_package_new_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); - let min_signers = public_key_package.min_signers().unwrap(); + let min_signers = public_key_package.min_signers(); let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); diff --git a/frost-ed448/tests/helpers/samples.rs b/frost-ed448/tests/helpers/samples.rs index d4e045217..529633b88 100644 --- a/frost-ed448/tests/helpers/samples.rs +++ b/frost-ed448/tests/helpers/samples.rs @@ -116,7 +116,7 @@ pub fn public_key_package_new() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key, 2) + PublicKeyPackage::new(verifying_shares, verifying_key, Some(2)) } /// Generate a sample round1::SecretPackage. diff --git a/frost-ed448/tests/recreation_tests.rs b/frost-ed448/tests/recreation_tests.rs index a0a457aa5..e206ccf08 100644 --- a/frost-ed448/tests/recreation_tests.rs +++ b/frost-ed448/tests/recreation_tests.rs @@ -116,7 +116,7 @@ fn check_public_key_package_new_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); - let min_signers = public_key_package.min_signers().unwrap(); + let min_signers = public_key_package.min_signers(); let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); diff --git a/frost-p256/tests/helpers/samples.rs b/frost-p256/tests/helpers/samples.rs index df9407b9d..e6a2fd260 100644 --- a/frost-p256/tests/helpers/samples.rs +++ b/frost-p256/tests/helpers/samples.rs @@ -116,7 +116,7 @@ pub fn public_key_package_new() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key, 2) + PublicKeyPackage::new(verifying_shares, verifying_key, Some(2)) } /// Generate a sample round1::SecretPackage. diff --git a/frost-p256/tests/recreation_tests.rs b/frost-p256/tests/recreation_tests.rs index e5816bfe2..99c39a2d1 100644 --- a/frost-p256/tests/recreation_tests.rs +++ b/frost-p256/tests/recreation_tests.rs @@ -116,7 +116,7 @@ fn check_public_key_package_new_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); - let min_signers = public_key_package.min_signers().unwrap(); + let min_signers = public_key_package.min_signers(); let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); diff --git a/frost-ristretto255/tests/helpers/samples.rs b/frost-ristretto255/tests/helpers/samples.rs index 0b0a6e304..b0bf5868b 100644 --- a/frost-ristretto255/tests/helpers/samples.rs +++ b/frost-ristretto255/tests/helpers/samples.rs @@ -116,7 +116,7 @@ pub fn public_key_package_new() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key, 2) + PublicKeyPackage::new(verifying_shares, verifying_key, Some(2)) } /// Generate a sample round1::SecretPackage. diff --git a/frost-ristretto255/tests/recreation_tests.rs b/frost-ristretto255/tests/recreation_tests.rs index fc6947b29..0d8e51614 100644 --- a/frost-ristretto255/tests/recreation_tests.rs +++ b/frost-ristretto255/tests/recreation_tests.rs @@ -116,7 +116,7 @@ fn check_public_key_package_new_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); - let min_signers = public_key_package.min_signers().unwrap(); + let min_signers = public_key_package.min_signers(); let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); diff --git a/frost-secp256k1-tr/tests/helpers/samples.rs b/frost-secp256k1-tr/tests/helpers/samples.rs index e94ae01ee..8c39a9e61 100644 --- a/frost-secp256k1-tr/tests/helpers/samples.rs +++ b/frost-secp256k1-tr/tests/helpers/samples.rs @@ -116,7 +116,7 @@ pub fn public_key_package_new() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key, 2) + PublicKeyPackage::new(verifying_shares, verifying_key, Some(2)) } /// Generate a sample round1::SecretPackage. diff --git a/frost-secp256k1-tr/tests/recreation_tests.rs b/frost-secp256k1-tr/tests/recreation_tests.rs index bcdd3df4b..56cd8f443 100644 --- a/frost-secp256k1-tr/tests/recreation_tests.rs +++ b/frost-secp256k1-tr/tests/recreation_tests.rs @@ -116,7 +116,7 @@ fn check_public_key_package_new_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); - let min_signers = public_key_package.min_signers().unwrap(); + let min_signers = public_key_package.min_signers(); let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); diff --git a/frost-secp256k1/tests/helpers/samples.rs b/frost-secp256k1/tests/helpers/samples.rs index 2b1bb8ef5..d9cbf5b92 100644 --- a/frost-secp256k1/tests/helpers/samples.rs +++ b/frost-secp256k1/tests/helpers/samples.rs @@ -116,7 +116,7 @@ pub fn public_key_package_new() -> PublicKeyPackage { let verifying_key = VerifyingKey::deserialize(serialized_element.as_ref()).unwrap(); let verifying_shares = BTreeMap::from([(identifier, verifying_share)]); - PublicKeyPackage::new(verifying_shares, verifying_key, 2) + PublicKeyPackage::new(verifying_shares, verifying_key, Some(2)) } /// Generate a sample round1::SecretPackage. diff --git a/frost-secp256k1/tests/recreation_tests.rs b/frost-secp256k1/tests/recreation_tests.rs index f72c154c1..1e8eb2049 100644 --- a/frost-secp256k1/tests/recreation_tests.rs +++ b/frost-secp256k1/tests/recreation_tests.rs @@ -116,7 +116,7 @@ fn check_public_key_package_new_recreation() { let verifying_shares = public_key_package.verifying_shares(); let verifying_key = public_key_package.verifying_key(); - let min_signers = public_key_package.min_signers().unwrap(); + let min_signers = public_key_package.min_signers(); let new_public_key_package = PublicKeyPackage::new(verifying_shares.clone(), *verifying_key, min_signers); From 893afc7cc7c248df00fb9cb44914ccfd9a6d2fb8 Mon Sep 17 00:00:00 2001 From: Conrado Date: Tue, 27 Jan 2026 18:04:54 -0300 Subject: [PATCH 153/175] Release 3.0.0 (#995) * Release 3.0.0 * Apply suggestions from code review Co-authored-by: natalie * update frost-rerandomized changelog * use 3.0.0-rc.0 instead --------- Co-authored-by: natalie --- Cargo.lock | 16 +++++++------- Cargo.toml | 6 +++--- book/src/dev/release-checklist.md | 11 +++++----- book/src/tutorial/importing.md | 4 ++-- frost-core/CHANGELOG.md | 23 ++++++++++++++++++-- frost-core/README.md | 2 +- frost-ed25519/CHANGELOG.md | 4 ++++ frost-ed448/CHANGELOG.md | 4 ++++ frost-p256/CHANGELOG.md | 4 ++++ frost-rerandomized/CHANGELOG.md | 36 +++++++++++++++++++++++++++++++ frost-ristretto255/CHANGELOG.md | 4 ++++ frost-secp256k1-tr/CHANGELOG.md | 4 ++++ frost-secp256k1/CHANGELOG.md | 4 ++++ gencode/src/main.rs | 1 + 14 files changed, 101 insertions(+), 22 deletions(-) create mode 100644 frost-ed25519/CHANGELOG.md create mode 100644 frost-ed448/CHANGELOG.md create mode 100644 frost-p256/CHANGELOG.md create mode 100644 frost-rerandomized/CHANGELOG.md create mode 100644 frost-ristretto255/CHANGELOG.md create mode 100644 frost-secp256k1-tr/CHANGELOG.md create mode 100644 frost-secp256k1/CHANGELOG.md diff --git a/Cargo.lock b/Cargo.lock index d80823506..d49aafc0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -527,7 +527,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "frost-core" -version = "2.2.0" +version = "3.0.0-rc.0" dependencies = [ "byteorder", "const-crc32-nostd", @@ -555,7 +555,7 @@ dependencies = [ [[package]] name = "frost-ed25519" -version = "2.2.0" +version = "3.0.0-rc.0" dependencies = [ "criterion", "curve25519-dalek", @@ -577,7 +577,7 @@ dependencies = [ [[package]] name = "frost-ed448" -version = "2.2.0" +version = "3.0.0-rc.0" dependencies = [ "criterion", "document-features", @@ -598,7 +598,7 @@ dependencies = [ [[package]] name = "frost-p256" -version = "2.2.0" +version = "3.0.0-rc.0" dependencies = [ "criterion", "document-features", @@ -619,7 +619,7 @@ dependencies = [ [[package]] name = "frost-rerandomized" -version = "2.2.0" +version = "3.0.0-rc.0" dependencies = [ "derive-getters", "document-features", @@ -630,7 +630,7 @@ dependencies = [ [[package]] name = "frost-ristretto255" -version = "2.2.0" +version = "3.0.0-rc.0" dependencies = [ "criterion", "curve25519-dalek", @@ -652,7 +652,7 @@ dependencies = [ [[package]] name = "frost-secp256k1" -version = "2.2.0" +version = "3.0.0-rc.0" dependencies = [ "criterion", "document-features", @@ -673,7 +673,7 @@ dependencies = [ [[package]] name = "frost-secp256k1-tr" -version = "2.2.0" +version = "3.0.0-rc.0" dependencies = [ "criterion", "document-features", diff --git a/Cargo.toml b/Cargo.toml index dc52fea2d..9ed7aacfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ # in gencode/src/main.rs. edition = "2021" rust-version = "1.81" -version = "2.2.0" +version = "3.0.0-rc.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -43,8 +43,8 @@ rand_core = "0.6" serde_json = "1.0" tokio = { version = "1.0", features = ["rt", "time", "macros"] } -frost-core = { path = "frost-core", version = "2.2.0", default-features = false } -frost-rerandomized = { path = "frost-rerandomized", version = "2.2.0", default-features = false } +frost-core = { path = "frost-core", version = "3.0.0-rc.0", default-features = false } +frost-rerandomized = { path = "frost-rerandomized", version = "3.0.0-rc.0", default-features = false } [profile.test.package."*"] opt-level = 3 diff --git a/book/src/dev/release-checklist.md b/book/src/dev/release-checklist.md index 79aac8659..5005edf01 100644 --- a/book/src/dev/release-checklist.md +++ b/book/src/dev/release-checklist.md @@ -24,7 +24,11 @@ releases easier. versions, update those separately as required.) - Decide which version to tag the release with (e.g. v0.3.0), considering - [SemVer](https://doc.rust-lang.org/cargo/reference/semver.html). + [SemVer](https://doc.rust-lang.org/cargo/reference/semver.html). Run `cargo + semver-checks` to check if there are no API changes that break SemVer + compatibility. ([Installation + instructions](https://crates.io/crates/cargo-semver-checks)) Fix issues if + any (i.e. change the version, or revert/adapt the API change). - Create new issue. E.g. [Release v0.4.0](https://github.com/ZcashFoundation/frost/issues/377) @@ -34,11 +38,6 @@ releases easier. - Bump the version of the crates in the root Cargo.toml file. (If they ever get out of sync, you will need to bump in each crate Cargo.toml file.) -- Run `cargo semver-checks` to check if there are no API changes that break - SemVer compatibility. ([Installation - instructions](https://crates.io/crates/cargo-semver-checks)) Fix issues if - any (i.e. change the version, or revert/adapt the API change). - - Bump the version used in the tutorial (importing.md) - Check if the [changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md) is up to date and update if required (we’re only keeping the one in frost-core for now). Double check using [FROST releases](https://github.com/ZcashFoundation/frost/releases) which will have a list of all the PRs that have been closed since the last release. Things to include in the changelog will be anything that impacts production code and big documentation changes. I.e. script and test changes should not be included. NOTE: Please add to the changelog whenever you make changes to the library as this will make things simpler for the person in charge of the release. diff --git a/book/src/tutorial/importing.md b/book/src/tutorial/importing.md index 23be0455b..ca33d6aa6 100644 --- a/book/src/tutorial/importing.md +++ b/book/src/tutorial/importing.md @@ -6,7 +6,7 @@ Add to your `Cargo.toml` file: ``` [dependencies] -frost-ristretto255 = "2.2.0" +frost-ristretto255 = "3.0.0-rc.0" ``` ## Handling errors @@ -38,7 +38,7 @@ needs to be transmitted. The importing would look like: ``` [dependencies] -frost-ristretto255 = { version = "2.2.0", features = ["serde"] } +frost-ristretto255 = { version = "3.0.0-rc.0", features = ["serde"] } ``` Note that serde usage is optional. Applications can use different encodings, and diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 3e85019dd..a1fce406a 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -2,8 +2,12 @@ Entries are listed in reverse chronological order. + ## Unreleased + +## 3.0.0-rc.0 + ### Breaking Changes * The `cheater-detection` feature was removed. If you relied on it (either by @@ -12,7 +16,10 @@ Entries are listed in reverse chronological order. the default behaviour is now as if `cheater-detection` was enabled. If you explicitly *did not enable* it, you can avoid cheater detection by calling `aggregate_custom()` with `CheaterDetection::Disabled`. -* The `std` and `nightly` features were removed from all crates +* Changed `InvalidSignatureShare::culprit` to `culprits`; it is now a `Vec`. +* Changed `Error::culprit()` to `culprits()`; is is now a `Vec`. +* Added a `min_signers` argument to `PublicKeyPackage::new()`. +* The `std` and `nightly` features were removed from all crates. * Renamed `frost_core::keys::refresh::refresh_dkg_part_1` to `refresh_dkg_part1`. * Fixed the crate-specific versions of the `refresh` module to be non-generic. * Removed the `min_signers` and `max_signers` arguments from @@ -32,13 +39,25 @@ Entries are listed in reverse chronological order. `VerifiableSecretSharingCommitment`; and returns a `KeyPackage` instead of `SecretShare`. * These changes provide more type safety and are make it more useful since - `SecretPackage`s are not expected to be stored + `SecretPackage`s are not expected to be stored. +* The `Ciphersuite`, `Scalar` and `Element` traits now must implement `Send` and + `Sync`. This should be trivial in most cases. +* The `SignatureSerialization`, `Field::Serialization` and + `Element::Serialization` traits do not need to implement `TryFrom>` + anymore; instead, they must implement `AsMut<[u8]>` and `TryFrom<&[u8]>`. This + should be trivial in most cases since they are often implemented by arrays. ### Additional changes * Added DKG refresh functions to the crate-specific `refresh` modules. * Re-exported the `frost-rerandomized` crate in the ciphersuite functions, e.g. you can call `frost_ristretto255::rerandomized::sign_with_randomizer_seed()`. +* Added the `pre_commitment_aggregate()` and `pre_commitment_sign()` hooks + to the `Ciphersuite` trait. +* Added `aggregate_custom()` function to allow specifying which cheater + detection strategy to use. The original `aggregate()` behaviour is to use + the `CheaterDetection::FirstCheater` strategy. + ## 2.2.0 diff --git a/frost-core/README.md b/frost-core/README.md index e54be2f5c..2fe7e6833 100644 --- a/frost-core/README.md +++ b/frost-core/README.md @@ -8,4 +8,4 @@ For more details, refer to [The ZF FROST Book](https://frost.zfnd.org/). ## Example -See ciphersuite-specific crates, e.g. [`frost_ristretto255`]([../frost_ristretto255](https://crates.io/crates/frost-ristretto255)). +See ciphersuite-specific crates, e.g. [`frost_ristretto255`](https://crates.io/crates/frost-ristretto255). diff --git a/frost-ed25519/CHANGELOG.md b/frost-ed25519/CHANGELOG.md new file mode 100644 index 000000000..b1dedcb2b --- /dev/null +++ b/frost-ed25519/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +Refer to the [`frost-core` +changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md). \ No newline at end of file diff --git a/frost-ed448/CHANGELOG.md b/frost-ed448/CHANGELOG.md new file mode 100644 index 000000000..b1dedcb2b --- /dev/null +++ b/frost-ed448/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +Refer to the [`frost-core` +changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md). \ No newline at end of file diff --git a/frost-p256/CHANGELOG.md b/frost-p256/CHANGELOG.md new file mode 100644 index 000000000..b1dedcb2b --- /dev/null +++ b/frost-p256/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +Refer to the [`frost-core` +changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md). \ No newline at end of file diff --git a/frost-rerandomized/CHANGELOG.md b/frost-rerandomized/CHANGELOG.md new file mode 100644 index 000000000..5a1865710 --- /dev/null +++ b/frost-rerandomized/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog + +Entries are listed in reverse chronological order. + + +## Unreleased + + +## 3.0.0-rc.0 + +### Breaking Changes + +* The `cheater-detection` feature was removed. If you relied on it (either by + using the default features, or by explicitly enabling it), then you don't have + to do anything (other than not enabling it explicitly if you were doing so); + the default behaviour is now as if `cheater-detection` was enabled. If you + explicitly *did not enable* it, you can avoid cheater detection by calling + `aggregate_custom()` with `CheaterDetection::Disabled`. + +### Added + +There is a new revamped API which was motivated by integration with Zcash +but should have broader application. + +- Added `RandomizedParams::new_from_commitments()` which will generate the + randomizer based on the signing commitments and on some fresh random data. + This is better since all parties will contribute to the randomness of the + randomizer. The random data ("seed") will be returned along with the + `RandomizedParams`. +- Added `RandomizedParams::regenerate_from_seed_and_commitments()` which will + redo the procedure above with a given seed. +- Added `sign_with_randomizer_seed()` which is a helper function that will + rebuild the `RandomizedParams` with a given seed and proceed with the + signing. +- Added `Randomizer::{new_from_commitments(), regenerate_from_seed_and_commitments()}` + which are used by the above and will probably not need to be called directly. diff --git a/frost-ristretto255/CHANGELOG.md b/frost-ristretto255/CHANGELOG.md new file mode 100644 index 000000000..b1dedcb2b --- /dev/null +++ b/frost-ristretto255/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +Refer to the [`frost-core` +changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md). \ No newline at end of file diff --git a/frost-secp256k1-tr/CHANGELOG.md b/frost-secp256k1-tr/CHANGELOG.md new file mode 100644 index 000000000..b1dedcb2b --- /dev/null +++ b/frost-secp256k1-tr/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +Refer to the [`frost-core` +changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md). \ No newline at end of file diff --git a/frost-secp256k1/CHANGELOG.md b/frost-secp256k1/CHANGELOG.md new file mode 100644 index 000000000..b1dedcb2b --- /dev/null +++ b/frost-secp256k1/CHANGELOG.md @@ -0,0 +1,4 @@ +# Changelog + +Refer to the [`frost-core` +changelog](https://github.com/ZcashFoundation/frost/blob/main/frost-core/CHANGELOG.md). \ No newline at end of file diff --git a/gencode/src/main.rs b/gencode/src/main.rs index f88cd7a7b..9dee2d5da 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -354,6 +354,7 @@ fn main() -> ExitCode { // Generate files based on a template with simple search & replace. for filename in [ "README.md", + "CHANGELOG.md", "dkg.md", "src/rerandomized.rs", "src/keys/dkg.rs", From 179242740b57731eee7f423bf2d8e662282ad6d9 Mon Sep 17 00:00:00 2001 From: Conrado Date: Tue, 27 Jan 2026 18:53:07 -0300 Subject: [PATCH 154/175] book, docs: mention channel requirements for DKG; remove redundancy; expand (#989) * book, docs: mention channel requirements for DKG; remove redundancy; expand * Apply suggestions from code review Co-authored-by: natalie * improve clarity * fix typo * Apply suggestions from code review Co-authored-by: natalie * gencode --------- Co-authored-by: natalie --- book/src/terminology.md | 71 +++++++++++++++++++++++++---------- book/src/tutorial/dkg.md | 9 +++++ frost-core/src/keys/dkg.rs | 6 ++- frost-core/src/lib.rs | 1 - frost-ed25519/dkg.md | 45 +++++++++++----------- frost-ed25519/src/lib.rs | 1 - frost-ed448/dkg.md | 45 +++++++++++----------- frost-ed448/src/lib.rs | 1 - frost-p256/dkg.md | 45 +++++++++++----------- frost-p256/src/lib.rs | 1 - frost-ristretto255/dkg.md | 45 +++++++++++----------- frost-secp256k1-tr/dkg.md | 45 +++++++++++----------- frost-secp256k1-tr/src/lib.rs | 1 - frost-secp256k1/dkg.md | 45 +++++++++++----------- frost-secp256k1/src/lib.rs | 1 - 15 files changed, 204 insertions(+), 158 deletions(-) diff --git a/book/src/terminology.md b/book/src/terminology.md index 1b9305310..686dfd017 100644 --- a/book/src/terminology.md +++ b/book/src/terminology.md @@ -3,18 +3,40 @@ ### _Broadcast channel_ A secure broadcast channel in the context of multi-party computation protocols -such as FROST has the following properties: - -1. Consistent. Each participant has the same view of the message sent over the channel. -2. Authenticated. Players know that the message was in fact sent by the claimed sender. In practice, this -requirement is often fulfilled by a PKI. -3. Reliable Delivery. Player i knows that the message it sent was in fact received by the intended participants. -4. Unordered. The channel does not guarantee ordering of messages. - -Possible deployment options: -- Echo-broadcast (Goldwasser-Lindell) -- Posting commitments to an authenticated centralized server that is trusted to - provide a single view to all participants (also known as 'public bulletin board') +such as FROST must have a set of theoretical properties which can be a bit subtle +and depend on the specific protocol being implemented. However, most real +deployments use the protocol from the [Secure Computation Without +Agreement](https://eprint.iacr.org/2002/040) paper, which we describe below, and +which is also referred to as "echo broadcast". It has the following properties: +agreement (if an honest party outputs x, then all honest parties output x or +abort), validity (if the broadcaster is honest, then all honest parties output +the broadcast value) and non-triviality (if all parties are honest, they all +output the broadcast value). + +The echo broadcast works as follows, for a party `P[1]` which wants to broadcast +a value `x` to the other `P[i]` parties for `1 < i <= n` where `n` is the number +of participants: + +1. `P[1]` sends `x` to all other `n-1` parties. +2. For each `P[i]` other than `P[1]`: + 1. Set `x1` to the value received from `P[1]` in step 1, or to `null` if no + value was received. + 2. Send `x1` to the other `n-2` parties (excluding `1` and `i` themselves). + 3. Set `r[j]` to the value that `i` will receive from the other `n-2` parties, + indexed by their index `j`. + 4. Output `x1` if it is equal to every value in `r[j]` for all `j` in the + other `n-2` parties. Otherwise, output `null`. + +In the specific context of FROST, you will need to use the echo broadcast for +each participant to send their round 1 package to the other participants. This +means that you will need to run `n` instances of the echo-broadcast protocol +in parallel! + +As an alternative to using echo-broadcast, other mechanisms are possible +depending on the application. For example, posting commitments (round 1 +packages) to an authenticated centralized server. This server needs to be +trusted to provide a single view to all participants (also known as "public +bulletin board"). ### _Identifier_ @@ -32,15 +54,28 @@ This allows deriving identifiers from usernames or emails, for example. ### _Peer to peer channel_ -Peer-to-peer channels are authenticated, reliable, and unordered, per the -definitions above. Additionally, peer-to-peer channels are _confidential_; i.e., -only participants `i` and `j` are allowed to know the contents of -a message `msg_i,j`. +Peer-to-peer channels are required to send data back and forth between +participants (during DKG) and between coordinator and participants (during +signing) in order to use FROST. These channels have different requirements +in different scenarios: + +- They need to be authenticated when sending DKG messages, and when sending + signing messages if cheater detection is required. In this context, + "authenticated" means that the recipient must have assurance on who is the + sender of a message, using e.g. digital signatures. +- They need to be confidential when sending DKG messages, and when sending + signing messages if the messages being signed are confidential. In this + context, "confidential" means that no other party listening to the + communication can have access to the contents, using e.g. encryption. + +In practice there are multiple possible deployment options to achieve +authentication and confidentiality: -Possible deployment options: - Mutually authenticated TLS +- Noise protocol - Wireguard + ### _Threshold secret sharing_ Threshold secret sharing does not require a broadcast channel because the dealer is fully trusted. @@ -51,5 +86,3 @@ Verifiable secret sharing requires a broadcast channel because the dealer is _not_ fully trusted: keygen participants verify the VSS commitment which is transmitted over the broadcast channel before accepting the shares distributed from the dealer, to ensure all participants have the same view of the commitment. - - diff --git a/book/src/tutorial/dkg.md b/book/src/tutorial/dkg.md index 4bbd54355..91f0a4d0b 100644 --- a/book/src/tutorial/dkg.md +++ b/book/src/tutorial/dkg.md @@ -1,5 +1,9 @@ # Distributed Key Generation +ZF FROST supports a variant of the DKG described in the [original FROST +paper](https://eprint.iacr.org/2020/852.pdf) (the only difference is the absence +of the context string which was deemed unnecessary after further analysis). + The diagram below shows the distributed key generation process. Dashed lines represent data being sent through an [authenticated and confidential communication @@ -47,6 +51,11 @@ the protocol is aborted. Check the linked [Terminology section](https://frost.zfnd.org/terminology.html#broadcast-channel) for more details. +In the context of the DKG, `n` broadcast channels will need to be set up; one +for each participant. So each participant will broadcast their round 1 package +to the other participants, and each participant needs to handle the broadcast +from the other `n-1` participants. + **Failure in using a proper broadcast channel will make the key generation insecure.** ``` diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 5f56256e2..ad9d9a7d0 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -2,7 +2,11 @@ //! //! The DKG module supports generating FROST key shares in a distributed manner, //! without a trusted dealer, via two rounds of communication between all -//! participants. +//! participants (with one round requiring a broadcast channel, so totalling +//! three rounds of communication if using echo broadcast). +//! +//! For a higher level tutorial on how to use it, refer to the [ZF FROST +//! Book](https://frost.zfnd.org/tutorial/dkg.html). //! //! This implements FROST KeyGen from the original [FROST paper], specifically //! Figure 1. This protocol is a variant of [Pedersen's DKG] that additionally diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 8c1b54f03..e0766d625 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -6,7 +6,6 @@ #![forbid(unsafe_code)] #![deny(clippy::indexing_slicing)] #![deny(clippy::unwrap_used)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] diff --git a/frost-ed25519/dkg.md b/frost-ed25519/dkg.md index de3f6aef0..244d9ba3d 100644 --- a/frost-ed25519/dkg.md +++ b/frost-ed25519/dkg.md @@ -3,27 +3,20 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs a unique identifier, which can be built from -a `u16`. The process in which these identifiers are allocated is up to the application. - -The distributed key generation process has 3 parts, with 2 communication rounds -between them, in which each participant needs to send a "package" to every other -participant. In the first round, each participant sends the same package -(a [`round1::Package`]) to every other. In the second round, each receiver gets -their own package (a [`round2::Package`]). - -Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] -that MUST be kept secret. Between part 2 and 3, each participant needs to hold -onto a [`round2::SecretPackage`]. - -After the third part, each participant will get a [`KeyPackage`] with their -long-term secret share that must be kept secret, and a [`PublicKeyPackage`] -that is public (and will be the same between all participants). With those -they can proceed to sign messages with FROST. - +For a higher level tutorial on how to use it, refer to the [ZF FROST +Book](https://frost.zfnd.org/tutorial/dkg.html). ## Example +This example shows the whole procedure in a single program. Of course, in +practice, each participant will run their own part in their own devices and +packages will need to be sent between them, respecting the DKG requirements of +using [authenticated and confidential communication +channels](https://frost.zfnd.org/terminology.html#peer-to-peer-channel), +additionally with a [**broadcast +channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) for the +first round of communication to ensure all participants have the same value. + ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; @@ -47,7 +40,10 @@ let mut round1_secret_packages = BTreeMap::new(); // Keep track of all round 1 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through a [**broadcast +// channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) +// on top of an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round1_packages = BTreeMap::new(); // For each participant, perform the first part of the DKG protocol. @@ -69,7 +65,10 @@ for participant_index in 1..=max_signers { // "Send" the round 1 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through a [**broadcast + // channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) + // on top of an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). for receiver_participant_index in 1..=max_signers { if receiver_participant_index == participant_index { continue; @@ -95,7 +94,8 @@ let mut round2_secret_packages = BTreeMap::new(); // Keep track of all round 2 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round2_packages = BTreeMap::new(); // For each participant, perform the second part of the DKG protocol. @@ -117,7 +117,8 @@ for participant_index in 1..=max_signers { // "Send" the round 2 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). // Note that, in contrast to the previous part, here each other participant // gets its own specific package. for (receiver_identifier, round2_package) in round2_packages { diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index 6d756028a..f3bdae72d 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] diff --git a/frost-ed448/dkg.md b/frost-ed448/dkg.md index 039937846..ff304b959 100644 --- a/frost-ed448/dkg.md +++ b/frost-ed448/dkg.md @@ -3,27 +3,20 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs a unique identifier, which can be built from -a `u16`. The process in which these identifiers are allocated is up to the application. - -The distributed key generation process has 3 parts, with 2 communication rounds -between them, in which each participant needs to send a "package" to every other -participant. In the first round, each participant sends the same package -(a [`round1::Package`]) to every other. In the second round, each receiver gets -their own package (a [`round2::Package`]). - -Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] -that MUST be kept secret. Between part 2 and 3, each participant needs to hold -onto a [`round2::SecretPackage`]. - -After the third part, each participant will get a [`KeyPackage`] with their -long-term secret share that must be kept secret, and a [`PublicKeyPackage`] -that is public (and will be the same between all participants). With those -they can proceed to sign messages with FROST. - +For a higher level tutorial on how to use it, refer to the [ZF FROST +Book](https://frost.zfnd.org/tutorial/dkg.html). ## Example +This example shows the whole procedure in a single program. Of course, in +practice, each participant will run their own part in their own devices and +packages will need to be sent between them, respecting the DKG requirements of +using [authenticated and confidential communication +channels](https://frost.zfnd.org/terminology.html#peer-to-peer-channel), +additionally with a [**broadcast +channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) for the +first round of communication to ensure all participants have the same value. + ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; @@ -47,7 +40,10 @@ let mut round1_secret_packages = BTreeMap::new(); // Keep track of all round 1 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through a [**broadcast +// channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) +// on top of an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round1_packages = BTreeMap::new(); // For each participant, perform the first part of the DKG protocol. @@ -69,7 +65,10 @@ for participant_index in 1..=max_signers { // "Send" the round 1 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through a [**broadcast + // channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) + // on top of an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). for receiver_participant_index in 1..=max_signers { if receiver_participant_index == participant_index { continue; @@ -95,7 +94,8 @@ let mut round2_secret_packages = BTreeMap::new(); // Keep track of all round 2 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round2_packages = BTreeMap::new(); // For each participant, perform the second part of the DKG protocol. @@ -117,7 +117,8 @@ for participant_index in 1..=max_signers { // "Send" the round 2 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). // Note that, in contrast to the previous part, here each other participant // gets its own specific package. for (receiver_identifier, round2_package) in round2_packages { diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 3c45775aa..154f445f6 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -1,6 +1,5 @@ #![allow(non_snake_case)] #![deny(missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] diff --git a/frost-p256/dkg.md b/frost-p256/dkg.md index afb4bd44a..cd64fd941 100644 --- a/frost-p256/dkg.md +++ b/frost-p256/dkg.md @@ -3,27 +3,20 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs a unique identifier, which can be built from -a `u16`. The process in which these identifiers are allocated is up to the application. - -The distributed key generation process has 3 parts, with 2 communication rounds -between them, in which each participant needs to send a "package" to every other -participant. In the first round, each participant sends the same package -(a [`round1::Package`]) to every other. In the second round, each receiver gets -their own package (a [`round2::Package`]). - -Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] -that MUST be kept secret. Between part 2 and 3, each participant needs to hold -onto a [`round2::SecretPackage`]. - -After the third part, each participant will get a [`KeyPackage`] with their -long-term secret share that must be kept secret, and a [`PublicKeyPackage`] -that is public (and will be the same between all participants). With those -they can proceed to sign messages with FROST. - +For a higher level tutorial on how to use it, refer to the [ZF FROST +Book](https://frost.zfnd.org/tutorial/dkg.html). ## Example +This example shows the whole procedure in a single program. Of course, in +practice, each participant will run their own part in their own devices and +packages will need to be sent between them, respecting the DKG requirements of +using [authenticated and confidential communication +channels](https://frost.zfnd.org/terminology.html#peer-to-peer-channel), +additionally with a [**broadcast +channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) for the +first round of communication to ensure all participants have the same value. + ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; @@ -47,7 +40,10 @@ let mut round1_secret_packages = BTreeMap::new(); // Keep track of all round 1 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through a [**broadcast +// channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) +// on top of an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round1_packages = BTreeMap::new(); // For each participant, perform the first part of the DKG protocol. @@ -69,7 +65,10 @@ for participant_index in 1..=max_signers { // "Send" the round 1 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through a [**broadcast + // channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) + // on top of an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). for receiver_participant_index in 1..=max_signers { if receiver_participant_index == participant_index { continue; @@ -95,7 +94,8 @@ let mut round2_secret_packages = BTreeMap::new(); // Keep track of all round 2 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round2_packages = BTreeMap::new(); // For each participant, perform the second part of the DKG protocol. @@ -117,7 +117,8 @@ for participant_index in 1..=max_signers { // "Send" the round 2 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). // Note that, in contrast to the previous part, here each other participant // gets its own specific package. for (receiver_identifier, round2_package) in round2_packages { diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index c99a26fb5..1a0b3c88f 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] diff --git a/frost-ristretto255/dkg.md b/frost-ristretto255/dkg.md index 86995d721..fcba23beb 100644 --- a/frost-ristretto255/dkg.md +++ b/frost-ristretto255/dkg.md @@ -3,27 +3,20 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs a unique identifier, which can be built from -a `u16`. The process in which these identifiers are allocated is up to the application. - -The distributed key generation process has 3 parts, with 2 communication rounds -between them, in which each participant needs to send a "package" to every other -participant. In the first round, each participant sends the same package -(a [`round1::Package`]) to every other. In the second round, each receiver gets -their own package (a [`round2::Package`]). - -Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] -that MUST be kept secret. Between part 2 and 3, each participant needs to hold -onto a [`round2::SecretPackage`]. - -After the third part, each participant will get a [`KeyPackage`] with their -long-term secret share that must be kept secret, and a [`PublicKeyPackage`] -that is public (and will be the same between all participants). With those -they can proceed to sign messages with FROST. - +For a higher level tutorial on how to use it, refer to the [ZF FROST +Book](https://frost.zfnd.org/tutorial/dkg.html). ## Example +This example shows the whole procedure in a single program. Of course, in +practice, each participant will run their own part in their own devices and +packages will need to be sent between them, respecting the DKG requirements of +using [authenticated and confidential communication +channels](https://frost.zfnd.org/terminology.html#peer-to-peer-channel), +additionally with a [**broadcast +channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) for the +first round of communication to ensure all participants have the same value. + ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; @@ -47,7 +40,10 @@ let mut round1_secret_packages = BTreeMap::new(); // Keep track of all round 1 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through a [**broadcast +// channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) +// on top of an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round1_packages = BTreeMap::new(); // For each participant, perform the first part of the DKG protocol. @@ -69,7 +65,10 @@ for participant_index in 1..=max_signers { // "Send" the round 1 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through a [**broadcast + // channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) + // on top of an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). for receiver_participant_index in 1..=max_signers { if receiver_participant_index == participant_index { continue; @@ -95,7 +94,8 @@ let mut round2_secret_packages = BTreeMap::new(); // Keep track of all round 2 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round2_packages = BTreeMap::new(); // For each participant, perform the second part of the DKG protocol. @@ -117,7 +117,8 @@ for participant_index in 1..=max_signers { // "Send" the round 2 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). // Note that, in contrast to the previous part, here each other participant // gets its own specific package. for (receiver_identifier, round2_package) in round2_packages { diff --git a/frost-secp256k1-tr/dkg.md b/frost-secp256k1-tr/dkg.md index 31a96acf8..e0be3936c 100644 --- a/frost-secp256k1-tr/dkg.md +++ b/frost-secp256k1-tr/dkg.md @@ -3,27 +3,20 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs a unique identifier, which can be built from -a `u16`. The process in which these identifiers are allocated is up to the application. - -The distributed key generation process has 3 parts, with 2 communication rounds -between them, in which each participant needs to send a "package" to every other -participant. In the first round, each participant sends the same package -(a [`round1::Package`]) to every other. In the second round, each receiver gets -their own package (a [`round2::Package`]). - -Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] -that MUST be kept secret. Between part 2 and 3, each participant needs to hold -onto a [`round2::SecretPackage`]. - -After the third part, each participant will get a [`KeyPackage`] with their -long-term secret share that must be kept secret, and a [`PublicKeyPackage`] -that is public (and will be the same between all participants). With those -they can proceed to sign messages with FROST. - +For a higher level tutorial on how to use it, refer to the [ZF FROST +Book](https://frost.zfnd.org/tutorial/dkg.html). ## Example +This example shows the whole procedure in a single program. Of course, in +practice, each participant will run their own part in their own devices and +packages will need to be sent between them, respecting the DKG requirements of +using [authenticated and confidential communication +channels](https://frost.zfnd.org/terminology.html#peer-to-peer-channel), +additionally with a [**broadcast +channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) for the +first round of communication to ensure all participants have the same value. + ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; @@ -47,7 +40,10 @@ let mut round1_secret_packages = BTreeMap::new(); // Keep track of all round 1 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through a [**broadcast +// channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) +// on top of an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round1_packages = BTreeMap::new(); // For each participant, perform the first part of the DKG protocol. @@ -69,7 +65,10 @@ for participant_index in 1..=max_signers { // "Send" the round 1 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through a [**broadcast + // channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) + // on top of an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). for receiver_participant_index in 1..=max_signers { if receiver_participant_index == participant_index { continue; @@ -95,7 +94,8 @@ let mut round2_secret_packages = BTreeMap::new(); // Keep track of all round 2 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round2_packages = BTreeMap::new(); // For each participant, perform the second part of the DKG protocol. @@ -117,7 +117,8 @@ for participant_index in 1..=max_signers { // "Send" the round 2 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). // Note that, in contrast to the previous part, here each other participant // gets its own specific package. for (receiver_identifier, round2_package) in round2_packages { diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index d4e5f7edc..a376fbe2b 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] diff --git a/frost-secp256k1/dkg.md b/frost-secp256k1/dkg.md index 24c6a5352..48d76819f 100644 --- a/frost-secp256k1/dkg.md +++ b/frost-secp256k1/dkg.md @@ -3,27 +3,20 @@ The DKG module supports generating FROST key shares in a distributed manner, without a trusted dealer. -Before starting, each participant needs a unique identifier, which can be built from -a `u16`. The process in which these identifiers are allocated is up to the application. - -The distributed key generation process has 3 parts, with 2 communication rounds -between them, in which each participant needs to send a "package" to every other -participant. In the first round, each participant sends the same package -(a [`round1::Package`]) to every other. In the second round, each receiver gets -their own package (a [`round2::Package`]). - -Between part 1 and 2, each participant needs to hold onto a [`round1::SecretPackage`] -that MUST be kept secret. Between part 2 and 3, each participant needs to hold -onto a [`round2::SecretPackage`]. - -After the third part, each participant will get a [`KeyPackage`] with their -long-term secret share that must be kept secret, and a [`PublicKeyPackage`] -that is public (and will be the same between all participants). With those -they can proceed to sign messages with FROST. - +For a higher level tutorial on how to use it, refer to the [ZF FROST +Book](https://frost.zfnd.org/tutorial/dkg.html). ## Example +This example shows the whole procedure in a single program. Of course, in +practice, each participant will run their own part in their own devices and +packages will need to be sent between them, respecting the DKG requirements of +using [authenticated and confidential communication +channels](https://frost.zfnd.org/terminology.html#peer-to-peer-channel), +additionally with a [**broadcast +channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) for the +first round of communication to ensure all participants have the same value. + ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; @@ -47,7 +40,10 @@ let mut round1_secret_packages = BTreeMap::new(); // Keep track of all round 1 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through a [**broadcast +// channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) +// on top of an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round1_packages = BTreeMap::new(); // For each participant, perform the first part of the DKG protocol. @@ -69,7 +65,10 @@ for participant_index in 1..=max_signers { // "Send" the round 1 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through a [**broadcast + // channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) + // on top of an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). for receiver_participant_index in 1..=max_signers { if receiver_participant_index == participant_index { continue; @@ -95,7 +94,8 @@ let mut round2_secret_packages = BTreeMap::new(); // Keep track of all round 2 packages sent to the given participant. // This is used to simulate the broadcast; in practice the packages -// will be sent through some communication channel. +// will be sent through an [authenticated and confidential communication +// channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). let mut received_round2_packages = BTreeMap::new(); // For each participant, perform the second part of the DKG protocol. @@ -117,7 +117,8 @@ for participant_index in 1..=max_signers { // "Send" the round 2 package to all other participants. In this // test this is simulated using a BTreeMap; in practice this will be - // sent through some communication channel. + // sent through an [authenticated and confidential communication + // channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). // Note that, in contrast to the previous part, here each other participant // gets its own specific package. for (receiver_identifier, round2_package) in round2_packages { diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index ee9b87a77..991b745ba 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] -#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] From 3fa76ef6fb0d1b05af838b54d109df688762603f Mon Sep 17 00:00:00 2001 From: natalie Date: Wed, 28 Jan 2026 14:03:52 +0000 Subject: [PATCH 155/175] Clippy fixes (#1006) * Clippy fixes * fix cargo package warnings --------- Co-authored-by: Conrado Gouvea --- Cargo.lock | 171 +++++++++++++++++----------------- frost-core/src/lib.rs | 4 +- frost-core/src/round1.rs | 1 - frost-ed25519/src/lib.rs | 2 +- frost-p256/src/lib.rs | 2 +- frost-ristretto255/src/lib.rs | 2 +- frost-secp256k1-tr/src/lib.rs | 2 +- frost-secp256k1/src/lib.rs | 2 +- 8 files changed, 92 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d49aafc0e..2ee992f61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,9 +52,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bit-set" @@ -73,15 +73,15 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitcoin-io" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin_hashes" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", "hex-conservative", @@ -104,9 +104,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -122,9 +122,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.46" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "shlex", @@ -165,18 +165,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61" dependencies = [ "anstyle", "clap_lex", @@ -184,9 +184,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "cobs" @@ -314,9 +314,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -515,9 +515,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "fnv" @@ -703,9 +703,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -714,9 +714,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -788,23 +788,24 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ "arrayvec", ] [[package]] name = "insta" -version = "1.44.0" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b246c455fbf8e7bdda56a226b525b24b601c0bbe15458beb72412678319cda5a" +checksum = "248b42847813a1550dafd15296fd9748c651d0c32194559dbc05d804d54b21e8" dependencies = [ "console", "once_cell", "serde", "similar", + "tempfile", ] [[package]] @@ -836,15 +837,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -877,9 +878,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "linux-raw-sys" @@ -1016,9 +1017,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1050,9 +1051,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1081,7 +1082,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1101,7 +1102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1110,14 +1111,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -1128,7 +1129,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1191,9 +1192,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", @@ -1220,12 +1221,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -1312,15 +1307,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -1408,9 +1403,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -1419,9 +1414,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", @@ -1432,18 +1427,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1462,9 +1457,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "pin-project-lite", "tokio-macros", @@ -1543,18 +1538,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -1565,9 +1560,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1575,9 +1570,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -1588,18 +1583,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -1704,24 +1699,24 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "fdea86ddd5568519879b8187e1cf04e24fce28f7fe046ceecbce472ff19a2572" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "0c15e1b46eff7c6c91195752e0eeed8ef040e391cdece7c25376957d5f15df22" dependencies = [ "proc-macro2", "quote", @@ -1739,11 +1734,17 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index e0766d625..a0774e1e4 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -215,9 +215,7 @@ where /// A list of binding factors and their associated identifiers. #[derive(Clone)] -#[cfg_attr(feature = "internals", visibility::make(pub))] -#[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -pub(crate) struct BindingFactorList(BTreeMap, BindingFactor>); +pub struct BindingFactorList(BTreeMap, BindingFactor>); impl BindingFactorList where diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index e37c24938..d2468fde6 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -396,7 +396,6 @@ impl GroupCommitmentShare { /// commitment list. /// /// [`encode_group_commitment_list()`]: https://datatracker.ietf.org/doc/html/rfc9591#name-list-operations -#[cfg(feature = "internals")] #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] pub(super) fn encode_group_commitments( diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index f3bdae72d..cb7a818bf 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -144,7 +144,7 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 64] { h.update(i); } let mut output = [0u8; 64]; - output.copy_from_slice(h.finalize().as_slice()); + output.copy_from_slice(h.finalize().as_ref()); output } diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 1a0b3c88f..42aa94f1b 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -153,7 +153,7 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { h.update(i); } let mut output = [0u8; 32]; - output.copy_from_slice(h.finalize().as_slice()); + output.copy_from_slice(h.finalize().as_ref()); output } diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index bb137326e..490e34c87 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -133,7 +133,7 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 64] { h.update(i); } let mut output = [0u8; 64]; - output.copy_from_slice(h.finalize().as_slice()); + output.copy_from_slice(h.finalize().as_ref()); output } diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index a376fbe2b..7800d1479 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -162,7 +162,7 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { h.update(i); } let mut output = [0u8; 32]; - output.copy_from_slice(h.finalize().as_slice()); + output.copy_from_slice(h.finalize().as_ref()); output } diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index 991b745ba..bebcd1f5d 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -153,7 +153,7 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { h.update(i); } let mut output = [0u8; 32]; - output.copy_from_slice(h.finalize().as_slice()); + output.copy_from_slice(h.finalize().as_ref()); output } From 8315231e61462ccaa2ea2ce96552063408c6d306 Mon Sep 17 00:00:00 2001 From: Conrado Date: Wed, 28 Jan 2026 11:13:29 -0300 Subject: [PATCH 156/175] fix issues from specific feature combinations (#1008) --- frost-core/Cargo.toml | 1 + frost-core/src/serialization.rs | 3 +-- frost-core/src/tests/ciphersuite_generic.rs | 1 + frost-core/src/tests/refresh.rs | 8 +++++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index a5147822b..05aa3ad9d 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -48,6 +48,7 @@ proptest.workspace = true rand.workspace = true rand_chacha.workspace = true serde_json.workspace = true +tokio.workspace = true [features] diff --git a/frost-core/src/serialization.rs b/frost-core/src/serialization.rs index 01ca843c7..8e310fdf8 100644 --- a/frost-core/src/serialization.rs +++ b/frost-core/src/serialization.rs @@ -2,7 +2,6 @@ #[cfg(feature = "serde")] use alloc::collections::BTreeMap; -use alloc::string::String; use alloc::vec::Vec; #[cfg(feature = "serde")] use core::fmt::Formatter; @@ -326,7 +325,7 @@ where b"verifying_key" => Ok(Field::Field2), b"min_signers" => Ok(Field::Field3), _ => { - let __value = &String::from_utf8_lossy(__value); + let __value = &alloc::string::String::from_utf8_lossy(__value); Err(serde::de::Error::unknown_field(__value, FIELDS)) } } diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index a15bd2569..e769e32b9 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -1,5 +1,6 @@ //! Ciphersuite-generic test functions. #![allow(clippy::type_complexity)] +#![cfg(feature = "serialization")] use alloc::{borrow::ToOwned, collections::BTreeMap, vec::Vec}; use rand_core::{CryptoRng, RngCore}; diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 3d50d5988..8f2d56f6c 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -1,4 +1,5 @@ //! Test for Refreshing shares +#![cfg(feature = "serialization")] use rand_core::{CryptoRng, RngCore}; @@ -7,10 +8,11 @@ use crate::keys::generate_with_dealer; use crate::keys::refresh::{ compute_refreshing_shares, refresh_dkg_part1, refresh_dkg_part2, refresh_share, }; -#[cfg(feature = "serialization")] -use crate::keys::{PublicKeyPackage, SecretShare}; use crate::{self as frost}; -use crate::{keys::KeyPackage, Ciphersuite, Error, Identifier, Signature, VerifyingKey}; +use crate::{ + keys::{KeyPackage, PublicKeyPackage, SecretShare}, + Ciphersuite, Error, Identifier, Signature, VerifyingKey, +}; use crate::tests::ciphersuite_generic::check_part3_different_participants; From 6e2ed050014527181d51a2cda1b5bbf2e7d74cad Mon Sep 17 00:00:00 2001 From: Conrado Date: Wed, 28 Jan 2026 11:18:26 -0300 Subject: [PATCH 157/175] actually remove the std feature (#1010) --- frost-core/Cargo.toml | 4 +--- frost-ed25519/Cargo.toml | 4 +--- frost-ed448/Cargo.toml | 4 +--- frost-p256/Cargo.toml | 4 +--- frost-rerandomized/Cargo.toml | 4 +--- frost-ristretto255/Cargo.toml | 4 +--- frost-secp256k1-tr/Cargo.toml | 4 +--- frost-secp256k1/Cargo.toml | 4 +--- 8 files changed, 8 insertions(+), 24 deletions(-) diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 05aa3ad9d..27642bf41 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -52,9 +52,7 @@ tokio.workspace = true [features] -default = ["serialization", "std"] -# No longer needed. Kept for retrocompatibility until 3.0.0 -std = [] +default = ["serialization"] #! ## Features ## Expose internal types, which do not have SemVer guarantees. This is an advanced ## feature which can be useful if you need to build a modified version of FROST. diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index e16cda6f4..8a073961e 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -38,9 +38,7 @@ serde_json.workspace = true tokio.workspace = true [features] -default = ["serialization", "std"] -# No longer needed. Kept for retrocompatibility until 3.0.0 -std = [] +default = ["serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index d9a0bedc9..5c26e30ee 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -37,9 +37,7 @@ serde_json.workspace = true tokio.workspace = true [features] -default = ["serialization", "std"] -# No longer needed. Kept for retrocompatibility until 3.0.0 -std = [] +default = ["serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index 42c429bfd..68e63b3cf 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -37,9 +37,7 @@ serde_json.workspace = true tokio.workspace = true [features] -default = ["serialization", "std"] -# No longer needed. Kept for retrocompatibility until 3.0.0 -std = [] +default = ["serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index c255253e7..2579900e9 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -25,9 +25,7 @@ rand_core.workspace = true [dev-dependencies] [features] -default = ["serialization", "std"] -# No longer needed. Kept for retrocompatibility until 3.0.0 -std = [] +default = ["serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index d02077cb6..3233dc451 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -38,9 +38,7 @@ serde_json.workspace = true tokio.workspace = true [features] -default = ["serialization", "std"] -# No longer needed. Kept for retrocompatibility until 3.0.0 -std = [] +default = ["serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index 55f83eeef..ad45c8fb9 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -38,9 +38,7 @@ serde_json.workspace = true tokio.workspace = true [features] -default = ["serialization", "std"] -# No longer needed. Kept for retrocompatibility until 3.0.0 -std = [] +default = ["serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index ab20daba1..8d942718e 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -37,9 +37,7 @@ serde_json.workspace = true tokio.workspace = true [features] -default = ["serialization", "std"] -# No longer needed. Kept for retrocompatibility until 3.0.0 -std = [] +default = ["serialization"] #! ## Features ## Enable `serde` support for types that need to be communicated. You ## can use `serde` to serialize structs with any encoder that supports From 1399dc0f071fe9e75c5f495273d841ae5a99c2d5 Mon Sep 17 00:00:00 2001 From: scaraven <34778974+scaraven@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:43:45 +0000 Subject: [PATCH 158/175] feat(frost-core): add pre_commitment_aggregate hook to `verify_signature_share()` (#1016) feat(frost-core): add pre_commitment_aggregate hook to verify_signature_share --- frost-core/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index a0774e1e4..5482018cd 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -783,6 +783,8 @@ pub fn verify_signature_share( let binding_factor_list: BindingFactorList = compute_binding_factor_list(&signing_package, verifying_key, &[])?; + let signing_package = ::pre_commitment_aggregate(&signing_package, &binding_factor_list)?; + // Compute the group commitment from signing commitments produced in round one. let group_commitment = compute_group_commitment(&signing_package, &binding_factor_list)?; From f7954c388169f380aac602c5093405908e43ae2d Mon Sep 17 00:00:00 2001 From: Conrado Date: Fri, 20 Feb 2026 06:15:57 -0300 Subject: [PATCH 159/175] ci: migrate book to GitHub pages (#1020) --- .github/workflows/docs.yml | 46 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d5345c23f..0e05b1705 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,14 +15,20 @@ on: paths: # doc source files - 'book/**' - - '**/firebase.json' - - 'katex-header.html' + # source files; some md files include code snippets that we want to keep up to date + - 'frost-*/**' # workflow definitions - '.github/workflows/docs.yml' push: branches: - main +# Sets permissions for GitHub Pages deployment +permissions: + contents: read + pages: write + id-token: write + env: RUST_LOG: info RUST_BACKTRACE: full @@ -31,7 +37,7 @@ env: jobs: build: - name: Build and Deploy Docs (+beta) + name: Build Docs (+beta) timeout-minutes: 45 runs-on: ubuntu-latest steps: @@ -60,23 +66,23 @@ jobs: run: | mdbook build book/ - - name: Deploy FROST book to Firebase preview channel - uses: FirebaseExtended/action-hosting-deploy@v0 - if: ${{ github.event_name == 'pull_request' && github.actor != 'dependabot[bot]' }} - with: - entrypoint: "book/" - expires: 14d - firebaseServiceAccount: ${{ secrets.GCP_SA_KEY }} - repoToken: ${{ secrets.GITHUB_TOKEN }} - projectId: ${{ vars.FIREBASE_PROJECT_ID }} - - - name: Deploy FROST book to Firebase live channel - uses: FirebaseExtended/action-hosting-deploy@v0 + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 if: ${{ github.event_name == 'push' && github.ref_name == 'main' }} with: - channelId: live - entrypoint: "book/" - firebaseServiceAccount: ${{ secrets.GCP_SA_KEY }} - repoToken: ${{ secrets.GITHUB_TOKEN }} - projectId: ${{ vars.FIREBASE_PROJECT_ID }} + path: 'book/book' + + deploy: + name: Deploy to GitHub Pages + if: ${{ github.event_name == 'push' && github.ref_name == 'main' }} + needs: build + timeout-minutes: 10 + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From ff5ec8d1528f7aa95e5cbcecccfc7e824018518f Mon Sep 17 00:00:00 2001 From: Conrado Date: Wed, 1 Apr 2026 19:12:21 -0300 Subject: [PATCH 160/175] core: misc fixes (#1042) * core: misc fixes * core: remove Copy from SigningKey; add ZeroizeOnDrop * cargo fmt --- frost-core/CHANGELOG.md | 1 + frost-core/src/keys/dkg.rs | 14 +++++++++++--- frost-core/src/keys/repairable.rs | 2 +- frost-core/src/lib.rs | 10 ++++++++-- frost-core/src/signing_key.rs | 16 ++++++++++++++-- frost-core/src/tests/ciphersuite_generic.rs | 15 +++++++-------- frost-secp256k1-tr/src/lib.rs | 2 +- .../tests/interoperability_tests.rs | 2 +- 8 files changed, 44 insertions(+), 18 deletions(-) diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index a1fce406a..373eb4470 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -28,6 +28,7 @@ Entries are listed in reverse chronological order. you will need to fills its `min_signers` field with the original threshold before calling the function (recreate it with `PublicKeyPackage::new()`). The latter was simply redundant. +* `SigningKey` is no longer `Copy`; and now it implements `ZeroizeOnDrop`. * Refactored the `frost_core::keys::repairable` module: * `repair_share_step_1()` was renamed to `repair_share_part1()` and now takes a `KeyPackage` and returns a map with a new `Delta` type instead of a raw diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index ad9d9a7d0..0c4d77d6a 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -434,9 +434,7 @@ pub(crate) fn compute_proof_of_knowledge // > a context string to prevent replay attacks. let (k, R_i) = ::generate_nonce(&mut rng); let c_i = challenge::(identifier, &commitment.verifying_key()?, &R_i)?; - let a_i0 = *coefficients - .first() - .expect("coefficients must have at least one element"); + let a_i0 = *coefficients.first().ok_or(Error::InvalidCoefficients)?; let mu_i = k + a_i0 * c_i.0; Ok(Signature { R: R_i, z: mu_i }) } @@ -494,6 +492,10 @@ pub fn part2( return Err(Error::IncorrectNumberOfPackages); } + if round1_packages.contains_key(&secret_package.identifier) { + return Err(Error::UnknownIdentifier); + } + for package in round1_packages.values() { if package.commitment.min_signers() != secret_package.min_signers { return Err(Error::IncorrectNumberOfCommitments); @@ -564,6 +566,12 @@ pub fn part3( if round1_packages.len() != (round2_secret_package.max_signers - 1) as usize { return Err(Error::IncorrectNumberOfPackages); } + if round1_packages.contains_key(&round2_secret_package.identifier) { + return Err(Error::UnknownIdentifier); + } + if round2_packages.contains_key(&round2_secret_package.identifier) { + return Err(Error::UnknownIdentifier); + } if round1_packages.len() != round2_packages.len() { return Err(Error::IncorrectNumberOfPackages); } diff --git a/frost-core/src/keys/repairable.rs b/frost-core/src/keys/repairable.rs index da4f5f02a..0d6263657 100644 --- a/frost-core/src/keys/repairable.rs +++ b/frost-core/src/keys/repairable.rs @@ -117,7 +117,7 @@ pub fn repair_share_part1( rng: &mut R, participant: Identifier, ) -> Result, Delta>, Error> { - if helpers.len() < 2 { + if helpers.len() < *key_package_i.min_signers() as usize { return Err(Error::IncorrectNumberOfIdentifiers); } if !helpers.contains(&key_package_i.identifier) { diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 5482018cd..364beabb0 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -608,6 +608,12 @@ where return Err(Error::UnknownIdentifier); } + if let Some(min) = pubkeys.min_signers() { + if signature_shares.len() < min as usize { + return Err(Error::IncorrectNumberOfShares); + } + } + if !signing_package .signing_commitments() .keys() @@ -772,11 +778,11 @@ pub fn verify_signature_share( let verifying_share = pubkeys .verifying_shares() .get(&identifier) - .expect("pre_aggregate() must keep the identifiers"); + .ok_or(Error::UnknownIdentifier)?; let verifying_key = pubkeys.verifying_key(); let signature_share = signature_shares .get(&identifier) - .expect("pre_aggregate() must keep the identifiers"); + .ok_or(Error::UnknownIdentifier)?; // Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the // binding factor. diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index 574bf969a..20aa63408 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -3,6 +3,7 @@ use alloc::vec::Vec; use rand_core::{CryptoRng, RngCore}; +use zeroize::ZeroizeOnDrop; use crate::{ random_nonzero, serialization::SerializableScalar, Challenge, Ciphersuite, Error, Field, Group, @@ -10,7 +11,7 @@ use crate::{ }; /// A signing key for a Schnorr signature on a FROST [`Ciphersuite::Group`]. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct SigningKey where C: Ciphersuite, @@ -52,7 +53,7 @@ where mut rng: R, message: &[u8], ) -> Signature { - let public = VerifyingKey::::from(*self); + let public = VerifyingKey::::from(self.clone()); let (k, R) = ::generate_nonce(&mut rng); @@ -80,6 +81,17 @@ where } } +impl ZeroizeOnDrop for SigningKey where C: Ciphersuite {} + +impl Drop for SigningKey +where + C: Ciphersuite, +{ + fn drop(&mut self) { + self.scalar = <::Field as Field>::zero(); + } +} + impl core::fmt::Debug for SigningKey where C: Ciphersuite, diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index e769e32b9..57349f8f6 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -138,13 +138,12 @@ pub fn check_sign_with_dealer( frost::keys::KeyPackage::deserialize(&key_package.serialize().unwrap()).unwrap(); key_packages.insert(k, key_package); } - // Check if it fails with not enough signers. Usually this would return an - // error before even running the signing procedure, because `KeyPackage` - // contains the correct `min_signers` value and the signing procedure checks - // if the number of shares is at least `min_signers`. To bypass the check - // and test if the protocol itself fails with not enough signers, we modify - // the `KeyPackages`s, decrementing their saved `min_signers` value before - // running the signing procedure. + // Check if it fails with not enough signers. Both the KeyPackages and + // the PublicKeyPackage have their min_signers decremented so that the + // early validation check in aggregate() passes, and we can verify that + // the cryptographic aggregation itself fails when too few shares are used. + let mut pub_key_package_insufficient = pub_key_package.clone(); + pub_key_package_insufficient.min_signers = Some(min_signers - 1); let r = check_sign( min_signers - 1, key_packages @@ -158,7 +157,7 @@ pub fn check_sign_with_dealer( }) .collect(), &mut rng, - pub_key_package.clone(), + pub_key_package_insufficient, ); assert_eq!(r, Err(Error::InvalidSignature)); diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index 7800d1479..daa19608e 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -299,7 +299,7 @@ impl Ciphersuite for Secp256K1Sha256TR { rng: R, message: &[u8], ) -> Signature { - let signing_key = signing_key.into_even_y(None); + let signing_key = signing_key.clone().into_even_y(None); signing_key.default_sign(rng, message) } diff --git a/frost-secp256k1-tr/tests/interoperability_tests.rs b/frost-secp256k1-tr/tests/interoperability_tests.rs index 5be7f623d..3cf68f217 100644 --- a/frost-secp256k1-tr/tests/interoperability_tests.rs +++ b/frost-secp256k1-tr/tests/interoperability_tests.rs @@ -10,7 +10,7 @@ fn check_interoperability_in_regular_sign() { for _ in 0..256 { let signing_key = SigningKey::new(&mut rng); - let verifying_key = signing_key.into(); + let verifying_key = (&signing_key).into(); let signature = signing_key.sign(rng, b"message"); helpers::verify_signature(b"message", &signature, &verifying_key); } From 3c2f25f9b83e976e83e83231bfd4598f23c3601d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:15:10 +0000 Subject: [PATCH 161/175] build(deps): bump keccak from 0.1.5 to 0.1.6 (#1023) Bumps [keccak](https://github.com/RustCrypto/sponges) from 0.1.5 to 0.1.6. - [Commits](https://github.com/RustCrypto/sponges/compare/keccak-v0.1.5...keccak-v0.1.6) --- updated-dependencies: - dependency-name: keccak dependency-version: 0.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ee992f61..5809e100c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,9 +863,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] From 75d14b093a81b20343c3daaad2977481e8142657 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:15:14 +0000 Subject: [PATCH 162/175] build(deps): bump actions/upload-pages-artifact from 3 to 4 (#1026) Bumps [actions/upload-pages-artifact](https://github.com/actions/upload-pages-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-pages-artifact/releases) - [Commits](https://github.com/actions/upload-pages-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-pages-artifact dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0e05b1705..d64cfac11 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -67,7 +67,7 @@ jobs: mdbook build book/ - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 if: ${{ github.event_name == 'push' && github.ref_name == 'main' }} with: path: 'book/book' From 7967016563d5181d5358ef78fc1a9159e48939b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:15:44 +0000 Subject: [PATCH 163/175] build(deps): bump regex from 1.12.2 to 1.12.3 (#1028) Bumps [regex](https://github.com/rust-lang/regex) from 1.12.2 to 1.12.3. - [Release notes](https://github.com/rust-lang/regex/releases) - [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/regex/compare/1.12.2...1.12.3) --- updated-dependencies: - dependency-name: regex dependency-version: 1.12.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5809e100c..c7000db5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1154,9 +1154,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", From 5c0deb3e5a56357271cdf5d215ce00a720b66af0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:15:49 +0000 Subject: [PATCH 164/175] build(deps): bump codecov/codecov-action from 5.5.2 to 6.0.0 (#1043) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.2 to 6.0.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v5.5.2...v6.0.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 8e2786fed..55ce03720 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -41,4 +41,4 @@ jobs: run: cargo llvm-cov report --lcov --ignore-filename-regex '.*(tests).*|benches.rs|gencode|helpers.rs|interoperability_tests.rs' --output-path lcov.info - name: Upload coverage report to Codecov - uses: codecov/codecov-action@v5.5.2 + uses: codecov/codecov-action@v6.0.0 From d94090a00f699b56970b79656dc8ca51c7078b7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:16:14 +0000 Subject: [PATCH 165/175] build(deps): bump reviewdog/action-actionlint from 1.69.1 to 1.72.0 (#1044) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.69.1 to 1.72.0. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/v1.69.1...v1.72.0) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-version: 1.72.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d2659deb..c263aa9fb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -176,7 +176,7 @@ jobs: continue-on-error: true steps: - uses: actions/checkout@v6.0.2 - - uses: reviewdog/action-actionlint@v1.69.1 + - uses: reviewdog/action-actionlint@v1.72.0 with: level: warning fail_level: none From c48449920ff82eb4bc6be37fb1c7383afcf35a1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:16:17 +0000 Subject: [PATCH 166/175] build(deps): bump release-drafter/release-drafter from 6 to 7 (#1045) Bumps [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) from 6 to 7. - [Release notes](https://github.com/release-drafter/release-drafter/releases) - [Commits](https://github.com/release-drafter/release-drafter/compare/v6...v7) --- updated-dependencies: - dependency-name: release-drafter/release-drafter dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release-drafter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index a110f6d82..23952d0c8 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into main - - uses: release-drafter/release-drafter@v6 + - uses: release-drafter/release-drafter@v7 with: # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml config-name: release-drafter.yml From 4ea9c8657da316422b89cd2e9f5c95035a664497 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:16:40 +0000 Subject: [PATCH 167/175] build(deps): bump insta from 1.46.1 to 1.47.2 (#1046) Bumps [insta](https://github.com/mitsuhiko/insta) from 1.46.1 to 1.47.2. - [Release notes](https://github.com/mitsuhiko/insta/releases) - [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md) - [Commits](https://github.com/mitsuhiko/insta/compare/1.46.1...1.47.2) --- updated-dependencies: - dependency-name: insta dependency-version: 1.47.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 92 ++++++------------------------------------------------ 1 file changed, 9 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7000db5e..56ee55010 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,14 +199,13 @@ dependencies = [ [[package]] name = "console" -version = "0.15.11" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -482,7 +481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -797,9 +796,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.46.1" +version = "1.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248b42847813a1550dafd15296fd9748c651d0c32194559dbc05d804d54b21e8" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" dependencies = [ "console", "once_cell", @@ -1200,7 +1199,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1422,7 +1421,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1606,7 +1605,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -1615,15 +1614,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -1633,70 +1623,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "wit-bindgen" version = "0.51.0" From fb36ac91665bf69e1a1fb330ed66761a8c32aeb0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:16:43 +0000 Subject: [PATCH 168/175] build(deps): bump actions/deploy-pages from 4 to 5 (#1047) Bumps [actions/deploy-pages](https://github.com/actions/deploy-pages) from 4 to 5. - [Release notes](https://github.com/actions/deploy-pages/releases) - [Commits](https://github.com/actions/deploy-pages/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/deploy-pages dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d64cfac11..da14f626c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -84,5 +84,5 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 From 7bc107e1c3e7be3e4e1d38f8c43873acd3ef843a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:17:05 +0000 Subject: [PATCH 169/175] build(deps): bump proptest from 1.9.0 to 1.11.0 (#1048) Bumps [proptest](https://github.com/proptest-rs/proptest) from 1.9.0 to 1.11.0. - [Release notes](https://github.com/proptest-rs/proptest/releases) - [Changelog](https://github.com/proptest-rs/proptest/blob/main/CHANGELOG.md) - [Commits](https://github.com/proptest-rs/proptest/compare/v1.9.0...v1.11.0) --- updated-dependencies: - dependency-name: proptest dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56ee55010..c250b51b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1025,9 +1025,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", From 28a9b270e066c021391f587b7f6415067a86189f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:17:08 +0000 Subject: [PATCH 170/175] build(deps): bump tokio from 1.49.0 to 1.50.0 (#1049) Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.49.0 to 1.50.0. - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.49.0...tokio-1.50.0) --- updated-dependencies: - dependency-name: tokio dependency-version: 1.50.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c250b51b6..9f692371d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1456,9 +1456,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "pin-project-lite", "tokio-macros", From 7dcfd30bad387733e17bd807d38d0b659bd7919d Mon Sep 17 00:00:00 2001 From: Conrado Date: Thu, 16 Apr 2026 09:57:09 -0300 Subject: [PATCH 171/175] core: add ZeroizeOnDrop for dkg::round2::Package (#1040) --- frost-core/src/keys/dkg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 0c4d77d6a..016490fce 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -223,7 +223,7 @@ pub mod round2 { /// # Security /// /// The package must be sent on an *confidential* and *authenticated* channel. - #[derive(Clone, Debug, PartialEq, Eq, Getters)] + #[derive(Clone, Debug, PartialEq, Eq, Getters, Zeroize, ZeroizeOnDrop)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] From 4705e794e685b048617ac0c13d2628cab4cdfdb9 Mon Sep 17 00:00:00 2001 From: Conrado Date: Thu, 16 Apr 2026 09:57:12 -0300 Subject: [PATCH 172/175] book: update to mdbook 0.5 (#1022) * book: update to mdbook 0.5 * Fix typo in book/src/tutorial.md (#1022) --------- Co-authored-by: natalie --- .github/workflows/docs.yml | 3 +- book/book.toml | 8 --- book/src/frost.md | 60 ++++++++-------- book/src/tutorial.md | 11 ++- book/src/tutorial/dkg.md | 54 +++++++------- book/src/tutorial/refreshing-shares.md | 39 +++++----- book/src/tutorial/signing.md | 79 ++++++++++---------- book/src/tutorial/trusted-dealer.md | 99 ++++++++++++-------------- book/src/user/serialization.md | 9 ++- book/src/zcash/devtool-demo.md | 30 ++++---- book/src/zcash/server.md | 52 +++++++------- book/src/zcash/technical-details.md | 20 +++--- book/src/zcash/ywallet-demo.md | 47 ++++++------ 13 files changed, 232 insertions(+), 279 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index da14f626c..b31762c15 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -54,13 +54,12 @@ jobs: - name: Setup mdBook uses: peaceiris/actions-mdbook@v2.0.0 with: - mdbook-version: '0.4.18' + mdbook-version: '0.5.2' # TODO: actions-mdbook does not yet have an option to install mdbook-mermaid https://github.com/peaceiris/actions-mdbook/issues/426 - name: Install plugins run: | cargo install mdbook-mermaid - cargo install mdbook-admonish - name: Build FROST book run: | diff --git a/book/book.toml b/book/book.toml index e95c664fb..e9883297f 100644 --- a/book/book.toml +++ b/book/book.toml @@ -1,17 +1,9 @@ [book] authors = ["Zcash Foundation "] language = "en" -multilingual = false src = "src" title = "The ZF FROST Book" -[preprocessor] - -[preprocessor.admonish] -command = "mdbook-admonish" -assets_version = "3.0.2" # do not edit: managed by `mdbook-admonish install` - [output] [output.html] -additional-css = ["./mdbook-admonish.css", "book/mdbook-admonish.css"] diff --git a/book/src/frost.md b/book/src/frost.md index 4b03a3a1f..8dfc4d322 100644 --- a/book/src/frost.md +++ b/book/src/frost.md @@ -10,10 +10,9 @@ together generate a signature that can be validated by the corresponding verifyi key. One important aspect is that the resulting signature is indistinguishable from a non-threshold signature from the point of view of signature verifiers. -```admonish note -FROST only supports Schnorr signatures. Therefore it can't produce -ECDSA signatures. -``` +> [!NOTE] +> FROST only supports Schnorr signatures. Therefore it can't produce +> ECDSA signatures. ## Key Generation @@ -62,21 +61,19 @@ consolidates them and sends them to each participant. Each one will then produce a signature share, which is sent to the Coordinator who finally aggregates them and produces the final signature. -```admonish note -If having a single coordinator is not desired, then all participants -can act as coordinators. Refer to the -[spec](https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#removing-the-coordinator-role-no-coordinator) -for more information. -``` +> [!NOTE] +> If having a single coordinator is not desired, then all participants +> can act as coordinators. Refer to the +> [spec](https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#removing-the-coordinator-role-no-coordinator) +> for more information. -```admonish warning -ALL participants who are selected for generating the signature need -to produce their share, even if there are more than `t` of them. -For example, in 2-of-3 signing, if 3 participants are selected, -them all 3 must produce signature shares in order for the Coordinator -be able to produce the final signature. Of course, the Coordinator -is still free to start the process with only 2 participants if they wish. -``` +> [!WARNING] +> ALL participants who are selected for generating the signature need +> to produce their share, even if there are more than `t` of them. +> For example, in 2-of-3 signing, if 3 participants are selected, +> them all 3 must produce signature shares in order for the Coordinator +> be able to produce the final signature. Of course, the Coordinator +> is still free to start the process with only 2 participants if they wish. ## Verifying Signatures @@ -112,20 +109,19 @@ shares in a way that maintains the same group public key. Some applications are: in signing sessions with the others. (They can also then use the repair share functionality to issue a new share and move from 2-of-2 back to 2-of-3.) -```admonish danger -It is critically important to keep in mind that the **Refresh Shares -functionality does not "restore full security" to a group**. While the group -evolves and participants are removed and new participants are added, the -security of the group does not depend only on the threshold of the current -participants being honest, but also **on the threshold of all previous set of -participants being honest**! For example, if Alice, Mallory and Eve form a group -and Mallory is eventually excluded from the group and replaced with Bob, it is -not enough to trust 2 out of 3 between Alice, Bob and Eve. **You also need to -trust that Mallory won't collude with, say, Eve which could have kept her -original pre-refresh share and they could both together recompute the original -key and compromise the group.** If that's an unacceptable risk to your use case, -you will need to migrate to a new group if that makes sense to your application. -``` +> [!CAUTION] +> It is critically important to keep in mind that the **Refresh Shares +> functionality does not "restore full security" to a group**. While the group +> evolves and participants are removed and new participants are added, the +> security of the group does not depend only on the threshold of the current +> participants being honest, but also **on the threshold of all previous set of +> participants being honest**! For example, if Alice, Mallory and Eve form a group +> and Mallory is eventually excluded from the group and replaced with Bob, it is +> not enough to trust 2 out of 3 between Alice, Bob and Eve. **You also need to +> trust that Mallory won't collude with, say, Eve which could have kept her +> original pre-refresh share and they could both together recompute the original +> key and compromise the group.** If that's an unacceptable risk to your use case, +> you will need to migrate to a new group if that makes sense to your application. ## Ciphersuites diff --git a/book/src/tutorial.md b/book/src/tutorial.md index 15d0a2514..5d86ba57a 100644 --- a/book/src/tutorial.md +++ b/book/src/tutorial.md @@ -15,9 +15,8 @@ If you need to support multiple ciphersuites then feel free to use This tutorial will use the `frost-ristretto255` crate, but changing to another ciphersuite should be a matter of simply changing the import. -```admonish note -"The `frost-secp256k1` crate is not compatible with Bitcoin BIP-340 (Taproot) -signatures. Use -[frost-secp256k1-tr](https://crates.io/crates/frost-secp256k1-tr) instead -if you want to support it. -``` +> [!NOTE] +> The `frost-secp256k1` crate is not compatible with Bitcoin BIP-340 (Taproot) +> signatures. Use +> [frost-secp256k1-tr](https://crates.io/crates/frost-secp256k1-tr) instead +> if you want to support it. diff --git a/book/src/tutorial/dkg.md b/book/src/tutorial/dkg.md index 91f0a4d0b..755dc7d64 100644 --- a/book/src/tutorial/dkg.md +++ b/book/src/tutorial/dkg.md @@ -32,33 +32,31 @@ the application.) It returns a `round1::SecretPackage` and a `round1::Package`: {{#include ../../../frost-ristretto255/dkg.md:dkg_part1}} ``` -```admonish info -Check the crate documentation for a [full working example](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/keys/dkg/index.html#example); keep in mind it's an artificial -one since everything runs in the same program. -``` +> [!TIP] +> Check the crate documentation for a [full working example](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/keys/dkg/index.html#example); keep in mind it's an artificial +> one since everything runs in the same program. The `round1::SecretPackage` must be kept in memory to use in the next round. The `round1::Package` must be sent to all other participants using a [**broadcast channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) to ensure that all participants receive the same value. -```admonish danger -A [**broadcast -channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) in this -context is not simply broadcasting the value to all participants. It requires -running a protocol to ensure that all participants have the same value or that -the protocol is aborted. Check the linked [Terminology -section](https://frost.zfnd.org/terminology.html#broadcast-channel) for more -details. - -In the context of the DKG, `n` broadcast channels will need to be set up; one -for each participant. So each participant will broadcast their round 1 package -to the other participants, and each participant needs to handle the broadcast -from the other `n-1` participants. - -**Failure in using a proper broadcast channel will make the key generation -insecure.** -``` +> [!CAUTION] +> A [**broadcast +> channel**](https://frost.zfnd.org/terminology.html#broadcast-channel) in this +> context is not simply broadcasting the value to all participants. It requires +> running a protocol to ensure that all participants have the same value or that +> the protocol is aborted. Check the linked [Terminology +> section](https://frost.zfnd.org/terminology.html#broadcast-channel) for more +> details. +> +> In the context of the DKG, `n` broadcast channels will need to be set up; one +> for each participant. So each participant will broadcast their round 1 package +> to the other participants, and each participant needs to handle the broadcast +> from the other `n-1` participants. +> +> **Failure in using a proper broadcast channel will make the key generation +> insecure.** ## Part 2 @@ -84,11 +82,10 @@ The `round2::Package`s must be sent to their respective participants with the given `Identifier`s, using an [authenticated and confidential communication channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel). -```admonish danger -The `round2::Package`s MUST be encrypted, otherwise an attacker who can read -the content of the packages will be able to recreate the secret being -generated. -``` +> [!CAUTION] +> The `round2::Package`s MUST be encrypted, otherwise an attacker who can read +> the content of the packages will be able to recreate the secret being +> generated. ## Part 3 @@ -105,6 +102,5 @@ a `PublicKeyPackage` containing the group verifying key: {{#include ../../../frost-ristretto255/dkg.md:dkg_part3}} ``` -```admonish note -All participants will generate the same `PublicKeyPackage`. -``` \ No newline at end of file +> [!NOTE] +> All participants will generate the same `PublicKeyPackage`. \ No newline at end of file diff --git a/book/src/tutorial/refreshing-shares.md b/book/src/tutorial/refreshing-shares.md index fa45ce914..8b6f7a1e7 100644 --- a/book/src/tutorial/refreshing-shares.md +++ b/book/src/tutorial/refreshing-shares.md @@ -17,24 +17,21 @@ Each Participant then runs `refresh_share()` to generate a new `KeyPackage` which will replace their old `KeyPackage`; they must also replace their old `PublicKeyPackage` with the one sent by the Trusted Dealer. -```admonish danger -The refreshed `KeyPackage` contents must be stored securely and the original -`KeyPackage` should be deleted. For example: - -- Make sure other users in the system can't read it; -- If possible, use the OS secure storage such that the package - contents can only be opened with the user's password or biometrics. -``` - -```admonish danger -Applications should first ensure that all participants who refreshed their -`KeyPackages` were actually able to do so successfully, before deleting their old -`KeyPackages`. How this is done is up to the application; it might require -successfully generating a signature with all of those participants. -``` - -```admonish danger -Refreshing Shares may be not enough to address security concerns -after a share has been compromised. Refer to the [Understanding -FROST](../frost.md#refreshing-shares) section. -``` \ No newline at end of file +> [!CAUTION] +> The refreshed `KeyPackage` contents must be stored securely and the original +> `KeyPackage` should be deleted. For example: +> +> - Make sure other users in the system can't read it; +> - If possible, use the OS secure storage such that the package +> contents can only be opened with the user's password or biometrics. + +> [!CAUTION] +> Applications should first ensure that all participants who refreshed their +> `KeyPackages` were actually able to do so successfully, before deleting their old +> `KeyPackages`. How this is done is up to the application; it might require +> successfully generating a signature with all of those participants. + +> [!CAUTION] +> Refreshing Shares may be not enough to address security concerns +> after a share has been compromised. Refer to the [Understanding +> FROST](../frost.md#refreshing-shares) section. \ No newline at end of file diff --git a/book/src/tutorial/signing.md b/book/src/tutorial/signing.md index 18b120c46..fea49038e 100644 --- a/book/src/tutorial/signing.md +++ b/book/src/tutorial/signing.md @@ -27,11 +27,10 @@ their commitments (a `SigningCommitments`) by calling The `SigningNonces` must be kept by the participant to use in Round 2, while the `SigningCommitments` must be sent to the Coordinator. -```admonish info -FROST does not require using an [authenticated nor encrypted -channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) -during the **signing** process. -``` +> [!NOTE] +> FROST does not require using an [authenticated nor encrypted +> channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) +> during the **signing** process. ## Coordinator, Round 2 @@ -47,14 +46,13 @@ The `SigningPackage` must then be sent to all the participants. (If the message is confidential, then the channel must also be confidential, since the message is included in the `SigningPackage`.) -```admonish warning -In all of the main FROST ciphersuites, the entire message must -be sent to participants. In some cases, where the message is too big, it may be -necessary to send a hash of the message instead. We strongly suggest creating a -specific ciphersuite for this, and not just sending the hash as if it were the -message. For reference, see [how RFC 8032 handles -"pre-hashing"](https://datatracker.ietf.org/doc/html/rfc8032). -``` +> [!WARNING] +> In all of the main FROST ciphersuites, the entire message must +> be sent to participants. In some cases, where the message is too big, it may be +> necessary to send a hash of the message instead. We strongly suggest creating a +> specific ciphersuite for this, and not just sending the hash as if it were the +> message. For reference, see [how RFC 8032 handles +> "pre-hashing"](https://datatracker.ietf.org/doc/html/rfc8032). ## Participants, Round 2 @@ -69,12 +67,11 @@ their `SigningNonces` from Round 1, by calling The resulting `SignatureShare` must then be sent back to the Coordinator. -```admonish important -In most applications, it is important that the participant must be aware of what -they are signing. Thus the application should show the message to the -participant and obtain their consent to proceed before producing the signature -share. -``` +> [!IMPORTANT] +> In most applications, it is important that the participant must be aware of what +> they are signing. Thus the application should show the message to the +> participant and obtain their consent to proceed before producing the signature +> share. ## Coordinator, Aggregate @@ -92,29 +89,27 @@ with the same `SigningPackage` sent to the participants and the The returned signature, a `Signature`, will be a valid signature for the message in the `SigningPackage` in Round 2 for the group verifying key in the `PublicKeyPackage`. -```admonish note -FROST supports identifiable abort: if a participant misbehaves and produces an -invalid signature share, then aggregation will fail and the returned error -will have the identifier of the misbehaving participant. (If multiple participants -misbehave, only the first one detected will be returned. If you need to detect -all cheaters, use [`aggregate_custom()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/fn.aggregate_custom.html)) - -What should be done in that case is up to the application. The misbehaving participant -could be excluded from future signing sessions, for example. -``` - -```admonish danger -In `aggregate()` you need to provide a map from `Identifier` to -`SignatureShare`. If you need cheater detection, then it is important that these -identifiers come from a mapping between authenticated channels and identifiers; -i.e. you should not simply send the `Identifier` along with the -`SignatureShare`; otherwise the cheater could simply lie about their identifier. - -For example, if you authenticate the communication channels with TLS, then you -will need to create a public key -> identifier mapping, and use that mapping -to get the identifier for the connection where the `SignatureShare` was read -from. -``` +> [!NOTE] +> FROST supports identifiable abort: if a participant misbehaves and produces an +> invalid signature share, then aggregation will fail and the returned error +> will have the identifier of the misbehaving participant. (If multiple participants +> misbehave, only the first one detected will be returned. If you need to detect +> all cheaters, use [`aggregate_custom()`](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/fn.aggregate_custom.html)) +> +> What should be done in that case is up to the application. The misbehaving participant +> could be excluded from future signing sessions, for example. + +> [!CAUTION] +> In `aggregate()` you need to provide a map from `Identifier` to +> `SignatureShare`. If you need cheater detection, then it is important that these +> identifiers come from a mapping between authenticated channels and identifiers; +> i.e. you should not simply send the `Identifier` along with the +> `SignatureShare`; otherwise the cheater could simply lie about their identifier. +> +> For example, if you authenticate the communication channels with TLS, then you +> will need to create a public key -> identifier mapping, and use that mapping +> to get the identifier for the connection where the `SignatureShare` was read +> from. ## Verifying signatures diff --git a/book/src/tutorial/trusted-dealer.md b/book/src/tutorial/trusted-dealer.md index ee0700855..b17eb5d47 100644 --- a/book/src/tutorial/trusted-dealer.md +++ b/book/src/tutorial/trusted-dealer.md @@ -27,55 +27,50 @@ their signing share, verifying share and group verifying key. This is done with {{#include ../../../frost-ristretto255/README.md:tkg_verify}} ``` -```admonish info -Check the crate documentation for a [full working example](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/index.html#example-key-generation-with-trusted-dealer-and-frost-signing); keep in mind it's an artificial -one since everything runs in the same program. -``` - -```admonish info -You can specify which identifiers to use by using [`IdentifierList::Custom`](https://docs.rs/frost-core/latest/frost_core/frost/keys/enum.IdentifierList.html#variant.Custom). Refer to the [DKG](dkg.md#part-1) section for an example on how to create identifiers. -``` - -```admonish danger -Which [**authenticated** and **confidential** channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) -to use is up to the application. Some examples: - -- Manually require the dealer to send the `SecretShare`s to the - participants using some secure messenger such as Signal; -- Use a TLS connection, authenticating the server with a certificate - and the client with some user/password or another suitable authentication - mechanism; - -Refer to the [Terminology page](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) -for more details. - -Failure of using a **confidential** channel may lead to the shares being -stolen and possibly allowing signature forgeries if a threshold number of -them are stolen. - -Failure of using an **authenticated** channel may lead to shares being -sent to the wrong person, possibly allowing unintended parties -to generate signatures. -``` - -```admonish danger -The `KeyPackage` contents must be stored securely. For example: - -- Make sure other users in the system can't read it; -- If possible, use the OS secure storage such that the package - contents can only be opened with the user's password or biometrics. -``` - -```admonish warning -The participants may wish to not fully trust the dealer. While **the dealer -has access to the original secret and can forge signatures -by simply using the secret to sign** (and this can't be -possibly avoided with this method; use Distributed Key Generation -if that's an issue), the dealer could also tamper with the `SecretShare`s -in a way that the participants will never be able to generate a valid -signature in the future (denial of service). Participants can detect -such tampering by comparing the `VerifiableSecretSharingCommitment` -values from their `SecretShare`s (either by some manual process, or -by using a [broadcast channel](https://frost.zfnd.org/terminology.html#broadcast-channel)) -to make sure they are all equal. -``` +> [!TIP] +> Check the crate documentation for a [full working example](https://docs.rs/frost-ristretto255/latest/frost_ristretto255/index.html#example-key-generation-with-trusted-dealer-and-frost-signing); keep in mind it's an artificial +> one since everything runs in the same program. + +> [!TIP] +> You can specify which identifiers to use by using [`IdentifierList::Custom`](https://docs.rs/frost-core/latest/frost_core/frost/keys/enum.IdentifierList.html#variant.Custom). Refer to the [DKG](dkg.md#part-1) section for an example on how to create identifiers. + +> [!CAUTION] +> Which [**authenticated** and **confidential** channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) +> to use is up to the application. Some examples: +> +> - Manually require the dealer to send the `SecretShare`s to the +> participants using some secure messenger such as Signal; +> - Use a TLS connection, authenticating the server with a certificate +> and the client with some user/password or another suitable authentication +> mechanism; +> +> Refer to the [Terminology page](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) +> for more details. +> +> Failure of using a **confidential** channel may lead to the shares being +> stolen and possibly allowing signature forgeries if a threshold number of +> them are stolen. +> +> Failure of using an **authenticated** channel may lead to shares being +> sent to the wrong person, possibly allowing unintended parties +> to generate signatures. + +> [!CAUTION] +> The `KeyPackage` contents must be stored securely. For example: +> +> - Make sure other users in the system can't read it; +> - If possible, use the OS secure storage such that the package +> contents can only be opened with the user's password or biometrics. + +> [!WARNING] +> The participants may wish to not fully trust the dealer. While **the dealer +> has access to the original secret and can forge signatures +> by simply using the secret to sign** (and this can't be +> possibly avoided with this method; use Distributed Key Generation +> if that's an issue), the dealer could also tamper with the `SecretShare`s +> in a way that the participants will never be able to generate a valid +> signature in the future (denial of service). Participants can detect +> such tampering by comparing the `VerifiableSecretSharingCommitment` +> values from their `SecretShare`s (either by some manual process, or +> by using a [broadcast channel](https://frost.zfnd.org/terminology.html#broadcast-channel)) +> to make sure they are all equal. diff --git a/book/src/user/serialization.md b/book/src/user/serialization.md index 70c5389da..1cfd734f5 100644 --- a/book/src/user/serialization.md +++ b/book/src/user/serialization.md @@ -56,11 +56,10 @@ Is encoded as - `0b`: the length of the message - `68656c6c6f20776f726c64`: the message -```admonish note -The ciphersuite ID is encoded multiple times in this case because `SigningPackage` includes -`SigningCommitments`, which also need to be communicated in Round 1 and thus also encodes -its ciphersuite ID. This is the only instance where this happens. -``` +> [!NOTE] +> The ciphersuite ID is encoded multiple times in this case because `SigningPackage` includes +> `SigningCommitments`, which also need to be communicated in Round 1 and thus also encodes +> its ciphersuite ID. This is the only instance where this happens. ## Test Vectors diff --git a/book/src/zcash/devtool-demo.md b/book/src/zcash/devtool-demo.md index e8e713ae9..36aa7637d 100644 --- a/book/src/zcash/devtool-demo.md +++ b/book/src/zcash/devtool-demo.md @@ -60,11 +60,10 @@ frost-client init -c eve.toml This will create a config file for three users; Alice, Bob and Eve. -```admonish note -If you really want to run the demo in separate machines, then you can omit the -`-c alice.toml` part of the command (i.e. run `frost-client init`); it will -save to a default location in the user's home directory. -``` +> [!NOTE] +> If you really want to run the demo in separate machines, then you can omit the +> `-c alice.toml` part of the command (i.e. run `frost-client init`); it will +> save to a default location in the user's home directory. ## Generating FROST key shares @@ -92,11 +91,10 @@ Generation. If you did the previous section, skip to "Generating the Full Viewing Key for the wallet". -```admonish note -This section assumes each participant is running the commands in their own -machine. If you want to simulate all of them in a single machine, -specify the config file for the user (e.g. `-c alice.toml`) accordingly. -``` +> [!NOTE] +> This section assumes each participant is running the commands in their own +> machine. If you want to simulate all of them in a single machine, +> specify the config file for the user (e.g. `-c alice.toml`) accordingly. ### Initializing config files @@ -161,9 +159,8 @@ and the threshold number with the one given by the first participant. frost-client dkg -d "Alice, Bob and Eve's group" -s localhost:2744 -t 2 -C redpallas ``` -```admonish note -A future version might not require specifying the threshold and group name. -``` +> [!NOTE] +> A future version might not require specifying the threshold and group name. ## Generating the Full Viewing Key for the wallet @@ -271,10 +268,9 @@ the one generated with the `zcash-sign` tool and press enter. The tool will connect to the server and wait for the other participants. -```admonish warning -If you prefer to pass the message (SIGHASH) or randomizer as files by using -the `-m` and `-r` arguments, you will need to convert them to binary format. -``` +> [!WARNING] +> If you prefer to pass the message (SIGHASH) or randomizer as files by using +> the `-m` and `-r` arguments, you will need to convert them to binary format. ### Participant 1 (Alice) diff --git a/book/src/zcash/server.md b/book/src/zcash/server.md index 63503946b..50ba3cc89 100644 --- a/book/src/zcash/server.md +++ b/book/src/zcash/server.md @@ -159,29 +159,26 @@ For Participants: - Wait for round 2 message by repeatedly polling `/receive` each 2 seconds or longer - Send round 2 message by using `/send` -```admonish info -**Polling** is not optimal. The server will support a better mechanism in the -future. -``` - -```admonish info -Selecting sessions is tricky. Ideally, the user should select what session -to proceed by checking the message being signed; however, that is usually -sent in Round 2. There are multiple ways to handle this: - -- Simply show the users who are participants, hoping that is enough to - disambiguate (we assume that concurrent signing sessions won't be that common) -- Quietly proceed with all sessions, and only prompt the user after the message - is received. (It's harmless to do round 1 of FROST even if the user might - not have agreed to sign the message yet.) -- Change the application so that the message is sent to the participants first - (the server does not really care how the protocol is run). -``` - -```admonish critical -Always gather consent from the user by showing them the message before -signing it. -``` +> [!NOTE] +> **Polling** is not optimal. The server will support a better mechanism in the +> future. + +> [!NOTE] +> Selecting sessions is tricky. Ideally, the user should select what session +> to proceed by checking the message being signed; however, that is usually +> sent in Round 2. There are multiple ways to handle this: +> +> - Simply show the users who are participants, hoping that is enough to +> disambiguate (we assume that concurrent signing sessions won't be that common) +> - Quietly proceed with all sessions, and only prompt the user after the message +> is received. (It's harmless to do round 1 of FROST even if the user might +> not have agreed to sign the message yet.) +> - Change the application so that the message is sent to the participants first +> (the server does not really care how the protocol is run). + +> [!CAUTION] +> Always gather consent from the user by showing them the message before +> signing it. ### `/challenge` @@ -314,11 +311,10 @@ Coordinator, pass an empty list in `recipients` (**do not** use the Coordinator's public key, because that might be ambiguous if they're also a Participant). -```admonish critical -Messages **MUST** be end-to-end encrypted between recipients. The server can't -enforce this and if you fail to encrypt them then the server could read -all the messages. -``` +> [!CAUTION] +> Messages **MUST** be end-to-end encrypted between recipients. The server can't +> enforce this and if you fail to encrypt them then the server could read +> all the messages. ### `/receive` diff --git a/book/src/zcash/technical-details.md b/book/src/zcash/technical-details.md index e75adfe7d..9f0c39e37 100644 --- a/book/src/zcash/technical-details.md +++ b/book/src/zcash/technical-details.md @@ -74,17 +74,15 @@ not catastrophic. Users can recover their key share with the help of other participants, and would only need to remember their identifier (and other participants can probably help with that). -```admonish note -Orchard is simpler to handle, so it may be a good idea to just -support it with FROST. -``` - -```admonish note -The only secret information is the key share. So another possibility -is to just ask the user to backup it (using a seed phrase format, or other -string encoding) and get the remaining information from the other participants -when recovering a wallet. -``` +> [!NOTE] +> Orchard is simpler to handle, so it may be a good idea to just +> support it with FROST. + +> [!NOTE] +> The only secret information is the key share. So another possibility +> is to just ask the user to backup it (using a seed phrase format, or other +> string encoding) and get the remaining information from the other participants +> when recovering a wallet. ## Communications diff --git a/book/src/zcash/ywallet-demo.md b/book/src/zcash/ywallet-demo.md index 1972695ac..cece551a5 100644 --- a/book/src/zcash/ywallet-demo.md +++ b/book/src/zcash/ywallet-demo.md @@ -65,11 +65,10 @@ frost-client init -c eve.toml This will create a config file for three users; Alice, Bob and Eve. -```admonish note -If you really want to run the demo in separate machines, then you can omit the -`-c alice.toml` part of the command (i.e. run `frost-client init`); it will -save to a default location in the user's home directory. -``` +> [!NOTE] +> If you really want to run the demo in separate machines, then you can omit the +> `-c alice.toml` part of the command (i.e. run `frost-client init`); it will +> save to a default location in the user's home directory. ## Generating FROST key shares @@ -97,11 +96,10 @@ Generation. If you did the previous section, skip to "Generating the Full Viewing Key for the wallet". -```admonish note -This section assumes each participant is running the commands in their own -machine. If you want to simulate all of them in a single machine, -specify the config file for the user (e.g. `-c alice.toml`) accordingly. -``` +> [!NOTE] +> This section assumes each participant is running the commands in their own +> machine. If you want to simulate all of them in a single machine, +> specify the config file for the user (e.g. `-c alice.toml`) accordingly. ### Initializing config files @@ -166,9 +164,8 @@ and the threshold number with the one given by the first participant. frost-client dkg -d "Alice, Bob and Eve's group" -s localhost:2744 -t 2 -C redpallas ``` -```admonish note -A future version might not require specifying the threshold and group name. -``` +> [!NOTE] +> A future version might not require specifying the threshold and group name. ## Generating the Full Viewing Key for the wallet @@ -210,15 +207,14 @@ Now you will need to fund this wallet with some ZEC. Use the Orchard address printed by the signer (see warning below). Send ZEC to that address using another account (or try [ZecFaucet](https://zecfaucet.com/)). -```admonish danger -The address being show by Ywallet is a unified address that includes both an -Orchard and Sapling address. For the demo to work, you need to receive funds in -your Orchard address. Whether that will happen depends on multiple factors so -it's probably easier to use just the Orchard-only address printed by the signer. -In Ywallet, you can also swipe right on the QR Code until it shows the "Orchard -Address". **IF YOU SEND IT TO THE SAPLING ADDRESS, THE FUNDS WILL BECOME -UNSPENDABLE AND WILL BE LOST!** -``` +> [!CAUTION] +> The address being show by Ywallet is a unified address that includes both an +> Orchard and Sapling address. For the demo to work, you need to receive funds in +> your Orchard address. Whether that will happen depends on multiple factors so +> it's probably easier to use just the Orchard-only address printed by the signer. +> In Ywallet, you can also swipe right on the QR Code until it shows the "Orchard +> Address". **IF YOU SEND IT TO THE SAPLING ADDRESS, THE FUNDS WILL BECOME +> UNSPENDABLE AND WILL BE LOST!** ## Creating the transaction @@ -280,10 +276,9 @@ the one generated with the `zcash-sign` tool and press enter. The tool will connect to the server and wait for the other participants. -```admonish warning -If you prefer to pass the message (SIGHASH) or randomizer as files by using -the `-m` and `-r` arguments, you will need to convert them to binary format. -``` +> [!WARNING] +> If you prefer to pass the message (SIGHASH) or randomizer as files by using +> the `-m` and `-r` arguments, you will need to convert them to binary format. ### Participant 1 (Alice) From 1aef3fbdf7e845da2d389d99b7e2675468ebdf85 Mon Sep 17 00:00:00 2001 From: Conrado Date: Thu, 23 Apr 2026 08:50:37 -0300 Subject: [PATCH 173/175] Release 3.0.0 (#1041) * bump to 3.0.0 * fixes from Claude review --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 6 +++--- book/src/tutorial/importing.md | 4 ++-- frost-core/CHANGELOG.md | 27 +++++++++++++++++++++++++-- frost-rerandomized/CHANGELOG.md | 3 +++ 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f692371d..216200ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,7 +526,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "frost-core" -version = "3.0.0-rc.0" +version = "3.0.0" dependencies = [ "byteorder", "const-crc32-nostd", @@ -554,7 +554,7 @@ dependencies = [ [[package]] name = "frost-ed25519" -version = "3.0.0-rc.0" +version = "3.0.0" dependencies = [ "criterion", "curve25519-dalek", @@ -576,7 +576,7 @@ dependencies = [ [[package]] name = "frost-ed448" -version = "3.0.0-rc.0" +version = "3.0.0" dependencies = [ "criterion", "document-features", @@ -597,7 +597,7 @@ dependencies = [ [[package]] name = "frost-p256" -version = "3.0.0-rc.0" +version = "3.0.0" dependencies = [ "criterion", "document-features", @@ -618,7 +618,7 @@ dependencies = [ [[package]] name = "frost-rerandomized" -version = "3.0.0-rc.0" +version = "3.0.0" dependencies = [ "derive-getters", "document-features", @@ -629,7 +629,7 @@ dependencies = [ [[package]] name = "frost-ristretto255" -version = "3.0.0-rc.0" +version = "3.0.0" dependencies = [ "criterion", "curve25519-dalek", @@ -651,7 +651,7 @@ dependencies = [ [[package]] name = "frost-secp256k1" -version = "3.0.0-rc.0" +version = "3.0.0" dependencies = [ "criterion", "document-features", @@ -672,7 +672,7 @@ dependencies = [ [[package]] name = "frost-secp256k1-tr" -version = "3.0.0-rc.0" +version = "3.0.0" dependencies = [ "criterion", "document-features", diff --git a/Cargo.toml b/Cargo.toml index 9ed7aacfb..cf479d436 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ members = [ # in gencode/src/main.rs. edition = "2021" rust-version = "1.81" -version = "3.0.0-rc.0" +version = "3.0.0" authors = [ "Deirdre Connolly ", "Chelsea Komlo ", @@ -43,8 +43,8 @@ rand_core = "0.6" serde_json = "1.0" tokio = { version = "1.0", features = ["rt", "time", "macros"] } -frost-core = { path = "frost-core", version = "3.0.0-rc.0", default-features = false } -frost-rerandomized = { path = "frost-rerandomized", version = "3.0.0-rc.0", default-features = false } +frost-core = { path = "frost-core", version = "3.0.0", default-features = false } +frost-rerandomized = { path = "frost-rerandomized", version = "3.0.0", default-features = false } [profile.test.package."*"] opt-level = 3 diff --git a/book/src/tutorial/importing.md b/book/src/tutorial/importing.md index ca33d6aa6..40bc51f7e 100644 --- a/book/src/tutorial/importing.md +++ b/book/src/tutorial/importing.md @@ -6,7 +6,7 @@ Add to your `Cargo.toml` file: ``` [dependencies] -frost-ristretto255 = "3.0.0-rc.0" +frost-ristretto255 = "3.0.0" ``` ## Handling errors @@ -38,7 +38,7 @@ needs to be transmitted. The importing would look like: ``` [dependencies] -frost-ristretto255 = { version = "3.0.0-rc.0", features = ["serde"] } +frost-ristretto255 = { version = "3.0.0", features = ["serde"] } ``` Note that serde usage is optional. Applications can use different encodings, and diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 373eb4470..1fcb73649 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -5,6 +5,16 @@ Entries are listed in reverse chronological order. ## Unreleased +## 3.0.0 + +Refer to the `3.0.0-rc.0` entry below for the main changes if you are upgrading +from `2.x`. + +### Fixed + +* Fixed `verify_signature_share()` so that it calls the + `Ciphersuite::pre_commitment_aggregate()` hook + ## 3.0.0-rc.0 @@ -19,7 +29,9 @@ Entries are listed in reverse chronological order. * Changed `InvalidSignatureShare::culprit` to `culprits`; it is now a `Vec`. * Changed `Error::culprit()` to `culprits()`; is is now a `Vec`. * Added a `min_signers` argument to `PublicKeyPackage::new()`. -* The `std` and `nightly` features were removed from all crates. +* The `std` and `nightly` features were removed from all crates since the crates + are all `no-std`. If you enabled them, just remove the feature from the + import. * Renamed `frost_core::keys::refresh::refresh_dkg_part_1` to `refresh_dkg_part1`. * Fixed the crate-specific versions of the `refresh` module to be non-generic. * Removed the `min_signers` and `max_signers` arguments from @@ -48,7 +60,7 @@ Entries are listed in reverse chronological order. anymore; instead, they must implement `AsMut<[u8]>` and `TryFrom<&[u8]>`. This should be trivial in most cases since they are often implemented by arrays. -### Additional changes +### Added * Added DKG refresh functions to the crate-specific `refresh` modules. * Re-exported the `frost-rerandomized` crate in the ciphersuite functions, e.g. @@ -58,6 +70,17 @@ Entries are listed in reverse chronological order. * Added `aggregate_custom()` function to allow specifying which cheater detection strategy to use. The original `aggregate()` behaviour is to use the `CheaterDetection::FirstCheater` strategy. +* Added `ZeroizeOnDrop` for more types. +* Added `NonceCommitment` getters and `new()` under `internals` feature. + +### Fixed + +* Fixed `dkg::round2::SecretPackage` serialization (it would previously return + an error when deserialized). Technically this is a breaking change; if somehow + your application has serialized round 2 SecretPackages in long-term storage + and you need to deserialize then, you will need to manually parse them + and discard the first element of the `commitment` field. +* Fixed the build for some specific feature combinations. ## 2.2.0 diff --git a/frost-rerandomized/CHANGELOG.md b/frost-rerandomized/CHANGELOG.md index 5a1865710..eb10fc5fd 100644 --- a/frost-rerandomized/CHANGELOG.md +++ b/frost-rerandomized/CHANGELOG.md @@ -5,6 +5,9 @@ Entries are listed in reverse chronological order. ## Unreleased +## 3.0.0 + +* No changes. ## 3.0.0-rc.0 From 2016e44ba4a4757a996300350063b937a2ad33e8 Mon Sep 17 00:00:00 2001 From: natalie Date: Thu, 23 Apr 2026 14:53:04 +0100 Subject: [PATCH 174/175] Add explicit release-drafter permissions (#1061) Add explicit release-drafter permissions (#1060) --- .github/workflows/release-drafter.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 23952d0c8..2e0a7b6c3 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -8,6 +8,9 @@ on: jobs: update_release_draft: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read steps: # Drafts your next Release notes as Pull Requests are merged into main - uses: release-drafter/release-drafter@v7 From c78dbd362bf5f4bdb97eb4013cc31727a68afc75 Mon Sep 17 00:00:00 2001 From: StackOverflowExcept1on <109800286+StackOverflowExcept1on@users.noreply.github.com> Date: Wed, 15 Jul 2026 21:09:58 +0300 Subject: [PATCH 175/175] feat: bump MSRV to Rust 1.86 & update RustCrypto crates + dependencies (#903) --- .github/workflows/coverage.yaml | 2 +- .github/workflows/main.yml | 39 +- Cargo.lock | 621 ++++++++++-------- Cargo.toml | 76 ++- frost-core/CHANGELOG.md | 4 + frost-core/Cargo.toml | 54 +- frost-core/src/batch.rs | 4 +- frost-core/src/benches.rs | 12 +- frost-core/src/keys.rs | 8 +- frost-core/src/keys/dkg.rs | 6 +- frost-core/src/keys/refresh.rs | 6 +- frost-core/src/keys/repairable.rs | 5 +- frost-core/src/lib.rs | 10 +- frost-core/src/round1.rs | 10 +- frost-core/src/signing_key.rs | 13 +- frost-core/src/tests/batch.rs | 6 +- frost-core/src/tests/ciphersuite_generic.rs | 26 +- .../src/tests/coefficient_commitment.rs | 12 +- frost-core/src/tests/helpers.rs | 4 +- frost-core/src/tests/proptests.rs | 4 +- frost-core/src/tests/refresh.rs | 22 +- frost-core/src/tests/repairable.rs | 13 +- frost-core/src/tests/vss_commitment.rs | 16 +- frost-core/src/traits.rs | 8 +- frost-ed25519/Cargo.toml | 39 +- frost-ed25519/README.md | 2 +- frost-ed25519/benches/bench.rs | 4 +- frost-ed25519/dkg.md | 4 +- frost-ed25519/src/keys/dkg.rs | 2 +- frost-ed25519/src/keys/refresh.rs | 6 +- frost-ed25519/src/keys/repairable.rs | 25 +- frost-ed25519/src/lib.rs | 13 +- frost-ed25519/src/tests/batch.rs | 6 +- .../src/tests/coefficient_commitment.rs | 15 +- frost-ed25519/src/tests/vss_commitment.rs | 23 +- frost-ed25519/tests/common_traits_tests.rs | 2 +- frost-ed25519/tests/integration_tests.rs | 71 +- frost-ed25519/tests/interoperability_tests.rs | 4 +- frost-ed25519/tests/rerandomized_tests.rs | 2 +- frost-ed25519/tests/serde_tests.rs | 16 +- frost-ed448/Cargo.toml | 38 +- frost-ed448/README.md | 2 +- frost-ed448/benches/bench.rs | 4 +- frost-ed448/dkg.md | 4 +- frost-ed448/src/keys/dkg.rs | 2 +- frost-ed448/src/keys/refresh.rs | 6 +- frost-ed448/src/keys/repairable.rs | 25 +- frost-ed448/src/lib.rs | 61 +- frost-ed448/src/tests/batch.rs | 6 +- .../src/tests/coefficient_commitment.rs | 15 +- frost-ed448/src/tests/deserialize.rs | 6 +- frost-ed448/src/tests/vss_commitment.rs | 23 +- frost-ed448/tests/common_traits_tests.rs | 2 +- frost-ed448/tests/integration_tests.rs | 71 +- frost-ed448/tests/rerandomized_tests.rs | 2 +- frost-ed448/tests/serde_tests.rs | 16 +- frost-p256/Cargo.toml | 37 +- frost-p256/README.md | 2 +- frost-p256/benches/bench.rs | 4 +- frost-p256/dkg.md | 4 +- frost-p256/src/keys/dkg.rs | 2 +- frost-p256/src/keys/refresh.rs | 6 +- frost-p256/src/keys/repairable.rs | 25 +- frost-p256/src/lib.rs | 36 +- frost-p256/src/tests/batch.rs | 6 +- .../src/tests/coefficient_commitment.rs | 15 +- frost-p256/src/tests/vss_commitment.rs | 23 +- frost-p256/tests/common_traits_tests.rs | 2 +- frost-p256/tests/integration_tests.rs | 71 +- frost-p256/tests/rerandomized_tests.rs | 2 +- frost-p256/tests/serde_tests.rs | 16 +- frost-rerandomized/Cargo.toml | 16 +- frost-rerandomized/src/lib.rs | 10 +- frost-rerandomized/src/tests.rs | 12 +- frost-ristretto255/Cargo.toml | 39 +- frost-ristretto255/README.md | 2 +- frost-ristretto255/benches/bench.rs | 4 +- frost-ristretto255/dkg.md | 4 +- frost-ristretto255/src/keys/dkg.rs | 2 +- frost-ristretto255/src/keys/refresh.rs | 6 +- frost-ristretto255/src/keys/repairable.rs | 25 +- frost-ristretto255/src/lib.rs | 13 +- frost-ristretto255/src/tests/batch.rs | 6 +- .../src/tests/coefficient_commitment.rs | 15 +- .../src/tests/vss_commitment.rs | 23 +- .../tests/common_traits_tests.rs | 2 +- frost-ristretto255/tests/integration_tests.rs | 71 +- .../tests/rerandomized_tests.rs | 2 +- frost-ristretto255/tests/serde_tests.rs | 16 +- frost-secp256k1-tr/Cargo.toml | 39 +- frost-secp256k1-tr/README.md | 2 +- frost-secp256k1-tr/benches/bench.rs | 4 +- frost-secp256k1-tr/dkg.md | 4 +- frost-secp256k1-tr/src/keys/dkg.rs | 2 +- frost-secp256k1-tr/src/keys/refresh.rs | 6 +- frost-secp256k1-tr/src/keys/repairable.rs | 25 +- frost-secp256k1-tr/src/lib.rs | 52 +- frost-secp256k1-tr/src/tests/batch.rs | 6 +- .../src/tests/coefficient_commitment.rs | 15 +- .../src/tests/vss_commitment.rs | 23 +- .../tests/common_traits_tests.rs | 2 +- frost-secp256k1-tr/tests/integration_tests.rs | 71 +- .../tests/interoperability_tests.rs | 6 +- .../tests/rerandomized_tests.rs | 2 +- frost-secp256k1-tr/tests/serde_tests.rs | 16 +- frost-secp256k1-tr/tests/tweaking_tests.rs | 4 +- frost-secp256k1/Cargo.toml | 37 +- frost-secp256k1/README.md | 2 +- frost-secp256k1/benches/bench.rs | 4 +- frost-secp256k1/dkg.md | 4 +- frost-secp256k1/src/keys/dkg.rs | 2 +- frost-secp256k1/src/keys/refresh.rs | 6 +- frost-secp256k1/src/keys/repairable.rs | 25 +- frost-secp256k1/src/lib.rs | 39 +- frost-secp256k1/src/tests/batch.rs | 6 +- .../src/tests/coefficient_commitment.rs | 15 +- frost-secp256k1/src/tests/vss_commitment.rs | 23 +- frost-secp256k1/tests/common_traits_tests.rs | 2 +- frost-secp256k1/tests/integration_tests.rs | 71 +- frost-secp256k1/tests/rerandomized_tests.rs | 2 +- frost-secp256k1/tests/serde_tests.rs | 16 +- gencode/Cargo.toml | 19 +- gencode/src/main.rs | 5 +- rustfmt.toml | 2 + 124 files changed, 1303 insertions(+), 1273 deletions(-) create mode 100644 rustfmt.toml diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 55ce03720..43eb6b218 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -6,7 +6,7 @@ on: branches: - main pull_request: - path: + paths: - '**/*.rs' - '**/*.txt' - '**/Cargo.toml' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c263aa9fb..99c85a9bd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,7 @@ name: CI on: + workflow_dispatch: push: branches: - main @@ -14,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - uses: dtolnay/rust-toolchain@beta - run: cargo build @@ -23,27 +24,22 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - uses: dtolnay/rust-toolchain@stable - run: cargo update && cargo build --all-features build_msrv: - name: build with MSRV (1.81) + name: build with MSRV (1.86) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 # Re-resolve Cargo.lock with minimal versions. - # This only works with nightly. We pin to a specific version because - # newer versions use lock file version 4, but the MSRV cargo does not - # support that. - - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2024-09-20 + - uses: dtolnay/rust-toolchain@nightly - run: cargo update -Z minimal-versions # Now check that `cargo build` works with respect to the oldest possible # deps and the stated MSRV - - uses: dtolnay/rust-toolchain@1.81 + - uses: dtolnay/rust-toolchain@1.86 - run: cargo build --all-features # TODO: this is filling up the disk space in CI. See if there is a way to @@ -54,7 +50,7 @@ jobs: # runs-on: ubuntu-latest # steps: - # - uses: actions/checkout@v6.0.2 + # - uses: actions/checkout@v7.0.0 # - uses: dtolnay/rust-toolchain@stable # - run: cargo install cargo-all-features # # We check and then test because some test dependencies could help @@ -69,12 +65,11 @@ jobs: build_no_std: name: build with no_std runs-on: ubuntu-latest - # Skip ed448 which does not support it. strategy: matrix: - crate: [ristretto255, ed25519, p256, secp256k1, secp256k1-tr, rerandomized] + crate: [ed448, ristretto255, ed25519, p256, secp256k1, secp256k1-tr, rerandomized] steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - uses: dtolnay/rust-toolchain@master with: toolchain: stable @@ -87,7 +82,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - uses: dtolnay/rust-toolchain@beta - run: cargo test --release --all-features @@ -96,7 +91,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 with: persist-credentials: false @@ -113,7 +108,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run clippy action to produce annotations - uses: clechasseur/rs-clippy-check@v5 + uses: clechasseur/rs-clippy-check@v6.0.5 if: ${{ steps.check_permissions.outputs.has-permission }} with: args: --all-features --all-targets -- -D warnings @@ -127,7 +122,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 with: persist-credentials: false @@ -144,7 +139,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 with: persist-credentials: false @@ -163,7 +158,7 @@ jobs: RUSTDOCFLAGS: -D warnings steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 with: persist-credentials: false @@ -175,7 +170,7 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.0 - uses: reviewdog/action-actionlint@v1.72.0 with: level: warning diff --git a/Cargo.lock b/Cargo.lock index 216200ab5..b58992260 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "anes" version = "0.1.6" @@ -29,15 +38,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -51,10 +51,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] -name = "base64ct" -version = "1.8.3" +name = "base16ct" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" [[package]] name = "bit-set" @@ -95,11 +95,11 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ - "generic-array", + "hybrid-array", ] [[package]] @@ -136,6 +136,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -188,6 +199,12 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "cobs" version = "0.3.0" @@ -214,11 +231,29 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808ac43170e95b11dd23d78aa9eaac5bea45776a602955552c4e833f3f0f823d" +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" -version = "0.9.6" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "cpubits" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" [[package]] name = "cpufeatures" @@ -229,20 +264,31 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "criterion" -version = "0.6.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" dependencies = [ + "alloca", "anes", "cast", "ciborium", "clap", "criterion-plot", - "itertools 0.13.0", + "itertools", "num-traits", "oorandom", + "page_size", "plotters", "rayon", "regex", @@ -254,20 +300,14 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", - "itertools 0.10.5", + "itertools", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -301,38 +341,51 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" -version = "0.5.5" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" dependencies = [ - "generic-array", - "rand_core 0.6.4", + "cpubits", + "ctutils", + "hybrid-array", + "num-traits", + "rand_core 0.10.1", "subtle", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "generic-array", - "typenum", + "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", + "subtle", ] [[package]] name = "curve25519-dalek" -version = "4.1.3" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", "curve25519-dalek-derive", "digest", - "fiat-crypto 0.2.9", - "rand_core 0.6.4", + "fiat-crypto", + "rand_core 0.10.1", "rustc_version", "serde", "subtle", @@ -358,9 +411,9 @@ checksum = "b93fdcfc175d53094fca1d23d171685621bb67f1658413514f2053d575b3b38c" [[package]] name = "der" -version = "0.7.10" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ "const-oid", "zeroize", @@ -379,9 +432,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.7" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer", "crypto-common", @@ -398,23 +451,21 @@ dependencies = [ [[package]] name = "ed25519" -version = "2.2.3" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ - "pkcs8", "signature", ] [[package]] name = "ed25519-dalek" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" dependencies = [ "curve25519-dalek", "ed25519", - "serde", "sha2", "subtle", "zeroize", @@ -422,13 +473,14 @@ dependencies = [ [[package]] name = "ed448-goldilocks" -version = "0.9.0" +version = "0.14.0-pre.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88322282bccdc6fa7ab65b0c30cb877fba541547653436d08bb775fa4a4307b4" +checksum = "b805154de2e68f59874ec217ca36790dcffe500cd872c60fe509d28d0814a74d" dependencies = [ - "fiat-crypto 0.1.20", - "hex", - "rand_core 0.6.4", + "elliptic-curve", + "hash2curve", + "rand_core 0.10.1", + "shake", "subtle", ] @@ -440,17 +492,18 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" -version = "0.13.8" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +checksum = "9d65aa39b3a5c1c9c1b745c9a019234bb7a21b77abcb4f4d266d706e2d577d65" dependencies = [ - "base16ct", + "base16ct 1.0.0", "crypto-bigint", - "digest", + "crypto-common", "ff", - "generic-array", "group", - "rand_core 0.6.4", + "hybrid-array", + "pkcs8", + "rand_core 0.10.1", "sec1", "subtle", "zeroize", @@ -492,25 +545,19 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ff" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.10.1", "subtle", ] [[package]] name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - -[[package]] -name = "fiat-crypto" -version = "0.2.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" @@ -530,18 +577,16 @@ version = "3.0.0" dependencies = [ "byteorder", "const-crc32-nostd", + "const-hex", "criterion", "debugless-unwrap", "derive-getters", "document-features", - "hex", - "itertools 0.14.0", - "lazy_static", "postcard", "proptest", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_chacha 0.10.0", + "rand_core 0.10.1", "serde", "serde_json", "serdect", @@ -549,26 +594,24 @@ dependencies = [ "tokio", "visibility", "zeroize", - "zeroize_derive", ] [[package]] name = "frost-ed25519" version = "3.0.0" dependencies = [ + "const-hex", "criterion", "curve25519-dalek", "document-features", "ed25519-dalek", "frost-core", "frost-rerandomized", - "hex", "insta", - "lazy_static", "proptest", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_chacha 0.10.0", + "rand_core 0.10.1", "serde_json", "sha2", "tokio", @@ -578,20 +621,20 @@ dependencies = [ name = "frost-ed448" version = "3.0.0" dependencies = [ + "const-hex", "criterion", "document-features", "ed448-goldilocks", "frost-core", "frost-rerandomized", - "hex", "insta", - "lazy_static", "proptest", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_chacha 0.10.0", + "rand_core 0.10.1", "serde_json", "sha3", + "shake", "tokio", ] @@ -599,18 +642,17 @@ dependencies = [ name = "frost-p256" version = "3.0.0" dependencies = [ + "const-hex", "criterion", "document-features", "frost-core", "frost-rerandomized", - "hex", "insta", - "lazy_static", "p256", "proptest", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_chacha 0.10.0", + "rand_core 0.10.1", "serde_json", "sha2", "tokio", @@ -620,30 +662,29 @@ dependencies = [ name = "frost-rerandomized" version = "3.0.0" dependencies = [ + "const-hex", "derive-getters", "document-features", "frost-core", - "hex", - "rand_core 0.6.4", + "rand_core 0.10.1", ] [[package]] name = "frost-ristretto255" version = "3.0.0" dependencies = [ + "const-hex", "criterion", "curve25519-dalek", "document-features", "frost-core", "frost-rerandomized", - "hex", "insta", - "lazy_static", "postcard", "proptest", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_chacha 0.10.0", + "rand_core 0.10.1", "serde_json", "sha2", "tokio", @@ -653,18 +694,17 @@ dependencies = [ name = "frost-secp256k1" version = "3.0.0" dependencies = [ + "const-hex", "criterion", "document-features", "frost-core", "frost-rerandomized", - "hex", "insta", "k256", - "lazy_static", "proptest", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_chacha 0.10.0", + "rand_core 0.10.1", "serde_json", "sha2", "tokio", @@ -674,18 +714,17 @@ dependencies = [ name = "frost-secp256k1-tr" version = "3.0.0" dependencies = [ + "const-hex", "criterion", "document-features", "frost-core", "frost-rerandomized", - "hex", "insta", "k256", - "lazy_static", "proptest", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_chacha 0.10.0", + "rand_core 0.10.1", "secp256k1", "serde_json", "sha2", @@ -694,54 +733,44 @@ dependencies = [ [[package]] name = "gencode" -version = "0.1.0" +version = "3.0.0" dependencies = [ "regex", "serde_json", ] -[[package]] -name = "generic-array" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - [[package]] name = "getrandom" -version = "0.2.17" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi", + "r-efi 5.3.0", + "wasip2", ] [[package]] name = "getrandom" -version = "0.3.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi", - "wasip2", + "r-efi 6.0.0", + "rand_core 0.10.1", ] [[package]] name = "group" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core 0.10.1", "subtle", ] @@ -757,48 +786,40 @@ dependencies = [ ] [[package]] -name = "hash32" -version = "0.2.1" +name = "hash2curve" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +checksum = "1eaf40612d7d854743e7189228a6d528f0f6e8502cf6a0cb831d28a218b7f3f6" dependencies = [ - "byteorder", + "digest", + "elliptic-curve", ] [[package]] -name = "heapless" -version = "0.7.17" +name = "hex-conservative" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin", - "stable_deref_trait", + "arrayvec", ] [[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hex-conservative" -version = "0.2.2" +name = "hybrid-array" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ - "arrayvec", + "subtle", + "typenum", + "zeroize", ] [[package]] name = "insta" -version = "1.47.2" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" dependencies = [ "console", "once_cell", @@ -807,15 +828,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -825,15 +837,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.17" @@ -852,29 +855,27 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.4" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +checksum = "93f50113171a713f4a4231ef82eb26703607139b35dcb56241f0ceab2ae1f7d8" dependencies = [ - "cfg-if", + "cpubits", "elliptic-curve", + "hash2curve", + "primeorder", + "wnaf", ] [[package]] name = "keccak" -version = "0.1.6" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ - "cpufeatures", + "cfg-if", + "cpufeatures 0.3.0", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.180" @@ -893,15 +894,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "memchr" version = "2.7.6" @@ -931,14 +923,26 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "p256" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +checksum = "d2c9239b2dbc807adbbe147e8cf72ea7450c3a0aabe62cb8e75ff4ec22e1f72a" dependencies = [ "elliptic-curve", + "hash2curve", + "primefield", "primeorder", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -947,9 +951,9 @@ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pkcs8" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ "der", "spki", @@ -992,7 +996,6 @@ dependencies = [ "cobs", "embedded-io 0.4.0", "embedded-io 0.6.1", - "heapless", "serde", ] @@ -1005,13 +1008,29 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primefield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c555a6e4eb7d4e158fcb028c835c3b8642206ddc279b5c6b202ef9a8bdb592f4" +dependencies = [ + "crypto-bigint", + "crypto-common", + "ff", + "rand_core 0.10.1", + "subtle", + "zeroize", +] + [[package]] name = "primeorder" -version = "0.13.6" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "5c9f42978c78a00e3d68f69fc03e57a234debae69da4020a4fb588fcdcd07b06" dependencies = [ "elliptic-curve", + "primefield", + "wnaf", ] [[package]] @@ -1033,7 +1052,7 @@ dependencies = [ "bit-vec", "bitflags", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -1064,34 +1083,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "rand" -version = "0.8.5" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rand" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -1105,12 +1120,13 @@ dependencies = [ ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "rand_chacha" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb" dependencies = [ - "getrandom 0.2.17", + "ppv-lite86", + "rand_core 0.10.1", ] [[package]] @@ -1122,6 +1138,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -1153,9 +1175,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -1176,9 +1198,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustc_version" @@ -1229,21 +1251,16 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "sec1" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ - "base16ct", + "base16ct 1.0.0", + "ctutils", "der", - "generic-array", + "hybrid-array", "subtle", "zeroize", ] @@ -1255,7 +1272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.2", + "rand 0.9.4", "secp256k1-sys", ] @@ -1306,9 +1323,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -1323,29 +1340,41 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" dependencies = [ - "base16ct", + "base16ct 0.2.0", "serde", ] [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.3.0", "digest", ] [[package]] name = "sha3" -version = "0.10.8" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9bad02c26382724b2d2692c6f179285e4b54eeecd7968f52a50059c3c11759" +dependencies = [ + "digest", + "keccak", + "sponge-cursor", +] + +[[package]] +name = "shake" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" dependencies = [ "digest", "keccak", + "sponge-cursor", ] [[package]] @@ -1356,12 +1385,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signature" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "rand_core 0.6.4", -] +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" [[package]] name = "similar" @@ -1369,30 +1395,20 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "spki" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ - "base64ct", "der", ] [[package]] -name = "stable_deref_trait" -version = "1.2.1" +name = "sponge-cursor" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" [[package]] name = "subtle" @@ -1456,9 +1472,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.50.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "pin-project-lite", "tokio-macros", @@ -1466,9 +1482,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -1477,9 +1493,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unarray" @@ -1493,12 +1509,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - [[package]] name = "visibility" version = "0.1.1" @@ -1529,12 +1539,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -1599,6 +1603,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -1608,6 +1628,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-link" version = "0.2.1" @@ -1629,6 +1655,17 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +[[package]] +name = "wnaf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab12e7090f27e2ffd9322651492942d50c2926094af30601e1964337db39daf1" +dependencies = [ + "ff", + "group", + "hybrid-array", +] + [[package]] name = "zerocopy" version = "0.8.35" @@ -1651,18 +1688,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index cf479d436..73eecfb70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,22 +1,18 @@ [workspace] -resolver = "2" +resolver = "3" members = [ "frost-core", - "frost-ed25519", "frost-ed448", + "frost-ed25519", "frost-p256", + "frost-rerandomized", "frost-ristretto255", "frost-secp256k1", "frost-secp256k1-tr", - "frost-rerandomized", - "gencode" + "gencode", ] [workspace.package] -# If you update the edition, make sure to also update the argument to rustfmt -# in gencode/src/main.rs. -edition = "2021" -rust-version = "1.81" version = "3.0.0" authors = [ "Deirdre Connolly ", @@ -24,27 +20,61 @@ authors = [ "Conrado Gouvea ", "Natalie Eskinazi " ] -license = "MIT OR Apache-2.0" +# If you update the edition, make sure to also update the argument to rustfmt +# in gencode/src/main.rs. +edition = "2024" +rust-version = "1.86" +description = "ZF FROST (Flexible Round-Optimised Schnorr Threshold signatures)" +documentation = "https://docs.rs/frost-core" +readme = "README.md" +homepage = "https://frost.zfnd.org" repository = "https://github.com/ZcashFoundation/frost" -categories = ["cryptography"] +license = "MIT OR Apache-2.0" +license-file = "LICENSE" +keywords = ["cryptography", "crypto", "threshold", "signature", "schnorr"] +categories = ["cryptography", "algorithms", "mathematics", "security", "no-std"] [workspace.dependencies] -# Currently holding back from updating due to MSRV 1.86 of criterion 0.8 -# (we don't want to raise our MSRV just for this) -criterion = "0.6" -document-features = "0.2.7" -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -insta = { version = "1.31.0", features = ["yaml"] } -lazy_static = "1.4" -proptest = "1.0" -rand = "0.8" -rand_chacha = "0.3" -rand_core = "0.6" +byteorder = { version = "1.5", default-features = false } +const-crc32 = { version = "1.3", package = "const-crc32-nostd" } +criterion = "0.8" +curve25519-dalek = { version = "5.0", features = ["rand_core"] } +debugless-unwrap = "1.0" +derive-getters = "0.5" +document-features = "0.2.12" +ed448-goldilocks = { version = "0.14.0-pre.15", default-features = false } +ed25519-dalek = "3.0" +hex = { package = "const-hex", version = "1.19", default-features = false, features = ["alloc"] } +insta = { version = "1.48", features = ["yaml"] } +k256 = { version = "0.14", default-features = false, features = ["hash2curve"] } +p256 = { version = "0.14", default-features = false, features = ["hash2curve"] } +postcard = { version = "1.1", default-features = false, features = ["alloc"] } +proptest = "1.11" +rand = "0.10" +rand_chacha = "0.10" +rand_core = "0.10" +regex = "1.13" +secp256k1 = "0.31" +serde = { version = "1.0", default-features = false, features = ["derive"] } +serdect = "0.2" # TODO: https://github.com/ZcashFoundation/frost/issues/830 serde_json = "1.0" +sha2 = { version = "0.11", default-features = false } +sha3 = { version = "0.12", default-features = false } +shake = { version = "0.1", default-features = false, features = ["alloc"] } +thiserror = { version = "2.0", default-features = false } tokio = { version = "1.0", features = ["rt", "time", "macros"] } +visibility = "0.1" +zeroize = { version = "1.9", default-features = false, features = ["derive", "alloc"] } -frost-core = { path = "frost-core", version = "3.0.0", default-features = false } -frost-rerandomized = { path = "frost-rerandomized", version = "3.0.0", default-features = false } +frost-core = { version = "3.0.0", path = "frost-core", default-features = false } +frost-ed448 = { version = "3.0.0", path = "frost-ed448", default-features = false } +frost-ed25519 = { version = "3.0.0", path = "frost-ed25519", default-features = false } +frost-p256 = { version = "3.0.0", path = "frost-p256", default-features = false } +frost-rerandomized = { version = "3.0.0", path = "frost-rerandomized", default-features = false } +frost-ristretto255 = { version = "3.0.0", path = "frost-ristretto255", default-features = false } +frost-secp256k1 = { version = "3.0.0", path = "frost-secp256k1", default-features = false } +frost-secp256k1-tr = { version = "3.0.0", path = "frost-secp256k1-tr", default-features = false } +gencode = { version = "3.0.0", path = "gencode", default-features = false } [profile.test.package."*"] opt-level = 3 diff --git a/frost-core/CHANGELOG.md b/frost-core/CHANGELOG.md index 1fcb73649..24dbc1d93 100644 --- a/frost-core/CHANGELOG.md +++ b/frost-core/CHANGELOG.md @@ -5,6 +5,10 @@ Entries are listed in reverse chronological order. ## Unreleased +* All crates from RustCrypto have been bumped to the latest versions, + version `rand` has been updated to `0.10` +* MSRV has been bumped to Rust 1.86, making all crates no-std. + ## 3.0.0 Refer to the `3.0.0-rc.0` entry below for the main changes if you are upgrading diff --git a/frost-core/Cargo.toml b/frost-core/Cargo.toml index 27642bf41..82f076b26 100644 --- a/frost-core/Cargo.toml +++ b/frost-core/Cargo.toml @@ -1,56 +1,57 @@ [package] name = "frost-core" -edition.workspace = true -rust-version.workspace = true version.workspace = true authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "Types and traits to support implementing Flexible Round-Optimized Schnorr Threshold signature schemes (FROST)." +documentation = "https://docs.rs/frost-core" readme = "README.md" -license.workspace = true +homepage.workspace = true repository.workspace = true -categories.workspace = true +license.workspace = true keywords = ["cryptography", "crypto", "threshold", "signature", "schnorr"] -description = "Types and traits to support implementing Flexible Round-Optimized Schnorr Threshold signature schemes (FROST)." +categories.workspace = true [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + [dependencies] -byteorder = { version = "1.4", default-features = false } -const-crc32 = { version = "1.2.0", package = "const-crc32-nostd" } +byteorder.workspace = true +const-crc32.workspace = true +debugless-unwrap = { workspace = true, optional = true } +derive-getters.workspace = true document-features.workspace = true -debugless-unwrap = { version = "1.0.0", optional = true } -derive-getters = "0.5.0" hex.workspace = true -postcard = { version = "1.0.0", features = ["alloc"], optional = true } -rand_core = { version = "0.6", default-features = false } -serde = { version = "1.0.160", default-features = false, features = ["derive"], optional = true } -serdect = { version = "0.2.0", optional = true } -thiserror = { version = "2.0.3", default-features = false } -visibility = "0.1.0" -zeroize = { version = "1.5.4", default-features = false, features = ["derive", "alloc"] } -# We indirectly depend on this via `zeroize` but the minimal version enforced by -# `zeroize` does not work for us, so we specify it here. -zeroize_derive = { version = "1.4.2" } -itertools = { version = "0.14.0", default-features = false } +postcard = { workspace = true, optional = true } +rand_core.workspace = true +serde = { workspace = true, optional = true } +serdect = { workspace = true, optional = true } +thiserror.workspace = true +visibility.workspace = true +zeroize.workspace = true # Test dependencies used with the test-impl feature -proptest = { version = "1.0", optional = true } -serde_json = { version = "1.0", optional = true } criterion = { workspace = true, optional = true } +proptest = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } tokio = { workspace = true, optional = true } [dev-dependencies] -debugless-unwrap = "1.0.0" criterion.workspace = true -lazy_static.workspace = true +debugless-unwrap.workspace = true proptest.workspace = true rand.workspace = true rand_chacha.workspace = true serde_json.workspace = true tokio.workspace = true - [features] default = ["serialization"] #! ## Features @@ -65,6 +66,3 @@ serde = ["dep:serde", "dep:serdect"] serialization = ["serde", "dep:postcard"] # Exposes ciphersuite-generic tests for other crates to use test-impl = ["dep:proptest", "dep:serde_json", "dep:criterion", "dep:tokio", "dep:debugless-unwrap"] - -[lib] -bench = false diff --git a/frost-core/src/batch.rs b/frost-core/src/batch.rs index a30a109e4..3b1ca4ce8 100644 --- a/frost-core/src/batch.rs +++ b/frost-core/src/batch.rs @@ -7,7 +7,7 @@ //! of caller code (which must assemble a batch of signatures across //! work-items), and loss of the ability to easily pinpoint failing signatures. -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use crate::{scalar_mul::VartimeMultiscalarMul, Ciphersuite, Element, *}; @@ -109,7 +109,7 @@ where /// notation in the [protocol specification §B.1][ps]. /// /// [ps]: https://zips.z.cash/protocol/protocol.pdf#reddsabatchverify - pub fn verify(self, mut rng: R) -> Result<(), Error> { + pub fn verify(self, mut rng: R) -> Result<(), Error> { let n = self.signatures.len(); if n == 0 { diff --git a/frost-core/src/benches.rs b/frost-core/src/benches.rs index fd4ea7313..a66942632 100644 --- a/frost-core/src/benches.rs +++ b/frost-core/src/benches.rs @@ -4,7 +4,7 @@ use core::iter; use alloc::{collections::BTreeMap, format, vec::Vec}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use criterion::{BenchmarkId, Criterion, Throughput}; @@ -16,7 +16,7 @@ struct Item { sig: Signature, } -fn sigs_with_distinct_keys( +fn sigs_with_distinct_keys( rng: &mut R, ) -> impl Iterator> { let mut rng = rng.clone(); @@ -30,7 +30,7 @@ fn sigs_with_distinct_keys( } /// Benchmark batched signature verification with the specified ciphersuite. -pub fn bench_batch_verify( +pub fn bench_batch_verify( c: &mut Criterion, name: &str, rng: &mut R, @@ -81,7 +81,7 @@ pub fn bench_batch_verify( } /// Benchmark FROST signing with the specified ciphersuite. -pub fn bench_sign( +pub fn bench_sign( c: &mut Criterion, name: &str, rng: &mut R, @@ -89,9 +89,7 @@ pub fn bench_sign( let mut group = c.benchmark_group(format!("FROST Signing {name}")); for &n in [3u16, 10, 100, 1000].iter() { let max_signers = n; - // div_ceil is in 1.73.0 which is larger than the current MSRV - #[allow(clippy::manual_div_ceil)] - let min_signers = (n * 2 + 2) / 3; + let min_signers = (n * 2).div_ceil(3); group.bench_with_input( BenchmarkId::new("Key Generation with Dealer", max_signers), diff --git a/frost-core/src/keys.rs b/frost-core/src/keys.rs index 5389f5ebc..bda728d9b 100644 --- a/frost-core/src/keys.rs +++ b/frost-core/src/keys.rs @@ -16,7 +16,7 @@ use derive_getters::Getters; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use zeroize::{DefaultIsZeroes, Zeroize, ZeroizeOnDrop}; use crate::{ @@ -64,7 +64,7 @@ pub(crate) fn sum_commitments( } /// Return a vector of randomly generated polynomial coefficients ([`Scalar`]s). -pub(crate) fn generate_coefficients( +pub(crate) fn generate_coefficients( size: usize, rng: &mut R, ) -> Vec> { @@ -510,7 +510,7 @@ pub enum IdentifierList<'a, C: Ciphersuite> { /// Implements [`trusted_dealer_keygen`] from the spec. /// /// [`trusted_dealer_keygen`]: https://datatracker.ietf.org/doc/html/rfc9591#appendix-C -pub fn generate_with_dealer( +pub fn generate_with_dealer( max_signers: u16, min_signers: u16, identifiers: IdentifierList, @@ -526,7 +526,7 @@ pub fn generate_with_dealer( /// instead of generating a fresh one. This is useful in scenarios where /// the key needs to be generated externally or must be derived from e.g. a /// seed phrase. -pub fn split( +pub fn split( key: &SigningKey, max_signers: u16, min_signers: u16, diff --git a/frost-core/src/keys/dkg.rs b/frost-core/src/keys/dkg.rs index 016490fce..167041887 100644 --- a/frost-core/src/keys/dkg.rs +++ b/frost-core/src/keys/dkg.rs @@ -38,7 +38,7 @@ use core::iter; use alloc::collections::BTreeMap; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use crate::{ Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature, @@ -356,7 +356,7 @@ pub mod round2 { /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the DKG run. -pub fn part1( +pub fn part1( identifier: Identifier, max_signers: u16, min_signers: u16, @@ -420,7 +420,7 @@ where /// Compute the proof of knowledge of the secret coefficients used to generate /// the public secret sharing commitment. #[cfg_attr(feature = "internals", visibility::make(pub))] -pub(crate) fn compute_proof_of_knowledge( +pub(crate) fn compute_proof_of_knowledge( identifier: Identifier, coefficients: &[Scalar], commitment: &VerifiableSecretSharingCommitment, diff --git a/frost-core/src/keys/refresh.rs b/frost-core/src/keys/refresh.rs index cd80ad042..5fe17bfdd 100644 --- a/frost-core/src/keys/refresh.rs +++ b/frost-core/src/keys/refresh.rs @@ -33,7 +33,7 @@ use crate::{ generate_secret_shares, validate_num_of_signers, CoefficientCommitment, PublicKeyPackage, SigningKey, SigningShare, VerifyingShare, }, - Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier, RngCore, + Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier, }; use core::iter; @@ -55,7 +55,7 @@ use super::{dkg::round1::Package, KeyPackage, SecretShare, VerifiableSecretShari /// It returns a vectors of [`SecretShare`] that must be sent to the /// participants in the same order as `identifiers`, and the refreshed /// [`PublicKeyPackage`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( pub_key_package: PublicKeyPackage, identifiers: &[Identifier], rng: &mut R, @@ -176,7 +176,7 @@ pub fn refresh_share( /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the refresh run. -pub fn refresh_dkg_part1( +pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-core/src/keys/repairable.rs b/frost-core/src/keys/repairable.rs index 0d6263657..8dd67c13b 100644 --- a/frost-core/src/keys/repairable.rs +++ b/frost-core/src/keys/repairable.rs @@ -26,8 +26,7 @@ use alloc::vec::Vec; use crate::keys::{KeyPackage, PublicKeyPackage}; use crate::serialization::SerializableScalar; use crate::{ - compute_lagrange_coefficient, Ciphersuite, CryptoRng, Error, Field, Group, Identifier, RngCore, - Scalar, + compute_lagrange_coefficient, Ciphersuite, CryptoRng, Error, Field, Group, Identifier, Scalar, }; use super::{generate_coefficients, SigningShare}; @@ -111,7 +110,7 @@ where /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_part1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, diff --git a/frost-core/src/lib.rs b/frost-core/src/lib.rs index 364beabb0..c6171d56e 100644 --- a/frost-core/src/lib.rs +++ b/frost-core/src/lib.rs @@ -2,6 +2,7 @@ #![allow(non_snake_case)] // It's emitting false positives; see https://github.com/rust-lang/rust-clippy/issues/9413 #![allow(clippy::derive_partial_eq_without_eq)] +#![allow(clippy::wrong_self_convention)] #![deny(missing_docs)] #![forbid(unsafe_code)] #![deny(clippy::indexing_slicing)] @@ -25,7 +26,7 @@ use derive_getters::Getters; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; use keys::PublicKeyPackage; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use serialization::SerializableScalar; use zeroize::Zeroize; @@ -136,7 +137,7 @@ where /// It assumes that the Scalar Eq/PartialEq implementation is constant-time. #[cfg_attr(feature = "internals", visibility::make(pub))] #[cfg_attr(docsrs, doc(cfg(feature = "internals")))] -pub(crate) fn random_nonzero(rng: &mut R) -> Scalar { +pub(crate) fn random_nonzero(rng: &mut R) -> Scalar { loop { let scalar = <::Field>::random(rng); @@ -269,13 +270,12 @@ where fn from_hex>(hex: T) -> Result { let v: Vec = FromHex::from_hex(hex).map_err(|_| "invalid hex")?; - let ret = match v.as_slice().try_into() { + match v.as_slice().try_into() { Ok(bytes) => <::Field>::deserialize(&bytes) .map(|scalar| Self(scalar)) .map_err(|_| "malformed scalar encoding"), Err(_) => Err("malformed scalar encoding"), - }; - ret + } } } diff --git a/frost-core/src/round1.rs b/frost-core/src/round1.rs index d2468fde6..dfaa83a59 100644 --- a/frost-core/src/round1.rs +++ b/frost-core/src/round1.rs @@ -13,7 +13,7 @@ use derive_getters::Getters; #[cfg(any(test, feature = "test-impl"))] use hex::FromHex; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::{ @@ -48,7 +48,7 @@ where /// [spec]: https://datatracker.ietf.org/doc/html/rfc9591#name-nonce-generation pub fn new(secret: &SigningShare, rng: &mut R) -> Self where - R: CryptoRng + RngCore, + R: CryptoRng, { let mut random_bytes = [0; 32]; rng.fill_bytes(&mut random_bytes[..]); @@ -239,7 +239,7 @@ where /// operation. pub fn new(secret: &SigningShare, rng: &mut R) -> Self where - R: CryptoRng + RngCore, + R: CryptoRng, { let hiding = Nonce::::new(secret, rng); let binding = Nonce::::new(secret, rng); @@ -429,7 +429,7 @@ pub fn preprocess( ) -> (Vec>, Vec>) where C: Ciphersuite, - R: CryptoRng + RngCore, + R: CryptoRng, { let mut signing_nonces: Vec> = Vec::with_capacity(num_nonces as usize); let mut signing_commitments: Vec> = @@ -458,7 +458,7 @@ pub fn commit( ) -> (SigningNonces, SigningCommitments) where C: Ciphersuite, - R: CryptoRng + RngCore, + R: CryptoRng, { let (mut vec_signing_nonces, mut vec_signing_commitments) = preprocess(1, secret, rng); ( diff --git a/frost-core/src/signing_key.rs b/frost-core/src/signing_key.rs index 20aa63408..fd5751d77 100644 --- a/frost-core/src/signing_key.rs +++ b/frost-core/src/signing_key.rs @@ -1,8 +1,7 @@ //! Schnorr signature signing keys use alloc::vec::Vec; - -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use zeroize::ZeroizeOnDrop; use crate::{ @@ -24,7 +23,7 @@ where C: Ciphersuite, { /// Generate a new signing key. - pub fn new(rng: &mut R) -> SigningKey { + pub fn new(rng: &mut R) -> SigningKey { let scalar = random_nonzero::(rng); SigningKey { scalar } @@ -41,18 +40,14 @@ where } /// Create a signature `msg` using this `SigningKey`. - pub fn sign(&self, rng: R, message: &[u8]) -> Signature { + pub fn sign(&self, rng: R, message: &[u8]) -> Signature { ::single_sign(self, rng, message) } /// Create a signature `msg` using this `SigningKey` using the default /// signing. #[cfg_attr(feature = "internals", visibility::make(pub))] - pub(crate) fn default_sign( - &self, - mut rng: R, - message: &[u8], - ) -> Signature { + pub(crate) fn default_sign(&self, mut rng: R, message: &[u8]) -> Signature { let public = VerifyingKey::::from(self.clone()); let (k, R) = ::generate_nonce(&mut rng); diff --git a/frost-core/src/tests/batch.rs b/frost-core/src/tests/batch.rs index 008e0cb78..26aa38342 100644 --- a/frost-core/src/tests/batch.rs +++ b/frost-core/src/tests/batch.rs @@ -2,7 +2,7 @@ use crate::*; /// Test batch verification with a Ciphersuite. -pub fn batch_verify(mut rng: R) { +pub fn batch_verify(mut rng: R) { let mut batch = batch::Verifier::::new(); for _ in 0..1 { let sk = SigningKey::new(&mut rng); @@ -16,7 +16,7 @@ pub fn batch_verify(mut rng: R) { } /// Test failure case of batch verification with a Ciphersuite. -pub fn bad_batch_verify(mut rng: R) { +pub fn bad_batch_verify(mut rng: R) { let bad_index = 4; // must be even let mut batch = batch::Verifier::::new(); let mut items = Vec::new(); @@ -57,7 +57,7 @@ pub fn bad_batch_verify(mut rng: R) { /// Test if the empty batch fails to validate. /// Test case from NCC audit. -pub fn empty_batch_verify(rng: R) { +pub fn empty_batch_verify(rng: R) { let batch = batch::Verifier::::new(); assert!(batch.verify(rng).is_err()); } diff --git a/frost-core/src/tests/ciphersuite_generic.rs b/frost-core/src/tests/ciphersuite_generic.rs index 57349f8f6..1949fe793 100644 --- a/frost-core/src/tests/ciphersuite_generic.rs +++ b/frost-core/src/tests/ciphersuite_generic.rs @@ -3,7 +3,7 @@ #![cfg(feature = "serialization")] use alloc::{borrow::ToOwned, collections::BTreeMap, vec::Vec}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use crate as frost; use crate::keys::dkg::{round1, round2}; @@ -26,7 +26,7 @@ pub fn check_zero_key_fails() { } /// Test share generation with a Ciphersuite -pub fn check_share_generation(mut rng: R) { +pub fn check_share_generation(mut rng: R) { let secret = crate::SigningKey::::new(&mut rng); // Simulate serialization / deserialization to ensure it works let secret = SigningKey::deserialize(&secret.serialize()).unwrap(); @@ -81,7 +81,7 @@ pub fn check_share_generation(mut rng: R } /// Test share generation with a Ciphersuite -pub fn check_share_generation_fails_with_invalid_signers( +pub fn check_share_generation_fails_with_invalid_signers( min_signers: u16, max_signers: u16, error: Error, @@ -105,7 +105,7 @@ pub fn check_share_generation_fails_with_invalid_signers( +pub fn check_sign_with_dealer( mut rng: R, ) -> (Vec, Signature, VerifyingKey) { //////////////////////////////////////////////////////////////////////////// @@ -165,7 +165,7 @@ pub fn check_sign_with_dealer( } /// Test FROST signing with trusted dealer fails with invalid numbers of signers. -pub fn check_sign_with_dealer_fails_with_invalid_signers( +pub fn check_sign_with_dealer_fails_with_invalid_signers( min_signers: u16, max_signers: u16, error: Error, @@ -183,7 +183,7 @@ pub fn check_sign_with_dealer_fails_with_invalid_signers( +pub fn check_dkg_part1_fails_with_invalid_signers( min_signers: u16, max_signers: u16, error: Error, @@ -201,7 +201,7 @@ pub fn check_dkg_part1_fails_with_invalid_signers( +pub fn check_sign( min_signers: u16, key_packages: BTreeMap, frost::keys::KeyPackage>, mut rng: R, @@ -442,7 +442,7 @@ fn check_aggregate_invalid_share_identifier_for_verifying_shares( +pub fn check_sign_with_dkg( mut rng: R, ) -> (Vec, Signature, VerifyingKey) where @@ -766,7 +766,7 @@ fn check_part3_corrupted_share( /// Test FROST signing with trusted dealer with a Ciphersuite, using specified /// Identifiers. -pub fn check_sign_with_dealer_and_identifiers( +pub fn check_sign_with_dealer_and_identifiers( mut rng: R, ) -> (Vec, Signature, VerifyingKey) { // Check error cases first @@ -882,7 +882,7 @@ pub fn check_identifier_derivation() { } /// Checks the signer's identifier is included in the package -pub fn check_sign_with_missing_identifier(mut rng: R) { +pub fn check_sign_with_missing_identifier(mut rng: R) { //////////////////////////////////////////////////////////////////////////// // Key generation //////////////////////////////////////////////////////////////////////////// @@ -963,7 +963,7 @@ pub fn check_sign_with_missing_identifier(mut rng: R) { +pub fn check_sign_with_incorrect_commitments(mut rng: R) { //////////////////////////////////////////////////////////////////////////// // Key generation //////////////////////////////////////////////////////////////////////////// @@ -1089,9 +1089,7 @@ fn check_verify_signature_share( /// Test FROST signing in an async context. /// The ultimate goal of the test is to ensure that types are Send + Sync. -pub async fn async_check_sign( - mut rng: R, -) { +pub async fn async_check_sign(mut rng: R) { tokio::spawn(async move { let max_signers = 5; let min_signers = 3; diff --git a/frost-core/src/tests/coefficient_commitment.rs b/frost-core/src/tests/coefficient_commitment.rs index b491108b9..ae03ffb2c 100644 --- a/frost-core/src/tests/coefficient_commitment.rs +++ b/frost-core/src/tests/coefficient_commitment.rs @@ -3,15 +3,13 @@ use crate as frost; use crate::{keys::CoefficientCommitment, tests::helpers::generate_element, Group}; use debugless_unwrap::DebuglessUnwrapExt; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use serde_json::Value; use crate::Ciphersuite; /// Test retrieving Element from CoefficientCommitment -pub fn check_serialization_of_coefficient_commitment( - mut rng: R, -) { +pub fn check_serialization_of_coefficient_commitment(mut rng: R) { let element = generate_element::(&mut rng); let expected = ::serialize(&element).unwrap(); @@ -24,7 +22,7 @@ pub fn check_serialization_of_coefficient_commitment(mut rng: R) { +pub fn check_create_coefficient_commitment(mut rng: R) { let element = generate_element::(&mut rng); let expected = CoefficientCommitment::::new(element); @@ -55,9 +53,7 @@ pub fn check_create_coefficient_commitment_error( } /// Test retrieve Element from CoefficientCommitment -pub fn check_get_value_of_coefficient_commitment( - mut rng: R, -) { +pub fn check_get_value_of_coefficient_commitment(mut rng: R) { let element = generate_element::(&mut rng); let coeff_commitment = frost::keys::CoefficientCommitment::::new(element); diff --git a/frost-core/src/tests/helpers.rs b/frost-core/src/tests/helpers.rs index b441e7238..3535e555f 100644 --- a/frost-core/src/tests/helpers.rs +++ b/frost-core/src/tests/helpers.rs @@ -1,10 +1,10 @@ //! Helper function for testing use crate::{Ciphersuite, Field, Group}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; /// Helper function for randomly generating an element -pub fn generate_element( +pub fn generate_element( rng: &mut R, ) -> <::Group as Group>::Element { let scalar = <::Field>::random(rng); diff --git a/frost-core/src/tests/proptests.rs b/frost-core/src/tests/proptests.rs index 9b0fbcb9f..4d4a9120b 100644 --- a/frost-core/src/tests/proptests.rs +++ b/frost-core/src/tests/proptests.rs @@ -2,7 +2,7 @@ use crate::*; use proptest::prelude::*; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; /// A signature test-case, containing signature data and expected validity. #[derive(Clone, Debug)] @@ -40,7 +40,7 @@ where C: Ciphersuite, { /// Create a new SignatureCase. - pub fn new(mut rng: R, msg: Vec) -> Self { + pub fn new(mut rng: R, msg: Vec) -> Self { let sk = SigningKey::::new(&mut rng); let sig = sk.sign(&mut rng, &msg); let vk = VerifyingKey::::from(&sk); diff --git a/frost-core/src/tests/refresh.rs b/frost-core/src/tests/refresh.rs index 8f2d56f6c..cb2e9744f 100644 --- a/frost-core/src/tests/refresh.rs +++ b/frost-core/src/tests/refresh.rs @@ -1,7 +1,7 @@ //! Test for Refreshing shares #![cfg(feature = "serialization")] -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use crate::keys::dkg::{round1, round2}; use crate::keys::generate_with_dealer; @@ -22,7 +22,7 @@ use alloc::vec::Vec; use super::ciphersuite_generic::check_sign; /// We want to test that recovered share matches the original share -pub fn check_refresh_shares_with_dealer(mut rng: R) { +pub fn check_refresh_shares_with_dealer(mut rng: R) { // Compute shares //////////////////////////////////////////////////////////////////////////// @@ -92,10 +92,7 @@ pub fn check_refresh_shares_with_dealer( } /// We want to check that shares are refreshed with valid signers -pub fn check_refresh_shares_with_dealer_fails_with_invalid_signers< - C: Ciphersuite, - R: RngCore + CryptoRng, ->( +pub fn check_refresh_shares_with_dealer_fails_with_invalid_signers( identifiers: &[Identifier], error: Error, mut rng: R, @@ -112,7 +109,7 @@ pub fn check_refresh_shares_with_dealer_fails_with_invalid_signers< /// identifiers in the public key package pub fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package< C: Ciphersuite, - R: RngCore + CryptoRng, + R: CryptoRng, >( mut rng: R, ) { @@ -162,9 +159,7 @@ pub fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package< /// Check serialisation #[cfg(feature = "serialization")] -pub fn check_refresh_shares_with_dealer_serialisation( - mut rng: R, -) { +pub fn check_refresh_shares_with_dealer_serialisation(mut rng: R) { //////////////////////////////////////////////////////////////////////////// // Old Key generation //////////////////////////////////////////////////////////////////////////// @@ -225,7 +220,7 @@ pub fn check_refresh_shares_with_dealer_serialisation( +pub fn check_refresh_shares_with_dkg( mut rng: R, ) -> (Vec, Signature, VerifyingKey) where @@ -463,10 +458,7 @@ where /// Test FROST signing with DKG with a Ciphersuite, using a smaller /// threshold than the original one. -pub fn check_refresh_shares_with_dkg_smaller_threshold< - C: Ciphersuite + PartialEq, - R: RngCore + CryptoRng, ->( +pub fn check_refresh_shares_with_dkg_smaller_threshold( mut rng: R, ) where C::Group: core::cmp::PartialEq, diff --git a/frost-core/src/tests/repairable.rs b/frost-core/src/tests/repairable.rs index e4379732e..518d0b216 100644 --- a/frost-core/src/tests/repairable.rs +++ b/frost-core/src/tests/repairable.rs @@ -3,7 +3,7 @@ use alloc::collections::BTreeMap; use debugless_unwrap::DebuglessUnwrapExt; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use serde_json::Value; use crate as frost; @@ -19,7 +19,7 @@ use crate::{ }; /// We want to test that recovered share matches the original share -pub fn check_rts(mut rng: R) { +pub fn check_rts(mut rng: R) { // Compute shares //////////////////////////////////////////////////////////////////////////// @@ -109,7 +109,7 @@ fn generate_scalar_from_byte_string( } /// Test repair_share_part1 -pub fn check_repair_share_part1(mut rng: R) { +pub fn check_repair_share_part1(mut rng: R) { // Compute shares let max_signers = 5; @@ -184,7 +184,7 @@ pub fn check_repair_share_part2(repair_share_helpers: &Value) { } /// Test repair_share_part3 -pub fn check_repair_share_part3( +pub fn check_repair_share_part3( mut rng: R, repair_share_helpers: &Value, ) { @@ -231,10 +231,7 @@ pub fn check_repair_share_part3( } /// Test repair share part 1 fails with invalid numbers of signers. -pub fn check_repair_share_part1_fails_with_invalid_min_signers< - C: Ciphersuite, - R: RngCore + CryptoRng, ->( +pub fn check_repair_share_part1_fails_with_invalid_min_signers( mut rng: R, ) { // Generate shares diff --git a/frost-core/src/tests/vss_commitment.rs b/frost-core/src/tests/vss_commitment.rs index 62a27556f..64c6054c8 100644 --- a/frost-core/src/tests/vss_commitment.rs +++ b/frost-core/src/tests/vss_commitment.rs @@ -7,14 +7,14 @@ use crate::{ }; use alloc::vec::Vec; use debugless_unwrap::DebuglessUnwrapExt; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use serde_json::Value; use crate::keys::{generate_with_dealer, IdentifierList, PublicKeyPackage}; use crate::Ciphersuite; /// Test serialize VerifiableSecretSharingCommitment -pub fn check_serialize_vss_commitment(mut rng: R) { +pub fn check_serialize_vss_commitment(mut rng: R) { // Generate test CoefficientCommitments // --- @@ -48,7 +48,7 @@ pub fn check_serialize_vss_commitment(mu } /// Test serialize_whole VerifiableSecretSharingCommitment -pub fn check_serialize_whole_vss_commitment(mut rng: R) { +pub fn check_serialize_whole_vss_commitment(mut rng: R) { // Generate test CoefficientCommitments // --- @@ -82,7 +82,7 @@ pub fn check_serialize_whole_vss_commitment(mut rng: R) { +pub fn check_deserialize_vss_commitment(mut rng: R) { // Generate test CoefficientCommitments // --- @@ -112,7 +112,7 @@ pub fn check_deserialize_vss_commitment( } /// Test deserialize_whole VerifiableSecretSharingCommitment -pub fn check_deserialize_whole_vss_commitment(mut rng: R) { +pub fn check_deserialize_whole_vss_commitment(mut rng: R) { // Generate test CoefficientCommitments // --- @@ -146,7 +146,7 @@ pub fn check_deserialize_whole_vss_commitment( +pub fn check_deserialize_vss_commitment_error( mut rng: R, commitment_helpers: &Value, ) { @@ -179,7 +179,7 @@ pub fn check_deserialize_vss_commitment_error( +pub fn check_deserialize_whole_vss_commitment_error( mut rng: R, commitment_helpers: &Value, ) { @@ -233,7 +233,7 @@ pub fn check_deserialize_whole_vss_commitment_error(mut rng: R) { +pub fn check_compute_public_key_package(mut rng: R) { let max_signers = 3; let min_signers = 2; let (secret_shares, public_key_package) = diff --git a/frost-core/src/traits.rs b/frost-core/src/traits.rs index 9ae94319f..3ed312844 100644 --- a/frost-core/src/traits.rs +++ b/frost-core/src/traits.rs @@ -6,7 +6,7 @@ use core::{ }; use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec}; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use crate::{ challenge, @@ -54,7 +54,7 @@ pub trait Field: Copy { /// Generate a random scalar from the entire space [0, l-1] /// /// - fn random(rng: &mut R) -> Self::Scalar; + fn random(rng: &mut R) -> Self::Scalar; /// A member function of a [`Field`] that maps a [`Scalar`] to a unique byte array buf of /// fixed length Ne. @@ -237,7 +237,7 @@ pub trait Ciphersuite: Copy + PartialEq + Debug + 'static + Send + Sync { /// if required which is useful if FROST signing has been changed by the /// other Ciphersuite trait methods and regular signing should be changed /// accordingly to match. - fn single_sign( + fn single_sign( signing_key: &SigningKey, rng: R, message: &[u8], @@ -367,7 +367,7 @@ pub trait Ciphersuite: Copy + PartialEq + Debug + 'static + Send + Sync { /// Optional. Generate a nonce and a commitment to it. Used by /// [`SigningKey`] for regular (non-FROST) signing and internally by the DKG /// to generate proof-of-knowledge signatures. - fn generate_nonce( + fn generate_nonce( rng: &mut R, ) -> ( <::Field as Field>::Scalar, diff --git a/frost-ed25519/Cargo.toml b/frost-ed25519/Cargo.toml index 8a073961e..d2ea67e5c 100644 --- a/frost-ed25519/Cargo.toml +++ b/frost-ed25519/Cargo.toml @@ -1,36 +1,46 @@ [package] name = "frost-ed25519" -edition.workspace = true -rust-version.workspace = true version.workspace = true authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "A Schnorr signature scheme over Ed25519 that supports FROST." +documentation = "https://docs.rs/frost-ed25519" readme = "README.md" -license.workspace = true +homepage.workspace = true repository.workspace = true -categories.workspace = true +license.workspace = true keywords = ["cryptography", "crypto", "ed25519", "threshold", "signature"] -description = "A Schnorr signature scheme over Ed25519 that supports FROST." +categories.workspace = true [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false + [dependencies] -curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } +curve25519-dalek.workspace = true document-features.workspace = true frost-core.workspace = true frost-rerandomized.workspace = true rand_core.workspace = true -sha2 = { version = "0.10.2", default-features = false } +sha2.workspace = true [dev-dependencies] criterion.workspace = true +ed25519-dalek.workspace = true frost-core = { workspace = true, features = ["test-impl"] } frost-rerandomized = { workspace = true, features = ["test-impl"] } -ed25519-dalek = "2.1.0" -insta.workspace = true hex.workspace = true -lazy_static.workspace = true +insta.workspace = true proptest.workspace = true rand.workspace = true rand_chacha.workspace = true @@ -46,12 +56,3 @@ default = ["serialization"] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] - -[lib] -# Disables non-criterion benchmark which is not used; prevents errors -# when using criterion-specific flags -bench = false - -[[bench]] -name = "bench" -harness = false diff --git a/frost-ed25519/README.md b/frost-ed25519/README.md index 500f8f449..535855234 100644 --- a/frost-ed25519/README.md +++ b/frost-ed25519/README.md @@ -20,7 +20,7 @@ scenario in a single thread and it abstracts away any communication between peer use frost_ed25519 as frost; use std::collections::BTreeMap; -let mut rng = rand::rngs::OsRng; +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-ed25519/benches/bench.rs b/frost-ed25519/benches/bench.rs index 4317e05b1..e3286b97c 100644 --- a/frost-ed25519/benches/bench.rs +++ b/frost-ed25519/benches/bench.rs @@ -3,13 +3,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; use frost_ed25519::*; fn bench_ed25519_batch_verify(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_batch_verify::(c, "ed25519", &mut rng); } fn bench_ed25519_sign(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_sign::(c, "ed25519", &mut rng); } diff --git a/frost-ed25519/dkg.md b/frost-ed25519/dkg.md index 244d9ba3d..3cef012bf 100644 --- a/frost-ed25519/dkg.md +++ b/frost-ed25519/dkg.md @@ -20,11 +20,9 @@ first round of communication to ensure all participants have the same value. ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; - use frost_ed25519 as frost; -let mut rng = rand::rngs::OsRng; - +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; # // ANCHOR_END: dkg_import diff --git a/frost-ed25519/src/keys/dkg.rs b/frost-ed25519/src/keys/dkg.rs index 42407bbd3..9b7646214 100644 --- a/frost-ed25519/src/keys/dkg.rs +++ b/frost-ed25519/src/keys/dkg.rs @@ -46,7 +46,7 @@ pub mod round2 { /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the DKG run. -pub fn part1( +pub fn part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-ed25519/src/keys/refresh.rs b/frost-ed25519/src/keys/refresh.rs index bb4b6dd64..2d44f2bdb 100644 --- a/frost-ed25519/src/keys/refresh.rs +++ b/frost-ed25519/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, identifiers: &[Identifier], mut rng: &mut R, @@ -29,7 +29,7 @@ pub fn refresh_share( } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. -pub fn refresh_dkg_part1( +pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-ed25519/src/keys/repairable.rs b/frost-ed25519/src/keys/repairable.rs index 9e809ed30..74d9481cd 100644 --- a/frost-ed25519/src/keys/repairable.rs +++ b/frost-ed25519/src/keys/repairable.rs @@ -10,7 +10,7 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier}; use crate::{Ed25519Sha512, Error}; /// A delta value which is the output of part 1 of RTS. @@ -26,7 +26,7 @@ pub type Sigma = frost::keys::repairable::Sigma; /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_part1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, @@ -62,22 +62,17 @@ pub fn repair_share_part3( #[cfg(test)] mod tests { - - use lazy_static::lazy_static; - - use serde_json::Value; - use crate::Ed25519Sha512; + use serde_json::Value; + use std::sync::LazyLock; - lazy_static! { - pub static ref REPAIR_SHARE: Value = - serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()) - .unwrap(); - } + static REPAIR_SHARE: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()).unwrap() + }); #[test] fn check_repair_share_part1() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1::(rng); } @@ -89,7 +84,7 @@ mod tests { #[test] fn check_repair_share_part3() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, @@ -98,7 +93,7 @@ mod tests { #[test] fn check_repair_share_part1_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Ed25519Sha512, _, diff --git a/frost-ed25519/src/lib.rs b/frost-ed25519/src/lib.rs index cb7a818bf..4e57d5f1a 100644 --- a/frost-ed25519/src/lib.rs +++ b/frost-ed25519/src/lib.rs @@ -5,6 +5,9 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] +#[cfg(test)] +extern crate std; + extern crate alloc; use alloc::collections::BTreeMap; @@ -16,7 +19,7 @@ use curve25519_dalek::{ traits::Identity, }; use frost_rerandomized::RandomizedCiphersuite; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use sha2::{Digest, Sha512}; use frost_core as frost; @@ -60,7 +63,7 @@ impl Field for Ed25519ScalarField { } } - fn random(rng: &mut R) -> Self::Scalar { + fn random(rng: &mut R) -> Self::Scalar { Scalar::random(rng) } @@ -241,7 +244,7 @@ pub mod keys { /// Allows all participants' keys to be generated using a central, trusted /// dealer. - pub fn generate_with_dealer( + pub fn generate_with_dealer( max_signers: u16, min_signers: u16, identifiers: IdentifierList, @@ -256,7 +259,7 @@ pub mod keys { /// instead of generating a fresh one. This is useful in scenarios where /// the key needs to be generated externally or must be derived from e.g. a /// seed phrase. - pub fn split( + pub fn split( secret: &SigningKey, max_signers: u16, min_signers: u16, @@ -357,7 +360,7 @@ pub mod round1 { /// operation. pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where - RNG: CryptoRng + RngCore, + RNG: CryptoRng, { frost::round1::commit::(secret, rng) } diff --git a/frost-ed25519/src/tests/batch.rs b/frost-ed25519/src/tests/batch.rs index a57301f01..836f4d9d7 100644 --- a/frost-ed25519/src/tests/batch.rs +++ b/frost-ed25519/src/tests/batch.rs @@ -2,21 +2,21 @@ use crate::*; #[test] fn check_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-ed25519/src/tests/coefficient_commitment.rs b/frost-ed25519/src/tests/coefficient_commitment.rs index d45994e83..3614f1e85 100644 --- a/frost-ed25519/src/tests/coefficient_commitment.rs +++ b/frost-ed25519/src/tests/coefficient_commitment.rs @@ -1,18 +1,17 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization of CoefficientCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialization_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Ed25519Sha512, _, @@ -21,7 +20,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Ed25519Sha512, _, @@ -36,7 +35,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Ed25519Sha512, diff --git a/frost-ed25519/src/tests/vss_commitment.rs b/frost-ed25519/src/tests/vss_commitment.rs index 3e0fcd26e..78864e1d4 100644 --- a/frost-ed25519/src/tests/vss_commitment.rs +++ b/frost-ed25519/src/tests/vss_commitment.rs @@ -1,24 +1,23 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization VerifiableSecretSharingCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_serialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( rng, ); @@ -26,13 +25,13 @@ fn check_serialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } #[test] fn check_deserialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::( rng, ); @@ -40,7 +39,7 @@ fn check_deserialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -48,7 +47,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_deserialize_whole_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< Ed25519Sha512, _, @@ -57,6 +56,6 @@ fn check_deserialize_whole_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); } diff --git a/frost-ed25519/tests/common_traits_tests.rs b/frost-ed25519/tests/common_traits_tests.rs index db173929b..9d30215cd 100644 --- a/frost-ed25519/tests/common_traits_tests.rs +++ b/frost-ed25519/tests/common_traits_tests.rs @@ -19,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-ed25519/tests/integration_tests.rs b/frost-ed25519/tests/integration_tests.rs index 2cc6620a6..a2ba70708 100644 --- a/frost-ed25519/tests/integration_tests.rs +++ b/frost-ed25519/tests/integration_tests.rs @@ -1,6 +1,6 @@ use frost_ed25519::*; -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; #[test] fn check_zero_key_fails() { @@ -9,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -30,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -44,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -58,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( rng, @@ -81,7 +81,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Ed25519Sha512, @@ -91,7 +91,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -108,14 +108,14 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_refresh_shares_with_dkg_smaller_threshold() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::( rng, @@ -124,14 +124,14 @@ fn check_refresh_shares_with_dkg_smaller_threshold() { #[test] fn check_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -145,7 +145,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -159,7 +159,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -175,13 +175,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_ed25519_sha512() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 0; let max_signers = 3; @@ -195,7 +195,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -209,7 +209,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 0; @@ -221,17 +221,18 @@ fn check_share_generation_fails_with_invalid_max_signers() { >(min_signers, max_signers, error, rng); } -lazy_static! { - pub static ref VECTORS: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_BIG_IDENTIFIER: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_DKG: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) - .expect("Test vector is valid JSON"); -} +static VECTORS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_BIG_IDENTIFIER: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_DKG: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) + .expect("Test vector is valid JSON") +}); #[test] fn check_sign_with_test_vectors() { @@ -273,7 +274,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ed25519Sha512, @@ -283,7 +284,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( rng, ); @@ -291,7 +292,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::( rng, ); @@ -300,7 +301,7 @@ fn check_sign_with_incorrect_commitments() { #[tokio::test] async fn check_async_sign_with_dealer() { tokio::spawn(async { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; }) .await diff --git a/frost-ed25519/tests/interoperability_tests.rs b/frost-ed25519/tests/interoperability_tests.rs index e758ee248..16b060ac9 100644 --- a/frost-ed25519/tests/interoperability_tests.rs +++ b/frost-ed25519/tests/interoperability_tests.rs @@ -5,7 +5,7 @@ mod helpers; #[test] fn check_interoperability_in_sign_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. A smaller number of iterations is used @@ -20,7 +20,7 @@ fn check_interoperability_in_sign_with_dkg() { #[test] fn check_interoperability_in_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. diff --git a/frost-ed25519/tests/rerandomized_tests.rs b/frost-ed25519/tests/rerandomized_tests.rs index 121738744..0038e209c 100644 --- a/frost-ed25519/tests/rerandomized_tests.rs +++ b/frost-ed25519/tests/rerandomized_tests.rs @@ -2,7 +2,7 @@ use frost_ed25519::Ed25519Sha512; #[test] fn check_randomized_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-ed25519/tests/serde_tests.rs b/frost-ed25519/tests/serde_tests.rs index a36c278b9..2dabf4f72 100644 --- a/frost-ed25519/tests/serde_tests.rs +++ b/frost-ed25519/tests/serde_tests.rs @@ -19,7 +19,7 @@ fn check_signing_commitments_serialization() { let commitments = samples::signing_commitments(); let json = serde_json::to_string_pretty(&commitments).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); assert!(commitments == decoded_commitments); @@ -89,7 +89,7 @@ fn check_signing_package_serialization() { let signing_package = samples::signing_package(); let json = serde_json::to_string_pretty(&signing_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -204,7 +204,7 @@ fn check_signature_share_serialization() { let signature_share = samples::signature_share(); let json = serde_json::to_string_pretty(&signature_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); assert!(signature_share == decoded_signature_share); @@ -258,7 +258,7 @@ fn check_secret_share_serialization() { let secret_share = samples::secret_share(); let json = serde_json::to_string_pretty(&secret_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); assert!(secret_share == decoded_secret_share); @@ -342,7 +342,7 @@ fn check_key_package_serialization() { let key_package = samples::key_package(); let json = serde_json::to_string_pretty(&key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); assert!(key_package == decoded_key_package); @@ -437,7 +437,7 @@ fn check_public_key_package_serialization() { let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); assert!(public_key_package == decoded_public_key_package); @@ -532,7 +532,7 @@ fn check_round1_package_serialization() { let round1_package = samples::round1_package(); let json = serde_json::to_string_pretty(&round1_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); assert!(round1_package == decoded_round1_package); @@ -598,7 +598,7 @@ fn check_round2_package_serialization() { let round2_package = samples::round2_package(); let json = serde_json::to_string_pretty(&round2_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); assert!(round2_package == decoded_round2_package); diff --git a/frost-ed448/Cargo.toml b/frost-ed448/Cargo.toml index 5c26e30ee..e151ba184 100644 --- a/frost-ed448/Cargo.toml +++ b/frost-ed448/Cargo.toml @@ -1,35 +1,46 @@ [package] name = "frost-ed448" -edition.workspace = true -rust-version.workspace = true version.workspace = true authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "A Schnorr signature scheme over Ed448 that supports FROST." +documentation = "https://docs.rs/frost-ed448" readme = "README.md" -license.workspace = true +homepage.workspace = true repository.workspace = true -categories.workspace = true +license.workspace = true keywords = ["cryptography", "crypto", "ed448", "threshold", "signature"] -description = "A Schnorr signature scheme over Ed448 that supports FROST." +categories.workspace = true [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false + [dependencies] document-features.workspace = true -ed448-goldilocks = { version = "0.9.0" } +ed448-goldilocks.workspace = true frost-core.workspace = true frost-rerandomized.workspace = true rand_core.workspace = true -sha3 = { version = "0.10.6", default-features = false } +sha3.workspace = true +shake.workspace = true [dev-dependencies] criterion.workspace = true frost-core = { workspace = true, features = ["test-impl"] } frost-rerandomized = { workspace = true, features = ["test-impl"] } -lazy_static.workspace = true -insta.workspace = true hex.workspace = true +insta.workspace = true proptest.workspace = true rand.workspace = true rand_chacha.workspace = true @@ -45,12 +56,3 @@ default = ["serialization"] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] - -[lib] -# Disables non-criterion benchmark which is not used; prevents errors -# when using criterion-specific flags -bench = false - -[[bench]] -name = "bench" -harness = false diff --git a/frost-ed448/README.md b/frost-ed448/README.md index 77f1eb8c7..579552eb4 100644 --- a/frost-ed448/README.md +++ b/frost-ed448/README.md @@ -20,7 +20,7 @@ scenario in a single thread and it abstracts away any communication between peer use frost_ed448 as frost; use std::collections::BTreeMap; -let mut rng = rand::rngs::OsRng; +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-ed448/benches/bench.rs b/frost-ed448/benches/bench.rs index ba40feb33..13f60b26a 100644 --- a/frost-ed448/benches/bench.rs +++ b/frost-ed448/benches/bench.rs @@ -5,13 +5,13 @@ use frost_ed448::*; // bench_ed448_batch_verify not included until batch verification is fixed for Ed448 #[allow(unused)] fn bench_ed448_batch_verify(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_batch_verify::(c, "ed448", &mut rng); } fn bench_ed448_sign(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_sign::(c, "ed448", &mut rng); } diff --git a/frost-ed448/dkg.md b/frost-ed448/dkg.md index ff304b959..6ffb864d8 100644 --- a/frost-ed448/dkg.md +++ b/frost-ed448/dkg.md @@ -20,11 +20,9 @@ first round of communication to ensure all participants have the same value. ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; - use frost_ed448 as frost; -let mut rng = rand::rngs::OsRng; - +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; # // ANCHOR_END: dkg_import diff --git a/frost-ed448/src/keys/dkg.rs b/frost-ed448/src/keys/dkg.rs index 42407bbd3..9b7646214 100644 --- a/frost-ed448/src/keys/dkg.rs +++ b/frost-ed448/src/keys/dkg.rs @@ -46,7 +46,7 @@ pub mod round2 { /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the DKG run. -pub fn part1( +pub fn part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-ed448/src/keys/refresh.rs b/frost-ed448/src/keys/refresh.rs index bb4b6dd64..2d44f2bdb 100644 --- a/frost-ed448/src/keys/refresh.rs +++ b/frost-ed448/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, identifiers: &[Identifier], mut rng: &mut R, @@ -29,7 +29,7 @@ pub fn refresh_share( } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. -pub fn refresh_dkg_part1( +pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-ed448/src/keys/repairable.rs b/frost-ed448/src/keys/repairable.rs index f1e21d479..145bbfd5c 100644 --- a/frost-ed448/src/keys/repairable.rs +++ b/frost-ed448/src/keys/repairable.rs @@ -10,7 +10,7 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier}; use crate::{Ed448Shake256, Error}; /// A delta value which is the output of part 1 of RTS. @@ -26,7 +26,7 @@ pub type Sigma = frost::keys::repairable::Sigma; /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_part1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, @@ -62,22 +62,17 @@ pub fn repair_share_part3( #[cfg(test)] mod tests { - - use lazy_static::lazy_static; - - use serde_json::Value; - use crate::Ed448Shake256; + use serde_json::Value; + use std::sync::LazyLock; - lazy_static! { - pub static ref REPAIR_SHARE: Value = - serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()) - .unwrap(); - } + static REPAIR_SHARE: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()).unwrap() + }); #[test] fn check_repair_share_part1() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1::(rng); } @@ -89,7 +84,7 @@ mod tests { #[test] fn check_repair_share_part3() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, @@ -98,7 +93,7 @@ mod tests { #[test] fn check_repair_share_part1_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Ed448Shake256, _, diff --git a/frost-ed448/src/lib.rs b/frost-ed448/src/lib.rs index 154f445f6..9980e69b2 100644 --- a/frost-ed448/src/lib.rs +++ b/frost-ed448/src/lib.rs @@ -1,23 +1,22 @@ +#![no_std] #![allow(non_snake_case)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] +#[cfg(test)] +extern crate std; + extern crate alloc; use alloc::collections::BTreeMap; -use ed448_goldilocks::{ - curve::{edwards::CompressedEdwardsY, ExtendedPoint}, - Scalar, -}; +use ed448_goldilocks::{CompressedEdwardsY, EdwardsPoint, EdwardsScalar}; use frost_rerandomized::RandomizedCiphersuite; -use rand_core::{CryptoRng, RngCore}; -use sha3::{ - digest::{ExtendableOutput, Update, XofReader}, - Shake256, -}; +use rand_core::CryptoRng; +use sha3::digest::{ExtendableOutput, Update, XofReader}; +use shake::Shake256; use frost_core as frost; @@ -38,16 +37,16 @@ pub type Error = frost_core::Error; pub struct Ed448ScalarField; impl Field for Ed448ScalarField { - type Scalar = Scalar; + type Scalar = EdwardsScalar; type Serialization = [u8; 57]; fn zero() -> Self::Scalar { - Scalar::zero() + EdwardsScalar::ZERO } fn one() -> Self::Scalar { - Scalar::one() + EdwardsScalar::ONE } fn invert(scalar: &Self::Scalar) -> Result { @@ -58,16 +57,16 @@ impl Field for Ed448ScalarField { } } - fn random(rng: &mut R) -> Self::Scalar { - Scalar::random(rng) + fn random(rng: &mut R) -> Self::Scalar { + EdwardsScalar::random(rng) } fn serialize(scalar: &Self::Scalar) -> Self::Serialization { - scalar.to_bytes_rfc_8032() + scalar.to_bytes_rfc_8032().into() } fn deserialize(buf: &Self::Serialization) -> Result { - match Scalar::from_canonical_bytes(*buf) { + match EdwardsScalar::from_canonical_bytes(buf.into()).into() { Some(s) => Ok(s), None => Err(FieldError::MalformedScalar), } @@ -85,42 +84,42 @@ pub struct Ed448Group; impl Group for Ed448Group { type Field = Ed448ScalarField; - type Element = ExtendedPoint; + type Element = EdwardsPoint; type Serialization = [u8; 57]; fn cofactor() -> ::Scalar { - Scalar::one() + EdwardsScalar::ONE } fn identity() -> Self::Element { - Self::Element::identity() + Self::Element::IDENTITY } fn generator() -> Self::Element { - Self::Element::generator() + Self::Element::GENERATOR } fn serialize(element: &Self::Element) -> Result { if *element == Self::identity() { return Err(GroupError::InvalidIdentityElement); } - Ok(element.compress().0) + Ok(element.to_affine().compress().0) } fn deserialize(buf: &Self::Serialization) -> Result { let compressed = CompressedEdwardsY(*buf); - match compressed.decompress() { + match compressed.decompress_unchecked().into_option() { Some(point) => { - if point == Self::identity() { + if point == Self::identity().to_affine() { Err(GroupError::InvalidIdentityElement) - } else if point.is_torsion_free() { + } else if point.to_edwards().is_torsion_free().into() { // decompress() does not check for canonicality, so we // check by recompressing and comparing if point.compress().0 != compressed.0 { Err(GroupError::MalformedElement) } else { - Ok(point) + Ok(point.to_edwards()) } } else { Err(GroupError::InvalidNonPrimeOrderElement) @@ -142,9 +141,9 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 114] { output } -fn hash_to_scalar(inputs: &[&[u8]]) -> Scalar { - let output = hash_to_array(inputs); - Scalar::from_bytes_mod_order_wide(&output) +fn hash_to_scalar(inputs: &[&[u8]]) -> EdwardsScalar { + let temp = hash_to_array(inputs); + EdwardsScalar::from_bytes_mod_order_wide(&temp.into()) } /// Context string from the ciphersuite in the [spec] @@ -235,7 +234,7 @@ pub mod keys { /// Allows all participants' keys to be generated using a central, trusted /// dealer. - pub fn generate_with_dealer( + pub fn generate_with_dealer( max_signers: u16, min_signers: u16, identifiers: IdentifierList, @@ -250,7 +249,7 @@ pub mod keys { /// instead of generating a fresh one. This is useful in scenarios where /// the key needs to be generated externally or must be derived from e.g. a /// seed phrase. - pub fn split( + pub fn split( secret: &SigningKey, max_signers: u16, min_signers: u16, @@ -351,7 +350,7 @@ pub mod round1 { /// operation. pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where - RNG: CryptoRng + RngCore, + RNG: CryptoRng, { frost::round1::commit::(secret, rng) } diff --git a/frost-ed448/src/tests/batch.rs b/frost-ed448/src/tests/batch.rs index 5c84b5e50..2e1838af7 100644 --- a/frost-ed448/src/tests/batch.rs +++ b/frost-ed448/src/tests/batch.rs @@ -2,21 +2,21 @@ use crate::*; #[test] fn check_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-ed448/src/tests/coefficient_commitment.rs b/frost-ed448/src/tests/coefficient_commitment.rs index 0d2091aa6..be54d10cd 100644 --- a/frost-ed448/src/tests/coefficient_commitment.rs +++ b/frost-ed448/src/tests/coefficient_commitment.rs @@ -1,18 +1,17 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization of CoefficientCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialization_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Ed448Shake256, _, @@ -21,7 +20,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Ed448Shake256, _, @@ -36,7 +35,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Ed448Shake256, diff --git a/frost-ed448/src/tests/deserialize.rs b/frost-ed448/src/tests/deserialize.rs index 6c86b77f6..d5294e291 100644 --- a/frost-ed448/src/tests/deserialize.rs +++ b/frost-ed448/src/tests/deserialize.rs @@ -1,10 +1,10 @@ use crate::*; -use ed448_goldilocks::curve::ExtendedPoint; +use ed448_goldilocks::EdwardsPoint; use frost_core::Ciphersuite; #[test] fn check_deserialize_non_canonical() { - let mut encoded_generator = ExtendedPoint::generator().compress().0; + let mut encoded_generator = EdwardsPoint::GENERATOR.to_affine().compress().0; let r = ::Group::deserialize(&encoded_generator); assert!(r.is_ok()); @@ -35,7 +35,7 @@ fn check_deserialize_non_prime_order() { #[test] fn check_deserialize_identity() { - let encoded_identity = ExtendedPoint::identity().compress().0; + let encoded_identity = EdwardsPoint::IDENTITY.to_affine().compress().0; let r = ::Group::deserialize(&encoded_identity); assert_eq!(r, Err(GroupError::InvalidIdentityElement)); diff --git a/frost-ed448/src/tests/vss_commitment.rs b/frost-ed448/src/tests/vss_commitment.rs index 7e1f83441..b2ac27df4 100644 --- a/frost-ed448/src/tests/vss_commitment.rs +++ b/frost-ed448/src/tests/vss_commitment.rs @@ -1,24 +1,23 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization VerifiableSecretSharingCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_serialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( rng, ); @@ -26,13 +25,13 @@ fn check_serialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } #[test] fn check_deserialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::( rng, ); @@ -40,7 +39,7 @@ fn check_deserialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -48,7 +47,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_deserialize_whole_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< Ed448Shake256, _, @@ -57,6 +56,6 @@ fn check_deserialize_whole_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); } diff --git a/frost-ed448/tests/common_traits_tests.rs b/frost-ed448/tests/common_traits_tests.rs index 749cc7033..022954d42 100644 --- a/frost-ed448/tests/common_traits_tests.rs +++ b/frost-ed448/tests/common_traits_tests.rs @@ -19,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-ed448/tests/integration_tests.rs b/frost-ed448/tests/integration_tests.rs index 28bd22ad2..8d62303d6 100644 --- a/frost-ed448/tests/integration_tests.rs +++ b/frost-ed448/tests/integration_tests.rs @@ -1,6 +1,6 @@ use frost_ed448::*; -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; #[test] fn check_zero_key_fails() { @@ -9,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -30,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -44,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -58,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( rng, @@ -81,7 +81,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Ed448Shake256, @@ -91,7 +91,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -108,14 +108,14 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_refresh_shares_with_dkg_smaller_threshold() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::( rng, @@ -124,14 +124,14 @@ fn check_refresh_shares_with_dkg_smaller_threshold() { #[test] fn check_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -145,7 +145,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -159,7 +159,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -175,13 +175,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_ed448_shake256() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 0; let max_signers = 3; @@ -195,7 +195,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -209,7 +209,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 0; @@ -221,17 +221,18 @@ fn check_share_generation_fails_with_invalid_max_signers() { >(min_signers, max_signers, error, rng); } -lazy_static! { - pub static ref VECTORS: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_BIG_IDENTIFIER: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_DKG: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) - .expect("Test vector is valid JSON"); -} +static VECTORS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_BIG_IDENTIFIER: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_DKG: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) + .expect("Test vector is valid JSON") +}); #[test] fn check_sign_with_test_vectors() { @@ -273,7 +274,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ed448Shake256, @@ -283,7 +284,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( rng, ); @@ -291,7 +292,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::( rng, ); @@ -300,7 +301,7 @@ fn check_sign_with_incorrect_commitments() { #[tokio::test] async fn check_async_sign_with_dealer() { tokio::spawn(async { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; }) .await diff --git a/frost-ed448/tests/rerandomized_tests.rs b/frost-ed448/tests/rerandomized_tests.rs index e16d906ce..fd1783c0d 100644 --- a/frost-ed448/tests/rerandomized_tests.rs +++ b/frost-ed448/tests/rerandomized_tests.rs @@ -2,7 +2,7 @@ use frost_ed448::Ed448Shake256; #[test] fn check_randomized_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-ed448/tests/serde_tests.rs b/frost-ed448/tests/serde_tests.rs index db380eed1..3d5720322 100644 --- a/frost-ed448/tests/serde_tests.rs +++ b/frost-ed448/tests/serde_tests.rs @@ -19,7 +19,7 @@ fn check_signing_commitments_serialization() { let commitments = samples::signing_commitments(); let json = serde_json::to_string_pretty(&commitments).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); assert!(commitments == decoded_commitments); @@ -89,7 +89,7 @@ fn check_signing_package_serialization() { let signing_package = samples::signing_package(); let json = serde_json::to_string_pretty(&signing_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -204,7 +204,7 @@ fn check_signature_share_serialization() { let signature_share = samples::signature_share(); let json = serde_json::to_string_pretty(&signature_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); assert!(signature_share == decoded_signature_share); @@ -258,7 +258,7 @@ fn check_secret_share_serialization() { let secret_share = samples::secret_share(); let json = serde_json::to_string_pretty(&secret_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); assert!(secret_share == decoded_secret_share); @@ -342,7 +342,7 @@ fn check_key_package_serialization() { let key_package = samples::key_package(); let json = serde_json::to_string_pretty(&key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); assert!(key_package == decoded_key_package); @@ -437,7 +437,7 @@ fn check_public_key_package_serialization() { let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); assert!(public_key_package == decoded_public_key_package); @@ -532,7 +532,7 @@ fn check_round1_package_serialization() { let round1_package = samples::round1_package(); let json = serde_json::to_string_pretty(&round1_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); assert!(round1_package == decoded_round1_package); @@ -598,7 +598,7 @@ fn check_round2_package_serialization() { let round2_package = samples::round2_package(); let json = serde_json::to_string_pretty(&round2_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); assert!(round2_package == decoded_round2_package); diff --git a/frost-p256/Cargo.toml b/frost-p256/Cargo.toml index 68e63b3cf..d6de547ee 100644 --- a/frost-p256/Cargo.toml +++ b/frost-p256/Cargo.toml @@ -1,35 +1,45 @@ [package] name = "frost-p256" -edition.workspace = true -rust-version.workspace = true version.workspace = true authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "A Schnorr signature scheme over the NIST P-256 curve that supports FROST." +documentation = "https://docs.rs/frost-p256" readme = "README.md" -license.workspace = true +homepage.workspace = true repository.workspace = true -categories.workspace = true +license.workspace = true keywords = ["cryptography", "crypto", "p256", "threshold", "signature"] -description = "A Schnorr signature scheme over the NIST P-256 curve that supports FROST." +categories.workspace = true [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false + [dependencies] document-features.workspace = true -p256 = { version = "0.13.0", features = ["hash2curve"], default-features = false } frost-core.workspace = true frost-rerandomized.workspace = true +p256.workspace = true rand_core.workspace = true -sha2 = { version = "0.10.2", default-features = false } +sha2.workspace = true [dev-dependencies] criterion.workspace = true frost-core = { workspace = true, features = ["test-impl"] } frost-rerandomized = { workspace = true, features = ["test-impl"] } -insta.workspace = true hex.workspace = true -lazy_static.workspace = true +insta.workspace = true proptest.workspace = true rand.workspace = true rand_chacha.workspace = true @@ -45,12 +55,3 @@ default = ["serialization"] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] - -[lib] -# Disables non-criterion benchmark which is not used; prevents errors -# when using criterion-specific flags -bench = false - -[[bench]] -name = "bench" -harness = false diff --git a/frost-p256/README.md b/frost-p256/README.md index 47f391fb2..3e6fc4e2c 100644 --- a/frost-p256/README.md +++ b/frost-p256/README.md @@ -20,7 +20,7 @@ scenario in a single thread and it abstracts away any communication between peer use frost_p256 as frost; use std::collections::BTreeMap; -let mut rng = rand::rngs::OsRng; +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-p256/benches/bench.rs b/frost-p256/benches/bench.rs index 8ae524f5c..d2aed996b 100644 --- a/frost-p256/benches/bench.rs +++ b/frost-p256/benches/bench.rs @@ -3,13 +3,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; use frost_p256::*; fn bench_p256_batch_verify(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_batch_verify::(c, "p256", &mut rng); } fn bench_p256_sign(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_sign::(c, "p256", &mut rng); } diff --git a/frost-p256/dkg.md b/frost-p256/dkg.md index cd64fd941..4e06a6d25 100644 --- a/frost-p256/dkg.md +++ b/frost-p256/dkg.md @@ -20,11 +20,9 @@ first round of communication to ensure all participants have the same value. ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; - use frost_p256 as frost; -let mut rng = rand::rngs::OsRng; - +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; # // ANCHOR_END: dkg_import diff --git a/frost-p256/src/keys/dkg.rs b/frost-p256/src/keys/dkg.rs index dfd95384a..ed2d5bdef 100644 --- a/frost-p256/src/keys/dkg.rs +++ b/frost-p256/src/keys/dkg.rs @@ -46,7 +46,7 @@ pub mod round2 { /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the DKG run. -pub fn part1( +pub fn part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-p256/src/keys/refresh.rs b/frost-p256/src/keys/refresh.rs index bb4b6dd64..2d44f2bdb 100644 --- a/frost-p256/src/keys/refresh.rs +++ b/frost-p256/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, identifiers: &[Identifier], mut rng: &mut R, @@ -29,7 +29,7 @@ pub fn refresh_share( } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. -pub fn refresh_dkg_part1( +pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-p256/src/keys/repairable.rs b/frost-p256/src/keys/repairable.rs index 98db9d1aa..0682db99a 100644 --- a/frost-p256/src/keys/repairable.rs +++ b/frost-p256/src/keys/repairable.rs @@ -10,7 +10,7 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier}; use crate::{Error, P256Sha256}; /// A delta value which is the output of part 1 of RTS. @@ -26,7 +26,7 @@ pub type Sigma = frost::keys::repairable::Sigma; /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_part1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, @@ -62,22 +62,17 @@ pub fn repair_share_part3( #[cfg(test)] mod tests { - - use lazy_static::lazy_static; - - use serde_json::Value; - use crate::P256Sha256; + use serde_json::Value; + use std::sync::LazyLock; - lazy_static! { - pub static ref REPAIR_SHARE: Value = - serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()) - .unwrap(); - } + static REPAIR_SHARE: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()).unwrap() + }); #[test] fn check_repair_share_part1() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1::(rng); } @@ -89,7 +84,7 @@ mod tests { #[test] fn check_repair_share_part3() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, @@ -98,7 +93,7 @@ mod tests { #[test] fn check_repair_share_part1_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< P256Sha256, _, diff --git a/frost-p256/src/lib.rs b/frost-p256/src/lib.rs index 42aa94f1b..6495251a3 100644 --- a/frost-p256/src/lib.rs +++ b/frost-p256/src/lib.rs @@ -5,6 +5,9 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] +#[cfg(test)] +extern crate std; + extern crate alloc; use alloc::collections::BTreeMap; @@ -12,13 +15,13 @@ use alloc::collections::BTreeMap; use frost_rerandomized::RandomizedCiphersuite; use p256::{ elliptic_curve::{ - hash2curve::{hash_to_field, ExpandMsgXmd}, - sec1::{FromEncodedPoint, ToEncodedPoint}, + sec1::{FromSec1Point, ToSec1Point}, Field as FFField, PrimeField, }, - AffinePoint, ProjectivePoint, Scalar, + hash2curve::{hash_to_field, ExpandMsgXmd, MapToCurve}, + AffinePoint, NistP256, ProjectivePoint, Scalar, }; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use sha2::{Digest, Sha256}; use frost_core as frost; @@ -62,7 +65,7 @@ impl Field for P256ScalarField { } } - fn random(rng: &mut R) -> Self::Scalar { + fn random(rng: &mut R) -> Self::Scalar { Scalar::random(rng) } @@ -121,7 +124,7 @@ impl Group for P256Group { return Err(GroupError::InvalidIdentityElement); } let mut fixed_serialized = [0; 33]; - let serialized_point = element.to_encoded_point(true); + let serialized_point = element.to_sec1_point(true); let serialized = serialized_point.as_bytes(); fixed_serialized.copy_from_slice(serialized); Ok(fixed_serialized) @@ -129,9 +132,9 @@ impl Group for P256Group { fn deserialize(buf: &Self::Serialization) -> Result { let encoded_point = - p256::EncodedPoint::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?; + p256::Sec1Point::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?; - match Option::::from(AffinePoint::from_encoded_point(&encoded_point)) { + match Option::::from(AffinePoint::from_sec1_point(&encoded_point)) { Some(point) => { if point.is_identity().into() { // This is actually impossible since the identity is encoded in a single byte @@ -158,9 +161,14 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { } fn hash_to_scalar(domain: &[&[u8]], msg: &[u8]) -> Scalar { - let mut u = [P256ScalarField::zero()]; - hash_to_field::, Scalar>(&[msg], domain, &mut u) - .expect("should never return error according to error cases described in ExpandMsgXmd"); + let u = hash_to_field::< + 1, + ExpandMsgXmd, + ::SecurityLevel, + Scalar, + ::Length, + >(&[msg], domain) + .expect("should never return error according to error cases described in ExpandMsgXmd"); u[0] } @@ -251,7 +259,7 @@ pub mod keys { /// Allows all participants' keys to be generated using a central, trusted /// dealer. - pub fn generate_with_dealer( + pub fn generate_with_dealer( max_signers: u16, min_signers: u16, identifiers: IdentifierList, @@ -266,7 +274,7 @@ pub mod keys { /// instead of generating a fresh one. This is useful in scenarios where /// the key needs to be generated externally or must be derived from e.g. a /// seed phrase. - pub fn split( + pub fn split( secret: &SigningKey, max_signers: u16, min_signers: u16, @@ -367,7 +375,7 @@ pub mod round1 { /// operation. pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where - RNG: CryptoRng + RngCore, + RNG: CryptoRng, { frost::round1::commit::(secret, rng) } diff --git a/frost-p256/src/tests/batch.rs b/frost-p256/src/tests/batch.rs index 3a46bfdd3..47f01b01b 100644 --- a/frost-p256/src/tests/batch.rs +++ b/frost-p256/src/tests/batch.rs @@ -2,21 +2,21 @@ use crate::*; #[test] fn check_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-p256/src/tests/coefficient_commitment.rs b/frost-p256/src/tests/coefficient_commitment.rs index e52f8398f..4edbe2035 100644 --- a/frost-p256/src/tests/coefficient_commitment.rs +++ b/frost-p256/src/tests/coefficient_commitment.rs @@ -1,18 +1,17 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization of CoefficientCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialization_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< P256Sha256, _, @@ -21,7 +20,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::( rng, ); @@ -35,7 +34,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< P256Sha256, diff --git a/frost-p256/src/tests/vss_commitment.rs b/frost-p256/src/tests/vss_commitment.rs index 44c08c5be..ab15190ca 100644 --- a/frost-p256/src/tests/vss_commitment.rs +++ b/frost-p256/src/tests/vss_commitment.rs @@ -1,42 +1,41 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization VerifiableSecretSharingCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_serialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } #[test] fn check_deserialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::(rng); } #[test] fn check_deserialize_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -44,7 +43,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_deserialize_whole_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::( rng, &ELEMENTS, ); @@ -52,6 +51,6 @@ fn check_deserialize_whole_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); } diff --git a/frost-p256/tests/common_traits_tests.rs b/frost-p256/tests/common_traits_tests.rs index 8dc2d739d..574f5259e 100644 --- a/frost-p256/tests/common_traits_tests.rs +++ b/frost-p256/tests/common_traits_tests.rs @@ -19,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-p256/tests/integration_tests.rs b/frost-p256/tests/integration_tests.rs index d1d27f440..1902f4a62 100644 --- a/frost-p256/tests/integration_tests.rs +++ b/frost-p256/tests/integration_tests.rs @@ -1,6 +1,6 @@ use frost_p256::*; -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; #[test] fn check_zero_key_fails() { @@ -9,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -30,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -44,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -58,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( rng, @@ -81,7 +81,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< P256Sha256, @@ -91,7 +91,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -108,14 +108,14 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_refresh_shares_with_dkg_smaller_threshold() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::( rng, @@ -124,14 +124,14 @@ fn check_refresh_shares_with_dkg_smaller_threshold() { #[test] fn check_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -145,7 +145,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -159,7 +159,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -175,13 +175,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_p256_sha256() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 0; let max_signers = 3; @@ -195,7 +195,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -209,7 +209,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 0; @@ -221,17 +221,18 @@ fn check_share_generation_fails_with_invalid_max_signers() { >(min_signers, max_signers, error, rng); } -lazy_static! { - pub static ref VECTORS: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_BIG_IDENTIFIER: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_DKG: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) - .expect("Test vector is valid JSON"); -} +static VECTORS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_BIG_IDENTIFIER: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_DKG: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) + .expect("Test vector is valid JSON") +}); #[test] fn check_sign_with_test_vectors() { @@ -271,7 +272,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::( rng, @@ -280,7 +281,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( rng, ); @@ -288,7 +289,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::( rng, ); @@ -297,7 +298,7 @@ fn check_sign_with_incorrect_commitments() { #[tokio::test] async fn check_async_sign_with_dealer() { tokio::spawn(async { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; }) .await diff --git a/frost-p256/tests/rerandomized_tests.rs b/frost-p256/tests/rerandomized_tests.rs index 6dc482ce6..f03618b82 100644 --- a/frost-p256/tests/rerandomized_tests.rs +++ b/frost-p256/tests/rerandomized_tests.rs @@ -2,7 +2,7 @@ use frost_p256::P256Sha256; #[test] fn check_randomized_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-p256/tests/serde_tests.rs b/frost-p256/tests/serde_tests.rs index 7091bd7b3..08844489b 100644 --- a/frost-p256/tests/serde_tests.rs +++ b/frost-p256/tests/serde_tests.rs @@ -19,7 +19,7 @@ fn check_signing_commitments_serialization() { let commitments = samples::signing_commitments(); let json = serde_json::to_string_pretty(&commitments).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); assert!(commitments == decoded_commitments); @@ -89,7 +89,7 @@ fn check_signing_package_serialization() { let signing_package = samples::signing_package(); let json = serde_json::to_string_pretty(&signing_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -204,7 +204,7 @@ fn check_signature_share_serialization() { let signature_share = samples::signature_share(); let json = serde_json::to_string_pretty(&signature_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); assert!(signature_share == decoded_signature_share); @@ -258,7 +258,7 @@ fn check_secret_share_serialization() { let secret_share = samples::secret_share(); let json = serde_json::to_string_pretty(&secret_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); assert!(secret_share == decoded_secret_share); @@ -342,7 +342,7 @@ fn check_key_package_serialization() { let key_package = samples::key_package(); let json = serde_json::to_string_pretty(&key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); assert!(key_package == decoded_key_package); @@ -437,7 +437,7 @@ fn check_public_key_package_serialization() { let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); assert!(public_key_package == decoded_public_key_package); @@ -532,7 +532,7 @@ fn check_round1_package_serialization() { let round1_package = samples::round1_package(); let json = serde_json::to_string_pretty(&round1_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); assert!(round1_package == decoded_round1_package); @@ -598,7 +598,7 @@ fn check_round2_package_serialization() { let round2_package = samples::round2_package(); let json = serde_json::to_string_pretty(&round2_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); assert!(round2_package == decoded_round2_package); diff --git a/frost-rerandomized/Cargo.toml b/frost-rerandomized/Cargo.toml index 2579900e9..e526d5d29 100644 --- a/frost-rerandomized/Cargo.toml +++ b/frost-rerandomized/Cargo.toml @@ -1,29 +1,29 @@ [package] name = "frost-rerandomized" -edition.workspace = true -rust-version.workspace = true version.workspace = true authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "Types and traits to support implementing a re-randomized variant of Flexible Round-Optimized Schnorr Threshold signature schemes (FROST)." +documentation = "https://docs.rs/frost-rerandomized" readme = "README.md" -license.workspace = true +homepage.workspace = true repository.workspace = true -categories.workspace = true +license.workspace = true keywords = ["cryptography", "threshold", "signature", "schnorr", "randomized"] -description = "Types and traits to support implementing a re-randomized variant of Flexible Round-Optimized Schnorr Threshold signature schemes (FROST)." +categories.workspace = true [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -derive-getters = "0.5.0" +derive-getters.workspace = true document-features.workspace = true frost-core = { workspace = true, features = ["internals"] } hex.workspace = true rand_core.workspace = true -[dev-dependencies] - [features] default = ["serialization"] #! ## Features diff --git a/frost-rerandomized/src/lib.rs b/frost-rerandomized/src/lib.rs index 0b5e05715..95c2c228c 100644 --- a/frost-rerandomized/src/lib.rs +++ b/frost-rerandomized/src/lib.rs @@ -40,7 +40,7 @@ use frost_core::serde; // When pulled into `reddsa`, that has its own sibling `rand_core` import. // For the time being, we do not re-export this `rand_core`. -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; /// Randomize the given key type for usage in a FROST signing with re-randomized keys, /// using the given [`RandomizedParams`]. @@ -234,7 +234,7 @@ where #[deprecated( note = "switch to new_from_commitments(), passing the commitments from SigningPackage" )] - pub fn new( + pub fn new( mut rng: R, signing_package: &SigningPackage, ) -> Result> { @@ -273,7 +273,7 @@ where /// Returns the Randomizer and the generate randomizer seed. Both can be /// used to regenerate the Randomizer with /// [`Self::regenerate_from_seed_and_commitments()`]. - pub fn new_from_commitments( + pub fn new_from_commitments( mut rng: R, signing_commitments: &BTreeMap, SigningCommitments>, ) -> Result<(Self, Vec), Error> { @@ -381,7 +381,7 @@ where #[deprecated( note = "switch to new_from_commitments(), passing the commitments from SigningPackage" )] - pub fn new( + pub fn new( group_verifying_key: &VerifyingKey, signing_package: &SigningPackage, rng: R, @@ -404,7 +404,7 @@ where /// Returns the generated [`RandomizedParams`] and a randomizer seed. Both /// can be used to regenerate the [`RandomizedParams`] with /// [`Self::regenerate_from_seed_and_commitments()`]. - pub fn new_from_commitments( + pub fn new_from_commitments( group_verifying_key: &VerifyingKey, signing_commitments: &BTreeMap, SigningCommitments>, rng: R, diff --git a/frost-rerandomized/src/tests.rs b/frost-rerandomized/src/tests.rs index 0b5264988..de51a5b4c 100644 --- a/frost-rerandomized/src/tests.rs +++ b/frost-rerandomized/src/tests.rs @@ -9,12 +9,12 @@ use crate::{frost_core as frost, RandomizedCiphersuite, RandomizedParams, Random use frost_core::{ round1::SigningCommitments, Field, Group, Identifier, Signature, SigningPackage, VerifyingKey, }; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; /// Test re-randomized FROST signing with trusted dealer with a Ciphersuite. /// Returns the signed message, generated signature, and the randomized public key /// so that the caller can verify the signature with their own implementation. -pub fn check_randomized_sign_with_dealer( +pub fn check_randomized_sign_with_dealer( mut rng: R, ) -> (Vec, Signature, VerifyingKey) { //////////////////////////////////////////////////////////////////////////// @@ -133,7 +133,7 @@ pub fn check_randomized_sign_with_dealer( +fn check_randomizer( pubkeys: &frost::keys::PublicKeyPackage, signing_package: &frost::SigningPackage, mut rng: &mut R, @@ -145,7 +145,7 @@ fn check_randomizer( check_from_seed_and_signing_commitments(&mut rng, signing_package.signing_commitments()); } -fn check_from_randomizer( +fn check_from_randomizer( rng: &mut R, signing_package: &SigningPackage, pubkeys: &frost::keys::PublicKeyPackage, @@ -158,7 +158,7 @@ fn check_from_randomizer( assert!(*randomizer_params.randomizer() == randomizer); } -fn check_from_randomizer_and_signing_package( +fn check_from_randomizer_and_signing_package( mut rng: &mut R, signing_package: &SigningPackage, ) { @@ -190,7 +190,7 @@ fn check_from_randomizer_and_signing_package( +fn check_from_seed_and_signing_commitments( mut rng: &mut R, signing_commitments: &BTreeMap, SigningCommitments>, ) { diff --git a/frost-ristretto255/Cargo.toml b/frost-ristretto255/Cargo.toml index 3233dc451..fea6d615b 100644 --- a/frost-ristretto255/Cargo.toml +++ b/frost-ristretto255/Cargo.toml @@ -1,36 +1,46 @@ [package] name = "frost-ristretto255" -edition.workspace = true -rust-version.workspace = true version.workspace = true authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "A Schnorr signature scheme over the prime-order Ristretto group that supports FROST." +documentation = "https://docs.rs/frost-ristretto255" readme = "README.md" -license.workspace = true +homepage.workspace = true repository.workspace = true -categories.workspace = true +license.workspace = true keywords = ["cryptography", "crypto", "ristretto", "threshold", "signature"] -description = "A Schnorr signature scheme over the prime-order Ristretto group that supports FROST." +categories.workspace = true [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false + [dependencies] -curve25519-dalek = { version = "=4.1.3", features = ["rand_core"] } +curve25519-dalek.workspace = true document-features.workspace = true frost-core.workspace = true frost-rerandomized.workspace = true rand_core.workspace = true -sha2 = { version = "0.10.2", default-features = false } +sha2.workspace = true [dev-dependencies] criterion = { workspace = true, features = ["html_reports"] } frost-core = { workspace = true, features = ["test-impl"] } frost-rerandomized = { workspace = true, features = ["test-impl"] } -insta.workspace = true hex.workspace = true -lazy_static.workspace = true -postcard = { version = "1.0.0", features = ["use-std"] } +insta.workspace = true +postcard = { workspace = true, features = ["use-std"] } proptest.workspace = true rand.workspace = true rand_chacha.workspace = true @@ -46,12 +56,3 @@ default = ["serialization"] serde = ["frost-core/serde", "curve25519-dalek/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] - -[lib] -# Disables non-criterion benchmark which is not used; prevents errors -# when using criterion-specific flags -bench = false - -[[bench]] -name = "bench" -harness = false diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index 0f061bd95..30397b7a2 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -20,7 +20,7 @@ scenario in a single thread and it abstracts away any communication between peer use frost_ristretto255 as frost; use std::collections::BTreeMap; -let mut rng = rand::rngs::OsRng; +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-ristretto255/benches/bench.rs b/frost-ristretto255/benches/bench.rs index b7e9af3bd..b864c73b8 100644 --- a/frost-ristretto255/benches/bench.rs +++ b/frost-ristretto255/benches/bench.rs @@ -3,13 +3,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; use frost_ristretto255::*; fn bench_ristretto255_batch_verify(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_batch_verify::(c, "ristretto255", &mut rng); } fn bench_ristretto255_sign(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_sign::(c, "ristretto255", &mut rng); } diff --git a/frost-ristretto255/dkg.md b/frost-ristretto255/dkg.md index fcba23beb..21e446374 100644 --- a/frost-ristretto255/dkg.md +++ b/frost-ristretto255/dkg.md @@ -20,11 +20,9 @@ first round of communication to ensure all participants have the same value. ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; - use frost_ristretto255 as frost; -let mut rng = rand::rngs::OsRng; - +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; # // ANCHOR_END: dkg_import diff --git a/frost-ristretto255/src/keys/dkg.rs b/frost-ristretto255/src/keys/dkg.rs index 2d13265bb..65914810f 100644 --- a/frost-ristretto255/src/keys/dkg.rs +++ b/frost-ristretto255/src/keys/dkg.rs @@ -46,7 +46,7 @@ pub mod round2 { /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the DKG run. -pub fn part1( +pub fn part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-ristretto255/src/keys/refresh.rs b/frost-ristretto255/src/keys/refresh.rs index bb4b6dd64..2d44f2bdb 100644 --- a/frost-ristretto255/src/keys/refresh.rs +++ b/frost-ristretto255/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, identifiers: &[Identifier], mut rng: &mut R, @@ -29,7 +29,7 @@ pub fn refresh_share( } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. -pub fn refresh_dkg_part1( +pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-ristretto255/src/keys/repairable.rs b/frost-ristretto255/src/keys/repairable.rs index 3c316bcc3..cd8f94bd0 100644 --- a/frost-ristretto255/src/keys/repairable.rs +++ b/frost-ristretto255/src/keys/repairable.rs @@ -10,7 +10,7 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier}; use crate::{Error, Ristretto255Sha512}; /// A delta value which is the output of part 1 of RTS. @@ -26,7 +26,7 @@ pub type Sigma = frost::keys::repairable::Sigma; /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_part1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, @@ -62,22 +62,17 @@ pub fn repair_share_part3( #[cfg(test)] mod tests { - - use lazy_static::lazy_static; - - use serde_json::Value; - use crate::Ristretto255Sha512; + use serde_json::Value; + use std::sync::LazyLock; - lazy_static! { - pub static ref REPAIR_SHARE: Value = - serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()) - .unwrap(); - } + static REPAIR_SHARE: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()).unwrap() + }); #[test] fn check_repair_share_part1() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1::(rng); } @@ -91,7 +86,7 @@ mod tests { #[test] fn check_repair_share_part3() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, @@ -100,7 +95,7 @@ mod tests { #[test] fn check_repair_share_part1_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Ristretto255Sha512, _, diff --git a/frost-ristretto255/src/lib.rs b/frost-ristretto255/src/lib.rs index 490e34c87..064e8ff3a 100644 --- a/frost-ristretto255/src/lib.rs +++ b/frost-ristretto255/src/lib.rs @@ -3,6 +3,9 @@ #![deny(missing_docs)] #![doc = include_str!("../README.md")] +#[cfg(test)] +extern crate std; + extern crate alloc; use alloc::collections::BTreeMap; @@ -14,7 +17,7 @@ use curve25519_dalek::{ traits::Identity, }; use frost_rerandomized::RandomizedCiphersuite; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use sha2::{Digest, Sha512}; use frost_core as frost; @@ -60,7 +63,7 @@ impl Field for RistrettoScalarField { } } - fn random(rng: &mut R) -> Self::Scalar { + fn random(rng: &mut R) -> Self::Scalar { Scalar::random(rng) } @@ -230,7 +233,7 @@ pub mod keys { /// Allows all participants' keys to be generated using a central, trusted /// dealer. - pub fn generate_with_dealer( + pub fn generate_with_dealer( max_signers: u16, min_signers: u16, identifiers: IdentifierList, @@ -245,7 +248,7 @@ pub mod keys { /// instead of generating a fresh one. This is useful in scenarios where /// the key needs to be generated externally or must be derived from e.g. a /// seed phrase. - pub fn split( + pub fn split( secret: &SigningKey, max_signers: u16, min_signers: u16, @@ -346,7 +349,7 @@ pub mod round1 { /// operation. pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where - RNG: CryptoRng + RngCore, + RNG: CryptoRng, { frost::round1::commit::(secret, rng) } diff --git a/frost-ristretto255/src/tests/batch.rs b/frost-ristretto255/src/tests/batch.rs index b26d033cf..826a32dc2 100644 --- a/frost-ristretto255/src/tests/batch.rs +++ b/frost-ristretto255/src/tests/batch.rs @@ -2,21 +2,21 @@ use crate::*; #[test] fn check_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-ristretto255/src/tests/coefficient_commitment.rs b/frost-ristretto255/src/tests/coefficient_commitment.rs index a031d4c46..710bef7d4 100644 --- a/frost-ristretto255/src/tests/coefficient_commitment.rs +++ b/frost-ristretto255/src/tests/coefficient_commitment.rs @@ -1,18 +1,17 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization of CoefficientCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialization_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Ristretto255Sha512, _, @@ -21,7 +20,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Ristretto255Sha512, _, @@ -36,7 +35,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Ristretto255Sha512, diff --git a/frost-ristretto255/src/tests/vss_commitment.rs b/frost-ristretto255/src/tests/vss_commitment.rs index 06b16e422..f2b691072 100644 --- a/frost-ristretto255/src/tests/vss_commitment.rs +++ b/frost-ristretto255/src/tests/vss_commitment.rs @@ -1,24 +1,23 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization VerifiableSecretSharingCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_serialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( rng, ); @@ -26,7 +25,7 @@ fn check_serialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment::( rng, ); @@ -34,7 +33,7 @@ fn check_deserialize_vss_commitment() { #[test] fn check_deserialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::< Ristretto255Sha512, _, @@ -43,7 +42,7 @@ fn check_deserialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::< Ristretto255Sha512, _, @@ -52,7 +51,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_deserialize_whole_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< Ristretto255Sha512, _, @@ -61,7 +60,7 @@ fn check_deserialize_whole_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_compute_public_key_package::( rng, ); diff --git a/frost-ristretto255/tests/common_traits_tests.rs b/frost-ristretto255/tests/common_traits_tests.rs index 8d1fcf2b1..4ec4849d3 100644 --- a/frost-ristretto255/tests/common_traits_tests.rs +++ b/frost-ristretto255/tests/common_traits_tests.rs @@ -19,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-ristretto255/tests/integration_tests.rs b/frost-ristretto255/tests/integration_tests.rs index c4ef3be0e..50b19970f 100644 --- a/frost-ristretto255/tests/integration_tests.rs +++ b/frost-ristretto255/tests/integration_tests.rs @@ -1,6 +1,6 @@ use frost_ristretto255::*; -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; #[test] fn check_zero_key_fails() { @@ -9,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -30,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -44,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -58,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::< Ristretto255Sha512, @@ -82,7 +82,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Ristretto255Sha512, @@ -92,7 +92,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -109,14 +109,14 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_refresh_shares_with_dkg_smaller_threshold() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::< Ristretto255Sha512, @@ -126,14 +126,14 @@ fn check_refresh_shares_with_dkg_smaller_threshold() { #[test] fn check_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -147,7 +147,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -161,7 +161,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -177,13 +177,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_ristretto255_sha512() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 0; let max_signers = 3; @@ -197,7 +197,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -211,7 +211,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 0; @@ -223,17 +223,18 @@ fn check_share_generation_fails_with_invalid_max_signers() { >(min_signers, max_signers, error, rng); } -lazy_static! { - pub static ref VECTORS: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_BIG_IDENTIFIER: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_DKG: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) - .expect("Test vector is valid JSON"); -} +static VECTORS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_BIG_IDENTIFIER: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_DKG: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) + .expect("Test vector is valid JSON") +}); #[test] fn check_sign_with_test_vectors() { @@ -275,7 +276,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Ristretto255Sha512, @@ -285,7 +286,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::< Ristretto255Sha512, _, @@ -294,7 +295,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< Ristretto255Sha512, _, @@ -304,7 +305,7 @@ fn check_sign_with_incorrect_commitments() { #[tokio::test] async fn check_async_sign_with_dealer() { tokio::spawn(async { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::async_check_sign::(rng) .await; }) diff --git a/frost-ristretto255/tests/rerandomized_tests.rs b/frost-ristretto255/tests/rerandomized_tests.rs index 23277d0a9..b95d100b0 100644 --- a/frost-ristretto255/tests/rerandomized_tests.rs +++ b/frost-ristretto255/tests/rerandomized_tests.rs @@ -2,7 +2,7 @@ use frost_ristretto255::Ristretto255Sha512; #[test] fn check_randomized_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-ristretto255/tests/serde_tests.rs b/frost-ristretto255/tests/serde_tests.rs index d73e846ef..531fcf65f 100644 --- a/frost-ristretto255/tests/serde_tests.rs +++ b/frost-ristretto255/tests/serde_tests.rs @@ -19,7 +19,7 @@ fn check_signing_commitments_serialization() { let commitments = samples::signing_commitments(); let json = serde_json::to_string_pretty(&commitments).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); assert!(commitments == decoded_commitments); @@ -89,7 +89,7 @@ fn check_signing_package_serialization() { let signing_package = samples::signing_package(); let json = serde_json::to_string_pretty(&signing_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -204,7 +204,7 @@ fn check_signature_share_serialization() { let signature_share = samples::signature_share(); let json = serde_json::to_string_pretty(&signature_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); assert!(signature_share == decoded_signature_share); @@ -258,7 +258,7 @@ fn check_secret_share_serialization() { let secret_share = samples::secret_share(); let json = serde_json::to_string_pretty(&secret_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); assert!(secret_share == decoded_secret_share); @@ -342,7 +342,7 @@ fn check_key_package_serialization() { let key_package = samples::key_package(); let json = serde_json::to_string_pretty(&key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); assert!(key_package == decoded_key_package); @@ -437,7 +437,7 @@ fn check_public_key_package_serialization() { let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); assert!(public_key_package == decoded_public_key_package); @@ -532,7 +532,7 @@ fn check_round1_package_serialization() { let round1_package = samples::round1_package(); let json = serde_json::to_string_pretty(&round1_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); assert!(round1_package == decoded_round1_package); @@ -598,7 +598,7 @@ fn check_round2_package_serialization() { let round2_package = samples::round2_package(); let json = serde_json::to_string_pretty(&round2_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); assert!(round2_package == decoded_round2_package); diff --git a/frost-secp256k1-tr/Cargo.toml b/frost-secp256k1-tr/Cargo.toml index ad45c8fb9..0d6f7ed6b 100644 --- a/frost-secp256k1-tr/Cargo.toml +++ b/frost-secp256k1-tr/Cargo.toml @@ -1,39 +1,49 @@ [package] name = "frost-secp256k1-tr" -edition.workspace = true -rust-version.workspace = true version.workspace = true authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot." +documentation = "https://docs.rs/frost-secp256k1-tr" readme = "README.md" -license.workspace = true +homepage.workspace = true repository.workspace = true -categories.workspace = true +license.workspace = true keywords = ["cryptography", "crypto", "secp256k1", "threshold", "signature"] -description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST and Taproot." +categories.workspace = true [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false + [dependencies] document-features.workspace = true frost-core.workspace = true frost-rerandomized.workspace = true -k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } +k256.workspace = true rand_core.workspace = true -sha2 = { version = "0.10.2", default-features = false } +sha2.workspace = true [dev-dependencies] criterion.workspace = true frost-core = { workspace = true, features = ["test-impl"] } frost-rerandomized = { workspace = true, features = ["test-impl"] } -insta.workspace = true hex.workspace = true -lazy_static.workspace = true +insta.workspace = true proptest.workspace = true rand.workspace = true rand_chacha.workspace = true -secp256k1 = "0.31.0" +secp256k1.workspace = true serde_json.workspace = true tokio.workspace = true @@ -46,12 +56,3 @@ default = ["serialization"] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] - -[lib] -# Disables non-criterion benchmark which is not used; prevents errors -# when using criterion-specific flags -bench = false - -[[bench]] -name = "bench" -harness = false diff --git a/frost-secp256k1-tr/README.md b/frost-secp256k1-tr/README.md index bc74f3ef2..64fd8b814 100644 --- a/frost-secp256k1-tr/README.md +++ b/frost-secp256k1-tr/README.md @@ -20,7 +20,7 @@ scenario in a single thread and it abstracts away any communication between peer use frost_secp256k1_tr as frost; use std::collections::BTreeMap; -let mut rng = rand::rngs::OsRng; +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-secp256k1-tr/benches/bench.rs b/frost-secp256k1-tr/benches/bench.rs index d2ce56f2a..4e78e64df 100644 --- a/frost-secp256k1-tr/benches/bench.rs +++ b/frost-secp256k1-tr/benches/bench.rs @@ -3,13 +3,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; use frost_secp256k1_tr::*; fn bench_secp256k1_batch_verify(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); } fn bench_secp256k1_sign(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); } diff --git a/frost-secp256k1-tr/dkg.md b/frost-secp256k1-tr/dkg.md index e0be3936c..9c9015c87 100644 --- a/frost-secp256k1-tr/dkg.md +++ b/frost-secp256k1-tr/dkg.md @@ -20,11 +20,9 @@ first round of communication to ensure all participants have the same value. ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; - use frost_secp256k1_tr as frost; -let mut rng = rand::rngs::OsRng; - +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; # // ANCHOR_END: dkg_import diff --git a/frost-secp256k1-tr/src/keys/dkg.rs b/frost-secp256k1-tr/src/keys/dkg.rs index 9ea40b263..c5b0c231a 100644 --- a/frost-secp256k1-tr/src/keys/dkg.rs +++ b/frost-secp256k1-tr/src/keys/dkg.rs @@ -46,7 +46,7 @@ pub mod round2 { /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the DKG run. -pub fn part1( +pub fn part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-secp256k1-tr/src/keys/refresh.rs b/frost-secp256k1-tr/src/keys/refresh.rs index bb4b6dd64..2d44f2bdb 100644 --- a/frost-secp256k1-tr/src/keys/refresh.rs +++ b/frost-secp256k1-tr/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, identifiers: &[Identifier], mut rng: &mut R, @@ -29,7 +29,7 @@ pub fn refresh_share( } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. -pub fn refresh_dkg_part1( +pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-secp256k1-tr/src/keys/repairable.rs b/frost-secp256k1-tr/src/keys/repairable.rs index 3ae930d49..47bd0c7fe 100644 --- a/frost-secp256k1-tr/src/keys/repairable.rs +++ b/frost-secp256k1-tr/src/keys/repairable.rs @@ -10,7 +10,7 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier}; use crate::{Error, Secp256K1Sha256TR}; /// A delta value which is the output of part 1 of RTS. @@ -26,7 +26,7 @@ pub type Sigma = frost::keys::repairable::Sigma; /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_part1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, @@ -62,22 +62,17 @@ pub fn repair_share_part3( #[cfg(test)] mod tests { - - use lazy_static::lazy_static; - - use serde_json::Value; - use crate::Secp256K1Sha256TR; + use serde_json::Value; + use std::sync::LazyLock; - lazy_static! { - pub static ref REPAIR_SHARE: Value = - serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()) - .unwrap(); - } + static REPAIR_SHARE: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()).unwrap() + }); #[test] fn check_repair_share_part1() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1::(rng); } @@ -89,7 +84,7 @@ mod tests { #[test] fn check_repair_share_part3() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, @@ -98,7 +93,7 @@ mod tests { #[test] fn check_repair_share_part1_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Secp256K1Sha256TR, _, diff --git a/frost-secp256k1-tr/src/lib.rs b/frost-secp256k1-tr/src/lib.rs index daa19608e..07ec0f60d 100644 --- a/frost-secp256k1-tr/src/lib.rs +++ b/frost-secp256k1-tr/src/lib.rs @@ -5,25 +5,26 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] +#[cfg(test)] +extern crate std; + extern crate alloc; use alloc::vec; use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec}; use frost_rerandomized::RandomizedCiphersuite; -use k256::elliptic_curve::ops::Reduce; use k256::{ elliptic_curve::{ - bigint::U256, - group::prime::PrimeCurveAffine, - hash2curve::{hash_to_field, ExpandMsgXmd}, + ops::Reduce, point::AffineCoordinates, - sec1::{FromEncodedPoint, ToEncodedPoint}, - Field as FFField, PrimeField, + sec1::{FromSec1Point, ToSec1Point}, + CurveAffine, Field as FFField, PrimeField, }, - AffinePoint, ProjectivePoint, Scalar, + hash2curve::{hash_to_field, ExpandMsgXmd, MapToCurve}, + AffinePoint, ProjectivePoint, Scalar, Secp256k1, U256, }; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use sha2::{Digest, Sha256}; use frost_core::{self as frost, random_nonzero}; @@ -71,7 +72,7 @@ impl Field for Secp256K1ScalarField { } } - fn random(rng: &mut R) -> Self::Scalar { + fn random(rng: &mut R) -> Self::Scalar { Scalar::random(rng) } @@ -130,7 +131,7 @@ impl Group for Secp256K1Group { return Err(GroupError::InvalidIdentityElement); } let mut fixed_serialized = [0; 33]; - let serialized_point = element.to_affine().to_encoded_point(true); + let serialized_point = element.to_affine().to_sec1_point(true); let serialized = serialized_point.as_bytes(); fixed_serialized.copy_from_slice(serialized); Ok(fixed_serialized) @@ -138,9 +139,9 @@ impl Group for Secp256K1Group { fn deserialize(buf: &Self::Serialization) -> Result { let encoded_point = - k256::EncodedPoint::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?; + k256::Sec1Point::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?; - match Option::::from(AffinePoint::from_encoded_point(&encoded_point)) { + match Option::::from(AffinePoint::from_sec1_point(&encoded_point)) { Some(point) => { if point.is_identity().into() { // This is actually impossible since the identity is encoded a a single byte @@ -167,9 +168,14 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { } fn hash_to_scalar(domain: &[&[u8]], msg: &[u8]) -> Scalar { - let mut u = [Secp256K1ScalarField::zero()]; - hash_to_field::, Scalar>(&[msg], domain, &mut u) - .expect("should never return error according to error cases described in ExpandMsgXmd"); + let u = hash_to_field::< + 1, + ExpandMsgXmd, + ::SecurityLevel, + Scalar, + ::Length, + >(&[msg], domain) + .expect("should never return error according to error cases described in ExpandMsgXmd"); u[0] } @@ -187,7 +193,7 @@ fn hasher_to_scalar(hasher: Sha256) -> Scalar { // This is acceptable because secp256k1 curve order is close to 2^256, // and the input is uniformly random since it is a hash output, therefore // the bias is negligibly small. - Scalar::reduce(U256::from_be_slice(&hasher.finalize())) + Scalar::reduce(&U256::from_be_slice(&hasher.finalize())) } /// Create a BIP340 compliant tagged hash @@ -294,11 +300,7 @@ impl Ciphersuite for Secp256K1Sha256TR { } // Sign, negating the key if required by BIP-340. - fn single_sign( - signing_key: &SigningKey, - rng: R, - message: &[u8], - ) -> Signature { + fn single_sign(signing_key: &SigningKey, rng: R, message: &[u8]) -> Signature { let signing_key = signing_key.clone().into_even_y(None); signing_key.default_sign(rng, message) } @@ -362,7 +364,7 @@ impl Ciphersuite for Secp256K1Sha256TR { } // Generate a nonce, negating it if required by BIP-340. - fn generate_nonce( + fn generate_nonce( rng: &mut R, ) -> ( <::Field as Field>::Scalar, @@ -514,7 +516,7 @@ pub mod keys { /// Allows all participants' keys to be generated using a central, trusted /// dealer. - pub fn generate_with_dealer( + pub fn generate_with_dealer( max_signers: u16, min_signers: u16, identifiers: IdentifierList, @@ -529,7 +531,7 @@ pub mod keys { /// instead of generating a fresh one. This is useful in scenarios where /// the key needs to be generated externally or must be derived from e.g. a /// seed phrase. - pub fn split( + pub fn split( secret: &SigningKey, max_signers: u16, min_signers: u16, @@ -825,7 +827,7 @@ pub mod round1 { /// operation. pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where - RNG: CryptoRng + RngCore, + RNG: CryptoRng, { frost::round1::commit::(secret, rng) } diff --git a/frost-secp256k1-tr/src/tests/batch.rs b/frost-secp256k1-tr/src/tests/batch.rs index d22efdf5d..60f6ed923 100644 --- a/frost-secp256k1-tr/src/tests/batch.rs +++ b/frost-secp256k1-tr/src/tests/batch.rs @@ -2,21 +2,21 @@ use crate::*; #[test] fn check_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-secp256k1-tr/src/tests/coefficient_commitment.rs b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs index 71706ea62..636982b30 100644 --- a/frost-secp256k1-tr/src/tests/coefficient_commitment.rs +++ b/frost-secp256k1-tr/src/tests/coefficient_commitment.rs @@ -1,18 +1,17 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization of CoefficientCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialization_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Secp256K1Sha256TR, _, @@ -21,7 +20,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Secp256K1Sha256TR, _, @@ -36,7 +35,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Secp256K1Sha256TR, diff --git a/frost-secp256k1-tr/src/tests/vss_commitment.rs b/frost-secp256k1-tr/src/tests/vss_commitment.rs index f264c3328..5ef7566f3 100644 --- a/frost-secp256k1-tr/src/tests/vss_commitment.rs +++ b/frost-secp256k1-tr/src/tests/vss_commitment.rs @@ -1,24 +1,23 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization VerifiableSecretSharingCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_serialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( rng, ); @@ -26,7 +25,7 @@ fn check_serialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment::( rng, ); @@ -34,7 +33,7 @@ fn check_deserialize_vss_commitment() { #[test] fn check_deserialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::( rng, ); @@ -42,7 +41,7 @@ fn check_deserialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -50,7 +49,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_deserialize_whole_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< Secp256K1Sha256TR, _, @@ -59,7 +58,7 @@ fn check_deserialize_whole_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_compute_public_key_package::( rng, ); diff --git a/frost-secp256k1-tr/tests/common_traits_tests.rs b/frost-secp256k1-tr/tests/common_traits_tests.rs index 93265b7a8..91ef3e09e 100644 --- a/frost-secp256k1-tr/tests/common_traits_tests.rs +++ b/frost-secp256k1-tr/tests/common_traits_tests.rs @@ -19,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-secp256k1-tr/tests/integration_tests.rs b/frost-secp256k1-tr/tests/integration_tests.rs index 682db377d..33a4248b8 100644 --- a/frost-secp256k1-tr/tests/integration_tests.rs +++ b/frost-secp256k1-tr/tests/integration_tests.rs @@ -1,6 +1,6 @@ use frost_secp256k1_tr::*; -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; #[test] fn check_zero_key_fails() { @@ -9,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -30,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -44,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -58,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::< Secp256K1Sha256TR, @@ -82,7 +82,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Secp256K1Sha256TR, @@ -92,7 +92,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -109,14 +109,14 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_refresh_shares_with_dkg_smaller_threshold() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::< Secp256K1Sha256TR, @@ -126,14 +126,14 @@ fn check_refresh_shares_with_dkg_smaller_threshold() { #[test] fn check_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -147,7 +147,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -161,7 +161,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -177,13 +177,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_secp256k1_tr_sha256() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 0; let max_signers = 3; @@ -197,7 +197,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -211,7 +211,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 0; @@ -223,17 +223,18 @@ fn check_share_generation_fails_with_invalid_max_signers() { >(min_signers, max_signers, error, rng); } -lazy_static! { - pub static ref VECTORS: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_BIG_IDENTIFIER: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_DKG: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) - .expect("Test vector is valid JSON"); -} +static VECTORS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_BIG_IDENTIFIER: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_DKG: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) + .expect("Test vector is valid JSON") +}); #[test] fn check_sign_with_test_vectors() { @@ -275,7 +276,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Secp256K1Sha256TR, @@ -285,7 +286,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::< Secp256K1Sha256TR, _, @@ -294,7 +295,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< Secp256K1Sha256TR, _, @@ -304,7 +305,7 @@ fn check_sign_with_incorrect_commitments() { #[tokio::test] async fn check_async_sign_with_dealer() { tokio::spawn(async { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; }) .await diff --git a/frost-secp256k1-tr/tests/interoperability_tests.rs b/frost-secp256k1-tr/tests/interoperability_tests.rs index 3cf68f217..7dc5db7b8 100644 --- a/frost-secp256k1-tr/tests/interoperability_tests.rs +++ b/frost-secp256k1-tr/tests/interoperability_tests.rs @@ -6,7 +6,7 @@ mod helpers; #[test] fn check_interoperability_in_regular_sign() { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); for _ in 0..256 { let signing_key = SigningKey::new(&mut rng); @@ -18,7 +18,7 @@ fn check_interoperability_in_regular_sign() { #[test] fn check_interoperability_in_sign_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. A smaller number of iterations is used @@ -35,7 +35,7 @@ fn check_interoperability_in_sign_with_dkg() { #[test] fn check_interoperability_in_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); // Test with multiple keys/signatures to better exercise the key generation // and the interoperability check. diff --git a/frost-secp256k1-tr/tests/rerandomized_tests.rs b/frost-secp256k1-tr/tests/rerandomized_tests.rs index 7b4144c9b..fb6176991 100644 --- a/frost-secp256k1-tr/tests/rerandomized_tests.rs +++ b/frost-secp256k1-tr/tests/rerandomized_tests.rs @@ -2,7 +2,7 @@ use frost_secp256k1_tr::Secp256K1Sha256TR; #[test] fn check_randomized_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-secp256k1-tr/tests/serde_tests.rs b/frost-secp256k1-tr/tests/serde_tests.rs index e13cf1d9a..f460f371e 100644 --- a/frost-secp256k1-tr/tests/serde_tests.rs +++ b/frost-secp256k1-tr/tests/serde_tests.rs @@ -19,7 +19,7 @@ fn check_signing_commitments_serialization() { let commitments = samples::signing_commitments(); let json = serde_json::to_string_pretty(&commitments).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); assert!(commitments == decoded_commitments); @@ -89,7 +89,7 @@ fn check_signing_package_serialization() { let signing_package = samples::signing_package(); let json = serde_json::to_string_pretty(&signing_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -204,7 +204,7 @@ fn check_signature_share_serialization() { let signature_share = samples::signature_share(); let json = serde_json::to_string_pretty(&signature_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); assert!(signature_share == decoded_signature_share); @@ -258,7 +258,7 @@ fn check_secret_share_serialization() { let secret_share = samples::secret_share(); let json = serde_json::to_string_pretty(&secret_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); assert!(secret_share == decoded_secret_share); @@ -342,7 +342,7 @@ fn check_key_package_serialization() { let key_package = samples::key_package(); let json = serde_json::to_string_pretty(&key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); assert!(key_package == decoded_key_package); @@ -437,7 +437,7 @@ fn check_public_key_package_serialization() { let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); assert!(public_key_package == decoded_public_key_package); @@ -532,7 +532,7 @@ fn check_round1_package_serialization() { let round1_package = samples::round1_package(); let json = serde_json::to_string_pretty(&round1_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); assert!(round1_package == decoded_round1_package); @@ -598,7 +598,7 @@ fn check_round2_package_serialization() { let round2_package = samples::round2_package(); let json = serde_json::to_string_pretty(&round2_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); assert!(round2_package == decoded_round2_package); diff --git a/frost-secp256k1-tr/tests/tweaking_tests.rs b/frost-secp256k1-tr/tests/tweaking_tests.rs index dddbed12b..f5ee5bb11 100644 --- a/frost-secp256k1-tr/tests/tweaking_tests.rs +++ b/frost-secp256k1-tr/tests/tweaking_tests.rs @@ -17,7 +17,7 @@ fn check_tweaked_sign_with_dealer() -> Result<(), Box> { let merkle_root: Vec = vec![12; 32]; - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( @@ -137,7 +137,7 @@ fn taproot_tweak_pubkey(pubkey: [u8; 32], merkle_root: &[u8]) -> (bool, [u8; 32] .chain_update(merkle_root) .finalize(); let t = k256::Scalar::from( - k256::elliptic_curve::ScalarPrimitive::new(k256::U256::from_be_slice(&tweak_hash)).unwrap(), + k256::elliptic_curve::ScalarValue::new(k256::U256::from_be_slice(&tweak_hash)).unwrap(), ); let mut pubkey_even_bytes = [0x02; 33]; diff --git a/frost-secp256k1/Cargo.toml b/frost-secp256k1/Cargo.toml index 8d942718e..9d0a0e382 100644 --- a/frost-secp256k1/Cargo.toml +++ b/frost-secp256k1/Cargo.toml @@ -1,35 +1,45 @@ [package] name = "frost-secp256k1" -edition.workspace = true -rust-version.workspace = true version.workspace = true authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST." +documentation = "https://docs.rs/frost-secp256k1" readme = "README.md" -license.workspace = true +homepage.workspace = true repository.workspace = true -categories.workspace = true +license.workspace = true keywords = ["cryptography", "crypto", "secp256k1", "threshold", "signature"] -description = "A Schnorr signature scheme over the secp256k1 curve that supports FROST." +categories.workspace = true [package.metadata.docs.rs] features = ["serde"] rustdoc-args = ["--cfg", "docsrs"] +[lib] +# Disables non-criterion benchmark which is not used; prevents errors +# when using criterion-specific flags +bench = false + +[[bench]] +name = "bench" +harness = false + [dependencies] document-features.workspace = true frost-core.workspace = true frost-rerandomized.workspace = true -k256 = { version = "0.13.0", features = ["arithmetic", "expose-field", "hash2curve"], default-features = false } +k256.workspace = true rand_core.workspace = true -sha2 = { version = "0.10.2", default-features = false } +sha2.workspace = true [dev-dependencies] criterion.workspace = true frost-core = { workspace = true, features = ["test-impl"] } frost-rerandomized = { workspace = true, features = ["test-impl"] } -insta.workspace = true hex.workspace = true -lazy_static.workspace = true +insta.workspace = true proptest.workspace = true rand.workspace = true rand_chacha.workspace = true @@ -45,12 +55,3 @@ default = ["serialization"] serde = ["frost-core/serde"] ## Enable a default serialization format. Enables `serde`. serialization = ["serde", "frost-core/serialization", "frost-rerandomized/serialization"] - -[lib] -# Disables non-criterion benchmark which is not used; prevents errors -# when using criterion-specific flags -bench = false - -[[bench]] -name = "bench" -harness = false diff --git a/frost-secp256k1/README.md b/frost-secp256k1/README.md index c65e5789d..ab56c7c49 100644 --- a/frost-secp256k1/README.md +++ b/frost-secp256k1/README.md @@ -20,7 +20,7 @@ scenario in a single thread and it abstracts away any communication between peer use frost_secp256k1 as frost; use std::collections::BTreeMap; -let mut rng = rand::rngs::OsRng; +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; let (shares, pubkey_package) = frost::keys::generate_with_dealer( diff --git a/frost-secp256k1/benches/bench.rs b/frost-secp256k1/benches/bench.rs index cd89e8e2d..e7c2a7464 100644 --- a/frost-secp256k1/benches/bench.rs +++ b/frost-secp256k1/benches/bench.rs @@ -3,13 +3,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; use frost_secp256k1::*; fn bench_secp256k1_batch_verify(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_batch_verify::(c, "secp256k1", &mut rng); } fn bench_secp256k1_sign(c: &mut Criterion) { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::benches::bench_sign::(c, "secp256k1", &mut rng); } diff --git a/frost-secp256k1/dkg.md b/frost-secp256k1/dkg.md index 48d76819f..c8de33983 100644 --- a/frost-secp256k1/dkg.md +++ b/frost-secp256k1/dkg.md @@ -20,11 +20,9 @@ first round of communication to ensure all participants have the same value. ```rust # // ANCHOR: dkg_import use std::collections::BTreeMap; - use frost_secp256k1 as frost; -let mut rng = rand::rngs::OsRng; - +let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let max_signers = 5; let min_signers = 3; # // ANCHOR_END: dkg_import diff --git a/frost-secp256k1/src/keys/dkg.rs b/frost-secp256k1/src/keys/dkg.rs index 9ea40b263..c5b0c231a 100644 --- a/frost-secp256k1/src/keys/dkg.rs +++ b/frost-secp256k1/src/keys/dkg.rs @@ -46,7 +46,7 @@ pub mod round2 { /// It returns the [`round1::SecretPackage`] that must be kept in memory /// by the participant for the other steps, and the [`round1::Package`] that /// must be sent to each other participant in the DKG run. -pub fn part1( +pub fn part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-secp256k1/src/keys/refresh.rs b/frost-secp256k1/src/keys/refresh.rs index bb4b6dd64..2d44f2bdb 100644 --- a/frost-secp256k1/src/keys/refresh.rs +++ b/frost-secp256k1/src/keys/refresh.rs @@ -5,14 +5,14 @@ use crate::{ frost, keys::dkg::{round1, round2}, - CryptoRng, Error, Identifier, RngCore, + CryptoRng, Error, Identifier, }; use alloc::{collections::btree_map::BTreeMap, vec::Vec}; use super::{KeyPackage, PublicKeyPackage, SecretShare}; /// Refer to [`frost_core::keys::refresh::compute_refreshing_shares`]. -pub fn compute_refreshing_shares( +pub fn compute_refreshing_shares( old_pub_key_package: PublicKeyPackage, identifiers: &[Identifier], mut rng: &mut R, @@ -29,7 +29,7 @@ pub fn refresh_share( } /// Refer to [`frost_core::keys::refresh::refresh_dkg_part1`]. -pub fn refresh_dkg_part1( +pub fn refresh_dkg_part1( identifier: Identifier, max_signers: u16, min_signers: u16, diff --git a/frost-secp256k1/src/keys/repairable.rs b/frost-secp256k1/src/keys/repairable.rs index 48b1506aa..70315d060 100644 --- a/frost-secp256k1/src/keys/repairable.rs +++ b/frost-secp256k1/src/keys/repairable.rs @@ -10,7 +10,7 @@ use crate::keys::{KeyPackage, PublicKeyPackage}; // This is imported separately to make `gencode` work. // (if it were below, the position of the import would vary between ciphersuites // after `cargo fmt`) -use crate::{frost, Ciphersuite, CryptoRng, Identifier, RngCore}; +use crate::{frost, Ciphersuite, CryptoRng, Identifier}; use crate::{Error, Secp256K1Sha256}; /// A delta value which is the output of part 1 of RTS. @@ -26,7 +26,7 @@ pub type Sigma = frost::keys::repairable::Sigma; /// `participant` recover their share. /// /// Returns a BTreeMap mapping which value should be sent to which participant. -pub fn repair_share_part1( +pub fn repair_share_part1( helpers: &[Identifier], key_package_i: &KeyPackage, rng: &mut R, @@ -62,22 +62,17 @@ pub fn repair_share_part3( #[cfg(test)] mod tests { - - use lazy_static::lazy_static; - - use serde_json::Value; - use crate::Secp256K1Sha256; + use serde_json::Value; + use std::sync::LazyLock; - lazy_static! { - pub static ref REPAIR_SHARE: Value = - serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()) - .unwrap(); - } + static REPAIR_SHARE: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/repair-share.json").trim()).unwrap() + }); #[test] fn check_repair_share_part1() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1::(rng); } @@ -89,7 +84,7 @@ mod tests { #[test] fn check_repair_share_part3() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part3::( rng, &REPAIR_SHARE, @@ -98,7 +93,7 @@ mod tests { #[test] fn check_repair_share_part1_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_repair_share_part1_fails_with_invalid_min_signers::< Secp256K1Sha256, _, diff --git a/frost-secp256k1/src/lib.rs b/frost-secp256k1/src/lib.rs index bebcd1f5d..b18eb5685 100644 --- a/frost-secp256k1/src/lib.rs +++ b/frost-secp256k1/src/lib.rs @@ -5,6 +5,9 @@ #![doc = include_str!("../README.md")] #![doc = document_features::document_features!()] +#[cfg(test)] +extern crate std; + extern crate alloc; use alloc::collections::BTreeMap; @@ -12,14 +15,13 @@ use alloc::collections::BTreeMap; use frost_rerandomized::RandomizedCiphersuite; use k256::{ elliptic_curve::{ - group::prime::PrimeCurveAffine, - hash2curve::{hash_to_field, ExpandMsgXmd}, - sec1::{FromEncodedPoint, ToEncodedPoint}, - Field as FFField, PrimeField, + sec1::{FromSec1Point, ToSec1Point}, + CurveAffine, Field as FFField, PrimeField, }, - AffinePoint, ProjectivePoint, Scalar, + hash2curve::{hash_to_field, ExpandMsgXmd, MapToCurve}, + AffinePoint, ProjectivePoint, Scalar, Secp256k1, }; -use rand_core::{CryptoRng, RngCore}; +use rand_core::CryptoRng; use sha2::{Digest, Sha256}; use frost_core as frost; @@ -62,7 +64,7 @@ impl Field for Secp256K1ScalarField { } } - fn random(rng: &mut R) -> Self::Scalar { + fn random(rng: &mut R) -> Self::Scalar { Scalar::random(rng) } @@ -121,7 +123,7 @@ impl Group for Secp256K1Group { return Err(GroupError::InvalidIdentityElement); } let mut fixed_serialized = [0; 33]; - let serialized_point = element.to_affine().to_encoded_point(true); + let serialized_point = element.to_affine().to_sec1_point(true); let serialized = serialized_point.as_bytes(); fixed_serialized.copy_from_slice(serialized); Ok(fixed_serialized) @@ -129,9 +131,9 @@ impl Group for Secp256K1Group { fn deserialize(buf: &Self::Serialization) -> Result { let encoded_point = - k256::EncodedPoint::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?; + k256::Sec1Point::from_bytes(buf).map_err(|_| GroupError::MalformedElement)?; - match Option::::from(AffinePoint::from_encoded_point(&encoded_point)) { + match Option::::from(AffinePoint::from_sec1_point(&encoded_point)) { Some(point) => { if point.is_identity().into() { // This is actually impossible since the identity is encoded a a single byte @@ -158,9 +160,14 @@ fn hash_to_array(inputs: &[&[u8]]) -> [u8; 32] { } fn hash_to_scalar(domain: &[&[u8]], msg: &[u8]) -> Scalar { - let mut u = [Secp256K1ScalarField::zero()]; - hash_to_field::, Scalar>(&[msg], domain, &mut u) - .expect("should never return error according to error cases described in ExpandMsgXmd"); + let u = hash_to_field::< + 1, + ExpandMsgXmd, + ::SecurityLevel, + Scalar, + ::Length, + >(&[msg], domain) + .expect("should never return error according to error cases described in ExpandMsgXmd"); u[0] } @@ -251,7 +258,7 @@ pub mod keys { /// Allows all participants' keys to be generated using a central, trusted /// dealer. - pub fn generate_with_dealer( + pub fn generate_with_dealer( max_signers: u16, min_signers: u16, identifiers: IdentifierList, @@ -266,7 +273,7 @@ pub mod keys { /// instead of generating a fresh one. This is useful in scenarios where /// the key needs to be generated externally or must be derived from e.g. a /// seed phrase. - pub fn split( + pub fn split( secret: &SigningKey, max_signers: u16, min_signers: u16, @@ -367,7 +374,7 @@ pub mod round1 { /// operation. pub fn commit(secret: &SigningShare, rng: &mut RNG) -> (SigningNonces, SigningCommitments) where - RNG: CryptoRng + RngCore, + RNG: CryptoRng, { frost::round1::commit::(secret, rng) } diff --git a/frost-secp256k1/src/tests/batch.rs b/frost-secp256k1/src/tests/batch.rs index d3b1c6800..8dcfb7bff 100644 --- a/frost-secp256k1/src/tests/batch.rs +++ b/frost-secp256k1/src/tests/batch.rs @@ -2,21 +2,21 @@ use crate::*; #[test] fn check_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::batch_verify::(rng); } #[test] fn check_bad_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::bad_batch_verify::(rng); } #[test] fn empty_batch_verify() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::batch::empty_batch_verify::(rng); } diff --git a/frost-secp256k1/src/tests/coefficient_commitment.rs b/frost-secp256k1/src/tests/coefficient_commitment.rs index 7be35ead6..a564d6a9b 100644 --- a/frost-secp256k1/src/tests/coefficient_commitment.rs +++ b/frost-secp256k1/src/tests/coefficient_commitment.rs @@ -1,18 +1,17 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization of CoefficientCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialization_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_serialization_of_coefficient_commitment::< Secp256K1Sha256, _, @@ -21,7 +20,7 @@ fn check_serialization_of_coefficient_commitment() { #[test] fn check_create_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_create_coefficient_commitment::< Secp256K1Sha256, _, @@ -36,7 +35,7 @@ fn check_create_coefficient_commitment_error() { #[test] fn check_get_value_of_coefficient_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::coefficient_commitment::check_get_value_of_coefficient_commitment::< Secp256K1Sha256, diff --git a/frost-secp256k1/src/tests/vss_commitment.rs b/frost-secp256k1/src/tests/vss_commitment.rs index 79aa89a65..dac14a9f1 100644 --- a/frost-secp256k1/src/tests/vss_commitment.rs +++ b/frost-secp256k1/src/tests/vss_commitment.rs @@ -1,24 +1,23 @@ -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; use crate::*; // Tests for serialization and deserialization VerifiableSecretSharingCommitment -lazy_static! { - pub static ref ELEMENTS: Value = - serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap(); -} +static ELEMENTS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../../tests/helpers/elements.json").trim()).unwrap() +}); #[test] fn check_serialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_vss_commitment::(rng); } #[test] fn check_serialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_serialize_whole_vss_commitment::( rng, ); @@ -26,13 +25,13 @@ fn check_serialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment::(rng); } #[test] fn check_deserialize_whole_vss_commitment() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment::( rng, ); @@ -40,7 +39,7 @@ fn check_deserialize_whole_vss_commitment() { #[test] fn check_deserialize_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_vss_commitment_error::( rng, &ELEMENTS, ); @@ -48,7 +47,7 @@ fn check_deserialize_vss_commitment_error() { #[test] fn check_deserialize_whole_vss_commitment_error() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_deserialize_whole_vss_commitment_error::< Secp256K1Sha256, _, @@ -57,6 +56,6 @@ fn check_deserialize_whole_vss_commitment_error() { #[test] fn check_compute_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::vss_commitment::check_compute_public_key_package::(rng); } diff --git a/frost-secp256k1/tests/common_traits_tests.rs b/frost-secp256k1/tests/common_traits_tests.rs index e9f788eb1..38ab3df49 100644 --- a/frost-secp256k1/tests/common_traits_tests.rs +++ b/frost-secp256k1/tests/common_traits_tests.rs @@ -19,7 +19,7 @@ fn check_common_traits_for_type(v: #[test] fn check_signing_key_common_traits() { - let mut rng = rand::rngs::OsRng; + let mut rng = rand_core::UnwrapErr(rand::rngs::SysRng); let signing_key = SigningKey::new(&mut rng); check_common_traits_for_type(signing_key); } diff --git a/frost-secp256k1/tests/integration_tests.rs b/frost-secp256k1/tests/integration_tests.rs index 343214e1b..cae6c762e 100644 --- a/frost-secp256k1/tests/integration_tests.rs +++ b/frost-secp256k1/tests/integration_tests.rs @@ -1,6 +1,6 @@ use frost_secp256k1::*; -use lazy_static::lazy_static; use serde_json::Value; +use std::sync::LazyLock; #[test] fn check_zero_key_fails() { @@ -9,14 +9,14 @@ fn check_zero_key_fails() { #[test] fn check_sign_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dkg::(rng); } #[test] fn check_dkg_part1_fails_with_invalid_signers_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -30,7 +30,7 @@ fn check_dkg_part1_fails_with_invalid_signers_min_signers() { #[test] fn check_dkg_part1_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -44,7 +44,7 @@ fn check_dkg_part1_fails_with_min_signers_greater_than_max() { #[test] fn check_dkg_part1_fails_with_invalid_signers_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -58,21 +58,21 @@ fn check_dkg_part1_fails_with_invalid_signers_max_signers() { #[test] fn check_rts() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::repairable::check_rts::(rng); } #[test] fn check_refresh_shares_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer::(rng); } #[test] fn check_refresh_shares_with_dealer_serialisation() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_serialisation::( rng, @@ -81,7 +81,7 @@ fn check_refresh_shares_with_dealer_serialisation() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dealer_fails_with_invalid_public_key_package::< Secp256K1Sha256, @@ -91,7 +91,7 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_public_key_package() { #[test] fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let identifiers = vec![ Identifier::try_from(8).unwrap(), Identifier::try_from(3).unwrap(), @@ -108,14 +108,14 @@ fn check_refresh_shares_with_dealer_fails_with_invalid_identifier() { #[test] fn check_refresh_shares_with_dkg() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg::(rng); } #[test] fn check_refresh_shares_with_dkg_smaller_threshold() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::refresh::check_refresh_shares_with_dkg_smaller_threshold::( rng, @@ -124,14 +124,14 @@ fn check_refresh_shares_with_dkg_smaller_threshold() { #[test] fn check_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer::(rng); } #[test] fn check_sign_with_dealer_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 1; let max_signers = 3; @@ -145,7 +145,7 @@ fn check_sign_with_dealer_fails_with_invalid_min_signers() { #[test] fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -159,7 +159,7 @@ fn check_sign_with_dealer_fails_with_min_signers_greater_than_max() { #[test] fn check_sign_with_dealer_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 1; @@ -175,13 +175,13 @@ fn check_sign_with_dealer_fails_with_invalid_max_signers() { /// value is working. #[test] fn check_share_generation_secp256k1_sha256() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_share_generation::(rng); } #[test] fn check_share_generation_fails_with_invalid_min_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 0; let max_signers = 3; @@ -195,7 +195,7 @@ fn check_share_generation_fails_with_invalid_min_signers() { #[test] fn check_share_generation_fails_with_min_signers_greater_than_max() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 2; @@ -209,7 +209,7 @@ fn check_share_generation_fails_with_min_signers_greater_than_max() { #[test] fn check_share_generation_fails_with_invalid_max_signers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let min_signers = 3; let max_signers = 0; @@ -221,17 +221,18 @@ fn check_share_generation_fails_with_invalid_max_signers() { >(min_signers, max_signers, error, rng); } -lazy_static! { - pub static ref VECTORS: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_BIG_IDENTIFIER: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) - .expect("Test vector is valid JSON"); - pub static ref VECTORS_DKG: Value = - serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) - .expect("Test vector is valid JSON"); -} +static VECTORS: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_BIG_IDENTIFIER: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors-big-identifier.json").trim()) + .expect("Test vector is valid JSON") +}); +static VECTORS_DKG: LazyLock = LazyLock::new(|| { + serde_json::from_str(include_str!("../tests/helpers/vectors_dkg.json").trim()) + .expect("Test vector is valid JSON") +}); #[test] fn check_sign_with_test_vectors() { @@ -273,7 +274,7 @@ fn check_identifier_generation() -> Result<(), Error> { #[test] fn check_sign_with_dealer_and_identifiers() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::< Secp256K1Sha256, @@ -283,7 +284,7 @@ fn check_sign_with_dealer_and_identifiers() { #[test] fn check_sign_with_missing_identifier() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_missing_identifier::( rng, ); @@ -291,7 +292,7 @@ fn check_sign_with_missing_identifier() { #[test] fn check_sign_with_incorrect_commitments() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::check_sign_with_incorrect_commitments::< Secp256K1Sha256, _, @@ -301,7 +302,7 @@ fn check_sign_with_incorrect_commitments() { #[tokio::test] async fn check_async_sign_with_dealer() { tokio::spawn(async { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); frost_core::tests::ciphersuite_generic::async_check_sign::(rng).await; }) .await diff --git a/frost-secp256k1/tests/rerandomized_tests.rs b/frost-secp256k1/tests/rerandomized_tests.rs index c7845f170..c1a6f60bb 100644 --- a/frost-secp256k1/tests/rerandomized_tests.rs +++ b/frost-secp256k1/tests/rerandomized_tests.rs @@ -2,7 +2,7 @@ use frost_secp256k1::Secp256K1Sha256; #[test] fn check_randomized_sign_with_dealer() { - let rng = rand::rngs::OsRng; + let rng = rand_core::UnwrapErr(rand::rngs::SysRng); let (_msg, _group_signature, _group_pubkey) = frost_rerandomized::tests::check_randomized_sign_with_dealer::(rng); diff --git a/frost-secp256k1/tests/serde_tests.rs b/frost-secp256k1/tests/serde_tests.rs index 5b02fdaeb..5b0314966 100644 --- a/frost-secp256k1/tests/serde_tests.rs +++ b/frost-secp256k1/tests/serde_tests.rs @@ -19,7 +19,7 @@ fn check_signing_commitments_serialization() { let commitments = samples::signing_commitments(); let json = serde_json::to_string_pretty(&commitments).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_commitments: SigningCommitments = serde_json::from_str(&json).unwrap(); assert!(commitments == decoded_commitments); @@ -89,7 +89,7 @@ fn check_signing_package_serialization() { let signing_package = samples::signing_package(); let json = serde_json::to_string_pretty(&signing_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signing_package: SigningPackage = serde_json::from_str(&json).unwrap(); assert!(signing_package == decoded_signing_package); @@ -204,7 +204,7 @@ fn check_signature_share_serialization() { let signature_share = samples::signature_share(); let json = serde_json::to_string_pretty(&signature_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_signature_share: SignatureShare = serde_json::from_str(&json).unwrap(); assert!(signature_share == decoded_signature_share); @@ -258,7 +258,7 @@ fn check_secret_share_serialization() { let secret_share = samples::secret_share(); let json = serde_json::to_string_pretty(&secret_share).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_secret_share: SecretShare = serde_json::from_str(&json).unwrap(); assert!(secret_share == decoded_secret_share); @@ -342,7 +342,7 @@ fn check_key_package_serialization() { let key_package = samples::key_package(); let json = serde_json::to_string_pretty(&key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_key_package: KeyPackage = serde_json::from_str(&json).unwrap(); assert!(key_package == decoded_key_package); @@ -437,7 +437,7 @@ fn check_public_key_package_serialization() { let public_key_package = samples::public_key_package_new(); let json = serde_json::to_string_pretty(&public_key_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_public_key_package: PublicKeyPackage = serde_json::from_str(&json).unwrap(); assert!(public_key_package == decoded_public_key_package); @@ -532,7 +532,7 @@ fn check_round1_package_serialization() { let round1_package = samples::round1_package(); let json = serde_json::to_string_pretty(&round1_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round1_package: round1::Package = serde_json::from_str(&json).unwrap(); assert!(round1_package == decoded_round1_package); @@ -598,7 +598,7 @@ fn check_round2_package_serialization() { let round2_package = samples::round2_package(); let json = serde_json::to_string_pretty(&round2_package).unwrap(); - println!("{}", json); + println!("{json}"); let decoded_round2_package: round2::Package = serde_json::from_str(&json).unwrap(); assert!(round2_package == decoded_round2_package); diff --git a/gencode/Cargo.toml b/gencode/Cargo.toml index 44dead9ba..8dba8000f 100644 --- a/gencode/Cargo.toml +++ b/gencode/Cargo.toml @@ -1,16 +1,25 @@ [package] name = "gencode" -version = "0.1.0" +version.workspace = true +authors.workspace = true edition.workspace = true +rust-version.workspace = true +description = "Internal tool for generating FROST ciphersuite documentation and source files." +documentation = "https://github.com/ZcashFoundation/frost/tree/main/gencode" +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords = ["cryptography", "frost", "code-generation", "documentation", "development-tools"] +categories = ["development-tools::build-utils", "command-line-utilities", "cryptography"] publish = false -[dependencies] -regex = "1.6.0" -serde_json.workspace = true - [[bin]] name = "gencode" path = "src/main.rs" # Disables non-criterion benchmark which is not used; prevents errors # when using criterion-specific flags bench = false + +[dependencies] +regex.workspace = true +serde_json.workspace = true diff --git a/gencode/src/main.rs b/gencode/src/main.rs index 9dee2d5da..6451533f7 100644 --- a/gencode/src/main.rs +++ b/gencode/src/main.rs @@ -124,8 +124,7 @@ fn write_docs( let new_doc = docs.get(old_name).map(|v| v.1.clone()); let Some(new_doc) = new_doc else { eprintln!( - "WARNING: documentation for {} is not available in base file. This can mean it's a specific type for the ciphersuite, or that there is a bug in gencode", - old_name + "WARNING: documentation for {old_name} is not available in base file. This can mean it's a specific type for the ciphersuite, or that there is a bug in gencode" ); continue; }; @@ -168,7 +167,7 @@ fn copy_and_replace( pub fn rustfmt(source: String) -> String { let mut child = Command::new("rustfmt") .arg("--edition") - .arg("2021") + .arg("2021") // TODO: remove this file once PR in merged (this is used to reduce diff) .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..23b8245b6 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +# TODO: remove this file once PR in merged (this is used to reduce diff) +style_edition = "2021"