diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 9829d60f..00000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# 0.8.0
-
-- Added an extra requirement to Curve API for constant-time scalar sampling
-
-# 0.7.0
-
-- Remove triple setup interface, always doing a fresh setup, for security reasons.
-- Fix various security bugs.
-- Update dependencies.
-
-# 0.6.0
-
-- Modify specification to use a single threshold (turns out the code accidentally enforced this already)
-- Modify code to match simplified presigning protocol because of this threshold.
-- Modify specification to pre-commit to C polynomial in triple generation.
-- Modify code accordingly.
-
-# 0.5.0
-
-- Modify specification & implementation to use perfectly hiding commitments.
-- Update dependencies to recent Rust-Crypto ECDSA versions.
-- Support arbitrary curves and message hashes.
-- Add curve description (name) to transcript in keysharing and triple generation.
-
-# 0.4.0
-
-- Added key refresh and resharing protocols.
diff --git a/Cargo.lock b/Cargo.lock
index a4c9d902..870f6c44 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -30,9 +30,9 @@ dependencies = [
[[package]]
name = "async-fs"
-version = "2.1.2"
+version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
+checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50"
dependencies = [
"async-lock",
"blocking",
@@ -41,9 +41,9 @@ dependencies = [
[[package]]
name = "async-io"
-version = "2.4.1"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3"
+checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
dependencies = [
"async-lock",
"cfg-if",
@@ -54,8 +54,7 @@ dependencies = [
"polling",
"rustix",
"slab",
- "tracing",
- "windows-sys 0.59.0",
+ "windows-sys",
]
[[package]]
@@ -82,9 +81,9 @@ dependencies = [
[[package]]
name = "async-process"
-version = "2.3.1"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc"
+checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00"
dependencies = [
"async-channel",
"async-io",
@@ -96,14 +95,13 @@ dependencies = [
"event-listener",
"futures-lite",
"rustix",
- "tracing",
]
[[package]]
name = "async-signal"
-version = "0.2.11"
+version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d"
+checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1"
dependencies = [
"async-io",
"async-lock",
@@ -114,7 +112,7 @@ dependencies = [
"rustix",
"signal-hook-registry",
"slab",
- "windows-sys 0.59.0",
+ "windows-sys",
]
[[package]]
@@ -410,7 +408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
- "windows-sys 0.60.2",
+ "windows-sys",
]
[[package]]
@@ -840,17 +838,16 @@ dependencies = [
[[package]]
name = "polling"
-version = "3.8.0"
+version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50"
+checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix",
- "tracing",
- "windows-sys 0.59.0",
+ "windows-sys",
]
[[package]]
@@ -901,9 +898,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
-version = "0.9.1"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
+checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core 0.9.3",
@@ -980,15 +977,15 @@ dependencies = [
[[package]]
name = "rustix"
-version = "1.0.7"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
+checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.59.0",
+ "windows-sys",
]
[[package]]
@@ -1220,22 +1217,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "tracing"
-version = "0.1.41"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
-dependencies = [
- "pin-project-lite",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-core"
-version = "0.1.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
-
[[package]]
name = "typenum"
version = "1.18.0"
@@ -1280,38 +1261,13 @@ dependencies = [
"wit-bindgen-rt",
]
-[[package]]
-name = "windows-sys"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
- "windows-targets 0.53.2",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
-dependencies = [
- "windows_aarch64_gnullvm 0.52.6",
- "windows_aarch64_msvc 0.52.6",
- "windows_i686_gnu 0.52.6",
- "windows_i686_gnullvm 0.52.6",
- "windows_i686_msvc 0.52.6",
- "windows_x86_64_gnu 0.52.6",
- "windows_x86_64_gnullvm 0.52.6",
- "windows_x86_64_msvc 0.52.6",
+ "windows-targets",
]
[[package]]
@@ -1320,106 +1276,58 @@ version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
dependencies = [
- "windows_aarch64_gnullvm 0.53.0",
- "windows_aarch64_msvc 0.53.0",
- "windows_i686_gnu 0.53.0",
- "windows_i686_gnullvm 0.53.0",
- "windows_i686_msvc 0.53.0",
- "windows_x86_64_gnu 0.53.0",
- "windows_x86_64_gnullvm 0.53.0",
- "windows_x86_64_msvc 0.53.0",
+ "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_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
-[[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_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
-[[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_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
-[[package]]
-name = "windows_i686_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
-[[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_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
-[[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_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
diff --git a/Cargo.toml b/Cargo.toml
index c3ae18b3..0ecd1c84 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ frost-ed25519 = { version = "2.1.0", default-features = false, features = ["seri
frost-secp256k1 = { version = "2.1.0", default-features = false, features = ["serialization", "std"] }
futures = "0.3.31"
itertools = "0.14.0"
-k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"], optional = true }
+k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"]}
keccak = "0.1.5"
rand = "0.9.0"
# updating this one requires quite a few code changes
@@ -29,9 +29,3 @@ sha3 = "0.10.8"
smol = "2.0.2"
subtle = "2.5.0"
zeroize = "1.8.1"
-
-[dev-dependencies]
-k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"], optional = false }
-
-[features]
-k256 = ["dep:k256"]
diff --git a/LICENSE b/LICENSE
index cf935f97..dd1c05b5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,5 @@
Copyright (c) 2023 Lúcás C. Meier
+Copyright (c) 2025 NEAR One Limited
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -16,4 +17,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
+SOFTWARE.
diff --git a/README.md b/README.md
index 1f180d9e..e4d41861 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,71 @@
-# MPC Threshold Signatures
-This repo contains implementations of Cait-Sith and FROST, used by the [MPC node](https://github.com/near/mpc) to compute threshold signatures.
+# Threshold Signing
+This repository offers cryptographic implementations of **threshold ECDSA** and **threshold EdDSA**. Previous to [PR#15](https://github.com/near/threshold-signatures/pull/15) The implementation had undergone professional audit.
+
+The ECDSA code implements an OT-based threshold protocol and a Secret-Sharing based one. The former
+is originally imported from the [Cait-Sith](https://github.com/cronokirby/cait-sith) library and amended to meet our industrial needs. This includes modifying parts of the code to improve the performance, augment the security, and generalize functions' syntax. The latter however is implemented from scratch and follows \[[DJNPØ](https://eprint.iacr.org/2020/501)\]
+
+
+The EdDSA implementation is mainly a wrapper of the [Frost](https://github.com/ZcashFoundation/frost) signing functions instantiated with Curve25519.
+
+# Code organization
+
+The repository provides implementations for both ECDSA and EdDSA.
+Each signature scheme has its own repository that implements it, namely, `src/ecdsa` and `src/eddsa`.\
+Additionally `src/crypto` implements generic mathematical and cryptographic tools used for both schemes such as polynomial manipulations, randomness generation, commitment schemes, etc... `src/crypto/proofs` implements \[[Mau09](https://crypto.ethz.ch/publications/files/Maurer09.pdf)\] proofs for discrete logarithms, and `src/protocol` allows defining participants, communication channels, asynchronous functions that run and test the protocol and reliable broadcast channel.\
+Some additional files are found in `src`. `src/participants.rs` provides complex structures related to participants mainly based on hash maps and `src/generic_dkg.rs` implements a distributed key generation (DKG) that is agnostic of the curve.
+
+# Important Technical Details
+### Threshold ECDSA Functionalities
+The threshold ECDSA scheme is implemented over curve Secp256k1.
+The following functionalities are provided:
+1) **Distributed Key Generation (DKG)**: allows multiple parties to each generate its own secret key shares and a corresponding master public key.
+2) **Key Resharing**: allows multiple parties to reshare their keys adding new members or kicking old members. If the sets of new/old participants is the same, then we talk about *key refreshing*.
+3) **Beaver Triple Generation (offline)**: Allows the distributive generation of multiplicative (Beaver) triples $(a,b,c)$ and their commitments $(A, B, C)$ where
+$c = a\cdot b$ and where $(A,B,C) = (g^a, g^b, g^c)$. These triples are essential for creating the presignatures.
+4) **Presigning (offline)**: Allows generating some presignatures during an offline signing phase that will be consumed during the online signing phase when the message to be signed is known to the signers.
+5) **Signing (online)**: Corresponds to the online signing phase in which the signing parties produce a valid signature
+
+### Threshold EdDSA Functionalities
+The threshold EdDSA scheme is implemented over curve
+Curve25519. We refer to such scheme as Ed25519.
+The following functionalities are provided:
+1) **Distributed Key Generation (DKG)**: Same as in ECDSA.
+2) **Key Resharing**: Same as in ECDSA.
+3) **Signing (online)**: Threshold EdDSA is generally more efficient than threshold ECDSA due to the mathematical formula behind the signature computation.Our Ed25519 implementation does not necessitate an offline phase of computation.
+
+### General Notifications
+
+* We do not implement any verification algorithm. In fact, a party possessing the message-signature pair can simply run the verification algorithm of the corresponding classic, non-distributed scheme using the master verification key.
+
+* Both implemented ECDSA and Ed25519 schemes do not currently provide **Robustness** i.e. recovery in case a participants drops out during presigning/signing.
+
+* Our ECDSA signing scheme outsources the message hash to the function caller (i.e. expects a hashed message as input and does not internally hash the input). However, our EdDSA implementation does not outsource the message hashing instead internally perfoms the message hash. This distinction is an artifact of the multiple different verifiers implemented in the wild where some might perform a "double hashing" and others not.
+(See \[[PoeRas24](https://link.springer.com/chapter/10.1007/978-3-031-57718-5_10)\] for an in-depth security study of ECDSA with outsourced hashing).
+
+* This implementation allows abitrary number of parties and thresholds as long as the latter verifies some basic requirements (see the documentation). However, it is worth mentioning that the ECDSA scheme scales non-efficiently with the number of participants (See benchmarks).
+
+* **🚨 Important 🚨:** Our DKG/Resharing protocol is the same for both ECDSA and EdDSA except the underlying elliptic curve instantiation. Internally, this DKG makes use of a reliable broadcast channel implemented for asynchronous peer-to-peer communication. Due to a fundamental impossibility theorem for asynchronous broadcast channel, our DKG/Resharing protocol can only tolerate $n/3$ malicious parties where $n$ is the total number of parties.
+
+# Build and Test
+Building the crate is fairly simple using
+``cargo build``.
+
+Run ``cargo test`` to run all the built-in test cases. Some the tests might take some time to run as they require running complex protocols with multiple participants at once.
+
+# Benchmarks
+* Benchmarks with 8 nodes -- TODO: https://github.com/near/threshold-signatures/issues/8
+
+# Acknowledgements
+This implementation relies on
+[Cait-Sith](https://github.com/cronokirby/cait-sith) and
+[Frost](https://github.com/ZcashFoundation/frost) and was possible thanks to contributors that actively put this together:
+
+ Mårten Blankfors
+ Robin Cheng
+ Reynaldo Gil Pons
+ Chelsea Komlo
+ George Kuska
+ Matej Pavlovic
+ Simon Rastikian
+ Bowen Wang
+
diff --git a/docs/images/dark/dependencies.svg b/docs/ecdsa/images/dark/dependencies.svg
similarity index 100%
rename from docs/images/dark/dependencies.svg
rename to docs/ecdsa/images/dark/dependencies.svg
diff --git a/docs/images/light/dependencies.svg b/docs/ecdsa/images/light/dependencies.svg
similarity index 100%
rename from docs/images/light/dependencies.svg
rename to docs/ecdsa/images/light/dependencies.svg
diff --git a/docs/intro.md b/docs/ecdsa/intro.md
similarity index 100%
rename from docs/intro.md
rename to docs/ecdsa/intro.md
diff --git a/docs/key-generation.md b/docs/ecdsa/key-generation.md
similarity index 100%
rename from docs/key-generation.md
rename to docs/ecdsa/key-generation.md
diff --git a/docs/orchestration.md b/docs/ecdsa/orchestration.md
similarity index 100%
rename from docs/orchestration.md
rename to docs/ecdsa/orchestration.md
diff --git a/docs/proofs.md b/docs/ecdsa/proofs.md
similarity index 100%
rename from docs/proofs.md
rename to docs/ecdsa/proofs.md
diff --git a/docs/signing.md b/docs/ecdsa/signing.md
similarity index 100%
rename from docs/signing.md
rename to docs/ecdsa/signing.md
diff --git a/docs/triples.md b/docs/ecdsa/triples.md
similarity index 100%
rename from docs/triples.md
rename to docs/ecdsa/triples.md
diff --git a/logo.png b/logo.png
deleted file mode 100644
index 33b8c3b3..00000000
Binary files a/logo.png and /dev/null differ
diff --git a/src/compat/mod.rs b/src/compat/mod.rs
deleted file mode 100644
index c3c2f964..00000000
--- a/src/compat/mod.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-use elliptic_curve::{ops::Reduce, point::AffineCoordinates, Curve, CurveArithmetic, PrimeCurve};
-use rand_core::CryptoRngCore;
-use serde::{Deserialize, Deserializer, Serialize, Serializer};
-
-/// Represents a curve suitable for use in cait-sith.
-///
-/// This is the trait that any curve usable in this library must implement.
-/// This library does provide a few feature-gated implementations for curves
-/// itself, beyond that you'll need to implement this trait yourself.
-///
-/// The bulk of the trait are the bounds requiring a curve according
-/// to RustCrypto's traits.
-///
-/// Beyond that, we also require that curves have a name, for domain separation,
-/// and a way to serialize points with serde.
-pub trait CSCurve: PrimeCurve + CurveArithmetic {
- const NAME: &'static [u8];
-
- const BITS: usize;
- /// Serialize a point with serde.
- fn serialize_point(
- point: &Self::AffinePoint,
- serializer: S,
- ) -> Result;
-
- /// Deserialize a point with serde.
- fn deserialize_point<'de, D: Deserializer<'de>>(
- deserializer: D,
- ) -> Result;
-
- /// transform bytes into scalar
- fn from_bytes_to_scalar(bytes: [u8; 32]) -> Option;
-
- /// transform bytes into affine point
- fn from_bytes_to_affine(bytes: [u8; 33]) -> Option;
-
- /// A function to sample a random scalar, guaranteed to be constant-time.
- ///
- /// By this, it's meant that we will make pull a fixed amount of
- /// data from the rng.
- fn sample_scalar_constant_time(r: &mut R) -> Self::Scalar;
-}
-
-#[cfg(any(feature = "k256", test))]
-mod k256_impl {
- use super::*;
-
- use elliptic_curve::bigint::{Bounded, U512};
- use elliptic_curve::sec1::FromEncodedPoint;
- use k256::{
- elliptic_curve::{bigint::ArrayEncoding, PrimeField},
- Scalar, Secp256k1, U256,
- };
-
- impl CSCurve for Secp256k1 {
- const NAME: &'static [u8] = b"Secp256k1";
- const BITS: usize = ::BITS;
-
- fn serialize_point(
- point: &Self::AffinePoint,
- serializer: S,
- ) -> Result {
- point.serialize(serializer)
- }
-
- fn deserialize_point<'de, D: Deserializer<'de>>(
- deserializer: D,
- ) -> Result {
- Self::AffinePoint::deserialize(deserializer)
- }
-
- fn sample_scalar_constant_time(r: &mut R) -> Self::Scalar {
- let mut data = [0u8; 64];
- r.fill_bytes(&mut data);
- >::reduce_bytes(&data.into())
- }
-
- fn from_bytes_to_scalar(bytes: [u8; 32]) -> Option {
- let bytes = U256::from_be_slice(bytes.as_slice());
- Scalar::from_repr(bytes.to_be_byte_array()).into_option()
- }
-
- fn from_bytes_to_affine(bytes: [u8; 33]) -> Option {
- let encoded_point = match k256::EncodedPoint::from_bytes(bytes) {
- Ok(encoded) => encoded,
- Err(_) => return None,
- };
- Option::::from(Self::AffinePoint::from_encoded_point(&encoded_point))
- .map(Self::ProjectivePoint::from)
- }
- }
-}
-
-#[cfg(test)]
-mod test_scalar_hash {
- use super::*;
-
- use digest::{Digest, FixedOutput};
- use ecdsa::hazmat::DigestPrimitive;
- use elliptic_curve::{ops::Reduce, Curve};
- use k256::{FieldBytes, Scalar, Secp256k1};
-
- #[cfg(test)]
- pub(crate) fn scalar_hash(msg: &[u8]) -> ::Scalar {
- let digest = ::Digest::new_with_prefix(msg);
- let m_bytes: FieldBytes = digest.finalize_fixed();
- ::Uint>>::reduce_bytes(&m_bytes)
- }
-}
-
-#[cfg(test)]
-pub(crate) use test_scalar_hash::scalar_hash;
-
-#[derive(Clone, Copy)]
-pub(crate) struct SerializablePoint(C::AffinePoint);
-
-impl SerializablePoint {
- pub fn to_projective(self) -> C::ProjectivePoint {
- self.0.into()
- }
-
- pub fn from_projective(point: &C::ProjectivePoint) -> Self {
- Self((*point).into())
- }
-}
-
-impl Serialize for SerializablePoint {
- fn serialize(&self, serializer: S) -> Result
- where
- S: Serializer,
- {
- C::serialize_point(&self.0, serializer)
- }
-}
-
-impl<'de, C: CSCurve> Deserialize<'de> for SerializablePoint {
- fn deserialize(deserializer: D) -> Result
- where
- D: Deserializer<'de>,
- {
- let affine = C::deserialize_point(deserializer)?;
- Ok(Self(affine))
- }
-}
-
-/// Get the x coordinate of a point, as a scalar
-pub(crate) fn x_coordinate(point: &C::AffinePoint) -> C::Scalar {
- ::Uint>>::reduce_bytes(&point.x())
-}
diff --git a/src/constants.rs b/src/constants.rs
deleted file mode 100644
index 33717153..00000000
--- a/src/constants.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-/// The security parameter we use for different constructions
-pub const SECURITY_PARAMETER: usize = 128;
diff --git a/src/crypto.rs b/src/crypto.rs
deleted file mode 100644
index f892377f..00000000
--- a/src/crypto.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-use sha2::{Digest, Sha256};
-
-use rand_core::CryptoRngCore;
-use serde::{Deserialize, Serialize};
-
-use crate::serde::encode_writer;
-
-const COMMIT_LABEL: &[u8] = b"Near threshold signature commitment";
-const COMMIT_LEN: usize = 32;
-const RANDOMIZER_LEN: usize = 32;
-const HASH_LABEL: &[u8] = b"Near threshold signature generic hash";
-const HASH_LEN: usize = 32;
-
-/// Represents the randomizer used to make a commit hiding.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct Randomizer([u8; RANDOMIZER_LEN]);
-
-impl Randomizer {
- /// Generate a new randomizer value by sampling from an RNG.
- fn random(rng: &mut R) -> Self {
- let mut out = [0u8; RANDOMIZER_LEN];
- rng.fill_bytes(&mut out);
- Self(out)
- }
-}
-
-impl AsRef<[u8]> for Randomizer {
- fn as_ref(&self) -> &[u8] {
- &self.0
- }
-}
-
-/// Represents a commitment to some value.
-///
-/// This commit is both binding, in that it can't be opened to a different
-/// value than the one committed, and hiding, in that it hides the value
-/// committed inside (perfectly).
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct Commitment([u8; COMMIT_LEN]);
-
-impl Commitment {
- fn compute(val: &T, r: &Randomizer) -> Self {
- let mut hasher = Sha256::new();
- hasher.update(COMMIT_LABEL);
- hasher.update(r.as_ref());
- hasher.update(b"start data");
- encode_writer(&mut hasher, val);
- Commitment(hasher.finalize().into())
- }
-
- /// Check that a value and a randomizer match this commitment.
- #[must_use]
- pub fn check(&self, val: &T, r: &Randomizer) -> bool {
- let actual = Self::compute(val, r);
- *self == actual
- }
-}
-
-/// Commit to an arbitrary serializable value.
-///
-/// This also returns a fresh randomizer, which is used to make sure that the
-/// commitment perfectly hides the value contained inside.
-///
-/// This value will need to be sent when opening the commitment to allow
-/// others to check that the opening is valid.
-pub fn commit(rng: &mut R, val: &T) -> (Commitment, Randomizer) {
- let r = Randomizer::random(rng);
- let c = Commitment::compute(val, &r);
- (c, r)
-}
-
-/// The output of a generic hash function.
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
-pub struct HashOutput([u8; HASH_LEN]);
-
-impl AsRef<[u8]> for HashOutput {
- fn as_ref(&self) -> &[u8] {
- &self.0
- }
-}
-
-/// Hash some value to produce a short digest.
-pub fn hash(val: &T) -> HashOutput {
- let mut hasher = Sha256::new();
- hasher.update(HASH_LABEL);
- encode_writer(&mut hasher, val);
- HashOutput(hasher.finalize().into())
-}
diff --git a/src/crypto/README.md b/src/crypto/README.md
new file mode 100644
index 00000000..f1d15131
--- /dev/null
+++ b/src/crypto/README.md
@@ -0,0 +1 @@
+Common Crypto for all
\ No newline at end of file
diff --git a/src/crypto/ciphersuite.rs b/src/crypto/ciphersuite.rs
new file mode 100644
index 00000000..e97a217b
--- /dev/null
+++ b/src/crypto/ciphersuite.rs
@@ -0,0 +1,14 @@
+// Generic Ciphersuite Trait
+use frost_core::Group;
+
+pub enum BytesOrder {
+ BigEndian,
+ LittleEndian,
+}
+
+pub trait ScalarSerializationFormat {
+ fn bytes_order() -> BytesOrder;
+}
+pub trait Ciphersuite: frost_core::Ciphersuite + ScalarSerializationFormat {}
+
+pub(crate) type Element = <::Group as Group>::Element;
diff --git a/src/crypto/commit.rs b/src/crypto/commit.rs
new file mode 100644
index 00000000..83d9184f
--- /dev/null
+++ b/src/crypto/commit.rs
@@ -0,0 +1,50 @@
+use rand_core::CryptoRngCore;
+use rmp_serde::encode::Error;
+use serde::{Deserialize, Serialize};
+use sha2::{Digest, Sha256};
+
+use super::random::Randomizer;
+
+const COMMIT_LABEL: &[u8] = b"Near threshold signature commitment";
+const COMMIT_LEN: usize = 32;
+
+/// Represents a commitment to some value.
+///
+/// This commit is both binding, in that it can't be opened to a different
+/// value than the one committed, and hiding, in that it hides the value
+/// committed inside (perfectly).
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Commitment([u8; COMMIT_LEN]);
+
+impl Commitment {
+ fn compute(val: &T, r: &Randomizer) -> Result {
+ let mut hasher = Sha256::new();
+ hasher.update(COMMIT_LABEL);
+ hasher.update(r.as_ref());
+ hasher.update(b"start data");
+ rmp_serde::encode::write(&mut hasher, val)?;
+ Ok(Commitment(hasher.finalize().into()))
+ }
+
+ /// Check that a value and a randomizer match this commitment.
+ pub fn check(&self, val: &T, r: &Randomizer) -> Result {
+ let actual = Self::compute(val, r)?;
+ Ok(*self == actual)
+ }
+}
+
+/// Commit to an arbitrary serializable value.
+///
+/// This also returns a fresh randomizer, which is used to make sure that the
+/// commitment perfectly hides the value contained inside.
+///
+/// This value will need to be sent when opening the commitment to allow
+/// others to check that the opening is valid.
+pub fn commit(
+ rng: &mut R,
+ val: &T,
+) -> Result<(Commitment, Randomizer), Error> {
+ let r = Randomizer::random(rng);
+ let c = Commitment::compute(val, &r)?;
+ Ok((c, r))
+}
diff --git a/src/crypto/hash.rs b/src/crypto/hash.rs
new file mode 100644
index 00000000..dc49323c
--- /dev/null
+++ b/src/crypto/hash.rs
@@ -0,0 +1,50 @@
+use serde::{Deserialize, Serialize};
+use sha2::{Digest, Sha256};
+
+const HASH_LABEL: &[u8] = b"Near threshold signature generic hash";
+const HASH_LEN: usize = 32;
+
+/// The output of a generic hash function.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct HashOutput([u8; HASH_LEN]);
+
+impl AsRef<[u8]> for HashOutput {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+/// Hash some value to produce a short digest.
+pub fn hash(val: &T) -> HashOutput {
+ let mut hasher = Sha256::new();
+ hasher.update(HASH_LABEL);
+ rmp_serde::encode::write(&mut hasher, val).expect("failed to encode value");
+ HashOutput(hasher.finalize().into())
+}
+
+/// Hashes using a domain separator
+/// The domain separator has to be manually incremented after the use of this function
+pub fn domain_separate_hash(domain_separator: u32, data: &T) -> HashOutput {
+ let preimage = (domain_separator, data);
+ hash(&preimage)
+}
+
+#[cfg(test)]
+pub(crate) use test::scalar_hash;
+
+#[cfg(test)]
+mod test {
+ use elliptic_curve::{ops::Reduce, Curve, CurveArithmetic};
+
+ use digest::{Digest, FixedOutput};
+ use ecdsa::hazmat::DigestPrimitive;
+ use k256::{FieldBytes, Scalar, Secp256k1};
+
+ #[cfg(test)]
+ /// Hashes a message string into an arbitrary scalar
+ pub(crate) fn scalar_hash(msg: &[u8]) -> ::Scalar {
+ let digest = ::Digest::new_with_prefix(msg);
+ let m_bytes: FieldBytes = digest.finalize_fixed();
+ ::Uint>>::reduce_bytes(&m_bytes)
+ }
+}
diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs
new file mode 100644
index 00000000..b4e6fb94
--- /dev/null
+++ b/src/crypto/mod.rs
@@ -0,0 +1,6 @@
+pub mod ciphersuite;
+pub mod commit;
+pub mod hash;
+pub mod polynomials;
+pub mod proofs;
+pub mod random;
diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs
new file mode 100644
index 00000000..189aa807
--- /dev/null
+++ b/src/crypto/polynomials.rs
@@ -0,0 +1,527 @@
+use frost_core::{
+ keys::CoefficientCommitment, serialization::SerializableScalar, Field, Group, Scalar,
+};
+use rand_core::CryptoRngCore;
+
+use super::ciphersuite::Ciphersuite;
+use crate::protocol::{Participant, ProtocolError};
+
+use std::ops::Add;
+
+use serde::{Deserialize, Deserializer, Serialize};
+
+/// Polynomial structure of non-empty or non-zero coefficiants
+/// Represents a polynomial with coefficients in the scalar field of the curve.
+pub struct Polynomial {
+ /// The coefficients of our polynomial,
+ /// The 0 term being the constant term of the polynomial
+ coefficients: Vec>,
+}
+
+impl Polynomial {
+ /// Constructs the polynomial out of scalars
+ /// The first scalar (coefficients[0]) is the constant term
+ /// The highest degree null coefficients are dropped out
+ pub fn new(coefficients: Vec>) -> Result {
+ if coefficients.is_empty() {
+ return Err(ProtocolError::EmptyOrZeroCoefficients);
+ }
+ // count the number of zero coeffs before spotting the first non-zero
+ let count = coefficients
+ .iter()
+ .rev()
+ .take_while(|x| *x == &::Field::zero())
+ .count();
+ if count == coefficients.len() {
+ return Err(ProtocolError::EmptyOrZeroCoefficients);
+ }
+ // get the number of non-zero coeffs
+ let last_non_null = coefficients.len() - count;
+
+ Ok(Polynomial {
+ coefficients: coefficients[..last_non_null].to_vec(),
+ })
+ }
+
+ /// Returns the coeficients of the polynomial
+ pub fn get_coefficients(&self) -> Vec> {
+ self.coefficients.to_vec()
+ }
+
+ /// Creates a random polynomial p of the given degree
+ /// and sets p(0) = secret
+ /// if the secret is not given then it is picked at random
+ pub fn generate_polynomial(
+ secret: Option>,
+ degree: usize,
+ rng: &mut impl CryptoRngCore,
+ ) -> Result {
+ let poly_size = degree + 1;
+ let mut coefficients = Vec::with_capacity(poly_size);
+ // insert the secret share if exists
+ let secret = secret.unwrap_or_else(|| ::Field::random(rng));
+
+ coefficients.push(secret);
+ for _ in 1..poly_size {
+ coefficients.push(::Field::random(rng));
+ }
+ // fails only if:
+ // * polynomial is of degree 0 and the constant term is 0
+ // * polynomial degree is the max of usize, and so degree + 1 is 0
+ // such cases never happen in a classic (non-malicious) implementations
+ Self::new(coefficients)
+ }
+
+ /// Returns the constant term
+ pub fn eval_on_zero(&self) -> SerializableScalar {
+ SerializableScalar(self.coefficients[0])
+ }
+
+ /// Evaluates a polynomial on a certain scalar
+ /// Evaluate the polynomial with the given coefficients
+ /// at the point using Horner's method.
+ /// Implements [`polynomial_evaluate`] from the spec:
+ /// https://datatracker.ietf.org/doc/html/rfc9591#name-additional-polynomial-opera
+ pub fn eval_on_point(&self, point: Scalar) -> SerializableScalar {
+ if point == ::Field::zero() {
+ self.eval_on_zero()
+ } else {
+ let mut value = ::Field::zero();
+ for coeff in self.coefficients.iter().skip(1).rev() {
+ value = value + *coeff;
+ value = value * point;
+ }
+ value = value + self.coefficients[0];
+ SerializableScalar(value)
+ }
+ }
+
+ /// Evaluates a polynomial on the identifier of a participant
+ pub fn eval_on_participant(&self, participant: Participant) -> SerializableScalar {
+ let id = participant.scalar::();
+ self.eval_on_point(id)
+ }
+
+ /// Computes polynomial interpolation on a specific point
+ /// using a sequence of sorted elements
+ pub fn eval_interpolation(
+ identifiers: &[Scalar],
+ shares: &[SerializableScalar],
+ point: Option<&Scalar>,
+ ) -> Result, ProtocolError> {
+ let mut interpolation = ::Field::zero();
+ // raise Error if the lengths are not the same
+ if identifiers.len() != shares.len() {
+ return Err(ProtocolError::InvalidInterpolationArguments);
+ }
+
+ // Compute the Lagrange coefficients
+ for (id, share) in identifiers.iter().zip(shares) {
+ // would raise error if identifiers are not enough (<= 1)
+ let lagrange_coefficient = compute_lagrange_coefficient::(identifiers, id, point)?;
+
+ // Compute y = f(point) via polynomial interpolation of these points of f
+ interpolation = interpolation + (lagrange_coefficient.0 * share.0);
+ }
+
+ Ok(SerializableScalar(interpolation))
+ }
+
+ /// Commits to a polynomial returning a sequence of group coefficients
+ /// Creates a commitment vector of coefficients * G
+ pub fn commit_polynomial(&self) -> PolynomialCommitment {
+ // Computes the multiplication of every coefficient of p with the generator G
+ let coef_commitment = self
+ .coefficients
+ .iter()
+ .map(|c| CoefficientCommitment::new(C::Group::generator() * *c))
+ .collect();
+ PolynomialCommitment::new(coef_commitment)
+ .expect("coefficients must have at least one element set to non-zero")
+ }
+
+ /// Set the constant value of this polynomial to a new scalar
+ /// Abort if the output polynomial is zero
+ pub fn set_constant(&mut self, v: Scalar) -> Result<(), ProtocolError> {
+ if self.coefficients.len() == 1 && v == ::Field::zero() {
+ return Err(ProtocolError::EmptyOrZeroCoefficients);
+ }
+ self.coefficients[0] = v;
+ Ok(())
+ }
+
+ /// Extends the Polynomial with an extra value as a constant
+ /// Used usually after sending a smaller polynomial to prevent serialization from
+ /// failing if the constant term is the identity
+ pub fn extend_with_zero(&self) -> Self {
+ let mut coeffcommitment = vec![::Field::zero()];
+ coeffcommitment.extend(self.get_coefficients());
+ Polynomial::new(coeffcommitment).expect("coefficients must have at least one element")
+ }
+}
+
+/******************* Polynomial Commitment *******************/
+/// Contains the commited coefficients of a polynomial i.e. coeff * G
+#[derive(Clone)]
+pub struct PolynomialCommitment {
+ /// The committed coefficients which are group elements
+ /// (elliptic curve points)
+ coefficients: Vec>,
+}
+
+impl PolynomialCommitment {
+ /// Creates a PolynomialCommitment out of a vector of CoefficientCommitment
+ /// This function raises Error if the vector is empty or if it is the all identity vector
+ pub fn new(coefcommitments: Vec>) -> Result {
+ if coefcommitments.is_empty() {
+ return Err(ProtocolError::EmptyOrZeroCoefficients);
+ }
+ // count the number of zero coeffs before spotting the first non-zero
+ let count = coefcommitments
+ .iter()
+ .rev()
+ .take_while(|x| x.value() == C::Group::identity())
+ .count();
+ if count == coefcommitments.len() {
+ return Err(ProtocolError::EmptyOrZeroCoefficients);
+ }
+ // get the number of non-identity coeffs
+ let last_non_id = coefcommitments.len() - count;
+ Ok(PolynomialCommitment {
+ coefficients: coefcommitments[..last_non_id].to_vec(),
+ })
+ }
+
+ /// Returns the coefficients of the
+ pub fn get_coefficients(&self) -> Vec> {
+ self.coefficients.to_vec()
+ }
+
+ /// Outputs the degree of the commited polynomial
+ pub fn degree(&self) -> usize {
+ self.coefficients.len() - 1
+ }
+
+ /// Evaluates the commited polynomial on zero
+ /// In other words, outputs the constant term
+ pub fn eval_on_zero(&self) -> CoefficientCommitment {
+ self.coefficients[0]
+ }
+
+ /// Evaluates the commited polynomial at a specific value
+ pub fn eval_on_point(&self, point: Scalar) -> CoefficientCommitment {
+ let mut out = C::Group::identity();
+ for c in self.coefficients.iter().rev() {
+ out = out * point + c.value();
+ }
+ CoefficientCommitment::new(out)
+ }
+
+ /// Evaluates the commited polynomial on a participant identifier.
+ pub fn eval_on_participant(&self, participant: Participant) -> CoefficientCommitment {
+ let id = participant.scalar::();
+ self.eval_on_point(id)
+ }
+
+ /// Computes polynomial interpolation on the exponent on a specific point
+ /// using a sequence of sorted coefficient commitments
+ pub fn eval_exponent_interpolation(
+ identifiers: &[Scalar],
+ shares: &[CoefficientCommitment],
+ point: Option<&Scalar>,
+ ) -> Result, ProtocolError> {
+ let mut interpolation = C::Group::identity();
+ // raise Error if the lengths are not the same
+ if identifiers.len() != shares.len() {
+ return Err(ProtocolError::InvalidInterpolationArguments);
+ };
+
+ // Compute the Lagrange coefficients
+ for (id, share) in identifiers.iter().zip(shares) {
+ // would raises error if insufficient number of identifiers (<= 1)
+ let lagrange_coefficient = compute_lagrange_coefficient::(identifiers, id, point)?;
+
+ // Compute y = g^f(point) via polynomial interpolation of these points of f
+ interpolation = interpolation + (share.value() * lagrange_coefficient.0);
+ }
+
+ Ok(CoefficientCommitment::new(interpolation))
+ }
+
+ /// Extends the Commited Polynomial with an extra value as a constant
+ /// Used usually after sending a smaller polynomial to prevent serialization from
+ /// failing if the constant term is the identity
+ pub fn extend_with_identity(&self) -> Self {
+ let mut coeffcommitment = vec![CoefficientCommitment::::new(C::Group::identity())];
+ coeffcommitment.extend(self.get_coefficients());
+ PolynomialCommitment::new(coeffcommitment)
+ .expect("coefficients must have at least one element")
+ }
+
+ /// Set the constant value of this polynomial to a new group element
+ /// Aborts if the output polynomial is the identity
+ pub fn set_constant(&mut self, v: CoefficientCommitment) -> Result<(), ProtocolError> {
+ if self.coefficients.len() == 1 && v.value() == C::Group::identity() {
+ return Err(ProtocolError::EmptyOrZeroCoefficients);
+ }
+ self.coefficients[0] = v;
+
+ Ok(())
+ }
+}
+
+impl Serialize for PolynomialCommitment {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ self.coefficients.serialize(serializer)
+ }
+}
+
+// Deserialization enforcing non-empty vecs and non all-identity PolynomialCommitments
+impl<'de, C: Ciphersuite> Deserialize<'de> for PolynomialCommitment {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ let coefficients = Vec::>::deserialize(deserializer)?;
+ if coefficients.is_empty() {
+ Err(serde::de::Error::custom("Polynomial must not be empty"))
+ } else {
+ // counts the number of successive identity elements on the highest
+ // degree coefficients and aborts if the committed polynomial is the identity
+ let is_identity = coefficients
+ .iter()
+ .rev()
+ .all(|x| x.value() == C::Group::identity());
+ if is_identity {
+ return Err(serde::de::Error::custom(
+ "Polynomial must not be the identity",
+ ));
+ }
+ Ok(PolynomialCommitment { coefficients })
+ }
+ }
+}
+
+impl Add for &PolynomialCommitment {
+ type Output = PolynomialCommitment;
+
+ fn add(self, rhs: Self) -> Self::Output {
+ // zip iterates over the smaller vec
+ let mut coefficients: Vec> = self
+ .coefficients
+ .iter()
+ .zip(rhs.coefficients.iter())
+ .map(|(a, b)| CoefficientCommitment::new(a.value() + b.value()))
+ .collect();
+
+ // Append remaining coefficients from the larger polynomial
+ match self.coefficients.len().cmp(&rhs.coefficients.len()) {
+ std::cmp::Ordering::Less => {
+ coefficients.extend_from_slice(&rhs.coefficients[self.coefficients.len()..])
+ }
+ std::cmp::Ordering::Greater => {
+ coefficients.extend_from_slice(&self.coefficients[rhs.coefficients.len()..])
+ }
+ _ => (),
+ }
+
+ PolynomialCommitment::new(coefficients)
+ .expect("coefficients must have at least one element")
+ }
+}
+
+/// Computes the lagrange coefficient using a set of given points
+/// lamda_i(x) = \prod_j (x - x_j)/(x_i - x_j)
+/// where j != i
+/// If x is None then consider it as 0
+pub fn compute_lagrange_coefficient(
+ points_set: &[Scalar],
+ i: &Scalar,
+ x: Option<&Scalar>,
+) -> Result, ProtocolError> {
+ let mut num = ::Field::one();
+ let mut den = ::Field::one();
+
+ if points_set.len() <= 1 || !points_set.contains(i) {
+ // returns error if there is not enough points to interpolate
+ // or if i is not in the set of points
+ return Err(ProtocolError::InvalidInterpolationArguments);
+ }
+ if let Some(x) = x {
+ for j in points_set.iter() {
+ if *i == *j {
+ continue;
+ }
+ num = num * (*x - *j);
+ den = den * (*i - *j);
+ }
+ } else {
+ for j in points_set.iter() {
+ if *i == *j {
+ continue;
+ }
+ // Both signs inverted just to avoid requiring an extra negation
+ num = num * *j;
+ den = den * (*j - *i);
+ }
+ }
+
+ // raises error if the denominator is null, i.e., the set contains duplicates
+ let den = ::Field::invert(&den)
+ .map_err(|_| ProtocolError::InvalidInterpolationArguments)?;
+ Ok(SerializableScalar(num * den))
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use frost_core::Field;
+ use frost_secp256k1::{Secp256K1Group, Secp256K1ScalarField, Secp256K1Sha256};
+ use rand_core::OsRng;
+ type C = Secp256K1Sha256;
+ #[test]
+ fn abort_no_polynomial() {
+ let poly = Polynomial::::new(vec![]);
+ assert!(poly.is_err(), "Polynomial should be raising error");
+
+ let vec = vec![Secp256K1ScalarField::zero(); 10];
+ let poly = Polynomial::::new(vec);
+ assert!(poly.is_err(), "Polynomial should be raising error");
+ }
+
+ #[test]
+ fn abort_no_polynomial_commitments() {
+ let poly = PolynomialCommitment::::new(vec![]);
+ assert!(poly.is_err(), "Polynomial should be raising error");
+ let vec = vec![CoefficientCommitment::::new(Secp256K1Group::identity()); 10];
+ let poly = PolynomialCommitment::new(vec);
+ assert!(poly.is_err(), "Polynomial should be raising error");
+ }
+
+ #[test]
+ fn poly_generate_evaluate_interpolate() {
+ let degree = 5;
+ // generate polynomial of degree 5
+ let poly = Polynomial::::generate_polynomial(None, degree, &mut OsRng)
+ .expect("Generation must not fail with overwhealming probability");
+
+ // evaluate polynomial on 6 different points
+ let participants = (0..degree + 1)
+ .map(|i| Participant::from(i as u32))
+ .collect::>();
+
+ let shares = participants
+ .iter()
+ .map(|p| poly.eval_on_participant(*p))
+ .collect::>();
+
+ // interpolate the polynomial using the shares on arbitrary points
+ let scalars = participants
+ .iter()
+ .map(|p| p.scalar::())
+ .collect::>();
+ for _ in 0..100 {
+ // create arbitrary point
+ let point = Secp256K1ScalarField::random(&mut OsRng);
+ // interpolate on this point
+ let interpolation =
+ Polynomial::::eval_interpolation(&scalars, &shares, Some(&point))
+ .expect("Interpolation has the correct inputs");
+ // evaluate the polynomial on the point
+ let evaluation = poly.eval_on_point(point);
+
+ // verify that the interpolated points match the polynomial evaluation
+ assert_eq!(interpolation.0, evaluation.0);
+ }
+ }
+
+ #[test]
+ fn com_generate_evaluate_interpolate() {
+ let degree = 5;
+ // generate polynomial of degree 5
+ let poly = Polynomial::::generate_polynomial(None, degree, &mut OsRng)
+ .expect("Generation must not fail with overwhealming probability");
+
+ let compoly = poly.commit_polynomial();
+ // evaluate polynomial on 6 different points
+ let participants = (0..degree + 1)
+ .map(|i| Participant::from(i as u32))
+ .collect::>();
+
+ let shares = participants
+ .iter()
+ .map(|p| compoly.eval_on_participant(*p))
+ .collect::>();
+
+ // interpolate the polynomial using the shares on arbitrary points
+ let scalars = participants
+ .iter()
+ .map(|p| p.scalar::())
+ .collect::>();
+ for _ in 0..100 {
+ // create arbitrary point
+ let point = Secp256K1ScalarField::random(&mut OsRng);
+ // interpolate on this point
+ let interpolation = PolynomialCommitment::::eval_exponent_interpolation(
+ &scalars,
+ &shares,
+ Some(&point),
+ )
+ .expect("Interpolation has the correct inputs");
+ // evaluate the polynomial on the point
+ let evaluation = compoly.eval_on_point(point);
+
+ // verify that the interpolated points match the polynomial evaluation
+ assert_eq!(interpolation.value(), evaluation.value());
+ }
+ }
+
+ #[test]
+ fn add_polynomial_commitments() {
+ let degree = 5;
+ // generate polynomial of degree 5
+ let poly = Polynomial::::generate_polynomial(None, degree, &mut OsRng)
+ .expect("Generation must not fail with overwhealming probability");
+
+ let compoly = poly.commit_polynomial();
+ // add two polynomials of the same height
+ let sum = compoly.add(&compoly);
+
+ let coefpoly = compoly.get_coefficients();
+ let mut coefsum = sum.get_coefficients();
+
+ assert_eq!(coefpoly.len(), coefsum.len());
+
+ // I need the scalar 2
+ // the easiest way to do so is to create a participant with identity 1
+ // transforming the identity into scalar would add +1
+ let two = Participant::from(1u32).scalar::();
+ for (c, two_c) in coefpoly.iter().zip(&coefsum) {
+ assert_eq!(c.value() * two, two_c.value())
+ }
+
+ coefsum.extend(&coefsum.clone());
+ let extend_sum_compoly =
+ PolynomialCommitment::new(coefsum).expect("We have proper coefficients");
+ // add two polynomials of different heights
+ let ext_sum_left = extend_sum_compoly.add(&compoly).get_coefficients();
+ let ext_sum_right = compoly.add(&extend_sum_compoly).get_coefficients();
+ for (c_left, c_right) in ext_sum_left.iter().zip(ext_sum_right) {
+ assert_eq!(c_left.value(), c_right.value());
+ }
+
+ let three = Participant::from(2u32).scalar::();
+ for i in 0..ext_sum_left.len() {
+ let c = ext_sum_left[i].value();
+ if i < ext_sum_left.len() / 2 {
+ assert_eq!(c, coefpoly[i].value() * three);
+ } else {
+ let index = i - ext_sum_left.len() / 2;
+ assert_eq!(c, coefpoly[index].value() * two);
+ }
+ }
+ }
+}
diff --git a/src/crypto/proofs/dlog.rs b/src/crypto/proofs/dlog.rs
new file mode 100644
index 00000000..63ad565d
--- /dev/null
+++ b/src/crypto/proofs/dlog.rs
@@ -0,0 +1,158 @@
+use crate::crypto::ciphersuite::{Ciphersuite, Element};
+use frost_core::{serialization::SerializableScalar, Field, Group};
+
+use super::strobe_transcript::Transcript;
+use rand_core::CryptoRngCore;
+
+/// The label we use for hashing the statement.
+const STATEMENT_LABEL: &[u8] = b"dlog proof statement";
+/// The label we use for hashing the first prover message.
+const COMMITMENT_LABEL: &[u8] = b"dlog proof commitment";
+/// The label we use for generating the challenge.
+const CHALLENGE_LABEL: &[u8] = b"dlog proof challenge";
+
+/// The public statement for this proof.
+///
+/// This statement claims knowledge of the discrete logarithm of some point.
+#[derive(Clone, Copy)]
+pub struct Statement<'a, C: Ciphersuite> {
+ pub public: &'a Element,
+}
+
+impl Statement<'_, C> {
+ /// Calculate the homomorphism we want to prove things about.
+ fn phi(&self, x: &SerializableScalar) -> Element {
+ C::Group::generator() * x.0
+ }
+
+ /// Encode into Vec: some sort of serialization
+ fn encode(&self) -> Vec {
+ let mut enc = Vec::new();
+ enc.extend_from_slice(b"statement:");
+
+ match ::serialize(self.public) {
+ Ok(ser) => {
+ enc.extend_from_slice(b"public:");
+ enc.extend_from_slice(ser.as_ref());
+ }
+ _ => panic!("Expected non-identity element"),
+ };
+ enc
+ }
+}
+
+/// The private witness for this proof.
+/// This holds the scalar the prover needs to know.
+#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)]
+pub struct Witness {
+ pub x: SerializableScalar,
+}
+
+/// Represents a proof of the statement.
+#[derive(Clone, serde::Serialize, serde::Deserialize)]
+#[serde(bound = "C: Ciphersuite")]
+pub struct Proof {
+ e: SerializableScalar,
+ s: SerializableScalar,
+}
+
+/// Encodes an EC point into a vec including the identity point.
+/// Should be used with HIGH precaution as it allows serializing the identity point
+/// deviating from the standard
+fn encode_point(point: &Element) -> Vec {
+ // Need to create a serialization containing the all zero strings
+ let size = C::Group::serialize(&C::Group::generator())
+ .unwrap()
+ .as_ref()
+ .len();
+ // Serializing the identity might fail!
+ // this is a workaround to be able to serialize even this infinity point.
+ let ser = match <::Group as Group>::Serialization::try_from(vec![
+ 0u8;
+ size
+ ]) {
+ Ok(ser) => ser,
+ _ => panic!("Should not raise error"),
+ };
+ C::Group::serialize(point).unwrap_or(ser).as_ref().to_vec()
+}
+
+/// Prove that a witness satisfies a given statement.
+///
+/// We need some randomness for the proof, and also a transcript, which is
+/// used for the Fiat-Shamir transform.
+pub fn prove(
+ rng: &mut impl CryptoRngCore,
+ transcript: &mut Transcript,
+ statement: Statement<'_, C>,
+ witness: Witness,
+) -> Proof {
+ transcript.message(STATEMENT_LABEL, &statement.encode());
+
+ let k = ::Field::random(rng);
+ let big_k = statement.phi(&SerializableScalar(k));
+
+ transcript.message(COMMITMENT_LABEL, &encode_point::(&big_k));
+ let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL);
+ let e = ::Field::random(&mut rng);
+
+ let s = k + e * witness.x.0;
+ Proof {
+ e: SerializableScalar(e),
+ s: SerializableScalar(s),
+ }
+}
+
+/// Verify that a proof attesting to the validity of some statement.
+///
+/// We use a transcript in order to verify the Fiat-Shamir transformation.
+#[must_use]
+pub fn verify(
+ transcript: &mut Transcript,
+ statement: Statement<'_, C>,
+ proof: &Proof,
+) -> bool {
+ transcript.message(STATEMENT_LABEL, &statement.encode());
+
+ let big_k: Element = statement.phi(&proof.s) - *statement.public * proof.e.0;
+
+ transcript.message(COMMITMENT_LABEL, &encode_point::(&big_k));
+ let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL);
+ let e = ::Field::random(&mut rng);
+
+ e == proof.e.0
+}
+
+#[cfg(test)]
+mod test {
+ use rand_core::OsRng;
+
+ use super::*;
+ use frost_secp256k1::Secp256K1Sha256;
+ use k256::{ProjectivePoint, Scalar};
+
+ #[test]
+ fn test_valid_proof_verifies() {
+ let x = Scalar::generate_biased(&mut OsRng);
+
+ let statement = Statement:: {
+ public: &(ProjectivePoint::GENERATOR * x),
+ };
+ let witness = Witness {
+ x: SerializableScalar::(x),
+ };
+
+ let transcript = Transcript::new(b"protocol");
+
+ let proof = prove(
+ &mut OsRng,
+ &mut transcript.fork(b"party", &[1]),
+ statement,
+ witness,
+ );
+
+ let ok = verify(&mut transcript.fork(b"party", &[1]), statement, &proof);
+
+ assert!(ok);
+ }
+}
diff --git a/src/crypto/proofs/dlogeq.rs b/src/crypto/proofs/dlogeq.rs
new file mode 100644
index 00000000..4c7deafb
--- /dev/null
+++ b/src/crypto/proofs/dlogeq.rs
@@ -0,0 +1,204 @@
+use rand_core::CryptoRngCore;
+
+use crate::crypto::ciphersuite::{Ciphersuite, Element};
+
+use frost_core::{serialization::SerializableScalar, Field, Group};
+
+use super::strobe_transcript::Transcript;
+
+/// The label we use for hashing the statement.
+const STATEMENT_LABEL: &[u8] = b"dlogeq proof statement";
+/// The label we use for hashing the first prover message.
+const COMMITMENT_LABEL: &[u8] = b"dlogeq proof commitment";
+/// The label we use for generating the challenge.
+const CHALLENGE_LABEL: &[u8] = b"dlogeq proof challenge";
+
+/// The public statement for this proof.
+///
+/// This statement claims knowledge of a scalar that's the discrete logarithm
+/// of one point under the standard generator, and of another point under an alternate generator.
+#[derive(Clone, Copy)]
+pub struct Statement<'a, C: Ciphersuite> {
+ pub public0: &'a Element,
+ pub generator1: &'a Element,
+ pub public1: &'a Element,
+}
+
+fn element_into_or_panic(point: &Element, label: &[u8]) -> Vec {
+ let mut enc = Vec::new();
+ match ::serialize(point) {
+ Ok(ser) => {
+ enc.extend_from_slice(label);
+ enc.extend_from_slice(ser.as_ref());
+ }
+ // unreachable as either the statement is locally created
+ // and thus the points are well defined, or it is received
+ // from someone and thus it is serializable.
+ _ => panic!("Expected non-identity element"),
+ };
+ enc
+}
+
+impl Statement<'_, C> {
+ /// Calculate the homomorphism we want to prove things about.
+ fn phi(&self, x: &SerializableScalar) -> (Element, Element) {
+ (C::Group::generator() * x.0, *self.generator1 * x.0)
+ }
+
+ /// Encode into Vec: some sort of serialization
+ fn encode(&self) -> Vec {
+ let mut enc = Vec::new();
+ enc.extend_from_slice(b"statement:");
+ // None of the following calls should panic as neither public and generator are identity
+ let ser0 = element_into_or_panic::(self.public0, b"public 0:");
+ let ser1 = element_into_or_panic::(self.generator1, b"generator 1:");
+ let ser2 = element_into_or_panic::(self.public1, b"public 1:");
+ enc.extend_from_slice(&ser0);
+ enc.extend_from_slice(&ser1);
+ enc.extend_from_slice(&ser2);
+ enc
+ }
+}
+
+/// The private witness for this proof.
+///
+/// This holds the scalar the prover needs to know.
+#[derive(Clone, Copy)]
+pub struct Witness {
+ pub x: SerializableScalar,
+}
+
+/// Represents a proof of the statement.
+#[derive(Clone, serde::Serialize, serde::Deserialize)]
+#[serde(bound = "C: Ciphersuite")]
+pub struct Proof {
+ e: SerializableScalar,
+ s: SerializableScalar,
+}
+
+/// Encodes two EC points into a vec including the identity point.
+/// Should be used with HIGH precaution as it allows serializing the identity point
+/// deviating from the standard
+fn encode_two_points(point_1: &Element, point_2: &Element) -> Vec {
+ // Need to create a serialization containing the all zero strings
+ let size = C::Group::serialize(&C::Group::generator())
+ .unwrap()
+ .as_ref()
+ .len();
+ // Serializing the identity might fail!
+ // this is a workaround to be able to serialize even this infinity point.
+ let ser = match <::Group as Group>::Serialization::try_from(vec![
+ 0u8;
+ size
+ ]) {
+ Ok(ser) => ser,
+ _ => panic!("Should not raise error"),
+ };
+
+ let ser_1 = C::Group::serialize(point_1)
+ .unwrap_or(ser)
+ .as_ref()
+ .to_vec();
+
+ // Clone is not derived in Serialization type so I had to compute it again :(
+ let ser = match <::Group as Group>::Serialization::try_from(vec![
+ 0u8;
+ size
+ ]) {
+ Ok(ser) => ser,
+ _ => panic!("Should not raise error"),
+ };
+ let ser_2 = C::Group::serialize(point_2)
+ .unwrap_or(ser)
+ .as_ref()
+ .to_vec();
+ rmp_serde::encode::to_vec(&(ser_1, ser_2)).expect("failed to encode value")
+}
+
+/// Prove that a witness satisfies a given statement.
+///
+/// We need some randomness for the proof, and also a transcript, which is
+/// used for the Fiat-Shamir transform.
+pub fn prove(
+ rng: &mut impl CryptoRngCore,
+ transcript: &mut Transcript,
+ statement: Statement<'_, C>,
+ witness: Witness,
+) -> Proof {
+ transcript.message(STATEMENT_LABEL, &statement.encode());
+
+ let k = SerializableScalar::(::Field::random(rng));
+ let big_k = statement.phi(&k);
+
+ transcript.message(
+ COMMITMENT_LABEL,
+ &encode_two_points::(&big_k.0, &big_k.1),
+ );
+ let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL);
+ let e = ::Field::random(&mut rng);
+
+ let s = k.0 + e * witness.x.0;
+ Proof {
+ e: SerializableScalar::(e),
+ s: SerializableScalar::(s),
+ }
+}
+
+/// Verify that a proof attesting to the validity of some statement.
+///
+/// We use a transcript in order to verify the Fiat-Shamir transformation.
+#[must_use]
+pub fn verify(
+ transcript: &mut Transcript,
+ statement: Statement<'_, C>,
+ proof: &Proof,
+) -> bool {
+ transcript.message(STATEMENT_LABEL, &statement.encode());
+
+ let (phi0, phi1) = statement.phi(&proof.s);
+ let big_k0 = phi0 - *statement.public0 * proof.e.0;
+ let big_k1 = phi1 - *statement.public1 * proof.e.0;
+
+ transcript.message(COMMITMENT_LABEL, &encode_two_points::(&big_k0, &big_k1));
+ let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL);
+ let e = ::Field::random(&mut rng);
+
+ e == proof.e.0
+}
+
+#[cfg(test)]
+mod test {
+ use rand_core::OsRng;
+
+ use super::*;
+ use frost_secp256k1::Secp256K1Sha256;
+ use k256::{ProjectivePoint, Scalar};
+
+ #[test]
+ fn test_valid_proof_verifies() {
+ let x = Scalar::generate_biased(&mut OsRng);
+
+ let big_h = ProjectivePoint::GENERATOR * Scalar::generate_biased(&mut OsRng);
+ let statement = Statement:: {
+ public0: &(ProjectivePoint::GENERATOR * x),
+ generator1: &big_h,
+ public1: &(big_h * x),
+ };
+ let witness = Witness {
+ x: SerializableScalar::(x),
+ };
+
+ let transcript = Transcript::new(b"protocol");
+
+ let proof = prove(
+ &mut OsRng,
+ &mut transcript.fork(b"party", &[1]),
+ statement,
+ witness,
+ );
+
+ let ok = verify(&mut transcript.fork(b"party", &[1]), statement, &proof);
+
+ assert!(ok);
+ }
+}
diff --git a/src/proofs/mod.rs b/src/crypto/proofs/mod.rs
similarity index 100%
rename from src/proofs/mod.rs
rename to src/crypto/proofs/mod.rs
diff --git a/src/proofs/strobe.rs b/src/crypto/proofs/strobe.rs
similarity index 100%
rename from src/proofs/strobe.rs
rename to src/crypto/proofs/strobe.rs
diff --git a/src/proofs/strobe_transcript.rs b/src/crypto/proofs/strobe_transcript.rs
similarity index 99%
rename from src/proofs/strobe_transcript.rs
rename to src/crypto/proofs/strobe_transcript.rs
index 84b538a8..8a95e515 100644
--- a/src/proofs/strobe_transcript.rs
+++ b/src/crypto/proofs/strobe_transcript.rs
@@ -1,6 +1,6 @@
use zeroize::Zeroize;
-use crate::proofs::strobe::Strobe128;
+use super::strobe::Strobe128;
pub const MERLIN_PROTOCOL_LABEL: &[u8] = b"Mini-Merlin";
diff --git a/src/crypto/random.rs b/src/crypto/random.rs
new file mode 100644
index 00000000..4d1f7acf
--- /dev/null
+++ b/src/crypto/random.rs
@@ -0,0 +1,21 @@
+use rand_core::CryptoRngCore;
+use serde::{Deserialize, Serialize};
+
+const RANDOMIZER_LEN: usize = 32;
+
+/// Represents the randomizer used to make a commit hiding.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Randomizer([u8; RANDOMIZER_LEN]);
+impl Randomizer {
+ /// Generate a new randomizer value by sampling from an RNG.
+ pub fn random(rng: &mut R) -> Self {
+ let mut out = [0u8; RANDOMIZER_LEN];
+ rng.fill_bytes(&mut out);
+ Self(out)
+ }
+}
+impl AsRef<[u8]> for Randomizer {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
+ }
+}
diff --git a/src/ecdsa/dkg_ecdsa.rs b/src/ecdsa/dkg_ecdsa.rs
index aae8c179..96462533 100644
--- a/src/ecdsa/dkg_ecdsa.rs
+++ b/src/ecdsa/dkg_ecdsa.rs
@@ -125,9 +125,9 @@ mod test {
result[2].1.private_share.to_scalar(),
];
let p_list = ParticipantList::new(&participants).unwrap();
- let x = p_list.generic_lagrange::(participants[0]) * shares[0]
- + p_list.generic_lagrange::(participants[1]) * shares[1]
- + p_list.generic_lagrange::(participants[2]) * shares[2];
+ let x = p_list.lagrange::(participants[0]) * shares[0]
+ + p_list.lagrange::(participants[1]) * shares[1]
+ + p_list.lagrange::(participants[2]) * shares[2];
assert_eq!(::generator() * x, pub_key);
Ok(())
}
@@ -156,9 +156,9 @@ mod test {
result1[2].1.private_share.to_scalar(),
];
let p_list = ParticipantList::new(&participants).unwrap();
- let x = p_list.generic_lagrange::(participants[0]) * shares[0]
- + p_list.generic_lagrange::(participants[1]) * shares[1]
- + p_list.generic_lagrange::(participants[2]) * shares[2];
+ let x = p_list.lagrange::(participants[0]) * shares[0]
+ + p_list.lagrange::(participants[1]) * shares[1]
+ + p_list.lagrange::(participants[2]) * shares[2];
assert_eq!(::generator() * x, pub_key);
Ok(())
}
@@ -198,10 +198,10 @@ mod test {
result1[3].1.private_share.to_scalar(),
];
let p_list = ParticipantList::new(&participants).unwrap();
- let x = p_list.generic_lagrange::(participants[0]) * shares[0]
- + p_list.generic_lagrange::(participants[1]) * shares[1]
- + p_list.generic_lagrange::(participants[2]) * shares[2]
- + p_list.generic_lagrange::(participants[3]) * shares[3];
+ let x = p_list.lagrange::(participants[0]) * shares[0]
+ + p_list.lagrange::(participants[1]) * shares[1]
+ + p_list.lagrange::(participants[2]) * shares[2]
+ + p_list.lagrange::(participants[3]) * shares[3];
assert_eq!(::generator() * x, pub_key.to_element());
Ok(())
diff --git a/src/ecdsa/math.rs b/src/ecdsa/math.rs
deleted file mode 100644
index a135b68c..00000000
--- a/src/ecdsa/math.rs
+++ /dev/null
@@ -1,273 +0,0 @@
-use std::ops::{Add, AddAssign, Index, Mul, MulAssign};
-
-use elliptic_curve::{Field, Group};
-use rand_core::CryptoRngCore;
-use serde::{Deserialize, Serialize};
-
-use crate::{
- compat::CSCurve,
- serde::{deserialize_projective_points, serialize_projective_points},
-};
-
-/// Represents a polynomial with coefficients in the scalar field of the curve.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct Polynomial {
- /// The coefficients of our polynomial, from 0..size-1.
- coefficients: Vec,
-}
-
-impl Polynomial {
- /// Generate a random polynomial with a certain number of coefficients.
- pub fn random(rng: &mut impl CryptoRngCore, size: usize) -> Self {
- let coefficients = (0..size).map(|_| C::Scalar::random(&mut *rng)).collect();
- Self { coefficients }
- }
-
- /// Extend a constant to a random polynomial of a certain size.
- ///
- /// This is useful if you want the polynomial to have a certain value, but
- /// otherwise be random.
- pub fn extend_random(rng: &mut impl CryptoRngCore, size: usize, constant: &C::Scalar) -> Self {
- let mut coefficients = Vec::with_capacity(size);
- coefficients.push(*constant);
- for _ in 1..size {
- coefficients.push(C::Scalar::random(&mut *rng));
- }
- Self { coefficients }
- }
-
- /// Modify this polynomial by adding another polynomial.
- pub fn add_mut(&mut self, other: &Self) {
- let new_len = self.coefficients.len().max(other.coefficients.len());
- self.coefficients.resize(new_len, C::Scalar::ZERO);
- self.coefficients
- .iter_mut()
- .zip(other.coefficients.iter())
- .for_each(|(a, b)| *a += b);
- }
-
- /// Return the addition of this polynomial with another.
- pub fn add(&self, other: &Self) -> Self {
- let mut out = self.clone();
- out.add_mut(other);
- out
- }
-
- /// Scale this polynomial in place by a field element.
- pub fn scale_mut(&mut self, scale: &C::Scalar) {
- self.coefficients.iter_mut().for_each(|a| *a *= scale);
- }
-
- /// Return the result of scaling this polynomial by a field element.
- pub fn scale(&self, scale: &C::Scalar) -> Self {
- let mut out = self.clone();
- out.scale_mut(scale);
- out
- }
-
- /// Evaluate this polynomial at 0.
- ///
- /// This is much more efficient than evaluating at other points.
- pub fn evaluate_zero(&self) -> C::Scalar {
- self.coefficients.first().cloned().unwrap_or_default()
- }
-
- /// Set the zero value of this polynomial to a new scalar
- pub fn set_zero(&mut self, v: C::Scalar) {
- if self.coefficients.is_empty() {
- self.coefficients.push(v)
- } else {
- self.coefficients[0] = v
- }
- }
-
- /// Evaluate this polynomial at a specific point.
- pub fn evaluate(&self, x: &C::Scalar) -> C::Scalar {
- let mut out = C::Scalar::ZERO;
- for c in self.coefficients.iter().rev() {
- out = out * x + c;
- }
- out
- }
-
- /// Commit to this polynomial by acting on the generator
- pub fn commit(&self) -> GroupPolynomial {
- let coefficients = self
- .coefficients
- .iter()
- .map(|x| C::ProjectivePoint::generator() * x)
- .collect();
- GroupPolynomial { coefficients }
- }
-
- /// Return the length of this polynomial.
- pub fn len(&self) -> usize {
- self.coefficients.len()
- }
-
- /// Required by https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
- pub fn is_empty(&self) -> bool {
- self.coefficients.is_empty()
- }
-}
-
-impl Index for Polynomial {
- type Output = C::Scalar;
-
- fn index(&self, i: usize) -> &Self::Output {
- &self.coefficients[i]
- }
-}
-
-impl Add for &Polynomial {
- type Output = Polynomial;
-
- fn add(self, rhs: Self) -> Self::Output {
- self.add(rhs)
- }
-}
-
-impl AddAssign<&Self> for Polynomial {
- fn add_assign(&mut self, rhs: &Self) {
- self.add_mut(rhs)
- }
-}
-
-impl Mul<&C::Scalar> for &Polynomial {
- type Output = Polynomial;
-
- fn mul(self, rhs: &C::Scalar) -> Self::Output {
- self.scale(rhs)
- }
-}
-
-impl MulAssign<&C::Scalar> for Polynomial {
- fn mul_assign(&mut self, rhs: &C::Scalar) {
- self.scale_mut(rhs)
- }
-}
-
-/// A polynomial with group coefficients.
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct GroupPolynomial {
- #[serde(
- serialize_with = "serialize_projective_points::",
- deserialize_with = "deserialize_projective_points::"
- )]
- coefficients: Vec,
-}
-
-impl GroupPolynomial {
- /// Modify this polynomial by adding another one.
- pub fn add_mut(&mut self, other: &Self) {
- self.coefficients
- .iter_mut()
- .zip(other.coefficients.iter())
- .for_each(|(a, b)| *a += b)
- }
-
- /// The result of adding this polynomial with another.
- pub fn add(&self, other: &Self) -> Self {
- let coefficients = self
- .coefficients
- .iter()
- .zip(other.coefficients.iter())
- .map(|(a, b)| *a + *b)
- .collect();
- Self { coefficients }
- }
-
- /// Evaluate this polynomial at 0.
- ///
- /// This is more efficient than evaluating at an arbitrary point.
- pub fn evaluate_zero(&self) -> C::ProjectivePoint {
- self.coefficients.first().cloned().unwrap_or_default()
- }
-
- /// Evaluate this polynomial at a specific value.
- pub fn evaluate(&self, x: &C::Scalar) -> C::ProjectivePoint {
- let mut out = C::ProjectivePoint::identity();
- for c in self.coefficients.iter().rev() {
- out = out * x + c;
- }
- out
- }
-
- /// Set the zero value of this polynomial to a new group value.
- pub fn set_zero(&mut self, v: C::ProjectivePoint) {
- if self.coefficients.is_empty() {
- self.coefficients.push(v)
- } else {
- self.coefficients[0] = v
- }
- }
-
- /// Return the length of this polynomial.
- pub fn len(&self) -> usize {
- self.coefficients.len()
- }
-
- /// Required by https://rust-lang.github.io/rust-clippy/master/index.html#len_without_is_empty
- pub fn is_empty(&self) -> bool {
- self.coefficients.is_empty()
- }
-}
-
-impl