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 Add for &GroupPolynomial { - type Output = GroupPolynomial; - - fn add(self, rhs: Self) -> Self::Output { - self.add(rhs) - } -} - -impl AddAssign<&Self> for GroupPolynomial { - fn add_assign(&mut self, rhs: &Self) { - self.add_mut(rhs) - } -} - -#[cfg(test)] -mod test { - use super::*; - use k256::{Scalar, Secp256k1}; - - #[test] - fn test_addition() { - let mut f = Polynomial:: { - coefficients: vec![Scalar::from(1u32), Scalar::from(2u32)], - }; - let g = Polynomial { - coefficients: vec![Scalar::from(1u32), Scalar::from(2u32), Scalar::from(3u32)], - }; - let h = Polynomial { - coefficients: vec![Scalar::from(2u32), Scalar::from(4u32), Scalar::from(3u32)], - }; - assert_eq!(&f + &g, h); - f += &g; - assert_eq!(f, h); - } - - #[test] - fn test_scaling() { - let s = Scalar::from(2u32); - let mut f = Polynomial:: { - coefficients: vec![Scalar::from(1u32), Scalar::from(2u32)], - }; - let h = Polynomial { - coefficients: vec![Scalar::from(2u32), Scalar::from(4u32)], - }; - assert_eq!(&f * &s, h); - f *= &s; - assert_eq!(f, h); - } - - #[test] - fn test_evaluation() { - let f = Polynomial:: { - coefficients: vec![Scalar::from(1u32), Scalar::from(2u32)], - }; - assert_eq!(f.evaluate(&Scalar::from(1u32)), Scalar::from(3u32)); - assert_eq!(f.evaluate(&Scalar::from(2u32)), Scalar::from(5u32)); - } -} diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 5ec59694..eabd5486 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -1,15 +1,29 @@ -//! This module serves as a wrapper for Frost protocol. +//! This module serves as a wrapper for ECDSA scheme. +use elliptic_curve::{ + bigint::U256, + ops::{Invert, Reduce}, + point::AffineCoordinates, +}; -use crate::generic_dkg::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; -use frost_secp256k1::keys::SigningShare; -use frost_secp256k1::{Secp256K1Sha256, VerifyingKey}; +use frost_secp256k1::{ + keys::SigningShare, Field, Secp256K1ScalarField, Secp256K1Sha256, VerifyingKey, +}; +use k256::{AffinePoint, ProjectivePoint}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Eq, PartialEq)] +use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; + +#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] pub struct KeygenOutput { pub private_share: SigningShare, pub public_key: VerifyingKey, } +pub type Scalar = ::Scalar; +pub type CoefficientCommitment = frost_core::keys::CoefficientCommitment; +pub type Polynomial = crate::crypto::polynomials::Polynomial; +pub type PolynomialCommitment = crate::crypto::polynomials::PolynomialCommitment; + impl From> for KeygenOutput { fn from(value: crate::generic_dkg::KeygenOutput) -> Self { Self { @@ -27,10 +41,45 @@ impl ScalarSerializationFormat for Secp256K1Sha256 { impl Ciphersuite for Secp256K1Sha256 {} +/// Get the x coordinate of a point, as a scalar +pub(crate) fn x_coordinate(point: &AffinePoint) -> Scalar { + >::reduce_bytes(&point.x()) +} + +/// Represents a signature that supports different variants of ECDSA. +/// +/// An ECDSA signature is usually two scalars. +/// The first is derived from using the x-coordinate of an elliptic curve point (big_r), +/// and the second is computed using the typical ecdsa signing equation. +/// Deriving the x-coordination implies losing information about big_r, some variants +/// may thus include an extra information to recover this point. +/// +/// This signature supports all variants by containing big_r entirely +#[derive(Clone)] +pub struct FullSignature { + /// This is the entire first point. + pub big_r: AffinePoint, + /// This is the second scalar, normalized to be in the lower range. + pub s: Scalar, +} + +impl FullSignature { + #[must_use] + // This verification tests the signature including whether s has been normalized + pub fn verify(&self, public_key: &AffinePoint, msg_hash: &Scalar) -> bool { + let r: Scalar = x_coordinate(&self.big_r); + if r.is_zero().into() || self.s.is_zero().into() { + return false; + } + let s_inv = self.s.invert_vartime().unwrap(); + let reproduced = (ProjectivePoint::GENERATOR * (*msg_hash * s_inv)) + + (ProjectivePoint::from(*public_key) * (r * s_inv)); + x_coordinate(&reproduced.into()) == r + } +} + pub mod dkg_ecdsa; -pub mod math; -pub mod presign; -pub mod sign; +pub mod ot_based_ecdsa; +pub mod robust_ecdsa; #[cfg(test)] mod test; -pub mod triples; diff --git a/src/ecdsa/ot_based_ecdsa/mod.rs b/src/ecdsa/ot_based_ecdsa/mod.rs new file mode 100644 index 00000000..03f7c49f --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/mod.rs @@ -0,0 +1,38 @@ +use crate::ecdsa::{AffinePoint, KeygenOutput, Scalar}; +use serde::{Deserialize, Serialize}; +use triples::{TriplePub, TripleShare}; + +/// The output of the presigning protocol. +/// +/// This output is basically all the parts of the signature that we can perform +/// without knowing the message. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PresignOutput { + /// The public nonce commitment. + pub big_r: AffinePoint, + /// Our share of the nonce value. + pub k: Scalar, + /// Our share of the sigma value. + pub sigma: Scalar, +} + +/// The arguments needed to create a presignature. +#[derive(Debug, Clone)] +pub struct PresignArguments { + /// The first triple's public information, and our share. + pub triple0: (TripleShare, TriplePub), + /// Ditto, for the second triple. + pub triple1: (TripleShare, TriplePub), + /// The output of key generation, i.e. our share of the secret key, and the public key package. + /// This is of type KeygenOutput from Frost implementation + pub keygen_out: KeygenOutput, + /// The desired threshold for the presignature, which must match the original threshold + pub threshold: usize, +} + +pub mod presign; +pub mod sign; +pub mod triples; + +#[cfg(test)] +mod test; diff --git a/src/ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs similarity index 51% rename from src/ecdsa/presign.rs rename to src/ecdsa/ot_based_ecdsa/presign.rs index 7fd5c35d..0a0ada0d 100644 --- a/src/ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -1,94 +1,36 @@ -use crate::compat::CSCurve; -use crate::ecdsa::triples::{TriplePub, TripleShare}; -use crate::ecdsa::KeygenOutput; -use crate::participants::ParticipantCounter; -use crate::protocol::internal::{make_protocol, Comms, SharedChannel}; -use crate::protocol::{InitializationError, Protocol}; -use crate::{ - participants::ParticipantList, - protocol::{Participant, ProtocolError}, +use super::{PresignArguments, PresignOutput}; +use crate::ecdsa::{ProjectivePoint, Scalar, Secp256K1Sha256}; +use crate::participants::{ParticipantCounter, ParticipantList}; +use crate::protocol::{ + internal::{make_protocol, Comms, SharedChannel}, + InitializationError, Participant, Protocol, ProtocolError, }; -use elliptic_curve::{Field, Group, ScalarPrimitive}; -use frost_secp256k1::keys::SigningShare; -use frost_secp256k1::VerifyingKey; -use serde::{Deserialize, Serialize}; -/// The output of the presigning protocol. -/// -/// This output is basically all the parts of the signature that we can perform -/// without knowing the message. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PresignOutput { - /// The public nonce commitment. - pub big_r: C::AffinePoint, - /// Our share of the nonce value. - pub k: C::Scalar, - /// Our share of the sigma value. - pub sigma: C::Scalar, -} - -/// The arguments needed to create a presignature. -#[derive(Debug, Clone)] -pub struct PresignArguments { - /// The first triple's public information, and our share. - pub triple0: (TripleShare, TriplePub), - /// Ditto, for the second triple. - pub triple1: (TripleShare, TriplePub), - /// The output of key generation, i.e. our share of the secret key, and the public key package. - /// This is of type KeygenOutput from Frost implementation - pub keygen_out: KeygenOutput, - /// The desired threshold for the presignature, which must match the original threshold - pub threshold: usize, -} +type C = Secp256K1Sha256; -/// Transforms a verification key of type Secp256k1SHA256 to CSCurve of cait-sith -fn from_secp256k1sha256_to_cscurve_vk( - verifying_key: &VerifyingKey, -) -> Result { - // serializes into a canonical byte array buf of length 33 bytes using the affine point representation - let bytes = verifying_key - .serialize() - .map_err(|_| ProtocolError::PointSerialization)?; - - let bytes: [u8; 33] = bytes.try_into().expect("Slice is not 33 bytes long"); - let point = match C::from_bytes_to_affine(bytes) { - Some(point) => point, - _ => return Err(ProtocolError::PointSerialization), - }; - Ok(point) -} - -/// Transforms a secret key of type Secp256k1Sha256 to CSCurve of cait-sith -fn from_secp256k1sha256_to_cscurve_sk(private_share: &SigningShare) -> C::Scalar { - let bytes = private_share.to_scalar().to_bytes(); - #[allow(clippy::unnecessary_fallible_conversions)] - let bytes: [u8; 32] = bytes.try_into().expect("Slice is not 32 bytes long"); - C::from_bytes_to_scalar(bytes).unwrap() -} - -async fn do_presign( +async fn do_presign( mut chan: SharedChannel, participants: ParticipantList, me: Participant, bt_participants: ParticipantList, bt_id: Participant, - args: PresignArguments, -) -> Result, ProtocolError> { + args: PresignArguments, +) -> Result { // Spec 1.2 + 1.3 - let big_k: C::ProjectivePoint = args.triple0.1.big_a.into(); + let big_k: ProjectivePoint = args.triple0.1.big_a.into(); let big_d = args.triple0.1.big_b; let big_kd = args.triple0.1.big_c; - let big_a: C::ProjectivePoint = args.triple1.1.big_a.into(); - let big_b: C::ProjectivePoint = args.triple1.1.big_b.into(); + let big_a: ProjectivePoint = args.triple1.1.big_a.into(); + let big_b: ProjectivePoint = args.triple1.1.big_b.into(); let sk_lambda = participants.lagrange::(me); let bt_lambda = bt_participants.lagrange::(bt_id); let k_i = args.triple0.0.a; let k_prime_i = bt_lambda * k_i; - let kd_i: C::Scalar = bt_lambda * args.triple0.0.c; // if this is zero, then the broadcast kdi is also zero. + let kd_i: Scalar = bt_lambda * args.triple0.0.c; // if this is zero, then the broadcast kdi is also zero. let a_i = args.triple1.0.a; let b_i = args.triple1.0.b; @@ -96,36 +38,28 @@ async fn do_presign( let a_prime_i = bt_lambda * a_i; let b_prime_i = bt_lambda * b_i; - let public_key = from_secp256k1sha256_to_cscurve_vk::(&args.keygen_out.public_key)?; - let big_x: C::ProjectivePoint = public_key; - let private_share = from_secp256k1sha256_to_cscurve_sk::(&args.keygen_out.private_share); + let big_x: ProjectivePoint = args.keygen_out.public_key.to_element(); + let private_share = args.keygen_out.private_share.to_scalar(); let x_prime_i = sk_lambda * private_share; // Spec 1.4 let wait0 = chan.next_waitpoint(); - { - let kd_i: ScalarPrimitive = kd_i.into(); - chan.send_many(wait0, &kd_i); - } + chan.send_many(wait0, &kd_i); // Spec 1.9 - let ka_i: C::Scalar = k_prime_i + a_prime_i; - let xb_i: C::Scalar = x_prime_i + b_prime_i; + let ka_i: Scalar = k_prime_i + a_prime_i; + let xb_i: Scalar = x_prime_i + b_prime_i; // Spec 1.10 let wait1 = chan.next_waitpoint(); - { - let ka_i: ScalarPrimitive = ka_i.into(); - let xb_i: ScalarPrimitive = xb_i.into(); - chan.send_many(wait1, &(ka_i, xb_i)); - } + chan.send_many(wait1, &(ka_i, xb_i)); // Spec 2.1 and 2.2 let mut kd = kd_i; let mut seen = ParticipantCounter::new(&participants); seen.put(me); while !seen.full() { - let (from, kd_j): (_, ScalarPrimitive) = chan.recv(wait0).await?; + let (from, kd_j): (_, Scalar) = chan.recv(wait0).await?; if kd_j.is_zero().into() { return Err(ProtocolError::AssertionFailed( @@ -136,11 +70,11 @@ async fn do_presign( if !seen.put(from) { continue; } - kd += C::Scalar::from(kd_j); + kd += kd_j; } // Spec 2.3 - if big_kd != (C::ProjectivePoint::generator() * kd).into() { + if big_kd != (ProjectivePoint::GENERATOR * kd).to_affine() { return Err(ProtocolError::AssertionFailed( "received incorrect shares of kd".to_string(), )); @@ -152,18 +86,17 @@ async fn do_presign( seen.clear(); seen.put(me); while !seen.full() { - let (from, (ka_j, xb_j)): (_, (ScalarPrimitive, ScalarPrimitive)) = - chan.recv(wait1).await?; + let (from, (ka_j, xb_j)): (_, (Scalar, Scalar)) = chan.recv(wait1).await?; if !seen.put(from) { continue; } - ka += C::Scalar::from(ka_j); - xb += C::Scalar::from(xb_j); + ka += ka_j; + xb += xb_j; } // Spec 2.6 - if (C::ProjectivePoint::generator() * ka != big_k + big_a) - || (C::ProjectivePoint::generator() * xb != big_x + big_b) + if (ProjectivePoint::GENERATOR * ka != big_k + big_a) + || (ProjectivePoint::GENERATOR * xb != big_x + big_b) { return Err(ProtocolError::AssertionFailed( "received incorrect shares of additive triple phase.".to_string(), @@ -171,10 +104,10 @@ async fn do_presign( } // Spec 2.7 - let kd_inv: Option = kd.invert().into(); + let kd_inv: Option = kd.invert().into(); let kd_inv = kd_inv.ok_or_else(|| ProtocolError::AssertionFailed("failed to invert kd".to_string()))?; - let big_r = (C::ProjectivePoint::from(big_d) * kd_inv).into(); + let big_r = (big_d * kd_inv).into(); // Spec 2.8 let lambda_diff = bt_lambda * sk_lambda.invert().expect("to invert sk_lambda"); @@ -194,13 +127,13 @@ async fn do_presign( /// /// This work does depend on the private key though, and it's crucial /// that a presignature is never used. -pub fn presign( +pub fn presign( participants: &[Participant], me: Participant, bt_participants: &[Participant], bt_id: Participant, - args: PresignArguments, -) -> Result>, InitializationError> { + args: PresignArguments, +) -> Result, InitializationError> { if participants.len() < 2 { return Err(InitializationError::BadParameters(format!( "participant count cannot be < 2, found: {}", @@ -213,6 +146,7 @@ pub fn presign( "threshold must be <= participant count".to_string(), )); } + // NOTE: We omit the check that the new participant set was present for // the triple generation, because presumably they need to have been present // in order to have shares. @@ -234,6 +168,12 @@ pub fn presign( ) })?; + if !participants.contains(me) { + return Err(InitializationError::BadParameters( + "participant list does not contain me".to_string(), + )); + }; + let ctx = Comms::new(); let fut = do_presign( ctx.shared_channel(), @@ -249,15 +189,17 @@ pub fn presign( #[cfg(test)] mod test { use super::*; + use crate::{ + ecdsa::{ot_based_ecdsa::triples::test::deal, KeygenOutput, Polynomial, ProjectivePoint}, + protocol::run_protocol, + }; + use frost_secp256k1::{ + keys::{PublicKeyPackage, SigningShare}, + VerifyingKey, + }; use rand_core::OsRng; - - use crate::{ecdsa::math::Polynomial, ecdsa::triples, protocol::run_protocol}; - use frost_secp256k1::keys::{PublicKeyPackage, VerifyingShare}; - use frost_secp256k1::Identifier; use std::collections::BTreeMap; - use k256::{ProjectivePoint, Secp256k1}; - #[test] fn test_presign() { let participants = vec![ @@ -267,21 +209,19 @@ mod test { Participant::from(3u32), ]; let original_threshold = 2; - let f = Polynomial::::random(&mut OsRng, original_threshold); - let big_x = ProjectivePoint::GENERATOR * f.evaluate_zero(); + let f = Polynomial::generate_polynomial(None, original_threshold - 1, &mut OsRng).unwrap(); + let big_x = ProjectivePoint::GENERATOR * f.eval_on_zero().0; let threshold = 2; let (triple0_pub, triple0_shares) = - triples::deal(&mut OsRng, &participants, original_threshold); + deal(&mut OsRng, &participants, original_threshold).unwrap(); let (triple1_pub, triple1_shares) = - triples::deal(&mut OsRng, &participants, original_threshold); + deal(&mut OsRng, &participants, original_threshold).unwrap(); #[allow(clippy::type_complexity)] - let mut protocols: Vec<( - Participant, - Box>>, - )> = Vec::with_capacity(participants.len()); + let mut protocols: Vec<(Participant, Box>)> = + Vec::with_capacity(participants.len()); for ((p, triple0), triple1) in participants .iter() @@ -289,10 +229,9 @@ mod test { .zip(triple0_shares.into_iter()) .zip(triple1_shares.into_iter()) { - let private_share = f.evaluate(&p.scalar::()); - let dummy_tree: BTreeMap = BTreeMap::new(); + let private_share = f.eval_on_participant(*p).0; let verifying_key = VerifyingKey::new(big_x); - let public_key_package = PublicKeyPackage::new(dummy_tree, verifying_key); + let public_key_package = PublicKeyPackage::new(BTreeMap::new(), verifying_key); let keygen_out = KeygenOutput { private_share: SigningShare::new(private_share), public_key: *public_key_package.verifying_key(), @@ -329,11 +268,11 @@ mod test { let k_shares = [result[0].1.k, result[1].1.k]; let sigma_shares = [result[0].1.sigma, result[1].1.sigma]; let p_list = ParticipantList::new(&participants).unwrap(); - let k = p_list.lagrange::(participants[0]) * k_shares[0] - + p_list.lagrange::(participants[1]) * k_shares[1]; + let k = p_list.lagrange::(participants[0]) * k_shares[0] + + p_list.lagrange::(participants[1]) * k_shares[1]; assert_eq!(ProjectivePoint::GENERATOR * k.invert().unwrap(), big_k); - let sigma = p_list.lagrange::(participants[0]) * sigma_shares[0] - + p_list.lagrange::(participants[1]) * sigma_shares[1]; - assert_eq!(sigma, k * f.evaluate_zero()); + let sigma = p_list.lagrange::(participants[0]) * sigma_shares[0] + + p_list.lagrange::(participants[1]) * sigma_shares[1]; + assert_eq!(sigma, k * f.eval_on_zero().0); } } diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs new file mode 100644 index 00000000..817d6059 --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -0,0 +1,170 @@ +use elliptic_curve::scalar::IsHigh; +use subtle::ConditionallySelectable; + +use super::PresignOutput; +use crate::{ + ecdsa::{x_coordinate, AffinePoint, FullSignature, Scalar, Secp256K1Sha256}, + participants::{ParticipantCounter, ParticipantList}, + protocol::{ + internal::{make_protocol, Comms, SharedChannel}, + InitializationError, Participant, Protocol, ProtocolError, + }, +}; + +async fn do_sign( + mut chan: SharedChannel, + participants: ParticipantList, + me: Participant, + public_key: AffinePoint, + presignature: PresignOutput, + msg_hash: Scalar, +) -> Result { + // Spec 1.1 + let lambda = participants.lagrange::(me); + let k_i = lambda * presignature.k; + + // Spec 1.2 + let sigma_i = lambda * presignature.sigma; + + // Spec 1.3 + let r = x_coordinate(&presignature.big_r); + let s_i = msg_hash * k_i + r * sigma_i; + + // Spec 1.4 + let wait0 = chan.next_waitpoint(); + chan.send_many(wait0, &s_i); + + // Spec 2.1 + 2.2 + let mut seen = ParticipantCounter::new(&participants); + let mut s = s_i; + seen.put(me); + while !seen.full() { + let (from, s_j): (_, Scalar) = chan.recv(wait0).await?; + if !seen.put(from) { + continue; + } + s += s_j + } + + // Spec 2.3 + // Optionally, normalize s + s.conditional_assign(&(-s), s.is_high()); + + let sig = FullSignature { + big_r: presignature.big_r, + s, + }; + if !sig.verify(&public_key, &msg_hash) { + return Err(ProtocolError::AssertionFailed( + "signature failed to verify".to_string(), + )); + } + + // Spec 2.4 + Ok(sig) +} + +/// The signature protocol, allowing us to use a presignature to sign a message. +/// +/// **WARNING** You must absolutely hash an actual message before passing it to +/// this function. Allowing the signing of arbitrary scalars *is* a security risk, +/// and this function only tolerates this risk to allow for genericity. +pub fn sign( + participants: &[Participant], + me: Participant, + public_key: AffinePoint, + presignature: PresignOutput, + msg_hash: Scalar, +) -> Result, InitializationError> { + if participants.len() < 2 { + return Err(InitializationError::BadParameters(format!( + "participant count cannot be < 2, found: {}", + participants.len() + ))); + }; + + let participants = ParticipantList::new(participants).ok_or_else(|| { + InitializationError::BadParameters("participant list cannot contain duplicates".to_string()) + })?; + + if !participants.contains(me) { + return Err(InitializationError::BadParameters( + "participant list does not contain me".to_string(), + )); + }; + + let ctx = Comms::new(); + let fut = do_sign( + ctx.shared_channel(), + participants, + me, + public_key, + presignature, + msg_hash, + ); + Ok(make_protocol(ctx, fut)) +} + +#[cfg(test)] +mod test { + use super::{sign, x_coordinate, FullSignature, PresignOutput}; + use crate::crypto::hash::scalar_hash; + use crate::{ + ecdsa::Polynomial, + protocol::{run_protocol, Participant, Protocol}, + }; + use ecdsa::Signature; + use k256::{ecdsa::signature::Verifier, ecdsa::VerifyingKey, ProjectivePoint, PublicKey}; + use rand_core::OsRng; + use std::error::Error; + + #[test] + fn test_sign() -> Result<(), Box> { + let threshold = 2; + let msg = b"hello?"; + + for _ in 0..100 { + let f = Polynomial::generate_polynomial(None, threshold - 1, &mut OsRng)?; + let x = f.eval_on_zero().0; + let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); + + let g = Polynomial::generate_polynomial(None, threshold - 1, &mut OsRng)?; + + let k = g.eval_on_zero().0; + let big_k = (ProjectivePoint::GENERATOR * k.invert().unwrap()).to_affine(); + + let sigma = k * x; + + let h = Polynomial::generate_polynomial(Some(sigma), threshold - 1, &mut OsRng)?; + + let participants = vec![Participant::from(0u32), Participant::from(1u32)]; + #[allow(clippy::type_complexity)] + let mut protocols: Vec<( + Participant, + Box>, + )> = Vec::with_capacity(participants.len()); + for p in &participants { + let presignature = PresignOutput { + big_r: big_k, + k: g.eval_on_participant(*p).0, + sigma: h.eval_on_participant(*p).0, + }; + let protocol = sign( + &participants, + *p, + public_key, + presignature, + scalar_hash(msg), + )?; + protocols.push((*p, Box::new(protocol))); + } + + let result = run_protocol(protocols)?; + let sig = result[0].1.clone(); + let sig = Signature::from_scalars(x_coordinate(&sig.big_r), sig.s)?; + VerifyingKey::from(&PublicKey::from_affine(public_key).unwrap()) + .verify(&msg[..], &sig)?; + } + Ok(()) + } +} diff --git a/src/ecdsa/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs new file mode 100644 index 00000000..a33030da --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -0,0 +1,236 @@ +use super::{ + presign::presign, + sign::sign, + triples::{test::deal, TriplePub, TripleShare}, + PresignArguments, PresignOutput, +}; +use crate::ecdsa::{ + test::{assert_public_key_invariant, run_keygen, run_reshare, run_sign}, + AffinePoint, FullSignature, KeygenOutput, Scalar, +}; +use crate::protocol::{run_protocol, InitializationError, Participant, Protocol}; +use rand_core::OsRng; +use std::error::Error; + +fn sign_box( + participants: &[Participant], + me: Participant, + public_key: AffinePoint, + presignature: PresignOutput, + msg_hash: Scalar, +) -> Result>, InitializationError> { + sign(participants, me, public_key, presignature, msg_hash) + .map(|sig| Box::new(sig) as Box>) +} + +pub fn run_presign( + participants: Vec<(Participant, KeygenOutput)>, + shares0: Vec, + shares1: Vec, + pub0: &TriplePub, + pub1: &TriplePub, + threshold: usize, +) -> Vec<(Participant, PresignOutput)> { + assert!(participants.len() == shares0.len()); + assert!(participants.len() == shares1.len()); + + #[allow(clippy::type_complexity)] + let mut protocols: Vec<(Participant, Box>)> = + Vec::with_capacity(participants.len()); + + let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); + + for (((p, keygen_out), share0), share1) in participants + .into_iter() + .zip(shares0.into_iter()) + .zip(shares1.into_iter()) + { + let protocol = presign( + &participant_list, + p, + &participant_list, + p, + PresignArguments { + triple0: (share0, pub0.clone()), + triple1: (share1, pub1.clone()), + keygen_out, + threshold, + }, + ); + assert!(protocol.is_ok()); + let protocol = protocol.unwrap(); + protocols.push((p, Box::new(protocol))); + } + + run_protocol(protocols).unwrap() +} + +#[test] +fn test_reshare_sign_more_participants() -> Result<(), Box> { + let participants = vec![ + Participant::from(0u32), + Participant::from(1u32), + Participant::from(2u32), + Participant::from(3u32), + Participant::from(4u32), + ]; + let threshold = 3; + let result0 = run_keygen(&participants, threshold)?; + assert_public_key_invariant(&result0)?; + + let pub_key = result0[2].1.public_key; + + // Run heavy reshare + let new_threshold = 5; + let mut new_participant = participants.clone(); + new_participant.push(Participant::from(31u32)); + new_participant.push(Participant::from(32u32)); + new_participant.push(Participant::from(33u32)); + let mut key_packages = run_reshare( + &participants, + &pub_key, + result0, + threshold, + new_threshold, + new_participant.clone(), + )?; + assert_public_key_invariant(&key_packages)?; + key_packages.sort_by_key(|(p, _)| *p); + + let public_key = key_packages[0].1.public_key; + // Prepare triples + let (pub0, shares0) = deal(&mut OsRng, &new_participant, new_threshold).unwrap(); + let (pub1, shares1) = deal(&mut OsRng, &new_participant, new_threshold).unwrap(); + + // Presign + let mut presign_result = + run_presign(key_packages, shares0, shares1, &pub0, &pub1, new_threshold); + presign_result.sort_by_key(|(p, _)| *p); + + let msg = b"hello world"; + + run_sign( + presign_result, + public_key.to_element().to_affine(), + msg, + sign_box, + ); + Ok(()) +} + +#[test] +fn test_reshare_sign_less_participants() -> Result<(), Box> { + let participants = vec![ + Participant::from(0u32), + Participant::from(1u32), + Participant::from(2u32), + Participant::from(3u32), + Participant::from(4u32), + ]; + let threshold = 4; + let result0 = run_keygen(&participants, threshold)?; + assert_public_key_invariant(&result0)?; + + let pub_key = result0[2].1.public_key; + + // Run heavy reshare + let new_threshold = 3; + let mut new_participant = participants.clone(); + new_participant.pop(); + let mut key_packages = run_reshare( + &participants, + &pub_key, + result0, + threshold, + new_threshold, + new_participant.clone(), + )?; + assert_public_key_invariant(&key_packages)?; + key_packages.sort_by_key(|(p, _)| *p); + + let public_key = key_packages[0].1.public_key; + // Prepare triples + let (pub0, shares0) = deal(&mut OsRng, &new_participant, new_threshold).unwrap(); + let (pub1, shares1) = deal(&mut OsRng, &new_participant, new_threshold).unwrap(); + + // Presign + let mut presign_result = + run_presign(key_packages, shares0, shares1, &pub0, &pub1, new_threshold); + presign_result.sort_by_key(|(p, _)| *p); + + let msg = b"hello world"; + + run_sign( + presign_result, + public_key.to_element().to_affine(), + msg, + sign_box, + ); + Ok(()) +} + +#[test] +fn test_e2e() -> Result<(), Box> { + let participants = vec![ + Participant::from(0u32), + Participant::from(1u32), + Participant::from(2u32), + ]; + let threshold = 3; + + let mut keygen_result = run_keygen(&participants.clone(), threshold)?; + keygen_result.sort_by_key(|(p, _)| *p); + + let public_key = keygen_result[0].1.public_key; + assert_eq!(keygen_result[0].1.public_key, keygen_result[1].1.public_key); + assert_eq!(keygen_result[1].1.public_key, keygen_result[2].1.public_key); + + let (pub0, shares0) = deal(&mut OsRng, &participants, threshold).unwrap(); + let (pub1, shares1) = deal(&mut OsRng, &participants, threshold).unwrap(); + + let mut presign_result = run_presign(keygen_result, shares0, shares1, &pub0, &pub1, threshold); + presign_result.sort_by_key(|(p, _)| *p); + + let msg = b"hello world"; + + run_sign( + presign_result, + public_key.to_element().to_affine(), + msg, + sign_box, + ); + Ok(()) +} + +#[test] +fn test_e2e_random_identifiers() -> Result<(), Box> { + let participants_count = 3; + let mut participants: Vec<_> = (0..participants_count) + .map(|_| Participant::from(rand::random::())) + .collect(); + participants.sort(); + let threshold = 3; + + let mut keygen_result = run_keygen(&participants.clone(), threshold)?; + keygen_result.sort_by_key(|(p, _)| *p); + + let public_key = keygen_result[0].1.public_key; + assert_eq!(keygen_result[0].1.public_key, keygen_result[1].1.public_key); + assert_eq!(keygen_result[1].1.public_key, keygen_result[2].1.public_key); + + let (pub0, shares0) = deal(&mut OsRng, &participants, threshold).unwrap(); + let (pub1, shares1) = deal(&mut OsRng, &participants, threshold).unwrap(); + + let mut presign_result = run_presign(keygen_result, shares0, shares1, &pub0, &pub1, threshold); + presign_result.sort_by_key(|(p, _)| *p); + + let msg = b"hello world"; + + run_sign( + presign_result, + public_key.to_element().to_affine(), + msg, + sign_box, + ); + Ok(()) +} diff --git a/src/ecdsa/triples/batch_random_ot.rs b/src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs similarity index 62% rename from src/ecdsa/triples/batch_random_ot.rs rename to src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs index 072b5ce5..1c0f20ff 100644 --- a/src/ecdsa/triples/batch_random_ot.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs @@ -1,69 +1,89 @@ -use elliptic_curve::{Field, Group}; use rand_core::OsRng; use sha2::{Digest, Sha256}; use std::sync::Arc; use subtle::ConditionallySelectable; use crate::{ - compat::{CSCurve, SerializablePoint}, - constants::SECURITY_PARAMETER, + ecdsa::{CoefficientCommitment, Field, ProjectivePoint, Secp256K1ScalarField}, protocol::{ - internal::{make_protocol, PrivateChannel}, + internal::{make_protocol, Comms, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, }, - serde::encode, }; use super::bits::{BitMatrix, BitVector, SquareBitMatrix, SEC_PARAM_8}; -use crate::protocol::internal::Comms; +use super::constants::SECURITY_PARAMETER; const BATCH_RANDOM_OT_HASH: &[u8] = b"Near threshold signatures batch ROT"; -fn hash( +fn hash( i: usize, - big_x_i: &SerializablePoint, - big_y: &SerializablePoint, - p: &C::ProjectivePoint, -) -> BitVector { + big_x_i: &CoefficientCommitment, + big_y: &CoefficientCommitment, + p: &CoefficientCommitment, +) -> Result { let mut hasher = Sha256::new(); hasher.update(BATCH_RANDOM_OT_HASH); hasher.update((i as u64).to_le_bytes()); - hasher.update(encode(&big_x_i)); - hasher.update(encode(&big_y)); - hasher.update(encode(&SerializablePoint::::from_projective(p))); + hasher.update( + &big_x_i + .serialize() + .map_err(|_| ProtocolError::PointSerialization)?, + ); + hasher.update( + &big_y + .serialize() + .map_err(|_| ProtocolError::PointSerialization)?, + ); + hasher.update( + &p.serialize() + .map_err(|_| ProtocolError::PointSerialization)?, + ); let bytes: [u8; 32] = hasher.finalize().into(); // the hash output is 256 bits // it is possible to take the first 128 bits out let bytes: [u8; SEC_PARAM_8] = bytes[0..SEC_PARAM_8].try_into().unwrap(); - BitVector::from_bytes(&bytes) + Ok(BitVector::from_bytes(&bytes)) } type BatchRandomOTOutputSender = (SquareBitMatrix, SquareBitMatrix); -pub async fn batch_random_ot_sender( +pub async fn batch_random_ot_sender( mut chan: PrivateChannel, ) -> Result { // Spec 1 - let y = C::Scalar::random(&mut OsRng); - let big_y = C::ProjectivePoint::generator() * y; + let y = Secp256K1ScalarField::random(&mut OsRng); + let big_y = ProjectivePoint::GENERATOR * y; let big_z = big_y * y; + // One way to be able to serialize and send big_y a verifying key out of it + // as it contains a private struct SerializableElement + let ser_big_y = CoefficientCommitment::new(big_y); let wait0 = chan.next_waitpoint(); - let big_y_affine = SerializablePoint::::from_projective(&big_y); - chan.send(wait0, &big_y_affine); + chan.send(wait0, &ser_big_y); let tasks = (0..SECURITY_PARAMETER).map(|i| { let mut chan = chan.child(i as u64); async move { let wait0 = chan.next_waitpoint(); - let big_x_i_affine: SerializablePoint = chan.recv(wait0).await?; - - let y_big_x_i = big_x_i_affine.to_projective() * y; - - let big_k0 = hash(i, &big_x_i_affine, &big_y_affine, &y_big_x_i); - let big_k1 = hash(i, &big_x_i_affine, &big_y_affine, &(y_big_x_i - big_z)); + let ser_big_x_i: CoefficientCommitment = chan.recv(wait0).await?; + + let y_big_x_i = ser_big_x_i.value() * y; + + let big_k0 = hash( + i, + &ser_big_x_i, + &ser_big_y, + &CoefficientCommitment::new(y_big_x_i), + )?; + let big_k1 = hash( + i, + &ser_big_x_i, + &ser_big_y, + &CoefficientCommitment::new(y_big_x_i - big_z), + )?; Ok::<_, ProtocolError>((big_k0, big_k1)) } @@ -75,7 +95,7 @@ pub async fn batch_random_ot_sender( Ok((big_k0.try_into().unwrap(), big_k1.try_into().unwrap())) } -pub async fn batch_random_ot_sender_many( +pub async fn batch_random_ot_sender_many( mut chan: PrivateChannel, ) -> Result, ProtocolError> { assert!(N > 0); @@ -84,8 +104,8 @@ pub async fn batch_random_ot_sender_many( let mut yv = vec![]; for _ in 0..N { // Spec 1 - let y = C::Scalar::random(&mut OsRng); - let big_y = C::ProjectivePoint::generator() * y; + let y = Secp256K1ScalarField::random(&mut OsRng); + let big_y = ProjectivePoint::GENERATOR * y; let big_z = big_y * y; yv.push(y); big_y_v.push(big_y); @@ -93,33 +113,42 @@ pub async fn batch_random_ot_sender_many( } let wait0 = chan.next_waitpoint(); - let mut big_y_affine_v = vec![]; - for big_y in big_y_v.iter() { - let big_y_affine = SerializablePoint::::from_projective(big_y); - big_y_affine_v.push(big_y_affine); + let mut big_y_ser_v = vec![]; + for big_y_verkey in big_y_v.iter() { + big_y_ser_v.push(CoefficientCommitment::new(*big_y_verkey)); } - chan.send(wait0, &big_y_affine_v); + chan.send(wait0, &big_y_ser_v); let y_v_arc = Arc::new(yv); - let big_y_affine_v_arc = Arc::new(big_y_affine_v); + let big_y_verkey_v_arc = Arc::new(big_y_ser_v); let big_z_v_arc = Arc::new(big_z_v); let tasks = (0..SECURITY_PARAMETER).map(|i| { let yv_arc = y_v_arc.clone(); - let big_y_affine_v_arc = big_y_affine_v_arc.clone(); + let big_y_verkey_v_arc = big_y_verkey_v_arc.clone(); let big_z_v_arc = big_z_v_arc.clone(); let mut chan = chan.child(i as u64); async move { let wait0 = chan.next_waitpoint(); - let big_x_i_affine_v: Vec> = chan.recv(wait0).await?; + let big_x_i_verkey_v: Vec = chan.recv(wait0).await?; let mut ret = vec![]; - for (j, big_x_i_affine_v_j) in big_x_i_affine_v.iter().enumerate() { + for (j, big_x_i_verkey_v_j) in big_x_i_verkey_v.iter().enumerate().take(N) { let y = &yv_arc.as_slice()[j]; - let big_y_affine = &big_y_affine_v_arc.as_slice()[j]; + let big_y_verkey = &big_y_verkey_v_arc.as_slice()[j]; let big_z = &big_z_v_arc.as_slice()[j]; - let y_big_x_i = big_x_i_affine_v_j.to_projective() * *y; - let big_k0 = hash(i, big_x_i_affine_v_j, big_y_affine, &y_big_x_i); - let big_k1 = hash(i, big_x_i_affine_v_j, big_y_affine, &(y_big_x_i - big_z)); + let y_big_x_i = big_x_i_verkey_v_j.value() * *y; + let big_k0 = hash( + i, + big_x_i_verkey_v_j, + big_y_verkey, + &CoefficientCommitment::new(y_big_x_i), + )?; + let big_k1 = hash( + i, + big_x_i_verkey_v_j, + big_y_verkey, + &CoefficientCommitment::new(y_big_x_i - big_z), + )?; ret.push((big_k0, big_k1)); } @@ -150,19 +179,14 @@ pub async fn batch_random_ot_sender_many( type BatchRandomOTOutputReceiver = (BitVector, SquareBitMatrix); -pub async fn batch_random_ot_receiver( +pub async fn batch_random_ot_receiver( mut chan: PrivateChannel, ) -> Result { // Step 3 let wait0 = chan.next_waitpoint(); - let big_y_affine: SerializablePoint = chan.recv(wait0).await?; - let big_y = big_y_affine.to_projective(); - if bool::from(big_y.is_identity()) { - return Err(ProtocolError::AssertionFailed( - "Big y in batch random OT was zero.".into(), - )); - } - + // deserialization prevents receiving the identity + let big_y_verkey: CoefficientCommitment = chan.recv(wait0).await?; + let big_y = big_y_verkey.value(); let delta = BitVector::random(&mut OsRng); let out = delta @@ -171,48 +195,48 @@ pub async fn batch_random_ot_receiver( .map(|(i, d_i)| { let mut chan = chan.child(i as u64); // Step 4 - let x_i = C::Scalar::random(&mut OsRng); - let mut big_x_i = C::ProjectivePoint::generator() * x_i; + let x_i = Secp256K1ScalarField::random(&mut OsRng); + let mut big_x_i = ProjectivePoint::GENERATOR * x_i; big_x_i.conditional_assign(&(big_x_i + big_y), d_i); // Step 6 let wait0 = chan.next_waitpoint(); - let big_x_i_affine = SerializablePoint::::from_projective(&big_x_i); - chan.send(wait0, &big_x_i_affine); + let big_x_i_verkey = CoefficientCommitment::new(big_x_i); + chan.send(wait0, &big_x_i_verkey); // Step 5 - hash(i, &big_x_i_affine, &big_y_affine, &(big_y * x_i)) + hash( + i, + &big_x_i_verkey, + &big_y_verkey, + &CoefficientCommitment::new(big_y * x_i), + ) }) - .collect::>(); + .collect::, _>>()?; let big_k: BitMatrix = out.into_iter().collect(); Ok((delta, big_k.try_into().unwrap())) } -pub async fn batch_random_ot_receiver_many( +pub async fn batch_random_ot_receiver_many( mut chan: PrivateChannel, ) -> Result, ProtocolError> { assert!(N > 0); // Step 3 let wait0 = chan.next_waitpoint(); - let big_y_affine_v: Vec> = chan.recv(wait0).await?; + // deserialization prevents receiving the identity + let big_y_verkey_v: Vec = chan.recv(wait0).await?; let mut big_y_v = vec![]; let mut deltav = vec![]; - for big_y_affine in big_y_affine_v.iter().take(N) { - let big_y = big_y_affine.to_projective(); - if bool::from(big_y.is_identity()) { - return Err(ProtocolError::AssertionFailed( - "Big y in batch random OT was zero.".into(), - )); - } - + for big_y_verkey in big_y_verkey_v.iter() { + let big_y = big_y_verkey.value(); let delta = BitVector::random(&mut OsRng); big_y_v.push(big_y); deltav.push(delta); } let big_y_v_arc = Arc::new(big_y_v); - let big_y_affine_v_arc = Arc::new(big_y_affine_v); + let big_y_verkey_v_arc = Arc::new(big_y_verkey_v); // inner is batch, outer is bits let mut choices: Vec> = Vec::new(); @@ -233,15 +257,15 @@ pub async fn batch_random_ot_receiver_many( // clone arcs let d_i_v = choicesi.clone(); let big_y_v_arc = big_y_v_arc.clone(); - let big_y_affine_v_arc = big_y_affine_v_arc.clone(); + let big_y_verkey_v_arc = big_y_verkey_v_arc.clone(); let hashv = { let mut x_i_v = Vec::new(); let mut big_x_i_v = Vec::new(); for j in 0..N { let d_i = d_i_v[j]; // Step 4 - let x_i = C::Scalar::random(&mut OsRng); - let mut big_x_i = C::ProjectivePoint::generator() * x_i; + let x_i = Secp256K1ScalarField::random(&mut OsRng); + let mut big_x_i = ProjectivePoint::GENERATOR * x_i; big_x_i.conditional_assign(&(big_x_i + big_y_v_arc[j]), d_i); x_i_v.push(x_i); big_x_i_v.push(big_x_i); @@ -249,21 +273,25 @@ pub async fn batch_random_ot_receiver_many( // Step 6 let wait0 = chan.next_waitpoint(); - let mut big_x_i_affine_v = Vec::new(); - for big_x_i_v_j in big_x_i_v.iter().take(N) { - let big_x_i_affine = SerializablePoint::::from_projective(big_x_i_v_j); - big_x_i_affine_v.push(big_x_i_affine); + let mut big_x_i_verkey_v = Vec::new(); + for big_x_i_verkey in big_x_i_v.iter() { + big_x_i_verkey_v.push(CoefficientCommitment::new(*big_x_i_verkey)); } - chan.send(wait0, &big_x_i_affine_v); + chan.send(wait0, &big_x_i_verkey_v); // Step 5 let mut hashv = Vec::new(); for j in 0..N { - let big_x_i_affine = big_x_i_affine_v[j]; - let big_y_affine = big_y_affine_v_arc[j]; + let big_x_i_verkey = big_x_i_verkey_v[j]; + let big_y_verkey = big_y_verkey_v_arc[j]; let big_y = big_y_v_arc[j]; let x_i = x_i_v[j]; - hashv.push(hash(i, &big_x_i_affine, &big_y_affine, &(big_y * x_i))); + hashv.push(hash( + i, + &big_x_i_verkey, + &big_y_verkey, + &CoefficientCommitment::new(big_y * x_i), + )?); } hashv }; @@ -294,7 +322,7 @@ pub async fn batch_random_ot_receiver_many( /// Run the batch random OT protocol between two parties. #[allow(dead_code)] -pub(crate) fn run_batch_random_ot( +pub(crate) fn run_batch_random_ot( ) -> Result<(BatchRandomOTOutputSender, BatchRandomOTOutputReceiver), ProtocolError> { let s = Participant::from(0u32); let r = Participant::from(1u32); @@ -306,18 +334,18 @@ pub(crate) fn run_batch_random_ot( r, &mut make_protocol( comms_s.clone(), - batch_random_ot_sender::(comms_s.private_channel(s, r)), + batch_random_ot_sender(comms_s.private_channel(s, r)), ), &mut make_protocol( comms_r.clone(), - batch_random_ot_receiver::(comms_r.private_channel(r, s)), + batch_random_ot_receiver(comms_r.private_channel(r, s)), ), ) } /// Run the batch random OT many protocol between two parties. #[allow(dead_code)] -pub(crate) fn run_batch_random_ot_many() -> Result< +pub(crate) fn run_batch_random_ot_many() -> Result< ( Vec, Vec, @@ -334,11 +362,11 @@ pub(crate) fn run_batch_random_ot_many() -> Result< r, &mut make_protocol( comms_s.clone(), - batch_random_ot_sender_many::(comms_s.private_channel(s, r)), + batch_random_ot_sender_many::(comms_s.private_channel(s, r)), ), &mut make_protocol( comms_r.clone(), - batch_random_ot_receiver_many::(comms_r.private_channel(r, s)), + batch_random_ot_receiver_many::(comms_r.private_channel(r, s)), ), ) } @@ -347,11 +375,9 @@ pub(crate) fn run_batch_random_ot_many() -> Result< mod test { use super::*; - use k256::Secp256k1; - #[test] fn test_batch_random_ot() { - let res = run_batch_random_ot::(); + let res = run_batch_random_ot(); assert!(res.is_ok()); let ((k0, k1), (delta, k_delta)) = res.unwrap(); @@ -373,7 +399,7 @@ mod test { #[test] fn test_batch_random_ot_many() { const N: usize = 10; - let res = run_batch_random_ot_many::(); + let res = run_batch_random_ot_many::(); assert!(res.is_ok()); let (a, b) = res.unwrap(); for i in 0..N { diff --git a/src/ecdsa/triples/bits.rs b/src/ecdsa/ot_based_ecdsa/triples/bits.rs similarity index 99% rename from src/ecdsa/triples/bits.rs rename to src/ecdsa/ot_based_ecdsa/triples/bits.rs index 791fc1e1..df3b545c 100644 --- a/src/ecdsa/triples/bits.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/bits.rs @@ -7,7 +7,7 @@ use sha3::{ }; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; -use crate::constants::SECURITY_PARAMETER; +use super::constants::SECURITY_PARAMETER; pub const SEC_PARAM_64: usize = SECURITY_PARAMETER.div_ceil(64); pub const SEC_PARAM_8: usize = SECURITY_PARAMETER.div_ceil(8); diff --git a/src/ecdsa/ot_based_ecdsa/triples/constants.rs b/src/ecdsa/ot_based_ecdsa/triples/constants.rs new file mode 100644 index 00000000..5ffe3b96 --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/triples/constants.rs @@ -0,0 +1,7 @@ +use ecdsa::elliptic_curve::{bigint::Bounded, Curve}; +use k256::Secp256k1; + +/// The security parameter we use for different constructions +pub const SECURITY_PARAMETER: usize = 128; +/// Field modulus +pub const BITS: usize = <::Uint as Bounded>::BITS; diff --git a/src/ecdsa/triples/correlated_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs similarity index 95% rename from src/ecdsa/triples/correlated_ot_extension.rs rename to src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs index ec9cbf65..ca456117 100644 --- a/src/ecdsa/triples/correlated_ot_extension.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs @@ -59,10 +59,9 @@ pub fn correlated_ot_receiver( #[cfg(test)] mod test { use super::*; - use crate::ecdsa::triples::batch_random_ot::run_batch_random_ot; + use crate::ecdsa::ot_based_ecdsa::triples::batch_random_ot::run_batch_random_ot; use crate::protocol::internal::{make_protocol, Comms}; use crate::protocol::{run_two_party_protocol, Participant}; - use k256::Secp256k1; use rand_core::OsRng; /// Run the correlated OT protocol between two parties. @@ -107,7 +106,7 @@ mod test { #[test] fn test_correlated_ot() -> Result<(), ProtocolError> { - let ((k0, k1), (delta, k)) = run_batch_random_ot::()?; + let ((k0, k1), (delta, k)) = run_batch_random_ot()?; let batch_size = 256; let x = BitMatrix::random(&mut OsRng, batch_size); let (q, t) = run_correlated_ot( diff --git a/src/ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs similarity index 70% rename from src/ecdsa/triples/generation.rs rename to src/ecdsa/ot_based_ecdsa/triples/generation.rs index 8b2e55b0..73040b8e 100644 --- a/src/ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -1,42 +1,55 @@ -use elliptic_curve::{Field, Group, ScalarPrimitive}; +use frost_core::serialization::SerializableScalar; use rand_core::OsRng; +use serde::Serialize; -use crate::crypto::{Commitment, Randomizer}; -use crate::ecdsa::triples::multiplication::multiplication_many; use crate::{ - compat::{CSCurve, SerializablePoint}, - crypto::{commit, hash, HashOutput}, - ecdsa::math::{GroupPolynomial, Polynomial}, + crypto::proofs::{dlog, dlogeq, strobe_transcript::Transcript}, + crypto::{ + commit::{commit, Commitment}, + hash::{hash, HashOutput}, + random::Randomizer, + }, + ecdsa::{ + CoefficientCommitment, Polynomial, PolynomialCommitment, ProjectivePoint, Scalar, + Secp256K1Sha256, + }, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, - proofs::{dlog, dlogeq, strobe_transcript::Transcript}, protocol::{ - internal::make_protocol, InitializationError, Participant, Protocol, ProtocolError, + internal::{make_protocol, Comms}, + InitializationError, Participant, Protocol, ProtocolError, }, - serde::encode, }; -use super::{multiplication::multiplication, TriplePub, TripleShare}; -use crate::protocol::internal::Comms; +use super::{ + multiplication::{multiplication, multiplication_many}, + TriplePub, TripleShare, +}; + +/// Encode an arbitrary serializable value into a vec. +fn encode(val: &T) -> Vec { + rmp_serde::encode::to_vec(val).expect("failed to encode value") +} /// The output of running the triple generation protocol. -pub type TripleGenerationOutput = (TripleShare, TriplePub); +pub type TripleGenerationOutput = (TripleShare, TriplePub); -pub type TripleGenerationOutputMany = Vec<(TripleShare, TriplePub)>; +pub type TripleGenerationOutputMany = Vec<(TripleShare, TriplePub)>; +type C = Secp256K1Sha256; const LABEL: &[u8] = b"Near threshold signatures triple generation"; - -async fn do_generation( +const NAME: &[u8] = b"Secp256K1Sha256"; +async fn do_generation( comms: Comms, participants: ParticipantList, me: Participant, threshold: usize, -) -> Result, ProtocolError> { +) -> Result { let mut rng = OsRng; let mut chan = comms.shared_channel(); let mut transcript = Transcript::new(LABEL); // Spec 1.1 - transcript.message(b"group", C::NAME); + transcript.message(b"group", NAME); transcript.message(b"participants", &encode(&participants)); // To allow interop between platforms where usize is different transcript.message( @@ -45,20 +58,21 @@ async fn do_generation( ); // Spec 1.2 - let e: Polynomial = Polynomial::random(&mut rng, threshold); - let f: Polynomial = Polynomial::random(&mut rng, threshold); - let mut l: Polynomial = Polynomial::random(&mut rng, threshold); - + let e = Polynomial::generate_polynomial(None, threshold - 1, &mut rng)?; + let f = Polynomial::generate_polynomial(None, threshold - 1, &mut rng)?; // Spec 1.3 - l.set_zero(C::Scalar::ZERO); + // We will generate a poly of degree threshold - 2 then later extend it with identity. + // This is to prevent serialization from failing + let mut l = Polynomial::generate_polynomial(None, threshold - 2, &mut rng)?; // Spec 1.4 - let big_e_i = e.commit(); - let big_f_i = f.commit(); - let big_l_i = l.commit(); + let big_e_i = e.commit_polynomial(); + let big_f_i = f.commit_polynomial(); + let big_l_i = l.commit_polynomial(); // Spec 1.5 - let (my_commitment, my_randomizer) = commit(&mut rng, &(&big_e_i, &big_f_i, &big_l_i)); + let (my_commitment, my_randomizer) = commit(&mut rng, &(&big_e_i, &big_f_i, &big_l_i)) + .map_err(|_| ProtocolError::PointSerialization)?; // Spec 1.6 let wait0 = chan.next_waitpoint(); @@ -80,26 +94,27 @@ async fn do_generation( // Spec 2.4 let multiplication_task = { - let e0 = e.evaluate_zero(); - let f0 = f.evaluate_zero(); - multiplication::( + // cannot fail as both polynomials are non-empty (generated locally) + let e0 = e.eval_on_zero(); + let f0 = f.eval_on_zero(); + multiplication( comms.clone(), my_confirmation, participants.clone(), me, - e0, - f0, + e0.0, + f0.0, ) }; - struct ParallelToMultiplicationTaskOutput<'a, C: CSCurve> { + struct ParallelToMultiplicationTaskOutput<'a> { seen: ParticipantCounter<'a>, - big_e: GroupPolynomial, - big_f: GroupPolynomial, - big_l: GroupPolynomial, - big_c: C::ProjectivePoint, - a_i: C::Scalar, - b_i: C::Scalar, + big_e: PolynomialCommitment, + big_f: PolynomialCommitment, + big_l: PolynomialCommitment, + big_c: ProjectivePoint, + a_i: Scalar, + b_i: Scalar, } let parallel_to_multiplication_task = async { // Spec 2.5 @@ -108,10 +123,10 @@ async fn do_generation( // Spec 2.6 let statement0 = dlog::Statement:: { - public: &big_e_i.evaluate_zero(), + public: &big_e_i.eval_on_zero().value(), }; let witness0 = dlog::Witness:: { - x: &e.evaluate_zero(), + x: e.eval_on_zero(), }; let my_phi_proof0 = dlog::prove( &mut rng, @@ -120,10 +135,10 @@ async fn do_generation( witness0, ); let statement1 = dlog::Statement:: { - public: &big_f_i.evaluate_zero(), + public: &big_f_i.eval_on_zero().value(), }; let witness1 = dlog::Witness:: { - x: &f.evaluate_zero(), + x: f.eval_on_zero(), }; let my_phi_proof1 = dlog::prove( &mut rng, @@ -151,12 +166,12 @@ async fn do_generation( // Spec 2.8 let wait3 = chan.next_waitpoint(); for p in participants.others(me) { - let a_i_j: ScalarPrimitive = e.evaluate(&p.scalar::()).into(); - let b_i_j: ScalarPrimitive = f.evaluate(&p.scalar::()).into(); + let a_i_j = e.eval_on_participant(p); + let b_i_j = f.eval_on_participant(p); chan.send_private(wait3, p, &(a_i_j, b_i_j)); } - let mut a_i = e.evaluate(&me.scalar::()); - let mut b_i = f.evaluate(&me.scalar::()); + let mut a_i = e.eval_on_participant(me).0; + let mut b_i = f.eval_on_participant(me).0; // Spec 3.1 + 3.2 let mut seen = ParticipantCounter::new(&participants); @@ -194,9 +209,9 @@ async fn do_generation( ): ( _, ( - GroupPolynomial, - GroupPolynomial, - GroupPolynomial, + PolynomialCommitment, + PolynomialCommitment, + PolynomialCommitment, _, _, _, @@ -206,32 +221,30 @@ async fn do_generation( continue; } - if their_big_e.len() != threshold - || their_big_f.len() != threshold - || their_big_l.len() != threshold + if their_big_e.degree() != threshold - 1 + || their_big_f.degree() != threshold - 1 + // testing threshold - 2 because the identity element is non-serializable + || their_big_l.degree() != threshold - 2 { return Err(ProtocolError::AssertionFailed(format!( "polynomial from {from:?} has the wrong length" ))); } - if !bool::from(their_big_l.evaluate_zero().is_identity()) { - return Err(ProtocolError::AssertionFailed(format!( - "L(0) from {from:?} is not 0" - ))); - } - - if !all_commitments[from].check( - &(&their_big_e, &their_big_f, &their_big_l), - &their_randomizer, - ) { + if !all_commitments[from] + .check( + &(&their_big_e, &their_big_f, &their_big_l), + &their_randomizer, + ) + .map_err(|_| ProtocolError::PointSerialization)? + { return Err(ProtocolError::AssertionFailed(format!( "commitment from {from:?} did not match revealed F" ))); } let statement0 = dlog::Statement:: { - public: &their_big_e.evaluate_zero(), + public: &their_big_e.eval_on_zero().value(), }; if !dlog::verify( @@ -245,7 +258,7 @@ async fn do_generation( } let statement1 = dlog::Statement:: { - public: &their_big_f.evaluate_zero(), + public: &their_big_f.eval_on_zero().value(), }; if !dlog::verify( &mut transcript.fork(b"dlog1", &from.bytes()), @@ -257,28 +270,28 @@ async fn do_generation( ))); } - big_e_j_zero.put(from, their_big_e.evaluate_zero()); - big_e += &their_big_e; - big_f += &their_big_f; - big_l += &their_big_l; + big_e_j_zero.put(from, their_big_e.eval_on_zero()); + big_e = &big_e + &their_big_e; + big_f = &big_f + &their_big_f; + big_l = &big_l + &their_big_l; } // Spec 3.5 + 3.6 seen.clear(); seen.put(me); while !seen.full() { - let (from, (a_j_i, b_j_i)): (_, (ScalarPrimitive, ScalarPrimitive)) = + let (from, (a_j_i, b_j_i)): (_, (SerializableScalar, SerializableScalar)) = chan.recv(wait3).await?; if !seen.put(from) { continue; } - a_i += &a_j_i.into(); - b_i += &b_j_i.into(); + a_i += &a_j_i.0; + b_i += &b_j_i.0; } // Spec 3.7 - if big_e.evaluate(&me.scalar::()) != C::ProjectivePoint::generator() * a_i - || big_f.evaluate(&me.scalar::()) != C::ProjectivePoint::generator() * b_i + if big_e.eval_on_participant(me).value() != ProjectivePoint::GENERATOR * a_i + || big_f.eval_on_participant(me).value() != ProjectivePoint::GENERATOR * b_i { return Err(ProtocolError::AssertionFailed( "received bad private share".to_string(), @@ -286,16 +299,16 @@ async fn do_generation( } // Spec 3.8 - let big_c_i = big_f.evaluate_zero() * e.evaluate_zero(); + let big_c_i = big_f.eval_on_zero().value() * e.eval_on_zero().0; // Spec 3.9 let statement = dlogeq::Statement:: { - public0: &big_e_i.evaluate_zero(), - generator1: &big_f.evaluate_zero(), + public0: &big_e_i.eval_on_zero().value(), + generator1: &big_f.eval_on_zero().value(), public1: &big_c_i, }; let witness = dlogeq::Witness { - x: &e.evaluate_zero(), + x: e.eval_on_zero(), }; let my_phi_proof = dlogeq::prove( &mut rng, @@ -306,29 +319,23 @@ async fn do_generation( // Spec 3.10 let wait4 = chan.next_waitpoint(); - chan.send_many( - wait4, - &( - SerializablePoint::::from_projective(&big_c_i), - my_phi_proof, - ), - ); + chan.send_many(wait4, &(CoefficientCommitment::new(big_c_i), my_phi_proof)); // Spec 4.1 + 4.2 + 4.3 seen.clear(); seen.put(me); let mut big_c = big_c_i; while !seen.full() { - let (from, (big_c_j, their_phi_proof)): (_, (SerializablePoint, _)) = + let (from, (big_c_j, their_phi_proof)): (_, (CoefficientCommitment, _)) = chan.recv(wait4).await?; if !seen.put(from) { continue; } - let big_c_j = big_c_j.to_projective(); + let big_c_j = big_c_j.value(); let statement = dlogeq::Statement:: { - public0: &big_e_j_zero[from], - generator1: &big_f.evaluate_zero(), + public0: &big_e_j_zero[from].value(), + generator1: &big_f.eval_on_zero().value(), public1: &big_c_j, }; @@ -348,7 +355,8 @@ async fn do_generation( seen, big_e, big_f, - big_l, + // extend big_l of degree threshold - 2 + big_l: big_l.extend_with_identity(), big_c, a_i, b_i, @@ -370,13 +378,15 @@ async fn do_generation( ) = futures::future::try_join(multiplication_task, parallel_to_multiplication_task).await?; // Spec 4.5 - let hat_big_c_i = C::ProjectivePoint::generator() * l0; + let hat_big_c_i = ProjectivePoint::GENERATOR * l0; // Spec 4.6 let statement = dlog::Statement:: { public: &hat_big_c_i, }; - let witness = dlog::Witness:: { x: &l0 }; + let witness = dlog::Witness:: { + x: SerializableScalar::(l0), + }; let my_phi_proof = dlog::prove( &mut rng, &mut transcript.fork(b"dlog2", &me.bytes()), @@ -384,37 +394,36 @@ async fn do_generation( witness, ); - // Spec 4.8 + // Spec 4.7 let wait5 = chan.next_waitpoint(); chan.send_many( wait5, - &( - SerializablePoint::::from_projective(&hat_big_c_i), - my_phi_proof, - ), + &(CoefficientCommitment::new(hat_big_c_i), my_phi_proof), ); - // Spec 4.9 - l.set_zero(l0); + // Spec 4.8 + // extend to make the degree threshold - 1 + l = l.extend_with_zero(); + l.set_constant(l0)?; let wait6 = chan.next_waitpoint(); for p in participants.others(me) { - let c_i_j: ScalarPrimitive = l.evaluate(&p.scalar::()).into(); + let c_i_j = l.eval_on_participant(p); chan.send_private(wait6, p, &c_i_j); } - let mut c_i = l.evaluate(&me.scalar::()); + let mut c_i = l.eval_on_participant(me).0; // Spec 5.1 + 5.2 + 5.3 seen.clear(); seen.put(me); let mut hat_big_c = hat_big_c_i; while !seen.full() { - let (from, (their_hat_big_c, their_phi_proof)): (_, (SerializablePoint, _)) = + let (from, (their_hat_big_c, their_phi_proof)): (_, (CoefficientCommitment, _)) = chan.recv(wait5).await?; if !seen.put(from) { continue; } - let their_hat_big_c = their_hat_big_c.to_projective(); + let their_hat_big_c = their_hat_big_c.value(); let statement = dlog::Statement:: { public: &their_hat_big_c, }; @@ -431,10 +440,10 @@ async fn do_generation( } // Spec 5.3 - big_l.set_zero(hat_big_c); + big_l.set_constant(CoefficientCommitment::new(hat_big_c))?; // Spec 5.4 - if big_l.evaluate_zero() != big_c { + if big_l.eval_on_zero().value() != big_c { return Err(ProtocolError::AssertionFailed( "final polynomial doesn't match C value".to_owned(), )); @@ -444,23 +453,23 @@ async fn do_generation( seen.clear(); seen.put(me); while !seen.full() { - let (from, c_j_i): (_, ScalarPrimitive) = chan.recv(wait6).await?; + let (from, c_j_i): (_, SerializableScalar) = chan.recv(wait6).await?; if !seen.put(from) { continue; } - c_i += C::Scalar::from(c_j_i); + c_i += c_j_i.0; } // Spec 5.7 - if big_l.evaluate(&me.scalar::()) != C::ProjectivePoint::generator() * c_i { + if big_l.eval_on_participant(me).value() != ProjectivePoint::GENERATOR * c_i { return Err(ProtocolError::AssertionFailed( "received bad private share of c".to_string(), )); } - let big_a = big_e.evaluate_zero().into(); - let big_b = big_f.evaluate_zero().into(); - let big_c = big_c.into(); + let big_a = big_e.eval_on_zero().value().to_affine(); + let big_b = big_f.eval_on_zero().value().to_affine(); + let big_c = big_c.to_affine(); Ok(( TripleShare { @@ -478,12 +487,12 @@ async fn do_generation( )) } -async fn do_generation_many( +async fn do_generation_many( comms: Comms, participants: ParticipantList, me: Participant, threshold: usize, -) -> Result, ProtocolError> { +) -> Result { assert!(N > 0); let mut rng = OsRng; @@ -491,7 +500,7 @@ async fn do_generation_many( let mut transcript = Transcript::new(LABEL); // Spec 1.1 - transcript.message(b"group", C::NAME); + transcript.message(b"group", NAME); transcript.message(b"participants", &encode(&participants)); // To allow interop between platforms where usize is different transcript.message( @@ -510,20 +519,18 @@ async fn do_generation_many( for _ in 0..N { // Spec 1.2 - let e: Polynomial = Polynomial::random(&mut rng, threshold); - let f: Polynomial = Polynomial::random(&mut rng, threshold); - let mut l: Polynomial = Polynomial::random(&mut rng, threshold); - - // Spec 1.3 - l.set_zero(C::Scalar::ZERO); + let e = Polynomial::generate_polynomial(None, threshold - 1, &mut rng)?; + let f = Polynomial::generate_polynomial(None, threshold - 1, &mut rng)?; + let l = Polynomial::generate_polynomial(None, threshold - 2, &mut rng)?; // Spec 1.4 - let big_e_i = e.commit(); - let big_f_i = f.commit(); - let big_l_i = l.commit(); + let big_e_i = e.commit_polynomial(); + let big_f_i = f.commit_polynomial(); + let big_l_i = l.commit_polynomial(); // Spec 1.5 - let (my_commitment, my_randomizer) = commit(&mut rng, &(&big_e_i, &big_f_i, &big_l_i)); + let (my_commitment, my_randomizer) = commit(&mut rng, &(&big_e_i, &big_f_i, &big_l_i)) + .map_err(|_| ProtocolError::PointSerialization)?; my_commitments.push(my_commitment); my_randomizers.push(my_randomizer); @@ -569,9 +576,9 @@ async fn do_generation_many( // Spec 2.4 let multiplication_task = { - let e0_v: Vec<_> = e_v.iter().map(|e| e.evaluate_zero()).collect(); - let f0_v: Vec<_> = f_v.iter().map(|f| f.evaluate_zero()).collect(); - multiplication_many::( + let e0_v: Vec<_> = e_v.iter().map(|e| e.eval_on_zero().0).collect(); + let f0_v: Vec<_> = f_v.iter().map(|f| f.eval_on_zero().0).collect(); + multiplication_many::( comms.clone(), my_confirmations.clone(), participants.clone(), @@ -581,14 +588,14 @@ async fn do_generation_many( ) }; - struct ParallelToMultiplicationTaskOutput<'a, C: CSCurve> { + struct ParallelToMultiplicationTaskOutput<'a> { seen: ParticipantCounter<'a>, - big_e_v: Vec>, - big_f_v: Vec>, - big_l_v: Vec>, - big_c_v: Vec, - a_i_v: Vec, - b_i_v: Vec, + big_e_v: Vec, + big_f_v: Vec, + big_l_v: Vec, + big_c_v: Vec, + a_i_v: Vec, + b_i_v: Vec, } let parallel_to_multiplication_task = async { // Spec 2.5 @@ -605,10 +612,10 @@ async fn do_generation_many( let f = &f_v[i]; // Spec 2.6 let statement0 = dlog::Statement:: { - public: &big_e_i.evaluate_zero(), + public: &big_e_i.eval_on_zero().value(), }; let witness0 = dlog::Witness:: { - x: &e.evaluate_zero(), + x: e.eval_on_zero(), }; let my_phi_proof0 = dlog::prove( &mut rng, @@ -617,10 +624,10 @@ async fn do_generation_many( witness0, ); let statement1 = dlog::Statement:: { - public: &big_f_i.evaluate_zero(), + public: &big_f_i.eval_on_zero().value(), }; let witness1 = dlog::Witness:: { - x: &f.evaluate_zero(), + x: f.eval_on_zero(), }; let my_phi_proof1 = dlog::prove( &mut rng, @@ -656,8 +663,8 @@ async fn do_generation_many( for i in 0..N { let e = &e_v[i]; let f = &f_v[i]; - let a_i_j: ScalarPrimitive = e.evaluate(&p.scalar::()).into(); - let b_i_j: ScalarPrimitive = f.evaluate(&p.scalar::()).into(); + let a_i_j = e.eval_on_participant(p).0; + let b_i_j = f.eval_on_participant(p).0; a_i_j_v.push(a_i_j); b_i_j_v.push(b_i_j); } @@ -668,10 +675,10 @@ async fn do_generation_many( for i in 0..N { let e = &e_v[i]; let f = &f_v[i]; - let a_i = e.evaluate(&me.scalar::()); - let b_i = f.evaluate(&me.scalar::()); - a_i_v.push(a_i); - b_i_v.push(b_i); + let a_i = e.eval_on_participant(me); + let b_i = f.eval_on_participant(me); + a_i_v.push(a_i.0); + b_i_v.push(b_i.0); } // Spec 3.1 + 3.2 @@ -717,9 +724,9 @@ async fn do_generation_many( ): ( _, ( - Vec>, - Vec>, - Vec>, + Vec, + Vec, + Vec, Vec, Vec>, Vec>, @@ -737,29 +744,29 @@ async fn do_generation_many( let their_randomizer = &their_randomizers[i]; let their_phi_proof0 = &their_phi_proof0_v[i]; let their_phi_proof1 = &their_phi_proof1_v[i]; - if their_big_e.len() != threshold - || their_big_f.len() != threshold - || their_big_l.len() != threshold + if their_big_e.degree() != threshold - 1 + || their_big_f.degree() != threshold - 1 + // degree is threshold - 2 because the constant element identity is not serializable + || their_big_l.degree() != threshold - 2 { return Err(ProtocolError::AssertionFailed(format!( "polynomial from {from:?} has the wrong length" ))); } - if !bool::from(their_big_l.evaluate_zero().is_identity()) { - return Err(ProtocolError::AssertionFailed(format!( - "L(0) from {from:?} is not 0" - ))); - } - if !all_commitments[from].check( - &(&their_big_e, &their_big_f, &their_big_l), - their_randomizer, - ) { + + if !all_commitments[from] + .check( + &(&their_big_e, &their_big_f, &their_big_l), + their_randomizer, + ) + .map_err(|_| ProtocolError::PointSerialization)? + { return Err(ProtocolError::AssertionFailed(format!( "commitment from {from:?} did not match revealed F" ))); } let statement0 = dlog::Statement:: { - public: &their_big_e.evaluate_zero(), + public: &their_big_e.eval_on_zero().value(), }; if !dlog::verify( &mut transcript.fork(b"dlog0", &from.bytes()), @@ -772,7 +779,7 @@ async fn do_generation_many( } let statement1 = dlog::Statement:: { - public: &their_big_f.evaluate_zero(), + public: &their_big_f.eval_on_zero().value(), }; if !dlog::verify( &mut transcript.fork(b"dlog1", &from.bytes()), @@ -784,11 +791,11 @@ async fn do_generation_many( ))); } - big_e_j_zero_v[i].put(from, their_big_e.evaluate_zero()); + big_e_j_zero_v[i].put(from, their_big_e.eval_on_zero()); - big_e_v[i] += their_big_e; - big_f_v[i] += their_big_f; - big_l_v[i] += their_big_l; + big_e_v[i] = &big_e_v[i] + their_big_e; + big_f_v[i] = &big_f_v[i] + their_big_f; + big_l_v[i] = &big_l_v[i] + their_big_l; } } @@ -799,7 +806,7 @@ async fn do_generation_many( #[allow(clippy::type_complexity)] let (from, (a_j_i_v, b_j_i_v)): ( _, - (Vec>, Vec>), + (Vec>, Vec>), ) = chan.recv(wait3).await?; if !seen.put(from) { continue; @@ -807,8 +814,8 @@ async fn do_generation_many( for i in 0..N { let a_j_i = &a_j_i_v[i]; let b_j_i = &b_j_i_v[i]; - a_i_v[i] += &(*a_j_i).into(); - b_i_v[i] += &(*b_j_i).into(); + a_i_v[i] += &a_j_i.0; + b_i_v[i] += &b_j_i.0; } } @@ -822,24 +829,24 @@ async fn do_generation_many( let b_i = &b_i_v[i]; let e = &e_v[i]; // Spec 3.7 - let check1 = big_e.evaluate(&me.scalar::()) != C::ProjectivePoint::generator() * a_i; - let check2 = big_f.evaluate(&me.scalar::()) != C::ProjectivePoint::generator() * b_i; + let check1 = big_e.eval_on_participant(me).value() != ProjectivePoint::GENERATOR * a_i; + let check2 = big_f.eval_on_participant(me).value() != ProjectivePoint::GENERATOR * b_i; if check1 || check2 { return Err(ProtocolError::AssertionFailed( "received bad private share".to_string(), )); } // Spec 3.8 - let big_c_i = big_f.evaluate_zero() * e.evaluate_zero(); + let big_c_i = big_f.eval_on_zero().value() * e.eval_on_zero().0; let big_e_i = &big_e_i_v[i]; // Spec 3.9 let statement = dlogeq::Statement:: { - public0: &big_e_i.evaluate_zero(), - generator1: &big_f.evaluate_zero(), + public0: &big_e_i.eval_on_zero().value(), + generator1: &big_f.eval_on_zero().value(), public1: &big_c_i, }; let witness = dlogeq::Witness { - x: &e.evaluate_zero(), + x: e.eval_on_zero(), }; let my_phi_proof = dlogeq::prove( &mut rng, @@ -847,7 +854,7 @@ async fn do_generation_many( statement, witness, ); - big_c_i_points.push(SerializablePoint::::from_projective(&big_c_i)); + big_c_i_points.push(CoefficientCommitment::new(big_c_i)); big_c_i_v.push(big_c_i); my_phi_proofs.push(my_phi_proof); } @@ -867,7 +874,7 @@ async fn do_generation_many( #[allow(clippy::type_complexity)] let (from, (big_c_j_v, their_phi_proofs)): ( _, - (Vec>, Vec>), + (Vec, Vec>), ) = chan.recv(wait4).await?; if !seen.put(from) { continue; @@ -876,12 +883,12 @@ async fn do_generation_many( let big_e_j_zero = &big_e_j_zero_v[i]; let big_f = &big_f_v[i]; - let big_c_j = big_c_j_v[i].to_projective(); + let big_c_j = big_c_j_v[i].value(); let their_phi_proof = &their_phi_proofs[i]; let statement = dlogeq::Statement:: { - public0: &big_e_j_zero[from], - generator1: &big_f.evaluate_zero(), + public0: &big_e_j_zero[from].value(), + generator1: &big_f.eval_on_zero().value(), public1: &big_c_j, }; @@ -897,6 +904,10 @@ async fn do_generation_many( big_c_v[i] += big_c_j; } } + let big_l_v = big_l_v + .iter() + .map(|big_l| big_l.extend_with_identity()) + .collect(); Ok(ParallelToMultiplicationTaskOutput { seen, big_e_v, @@ -925,22 +936,24 @@ async fn do_generation_many( let mut hat_big_c_i_points = vec![]; let mut hat_big_c_i_v = vec![]; let mut my_phi_proofs = vec![]; - for l0 in l0_v.iter().take(N) { + for l0 in l0_v.iter() { // Spec 4.5 - let hat_big_c_i = C::ProjectivePoint::generator() * l0; + let hat_big_c_i = ProjectivePoint::GENERATOR * l0; // Spec 4.6 let statement = dlog::Statement:: { public: &hat_big_c_i, }; - let witness = dlog::Witness:: { x: l0 }; + let witness = dlog::Witness:: { + x: SerializableScalar::(*l0), + }; let my_phi_proof = dlog::prove( &mut rng, &mut transcript.fork(b"dlog2", &me.bytes()), statement, witness, ); - hat_big_c_i_points.push(SerializablePoint::::from_projective(&hat_big_c_i)); + hat_big_c_i_points.push(CoefficientCommitment::new(hat_big_c_i)); hat_big_c_i_v.push(hat_big_c_i); my_phi_proofs.push(my_phi_proof); } @@ -953,21 +966,23 @@ async fn do_generation_many( for i in 0..N { let l = &mut l_v[i]; let l0 = &l0_v[i]; - l.set_zero(*l0); + // extend to make the degree threshold - 1 + *l = l.extend_with_zero(); + l.set_constant(*l0)?; } let wait6 = chan.next_waitpoint(); let mut c_i_v = vec![]; for p in participants.others(me) { let mut c_i_j_v = Vec::new(); - for l in l_v.iter_mut().take(N) { - let c_i_j: ScalarPrimitive = l.evaluate(&p.scalar::()).into(); + for l in l_v.iter_mut() { + let c_i_j = l.eval_on_participant(p).0; c_i_j_v.push(c_i_j); } chan.send_private(wait6, p, &c_i_j_v); } - for l in l_v.iter_mut().take(N) { - let c_i = l.evaluate(&me.scalar::()); - c_i_v.push(c_i); + for l in l_v.iter_mut() { + let c_i = l.eval_on_participant(me); + c_i_v.push(c_i.0); } // Spec 5.1 + 5.2 + 5.3 @@ -982,13 +997,13 @@ async fn do_generation_many( #[allow(clippy::type_complexity)] let (from, (their_hat_big_c_i_points, their_phi_proofs)): ( _, - (Vec>, Vec>), + (Vec, Vec>), ) = chan.recv(wait5).await?; if !seen.put(from) { continue; } for i in 0..N { - let their_hat_big_c = their_hat_big_c_i_points[i].to_projective(); + let their_hat_big_c = their_hat_big_c_i_points[i].value(); let their_phi_proof = &their_phi_proofs[i]; let statement = dlog::Statement:: { @@ -1013,10 +1028,10 @@ async fn do_generation_many( let big_c = &big_c_v[i]; // Spec 5.3 - big_l.set_zero(*hat_big_c); + big_l.set_constant(CoefficientCommitment::new(*hat_big_c))?; // Spec 5.4 - if big_l.evaluate_zero() != *big_c { + if big_l.eval_on_zero().value() != *big_c { return Err(ProtocolError::AssertionFailed( "final polynomial doesn't match C value".to_owned(), )); @@ -1027,13 +1042,13 @@ async fn do_generation_many( seen.clear(); seen.put(me); while !seen.full() { - let (from, c_j_i_v): (_, Vec>) = chan.recv(wait6).await?; + let (from, c_j_i_v): (_, Vec>) = chan.recv(wait6).await?; if !seen.put(from) { continue; } for i in 0..N { - let c_j_i = c_j_i_v[i]; - c_i_v[i] += C::Scalar::from(c_j_i); + let c_j_i = c_j_i_v[i].0; + c_i_v[i] += c_j_i; } } @@ -1048,13 +1063,13 @@ async fn do_generation_many( let big_f = &big_f_v[i]; let big_c = &big_c_v[i]; - if big_l.evaluate(&me.scalar::()) != C::ProjectivePoint::generator() * c_i { + if big_l.eval_on_participant(me).value() != ProjectivePoint::GENERATOR * c_i { return Err(ProtocolError::AssertionFailed( "received bad private share of c".to_string(), )); } - let big_a = big_e.evaluate_zero().into(); - let big_b = big_f.evaluate_zero().into(); + let big_a = big_e.eval_on_zero().value().to_affine(); + let big_b = big_f.eval_on_zero().value().to_affine(); let big_c = (*big_c).into(); ret.push(( @@ -1083,11 +1098,11 @@ async fn do_generation_many( /// /// The resulting triple will be threshold shared, according to the threshold /// provided to this function. -pub fn generate_triple( +pub fn generate_triple( participants: &[Participant], me: Participant, threshold: usize, -) -> Result>, InitializationError> { +) -> Result, InitializationError> { if participants.len() < 2 { return Err(InitializationError::BadParameters(format!( "participant count cannot be < 2, found: {}", @@ -1111,11 +1126,11 @@ pub fn generate_triple( } /// As [`generate_triple`] but for many triples at once -pub fn generate_triple_many( +pub fn generate_triple_many( participants: &[Participant], me: Participant, threshold: usize, -) -> Result>, InitializationError> { +) -> Result, InitializationError> { if participants.len() < 2 { return Err(InitializationError::BadParameters(format!( "participant count cannot be < 2, found: {}", @@ -1134,21 +1149,19 @@ pub fn generate_triple_many( })?; let ctx = Comms::new(); - let fut = do_generation_many::(ctx.clone(), participants, me, threshold); + let fut = do_generation_many::(ctx.clone(), participants, me, threshold); Ok(make_protocol(ctx, fut)) } #[cfg(test)] mod test { - use k256::{ProjectivePoint, Secp256k1}; - use crate::{ - ecdsa::triples::generate_triple, + ecdsa::{ot_based_ecdsa::triples::generate_triple, ProjectivePoint}, participants::ParticipantList, protocol::{run_protocol, Participant, Protocol, ProtocolError}, }; - use super::{generate_triple_many, TripleGenerationOutput, TripleGenerationOutputMany}; + use super::{generate_triple_many, TripleGenerationOutput, TripleGenerationOutputMany, C}; #[test] fn test_triple_generation() -> Result<(), ProtocolError> { @@ -1162,7 +1175,7 @@ mod test { #[allow(clippy::type_complexity)] let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants.len()); for &p in &participants { @@ -1188,19 +1201,19 @@ mod test { ]; let p_list = ParticipantList::new(&participants).unwrap(); - let a = p_list.lagrange::(participants[0]) * triple_shares[0].a - + p_list.lagrange::(participants[1]) * triple_shares[1].a - + p_list.lagrange::(participants[2]) * triple_shares[2].a; + let a = p_list.lagrange::(participants[0]) * triple_shares[0].a + + p_list.lagrange::(participants[1]) * triple_shares[1].a + + p_list.lagrange::(participants[2]) * triple_shares[2].a; assert_eq!(ProjectivePoint::GENERATOR * a, triple_pub.big_a); - let b = p_list.lagrange::(participants[0]) * triple_shares[0].b - + p_list.lagrange::(participants[1]) * triple_shares[1].b - + p_list.lagrange::(participants[2]) * triple_shares[2].b; + let b = p_list.lagrange::(participants[0]) * triple_shares[0].b + + p_list.lagrange::(participants[1]) * triple_shares[1].b + + p_list.lagrange::(participants[2]) * triple_shares[2].b; assert_eq!(ProjectivePoint::GENERATOR * b, triple_pub.big_b); - let c = p_list.lagrange::(participants[0]) * triple_shares[0].c - + p_list.lagrange::(participants[1]) * triple_shares[1].c - + p_list.lagrange::(participants[2]) * triple_shares[2].c; + let c = p_list.lagrange::(participants[0]) * triple_shares[0].c + + p_list.lagrange::(participants[1]) * triple_shares[1].c + + p_list.lagrange::(participants[2]) * triple_shares[2].c; assert_eq!(ProjectivePoint::GENERATOR * c, triple_pub.big_c); assert_eq!(a * b, c); @@ -1220,11 +1233,11 @@ mod test { #[allow(clippy::type_complexity)] let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants.len()); for &p in &participants { - let protocol = generate_triple_many::(&participants, p, threshold); + let protocol = generate_triple_many::<1>(&participants, p, threshold); assert!(protocol.is_ok()); let protocol = protocol.unwrap(); protocols.push((p, Box::new(protocol))); @@ -1246,19 +1259,19 @@ mod test { ]; let p_list = ParticipantList::new(&participants).unwrap(); - let a = p_list.lagrange::(participants[0]) * triple_shares[0].a - + p_list.lagrange::(participants[1]) * triple_shares[1].a - + p_list.lagrange::(participants[2]) * triple_shares[2].a; + let a = p_list.lagrange::(participants[0]) * triple_shares[0].a + + p_list.lagrange::(participants[1]) * triple_shares[1].a + + p_list.lagrange::(participants[2]) * triple_shares[2].a; assert_eq!(ProjectivePoint::GENERATOR * a, triple_pub.big_a); - let b = p_list.lagrange::(participants[0]) * triple_shares[0].b - + p_list.lagrange::(participants[1]) * triple_shares[1].b - + p_list.lagrange::(participants[2]) * triple_shares[2].b; + let b = p_list.lagrange::(participants[0]) * triple_shares[0].b + + p_list.lagrange::(participants[1]) * triple_shares[1].b + + p_list.lagrange::(participants[2]) * triple_shares[2].b; assert_eq!(ProjectivePoint::GENERATOR * b, triple_pub.big_b); - let c = p_list.lagrange::(participants[0]) * triple_shares[0].c - + p_list.lagrange::(participants[1]) * triple_shares[1].c - + p_list.lagrange::(participants[2]) * triple_shares[2].c; + let c = p_list.lagrange::(participants[0]) * triple_shares[0].c + + p_list.lagrange::(participants[1]) * triple_shares[1].c + + p_list.lagrange::(participants[2]) * triple_shares[2].c; assert_eq!(ProjectivePoint::GENERATOR * c, triple_pub.big_c); assert_eq!(a * b, c); diff --git a/src/ecdsa/ot_based_ecdsa/triples/mod.rs b/src/ecdsa/ot_based_ecdsa/triples/mod.rs new file mode 100644 index 00000000..d0c163e0 --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/triples/mod.rs @@ -0,0 +1,75 @@ +//! This module contains the types and protocols related to triple generation. +//! +//! The cait-sith signing protocol makes use of *committed* Beaver Triples. +//! A triple is a value of the form `(a, b, c), (A, B, C)`, such that +//! `c = a * b`, and `A = a * G`, `B = b * G`, `C = c * G`. This is a beaver +//! triple along with commitments to its values in the form of group elements. +//! +//! The signing protocols make use of a triple where the scalar values `(a, b, c)` +//! are secret-shared, and the commitments are public. Each signature requires +//! two triples. These triples can be generated in advance without knowledge +//! of the secret key used to sign. It's important that the value of the underlying +//! scalars in the triple is kept secret, otherwise the private key used to create +//! a signature with that triple could be recovered. +//! +//! There are two ways of generating these triples. +//! +//! One way is to have +//! a trusted third party generate them. This is supported by the [deal] function. +//! +//! The other way is to run a protocol generating a secret shared triple without any party +//! learning the secret values. This is better because no party learns the value of the +//! triple, which needs to be kept secret. This method is supported by the [generate_triple] +//! protocol. +//! +//! This protocol requires a setup protocol to be done once beforehand. +//! After this setup protocol has been run, an arbitrary number of triples can +//! be generated. +use serde::{Deserialize, Serialize}; + +use crate::{ + ecdsa::{AffinePoint, Scalar}, + protocol::Participant, +}; + +/// Represents the public part of a triple. +/// +/// This contains commitments to each part of the triple. +/// +/// We also record who participated in the protocol, +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct TriplePub { + pub big_a: AffinePoint, + pub big_b: AffinePoint, + pub big_c: AffinePoint, + /// The participants in generating this triple. + pub participants: Vec, + /// The threshold which will be able to reconstruct it. + pub threshold: usize, +} + +/// Represents a share of a triple. +/// +/// This consists of shares of each individual part. +/// +/// i.e. we have a share of a, b, and c such that a * b = c. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TripleShare { + pub a: Scalar, + pub b: Scalar, + pub c: Scalar, +} + +mod batch_random_ot; +mod bits; +mod constants; +mod correlated_ot_extension; +mod generation; +mod mta; +mod multiplication; +mod random_ot_extension; + +pub use generation::{generate_triple, generate_triple_many, TripleGenerationOutput}; + +#[cfg(test)] +pub(crate) mod test; diff --git a/src/ecdsa/ot_based_ecdsa/triples/mta.rs b/src/ecdsa/ot_based_ecdsa/triples/mta.rs new file mode 100644 index 00000000..444fbf4b --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/triples/mta.rs @@ -0,0 +1,184 @@ +use frost_core::{serialization::SerializableScalar, Field, Group}; +use rand_core::{OsRng, RngCore}; +use serde::{Deserialize, Serialize}; +use std::slice::Iter; +use subtle::{Choice, ConditionallySelectable}; + +use crate::protocol::internal::Comms; +use crate::{ + crypto::proofs::strobe_transcript::TranscriptRng, + protocol::{ + internal::{make_protocol, PrivateChannel}, + run_two_party_protocol, Participant, ProtocolError, + }, +}; + +use crate::ecdsa::{Scalar, Secp256K1Sha256}; + +type C = Secp256K1Sha256; + +#[derive(Serialize, Deserialize)] +struct MTAScalars(Vec<(SerializableScalar, SerializableScalar)>); + +impl MTAScalars { + fn len(&self) -> usize { + self.0.len() + } + + fn iter(&self) -> Iter<'_, (SerializableScalar, SerializableScalar)> { + self.0.iter() + } +} + +/// The sender for multiplicative to additive conversion. +pub async fn mta_sender( + mut chan: PrivateChannel, + v: Vec<(Scalar, Scalar)>, + a: Scalar, +) -> Result { + let size = v.len(); + + // Step 1 + let delta: Vec<_> = (0..size) + .map(|_| <::Group as Group>::Field::random(&mut OsRng)) + .collect(); + + // Step 2 + let c: MTAScalars = MTAScalars( + delta + .iter() + .zip(v.iter()) + .map(|(delta_i, (v0_i, v1_i))| { + ( + SerializableScalar(*v0_i + delta_i + a), + SerializableScalar(*v1_i + delta_i - a), + ) + }) + .collect(), + ); + let wait0 = chan.next_waitpoint(); + chan.send(wait0, &c); + + // Step 7 + let wait1 = chan.next_waitpoint(); + let (chi1, seed): (SerializableScalar, [u8; 32]) = chan.recv(wait1).await?; + + let mut alpha = delta[0] * chi1.0; + + let mut prng = TranscriptRng::new(&seed); + for &delta_i in &delta[1..] { + let chi_i = <::Group as Group>::Field::random(&mut prng); + alpha += delta_i * chi_i; + } + + Ok(-alpha) +} + +/// The receiver for multiplicative to additive conversion. +pub async fn mta_receiver( + mut chan: PrivateChannel, + tv: Vec<(Choice, Scalar)>, + b: Scalar, +) -> Result { + let size = tv.len(); + + // Step 3 + let wait0 = chan.next_waitpoint(); + let c: MTAScalars = chan.recv(wait0).await?; + if c.len() != tv.len() { + return Err(ProtocolError::AssertionFailed( + "length of c was incorrect".to_owned(), + )); + } + let mut m = tv + .iter() + .zip(c.iter()) + .map(|((t_i, v_i), (c0_i, c1_i))| Scalar::conditional_select(&c0_i.0, &c1_i.0, *t_i) - v_i); + + // Step 4 + let mut seed = [0u8; 32]; + OsRng.fill_bytes(&mut seed); + let mut prng = TranscriptRng::new(&seed); + let chi: Vec = (1..size) + .map(|_| <::Group as Group>::Field::random(&mut prng)) + .collect(); + + let mut chi1 = Scalar::ZERO; + for ((t_i, _), &chi_i) in tv.iter().skip(1).zip(chi.iter()) { + chi1 += Scalar::conditional_select(&chi_i, &(-chi_i), *t_i); + } + chi1 = b - chi1; + chi1.conditional_assign(&(-chi1), tv[0].0); + + // Step 5 + let mut beta = chi1 * m.next().unwrap(); + for (&chi_i, m_i) in chi.iter().zip(m) { + beta += chi_i * m_i; + } + + // Step 6 + let wait1 = chan.next_waitpoint(); + let chi1 = SerializableScalar::(chi1); + chan.send(wait1, &(chi1, seed)); + + Ok(beta) +} + +/// Run the multiplicative to additive protocol +#[allow(dead_code, clippy::type_complexity)] +fn run_mta( + (v, a): (Vec<(Scalar, Scalar)>, Scalar), + (tv, b): (Vec<(Choice, Scalar)>, Scalar), +) -> Result<(Scalar, Scalar), ProtocolError> { + let s = Participant::from(0u32); + let r = Participant::from(1u32); + let ctx_s = Comms::new(); + let ctx_r = Comms::new(); + + run_two_party_protocol( + s, + r, + &mut make_protocol(ctx_s.clone(), mta_sender(ctx_s.private_channel(s, r), v, a)), + &mut make_protocol( + ctx_r.clone(), + mta_receiver(ctx_r.private_channel(r, s), tv, b), + ), + ) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ecdsa::ot_based_ecdsa::triples::constants::{BITS, SECURITY_PARAMETER}; + use k256::Scalar; + use rand_core::RngCore; + + #[test] + fn test_mta() -> Result<(), ProtocolError> { + let batch_size = BITS + SECURITY_PARAMETER; + + let v: Vec<_> = (0..batch_size) + .map(|_| { + ( + Scalar::generate_biased(&mut OsRng), + Scalar::generate_biased(&mut OsRng), + ) + }) + .collect(); + let tv: Vec<_> = v + .iter() + .map(|(v0, v1)| { + let c = Choice::from((OsRng.next_u64() & 1) as u8); + (c, Scalar::conditional_select(v0, v1, c)) + }) + .collect(); + + let a = Scalar::generate_biased(&mut OsRng); + let b = Scalar::generate_biased(&mut OsRng); + let (alpha, beta) = run_mta((v, a), (tv, b))?; + + assert_eq!(a * b, alpha + beta); + + Ok(()) + } +} diff --git a/src/ecdsa/triples/multiplication.rs b/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs similarity index 80% rename from src/ecdsa/triples/multiplication.rs rename to src/ecdsa/ot_based_ecdsa/triples/multiplication.rs index 41351d0a..a45efd24 100644 --- a/src/ecdsa/triples/multiplication.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs @@ -1,34 +1,36 @@ use crate::{ - compat::CSCurve, - constants::SECURITY_PARAMETER, - crypto::HashOutput, + crypto::hash::{hash, HashOutput}, + ecdsa::Scalar, participants::ParticipantList, - protocol::{internal::PrivateChannel, Participant, ProtocolError}, + protocol::{ + internal::{Comms, PrivateChannel}, + Participant, ProtocolError, + }, }; use std::sync::Arc; use super::{ batch_random_ot::{batch_random_ot_receiver, batch_random_ot_sender}, + constants::{BITS, SECURITY_PARAMETER}, mta::{mta_receiver, mta_sender}, random_ot_extension::{ random_ot_extension_receiver, random_ot_extension_sender, RandomOtExtensionParams, }, }; -use crate::protocol::internal::Comms; use std::collections::VecDeque; -pub async fn multiplication_sender( +pub async fn multiplication_sender( chan: PrivateChannel, sid: &[u8], - a_i: &C::Scalar, - b_i: &C::Scalar, -) -> Result { + a_i: &Scalar, + b_i: &Scalar, +) -> Result { // First, run a fresh batch random OT ourselves - let (delta, k) = batch_random_ot_receiver::(chan.child(0)).await?; + let (delta, k) = batch_random_ot_receiver(chan.child(0)).await?; - let batch_size = C::BITS + SECURITY_PARAMETER; + let batch_size = BITS + SECURITY_PARAMETER; // Step 1 - let mut res0 = random_ot_extension_sender::( + let mut res0 = random_ot_extension_sender( chan.child(1), RandomOtExtensionParams { sid, @@ -41,8 +43,8 @@ pub async fn multiplication_sender( let res1 = res0.split_off(batch_size); // Step 2 - let task0 = mta_sender::(chan.child(2), res0, *a_i); - let task1 = mta_sender::(chan.child(3), res1, *b_i); + let task0 = mta_sender(chan.child(2), res0, *a_i); + let task1 = mta_sender(chan.child(3), res1, *b_i); // Step 3 let (gamma0, gamma1) = futures::future::join(task0, task1).await; @@ -50,18 +52,18 @@ pub async fn multiplication_sender( Ok(gamma0? + gamma1?) } -pub async fn multiplication_receiver( +pub async fn multiplication_receiver( chan: PrivateChannel, sid: &[u8], - a_i: &C::Scalar, - b_i: &C::Scalar, -) -> Result { + a_i: &Scalar, + b_i: &Scalar, +) -> Result { // First, run a fresh batch random OT ourselves - let (k0, k1) = batch_random_ot_sender::(chan.child(0)).await?; + let (k0, k1) = batch_random_ot_sender(chan.child(0)).await?; - let batch_size = C::BITS + SECURITY_PARAMETER; + let batch_size = BITS + SECURITY_PARAMETER; // Step 1 - let mut res0 = random_ot_extension_receiver::( + let mut res0 = random_ot_extension_receiver( chan.child(1), RandomOtExtensionParams { sid, @@ -74,8 +76,8 @@ pub async fn multiplication_receiver( let res1 = res0.split_off(batch_size); // Step 2 - let task0 = mta_receiver::(chan.child(2), res0, *b_i); - let task1 = mta_receiver::(chan.child(3), res1, *a_i); + let task0 = mta_receiver(chan.child(2), res0, *b_i); + let task1 = mta_receiver(chan.child(3), res1, *a_i); // Step 3 let (gamma0, gamma1) = futures::future::join(task0, task1).await; @@ -83,23 +85,23 @@ pub async fn multiplication_receiver( Ok(gamma0? + gamma1?) } -pub async fn multiplication( +pub async fn multiplication( comms: Comms, sid: HashOutput, participants: ParticipantList, me: Participant, - a_i: C::Scalar, - b_i: C::Scalar, -) -> Result { + a_i: Scalar, + b_i: Scalar, +) -> Result { let mut tasks = Vec::with_capacity(participants.len() - 1); for p in participants.others(me) { let fut = { let chan = comms.private_channel(me, p); async move { if p < me { - multiplication_sender::(chan, sid.as_ref(), &a_i, &b_i).await + multiplication_sender(chan, sid.as_ref(), &a_i, &b_i).await } else { - multiplication_receiver::(chan, sid.as_ref(), &a_i, &b_i).await + multiplication_receiver(chan, sid.as_ref(), &a_i, &b_i).await } } }; @@ -112,28 +114,28 @@ pub async fn multiplication( Ok(out) } -pub async fn multiplication_many( +pub async fn multiplication_many( comms: Comms, sid: Vec, participants: ParticipantList, me: Participant, - av_iv: Vec, - bv_iv: Vec, -) -> Result, ProtocolError> { + av_iv: Vec, + bv_iv: Vec, +) -> Result, ProtocolError> { assert!(N > 0); let sid_arc = Arc::new(sid); let av_iv_arc = Arc::new(av_iv); let bv_iv_arc = Arc::new(bv_iv); let mut tasks = Vec::with_capacity(participants.len() - 1); for i in 0..N { - let order_key_me = crate::crypto::hash(&(i, me)); + let order_key_me = hash(&(i, me)); for p in participants.others(me) { let sid_arc = sid_arc.clone(); let av_iv_arc = av_iv_arc.clone(); let bv_iv_arc = bv_iv_arc.clone(); let fut = { let chan = comms.private_channel(me, p).child(i as u64); - let order_key_other = crate::crypto::hash(&(i, p)); + let order_key_other = hash(&(i, p)); async move { // Use a deterministic but random comparison function to decide who @@ -141,7 +143,7 @@ pub async fn multiplication_many( // multiplication operation to put even networking load between the // participants. if order_key_other.as_ref() < order_key_me.as_ref() { - multiplication_sender::( + multiplication_sender( chan, sid_arc[i].as_ref(), &av_iv_arc[i], @@ -149,7 +151,7 @@ pub async fn multiplication_many( ) .await } else { - multiplication_receiver::( + multiplication_receiver( chan, sid_arc[i].as_ref(), &av_iv_arc[i], @@ -187,17 +189,17 @@ pub async fn multiplication_many( #[cfg(test)] mod test { - use k256::{Scalar, Secp256k1}; + use k256::Scalar; use rand_core::OsRng; use crate::{ - crypto::hash, + crypto::hash::hash, participants::ParticipantList, protocol::{internal::make_protocol, run_protocol, Participant, Protocol, ProtocolError}, }; use super::multiplication; - use crate::ecdsa::triples::multiplication::multiplication_many; + use crate::ecdsa::ot_based_ecdsa::triples::multiplication::multiplication_many; use crate::protocol::internal::Comms; #[test] @@ -228,7 +230,7 @@ mod test { let ctx = Comms::new(); let prot = make_protocol( ctx.clone(), - multiplication::( + multiplication( ctx, sid, ParticipantList::new(&participants).unwrap(), @@ -299,7 +301,7 @@ mod test { let ctx = Comms::new(); let prot = make_protocol( ctx.clone(), - multiplication_many::( + multiplication_many::( ctx, sids.clone(), ParticipantList::new(&participants).unwrap(), diff --git a/src/ecdsa/triples/random_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs similarity index 78% rename from src/ecdsa/triples/random_ot_extension.rs rename to src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs index 4ac85a9e..7151451b 100644 --- a/src/ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs @@ -1,27 +1,28 @@ -use elliptic_curve::CurveArithmetic; +use elliptic_curve::bigint::U512; use rand_core::{OsRng, RngCore}; use sha2::{Digest, Sha256}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::{ - compat::CSCurve, - constants::SECURITY_PARAMETER, - proofs::strobe_transcript::TranscriptRng, + crypto::proofs::strobe_transcript::TranscriptRng, + ecdsa::Scalar, protocol::{ - internal::{make_protocol, PrivateChannel}, + internal::{make_protocol, Comms, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, }, }; use super::{ bits::{BitMatrix, BitVector, ChoiceVector, DoubleBitVector, SquareBitMatrix}, + constants::SECURITY_PARAMETER, correlated_ot_extension::{correlated_ot_receiver, correlated_ot_sender, CorrelatedOtParams}, }; -use crate::protocol::internal::Comms; + +use elliptic_curve::ops::Reduce; const CTX: &[u8] = b"Random OT Extension Hash"; -fn hash_to_scalar(i: usize, v: &BitVector) -> C::Scalar { +fn hash_to_scalar(i: usize, v: &BitVector) -> Scalar { let mut hasher = Sha256::new(); let i64 = u64::try_from(i).expect("failed to convert usize to u64"); @@ -30,9 +31,9 @@ fn hash_to_scalar(i: usize, v: &BitVector) -> C::Scalar { hasher.update(v.bytes()); let seed = hasher.finalize().into(); - // Could in theory avoid one PRF call by using a more direct RNG wrapper - // over the prf function, but oh well. - C::sample_scalar_constant_time(&mut TranscriptRng::new(&seed)) + let mut data = [0u8; 64]; + TranscriptRng::new(&seed).fill_bytes(&mut data); + >::reduce_bytes(&data.into()) } fn adjust_size(size: usize) -> usize { @@ -53,20 +54,17 @@ pub struct RandomOtExtensionParams<'sid> { } /// The result that the sender gets. -pub type RandomOTExtensionSenderOut = Vec<( - ::Scalar, - ::Scalar, -)>; +pub type RandomOTExtensionSenderOut = Vec<(Scalar, Scalar)>; /// The result that the receiver gets. -pub type RandomOTExtensionReceiverOut = Vec<(Choice, ::Scalar)>; +pub type RandomOTExtensionReceiverOut = Vec<(Choice, Scalar)>; -pub async fn random_ot_extension_sender( +pub async fn random_ot_extension_sender( mut chan: PrivateChannel, params: RandomOtExtensionParams<'_>, delta: BitVector, k: &SquareBitMatrix, -) -> Result, ProtocolError> { +) -> Result { let adjusted_size = adjust_size(params.batch_size); // Step 2 @@ -123,20 +121,20 @@ pub async fn random_ot_extension_sender( let mut out = Vec::with_capacity(params.batch_size); for (i, q_i) in q.rows().take(params.batch_size).enumerate() { - let v0_i = hash_to_scalar::(i, q_i); - let v1_i = hash_to_scalar::(i, &(q_i ^ delta)); + let v0_i = hash_to_scalar(i, q_i); + let v1_i = hash_to_scalar(i, &(q_i ^ delta)); out.push((v0_i, v1_i)) } Ok(out) } -pub async fn random_ot_extension_receiver( +pub async fn random_ot_extension_receiver( mut chan: PrivateChannel, params: RandomOtExtensionParams<'_>, k0: &SquareBitMatrix, k1: &SquareBitMatrix, -) -> Result, ProtocolError> { +) -> Result { let adjusted_size = adjust_size(params.batch_size); // Step 1 @@ -194,7 +192,7 @@ pub async fn random_ot_extension_receiver( .zip(t.rows()) .take(params.batch_size) .enumerate() - .map(|(i, (b_i, t_i))| (b_i, hash_to_scalar::(i, t_i))) + .map(|(i, (b_i, t_i))| (b_i, hash_to_scalar(i, t_i))) .collect(); Ok(out) @@ -202,18 +200,12 @@ pub async fn random_ot_extension_receiver( /// Run the random OT protocol between two parties. #[allow(dead_code)] -fn run_random_ot( +fn run_random_ot( (delta, k): (BitVector, SquareBitMatrix), (k0, k1): (SquareBitMatrix, SquareBitMatrix), sid: Vec, batch_size: usize, -) -> Result< - ( - RandomOTExtensionSenderOut, - RandomOTExtensionReceiverOut, - ), - ProtocolError, -> { +) -> Result<(RandomOTExtensionSenderOut, RandomOTExtensionReceiverOut), ProtocolError> { let s = Participant::from(0u32); let r = Participant::from(1u32); let comms_s = Comms::new(); @@ -230,32 +222,32 @@ fn run_random_ot( sid: &sid_s, batch_size, }; - random_ot_extension_sender::(comms_s.private_channel(s, r), params, delta, &k).await + random_ot_extension_sender(comms_s.private_channel(s, r), params, delta, &k).await }), &mut make_protocol(comms_r.clone(), async move { let params = RandomOtExtensionParams { sid: &sid_r, batch_size, }; - random_ot_extension_receiver::(comms_r.private_channel(r, s), params, &k0, &k1).await + random_ot_extension_receiver(comms_r.private_channel(r, s), params, &k0, &k1).await }), ) } #[cfg(test)] mod test { - use crate::ecdsa::triples::batch_random_ot::run_batch_random_ot; + use crate::ecdsa::ot_based_ecdsa::triples::batch_random_ot::run_batch_random_ot; use super::*; - use k256::{Scalar, Secp256k1}; + use k256::Scalar; #[test] fn test_random_ot() -> Result<(), ProtocolError> { - let ((k0, k1), (delta, k)) = run_batch_random_ot::()?; + let ((k0, k1), (delta, k)) = run_batch_random_ot()?; let batch_size = 16; let (sender_out, receiver_out) = - run_random_ot::((delta, k), (k0, k1), b"test sid".to_vec(), batch_size)?; + run_random_ot((delta, k), (k0, k1), b"test sid".to_vec(), batch_size)?; assert_eq!(sender_out.len(), batch_size); assert_eq!(receiver_out.len(), batch_size); for ((v0_i, v1_i), (b_i, vb_i)) in sender_out.iter().zip(receiver_out.iter()) { diff --git a/src/ecdsa/ot_based_ecdsa/triples/test.rs b/src/ecdsa/ot_based_ecdsa/triples/test.rs new file mode 100644 index 00000000..459461e0 --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/triples/test.rs @@ -0,0 +1,50 @@ +use rand_core::CryptoRngCore; + +use super::{TriplePub, TripleShare}; +#[cfg(test)] +use crate::protocol::ProtocolError; +use crate::{ + ecdsa::{Field, Polynomial, ProjectivePoint, Secp256K1ScalarField}, + protocol::Participant, +}; + +/// Create a new triple from scratch. +/// +/// This can be used to generate a triple if you then trust the person running +/// this code to forget about the values they generated. +/// We prevent users from using it in non-testing env and attribute it to #[cfg(test)] +#[cfg(test)] +pub fn deal( + rng: &mut impl CryptoRngCore, + participants: &[Participant], + threshold: usize, +) -> Result<(TriplePub, Vec), ProtocolError> { + let a = Secp256K1ScalarField::random(&mut *rng); + let b = Secp256K1ScalarField::random(&mut *rng); + let c = a * b; + + let f_a = Polynomial::generate_polynomial(Some(a), threshold - 1, rng)?; + let f_b = Polynomial::generate_polynomial(Some(b), threshold - 1, rng)?; + let f_c = Polynomial::generate_polynomial(Some(c), threshold - 1, rng)?; + + let mut shares = Vec::with_capacity(participants.len()); + let mut participants_owned = Vec::with_capacity(participants.len()); + + for p in participants { + participants_owned.push(*p); + shares.push(TripleShare { + a: f_a.eval_on_participant(*p).0, + b: f_b.eval_on_participant(*p).0, + c: f_c.eval_on_participant(*p).0, + }); + } + + let triple_pub = TriplePub { + big_a: (ProjectivePoint::GENERATOR * a).into(), + big_b: (ProjectivePoint::GENERATOR * b).into(), + big_c: (ProjectivePoint::GENERATOR * c).into(), + participants: participants_owned, + threshold, + }; + Ok((triple_pub, shares)) +} diff --git a/src/ecdsa/robust_ecdsa/README.md b/src/ecdsa/robust_ecdsa/README.md new file mode 100644 index 00000000..e125ae1b --- /dev/null +++ b/src/ecdsa/robust_ecdsa/README.md @@ -0,0 +1,14 @@ +This is an amended version Robust ECDSA scheme of \[[DJNPO](https://eprint.iacr.org/2020/501.pdf)\]. +The amendment does away with several checks that the scheme requires to happen and thus dropping the security from active adversaries (under honest majority assumption) to honest-but-curious adversaries. + +This implementation is meant to be integrated to a Trusted Execution Environement (TEE) which is meant prevent an adversary from deviating from the protocol. Additionally, the communication between the parties is assumed to be encrypted under secret keys integrated into the TEE. + + +## ATTENTION: +Some papers define the number of malicious parties (eg this exact paper) to be the same as the threshold. +Other papers seem to define the number of malicious parties to be threshold - 1. + +The first case corresponds to robust ecdsa implementation. (explicit condition on the threshold eg n >= 3t + 1) +The second case corresponds to the ot-based ecdsa implementation. (no explicit condition e.g. n >= t) + +CARE TO UNIFY THE IMPLEMENTATION such as number of malicious parties = threshold. Discuss with the team such duality! diff --git a/src/ecdsa/robust_ecdsa/mod.rs b/src/ecdsa/robust_ecdsa/mod.rs new file mode 100644 index 00000000..377c0511 --- /dev/null +++ b/src/ecdsa/robust_ecdsa/mod.rs @@ -0,0 +1,31 @@ +use serde::{Deserialize, Serialize}; + +use crate::ecdsa::{AffinePoint, KeygenOutput, Scalar}; + +/// The arguments needed to create a presignature. +#[derive(Debug, Clone)] +pub struct PresignArguments { + /// The output of key generation, i.e. our share of the secret key, and the public key package. + /// This is of type KeygenOutput from Frost implementation + pub keygen_out: KeygenOutput, + /// The desired threshold for the presignature, which must match the original threshold + pub threshold: usize, +} + +// The output of the presigning protocol. +/// Contains the signature precomputed parts performed +/// independently of the message +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PresignOutput { + /// The public nonce commitment. + pub big_r: AffinePoint, + + /// Our secret shares of the nonces. + pub alpha_i: Scalar, + pub beta_i: Scalar, +} + +pub mod presign; +pub mod sign; +#[cfg(test)] +mod test; diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs new file mode 100644 index 00000000..b9e37415 --- /dev/null +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -0,0 +1,290 @@ +use elliptic_curve::point::AffineCoordinates; +use frost_core::serialization::SerializableScalar; +use frost_secp256k1::{Group, Secp256K1Group}; +use rand_core::OsRng; + +use super::{PresignArguments, PresignOutput}; +use crate::{ + ecdsa::{ + CoefficientCommitment, Field, Polynomial, PolynomialCommitment, Scalar, + Secp256K1ScalarField, Secp256K1Sha256, SigningShare, + }, + participants::{ParticipantCounter, ParticipantList, ParticipantMap}, + protocol::{ + internal::{make_protocol, Comms, SharedChannel}, + InitializationError, Participant, Protocol, ProtocolError, + }, +}; + +type C = Secp256K1Sha256; + +/// Generates a secret polynomial where the constant term is zero +fn zero_secret_polynomial(degree: usize, rng: &mut OsRng) -> Result { + let secret = Secp256K1ScalarField::zero(); + Polynomial::generate_polynomial(Some(secret), degree, rng) +} + +/// /!\ Warning: the threshold in this scheme is the exactly the +/// same as the max number of malicious parties. +async fn do_presign( + mut chan: SharedChannel, + participants: ParticipantList, + me: Participant, + args: PresignArguments, +) -> Result { + let threshold = args.threshold; + // Round 0 + let mut rng = OsRng; + + let polynomials = [ + // degree t random secret shares where t is the max number of malicious parties + Polynomial::generate_polynomial(None, threshold, &mut rng)?, // fk + Polynomial::generate_polynomial(None, threshold, &mut rng)?, // fa + // degree 2t zero secret shares where t is the max number of malicious parties + zero_secret_polynomial(2 * threshold, &mut rng)?, // fb + zero_secret_polynomial(2 * threshold, &mut rng)?, // fd + zero_secret_polynomial(2 * threshold, &mut rng)?, // fe + ]; + + // send polynomial evaluations to participants + let wait_round_0 = chan.next_waitpoint(); + + for p in participants.others(me) { + // Securely send to each other participant a secret share + let package = polynomials + .iter() + .map(|poly| poly.eval_on_participant(p)) + .collect::>(); + + // send the evaluation privately to participant p + chan.send_private(wait_round_0, p, &package); + } + + // Evaluate my secret shares for my polynomials + let shares = polynomials + .iter() + .map(|poly| poly.eval_on_participant(me)) + .collect::>(); + + // Extract the shares into a vec of scalars + let mut shares: Vec = shares.iter().map(|signing_share| signing_share.0).collect(); + + // Round 1 + // Receive evaluations from all participants + let mut seen = ParticipantCounter::new(&participants); + seen.put(me); + while !seen.full() { + let (from, package): (_, [SerializableScalar; 5]) = chan.recv(wait_round_0).await?; + if !seen.put(from) { + continue; + } + + // calculate the respective sum of the received different shares from each participant + for i in 0..shares.len() { + shares[i] += package[i].0; + } + } + + // Compute R_me = g^{k_me} + let big_r_me = Secp256K1Group::generator() * shares[0]; + let big_r_me = CoefficientCommitment::new(big_r_me); + + // Compute w_me = a_me * k_me + b_me + let w_me = shares[1] * shares[0] + shares[2]; + + // Send and receive + let wait_round_1 = chan.next_waitpoint(); + chan.send_many(wait_round_1, &(&big_r_me, &SigningShare::new(w_me))); + + // Store the sent items + let mut signingshares_map = ParticipantMap::new(&participants); + let mut verifyingshares_map = ParticipantMap::new(&participants); + signingshares_map.put(me, SerializableScalar(w_me)); + verifyingshares_map.put(me, big_r_me); + + // Receive and interpolate + seen.clear(); + seen.put(me); + while !seen.full() { + let (from, (big_r_p, w_p)): (_, (CoefficientCommitment, SigningShare)) = + chan.recv(wait_round_1).await?; + if !seen.put(from) { + continue; + } + // collect big_r_p and w_p in maps that will be later ordered + signingshares_map.put(from, SerializableScalar(w_p.to_scalar())); + + // ONLY FOR PASSIVE: Disregard t points + verifyingshares_map.put(from, big_r_p); + } + + let identifiers: Vec = signingshares_map + .participants() + .iter() + .map(|p| p.scalar::()) + .collect(); + + let signingshares = signingshares_map + .into_vec_or_none() + .ok_or(ProtocolError::InvalidInterpolationArguments)?; + + // polynomial interpolation of w + let w = Polynomial::eval_interpolation(&identifiers, &signingshares, None)?; + + // exponent interpolation of big R + let identifiers: Vec = verifyingshares_map + .participants() + .iter() + .map(|p| p.scalar::()) + .collect(); + let verifying_shares = verifyingshares_map + .into_vec_or_none() + .ok_or(ProtocolError::InvalidInterpolationArguments)?; + + // get only the first t+1 elements to interpolate + // we know that identifiers.len()>threshold+1 + // evaluate the exponent interpolation on zero + let big_r = PolynomialCommitment::eval_exponent_interpolation( + &identifiers[..threshold + 1], + &verifying_shares[..threshold + 1], + None, + )?; + + // check w is non-zero and that R is not the identity + if w.0.is_zero().into() { + return Err(ProtocolError::ZeroScalar); + } + if big_r.value().eq(&::identity()) { + return Err(ProtocolError::IdentityElement); + } + + // w is non-zero due to previous check and so I can unwrap safely + let h_me = w.0.invert().unwrap() * shares[1]; + + // Some extra computation is pushed in this offline phase + let alpha_me = h_me + shares[3]; + + let big_r_x_coordinate: [u8; 32] = big_r.value().to_affine().x().into(); + let big_r_x_coordinate = ::deserialize(&big_r_x_coordinate) + .map_err(|_| ProtocolError::ErrorReducingBytesToScalar)?; + let x_me = args.keygen_out.private_share.to_scalar(); + let beta_me = h_me * big_r_x_coordinate * x_me + shares[4]; + + Ok(PresignOutput { + big_r: big_r.value().to_affine(), + alpha_i: alpha_me, + beta_i: beta_me, + }) +} + +/// The presignature protocol. +/// +/// This is the first phase of performing a signature, in which we perform +/// all the work we can do without yet knowing the message to be signed. +/// +/// This work does depend on the private key though, and it's crucial +/// that a presignature is never used. +pub fn presign( + participants: &[Participant], + me: Participant, + args: PresignArguments, +) -> Result, InitializationError> { + if participants.len() < 2 { + return Err(InitializationError::BadParameters(format!( + "participant count cannot be strictly less than 2, found: {}", + participants.len() + ))); + }; + + if args.threshold > participants.len() { + return Err(InitializationError::BadParameters( + "threshold must be less or equals to participant count".to_string(), + )); + } + + if 2 * args.threshold + 1 > participants.len() { + return Err(InitializationError::BadParameters( + "2*threshold+1 must be less or equals to participant count".to_string(), + )); + } + + let participants = ParticipantList::new(participants).ok_or_else(|| { + InitializationError::BadParameters("participant list cannot contain duplicates".to_string()) + })?; + + if !participants.contains(me) { + return Err(InitializationError::BadParameters( + "Presign participant list does not contain me".to_string(), + )); + }; + + let ctx = Comms::new(); + let fut = do_presign(ctx.shared_channel(), participants, me, args); + Ok(make_protocol(ctx, fut)) +} + +#[cfg(test)] +mod test { + use super::*; + use rand_core::OsRng; + + use crate::{ecdsa::KeygenOutput, protocol::run_protocol}; + use frost_secp256k1::keys::PublicKeyPackage; + use frost_secp256k1::VerifyingKey; + use std::collections::BTreeMap; + + use k256::ProjectivePoint; + + #[test] + fn test_presign() { + let participants = vec![ + Participant::from(0u32), + Participant::from(1u32), + Participant::from(2u32), + Participant::from(3u32), + Participant::from(4u32), + ]; + let max_malicious = 2; + + let f = Polynomial::generate_polynomial(None, max_malicious, &mut OsRng).unwrap(); + let big_x = ProjectivePoint::GENERATOR * f.eval_on_zero().0; + + #[allow(clippy::type_complexity)] + let mut protocols: Vec<(Participant, Box>)> = + Vec::with_capacity(participants.len()); + + for p in &participants { + // simulating the key packages for each participant + let private_share = f.eval_on_participant(*p); + let verifying_key = VerifyingKey::new(big_x); + let public_key_package = PublicKeyPackage::new(BTreeMap::new(), verifying_key); + let keygen_out = KeygenOutput { + private_share: SigningShare::new(private_share.0), + public_key: *public_key_package.verifying_key(), + }; + + let protocol = presign( + &participants[..], + *p, + PresignArguments { + keygen_out, + threshold: max_malicious, + }, + ); + assert!(protocol.is_ok()); + let protocol = protocol.unwrap(); + protocols.push((*p, Box::new(protocol))); + } + + let result = run_protocol(protocols); + assert!(result.is_ok()); + let result = result.unwrap(); + + assert!(result.len() == 5); + // testing that big_r is the same accross participants + assert_eq!(result[0].1.big_r, result[1].1.big_r); + assert_eq!(result[1].1.big_r, result[2].1.big_r); + assert_eq!(result[2].1.big_r, result[3].1.big_r); + assert_eq!(result[3].1.big_r, result[4].1.big_r); + } +} diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs new file mode 100644 index 00000000..cc634a24 --- /dev/null +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -0,0 +1,199 @@ +use elliptic_curve::scalar::IsHigh; + +use frost_core::serialization::SerializableScalar; +use subtle::ConditionallySelectable; + +use super::PresignOutput; +use crate::{ + ecdsa::{AffinePoint, FullSignature, Polynomial, Scalar, Secp256K1Sha256}, + participants::{ParticipantCounter, ParticipantList, ParticipantMap}, + protocol::{ + internal::{make_protocol, Comms, SharedChannel}, + InitializationError, Participant, Protocol, ProtocolError, + }, +}; +type C = Secp256K1Sha256; + +async fn do_sign( + mut chan: SharedChannel, + participants: ParticipantList, + me: Participant, + public_key: AffinePoint, + presignature: PresignOutput, + msg_hash: Scalar, +) -> Result { + let s_me = msg_hash * presignature.alpha_i + presignature.beta_i; + let s_me = SerializableScalar(s_me); + + let wait_round = chan.next_waitpoint(); + chan.send_many(wait_round, &s_me); + + let mut seen = ParticipantCounter::new(&participants); + let mut s_map = ParticipantMap::new(&participants); + s_map.put(me, s_me); + + seen.put(me); + while !seen.full() { + let (from, s_i): (_, SerializableScalar) = chan.recv(wait_round).await?; + if !seen.put(from) { + continue; + } + s_map.put(from, s_i); + } + + let identifiers: Vec = s_map + .participants() + .iter() + .map(|p| p.scalar::()) + .collect(); + + let sshares = s_map + .into_vec_or_none() + .ok_or(ProtocolError::InvalidInterpolationArguments)?; + + let mut s = Polynomial::eval_interpolation(&identifiers, &sshares, None)?.0; + let big_r = presignature.big_r; + + // Normalize s + s.conditional_assign(&(-s), s.is_high()); + + let sig = FullSignature { big_r, s }; + + if !sig.verify(&public_key, &msg_hash) { + return Err(ProtocolError::AssertionFailed( + "signature failed to verify".to_string(), + )); + }; + + Ok(sig) +} + +pub fn sign( + participants: &[Participant], + me: Participant, + public_key: AffinePoint, + presignature: PresignOutput, + msg_hash: Scalar, +) -> Result, InitializationError> { + if participants.len() < 2 { + return Err(InitializationError::BadParameters(format!( + "participant count cannot be < 2, found: {}", + participants.len() + ))); + }; + + let participants = ParticipantList::new(participants).ok_or_else(|| { + InitializationError::BadParameters("participant list cannot contain duplicates".to_string()) + })?; + + if !participants.contains(me) { + return Err(InitializationError::BadParameters( + "participant list does not contain me".to_string(), + )); + }; + + let ctx = Comms::new(); + let fut = do_sign( + ctx.shared_channel(), + participants, + me, + public_key, + presignature, + msg_hash, + ); + Ok(make_protocol(ctx, fut)) +} + +#[cfg(test)] +mod test { + use std::error::Error; + + use ecdsa::Signature; + use k256::{ecdsa::signature::Verifier, ecdsa::VerifyingKey, PublicKey}; + use rand_core::OsRng; + + use super::*; + use crate::ecdsa::{x_coordinate, Field, ProjectivePoint, Secp256K1ScalarField}; + + use crate::{crypto::hash::scalar_hash, protocol::run_protocol}; + + #[test] + fn test_sign() -> Result<(), Box> { + let max_malicious = 2; + let threshold = max_malicious + 1; + let msg = b"hello?"; + + for _ in 0..100 { + let fx = Polynomial::generate_polynomial(None, threshold - 1, &mut OsRng).unwrap(); + // master secret key + let x = fx.eval_on_zero().0; + // master public key + let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); + + let fa = Polynomial::generate_polynomial(None, threshold - 1, &mut OsRng).unwrap(); + let fk = Polynomial::generate_polynomial(None, threshold - 1, &mut OsRng).unwrap(); + + let fd = Polynomial::generate_polynomial( + Some(Secp256K1ScalarField::zero()), + 2 * max_malicious, + &mut OsRng, + ) + .unwrap(); + let fe = Polynomial::generate_polynomial( + Some(Secp256K1ScalarField::zero()), + 2 * max_malicious, + &mut OsRng, + ) + .unwrap(); + + let k = fk.eval_on_zero().0; + let big_r = ProjectivePoint::GENERATOR * k; + let big_r_x_coordinate = x_coordinate(&big_r.to_affine()); + + let w = fa.eval_on_zero().0 * k; + let w_invert = w.invert().unwrap(); + + let participants = vec![ + Participant::from(0u32), + Participant::from(1u32), + Participant::from(2u32), + Participant::from(3u32), + Participant::from(4u32), + ]; + + #[allow(clippy::type_complexity)] + let mut protocols: Vec<( + Participant, + Box>, + )> = Vec::with_capacity(participants.len()); + for p in &participants { + let h_i = w_invert * fa.eval_on_participant(*p).0; + let alpha_i = h_i + fd.eval_on_participant(*p).0; + let beta_i = h_i * big_r_x_coordinate * fx.eval_on_participant(*p).0 + + fe.eval_on_participant(*p).0; + + let presignature = PresignOutput { + big_r: big_r.to_affine(), + alpha_i, + beta_i, + }; + + let protocol = sign( + &participants, + *p, + public_key, + presignature, + scalar_hash(msg), + )?; + protocols.push((*p, Box::new(protocol))); + } + + let result = run_protocol(protocols)?; + let sig = result[0].1.clone(); + let sig = Signature::from_scalars(x_coordinate(&sig.big_r), sig.s)?; + VerifyingKey::from(&PublicKey::from_affine(public_key).unwrap()) + .verify(&msg[..], &sig)?; + } + Ok(()) + } +} diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs new file mode 100644 index 00000000..87be8a0d --- /dev/null +++ b/src/ecdsa/robust_ecdsa/test.rs @@ -0,0 +1,221 @@ +use std::error::Error; + +use super::{presign::presign, sign::sign, PresignArguments, PresignOutput}; + +use crate::ecdsa::{ + test::{assert_public_key_invariant, run_keygen, run_reshare, run_sign}, + AffinePoint, FullSignature, KeygenOutput, Scalar, +}; +use crate::protocol::{run_protocol, InitializationError, Participant, Protocol}; + +#[cfg(test)] +fn sign_box( + participants: &[Participant], + me: Participant, + public_key: AffinePoint, + presignature: PresignOutput, + msg_hash: Scalar, +) -> Result>, InitializationError> { + sign(participants, me, public_key, presignature, msg_hash) + .map(|sig| Box::new(sig) as Box>) +} + +#[cfg(test)] +pub fn run_presign( + participants: Vec<(Participant, KeygenOutput)>, + max_malicious: usize, +) -> Vec<(Participant, PresignOutput)> { + #[allow(clippy::type_complexity)] + let mut protocols: Vec<(Participant, Box>)> = + Vec::with_capacity(participants.len()); + + let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); + + for (p, keygen_out) in participants.into_iter() { + let protocol = presign( + &participant_list, + p, + PresignArguments { + keygen_out, + threshold: max_malicious, + }, + ); + assert!(protocol.is_ok()); + let protocol = protocol.unwrap(); + protocols.push((p, Box::new(protocol))); + } + + run_protocol(protocols).unwrap() +} + +#[test] +fn test_reshare_sign_more_participants() -> Result<(), Box> { + let participants = vec![ + Participant::from(0u32), + Participant::from(1u32), + Participant::from(2u32), + Participant::from(3u32), + Participant::from(4u32), + Participant::from(5u32), + Participant::from(6u32), + Participant::from(7u32), + Participant::from(8u32), + Participant::from(9u32), + Participant::from(10u32), + ]; + let max_malicious = 3; + let threshold = max_malicious + 1; + let result0 = run_keygen(&participants, threshold)?; + assert_public_key_invariant(&result0)?; + + let pub_key = result0[2].1.public_key; + + // Run heavy reshare + let max_malicious = 4; + let new_threshold = max_malicious + 1; + + let mut new_participant = participants.clone(); + new_participant.push(Participant::from(31u32)); + new_participant.push(Participant::from(32u32)); + new_participant.push(Participant::from(33u32)); + let mut key_packages = run_reshare( + &participants, + &pub_key, + result0, + threshold, + new_threshold, + new_participant.clone(), + )?; + assert_public_key_invariant(&key_packages)?; + key_packages.sort_by_key(|(p, _)| *p); + + let public_key = key_packages[0].1.public_key; + + // Presign + let mut presign_result = run_presign(key_packages, max_malicious); + presign_result.sort_by_key(|(p, _)| *p); + + let msg = b"hello world"; + + run_sign( + presign_result, + public_key.to_element().to_affine(), + msg, + sign_box, + ); + Ok(()) +} + +#[test] +fn test_reshare_sign_less_participants() -> Result<(), Box> { + let participants = vec![ + Participant::from(0u32), + Participant::from(1u32), + Participant::from(2u32), + Participant::from(3u32), + Participant::from(4u32), + ]; + let max_malicious = 2; + let threshold = max_malicious + 1; + let result0 = run_keygen(&participants, threshold)?; + assert_public_key_invariant(&result0)?; + + let pub_key = result0[2].1.public_key; + + // Run heavy reshare + let max_malicious = 1; + let new_threshold = max_malicious + 1; + let mut new_participant = participants.clone(); + new_participant.pop(); + let mut key_packages = run_reshare( + &participants, + &pub_key, + result0, + threshold, + new_threshold, + new_participant.clone(), + )?; + assert_public_key_invariant(&key_packages)?; + key_packages.sort_by_key(|(p, _)| *p); + + let public_key = key_packages[0].1.public_key; + + // Presign + let mut presign_result = run_presign(key_packages, max_malicious); + presign_result.sort_by_key(|(p, _)| *p); + + let msg = b"hello world"; + + run_sign( + presign_result, + public_key.to_element().to_affine(), + msg, + sign_box, + ); + Ok(()) +} + +#[test] +fn test_e2e() -> Result<(), Box> { + let participants = vec![ + Participant::from(0u32), + Participant::from(1u32), + Participant::from(2u32), + Participant::from(3u32), + Participant::from(4u32), + Participant::from(5u32), + Participant::from(6u32), + Participant::from(7u32), + ]; + let max_malicious = 3; + + let mut keygen_result = run_keygen(&participants.clone(), max_malicious + 1)?; + keygen_result.sort_by_key(|(p, _)| *p); + + let public_key = keygen_result[0].1.public_key; + assert_eq!(keygen_result[0].1.public_key, keygen_result[1].1.public_key); + assert_eq!(keygen_result[1].1.public_key, keygen_result[2].1.public_key); + + let mut presign_result = run_presign(keygen_result, max_malicious); + presign_result.sort_by_key(|(p, _)| *p); + + let msg = b"hello world"; + + run_sign( + presign_result, + public_key.to_element().to_affine(), + msg, + sign_box, + ); + Ok(()) +} + +#[test] +fn test_e2e_random_identifiers() -> Result<(), Box> { + let participants_count = 7; + let mut participants: Vec<_> = (0..participants_count) + .map(|_| Participant::from(rand::random::())) + .collect(); + participants.sort(); + let max_malicious = 3; + + let mut keygen_result = run_keygen(&participants.clone(), max_malicious + 1)?; + keygen_result.sort_by_key(|(p, _)| *p); + + let public_key = keygen_result[0].1.public_key; + assert_eq!(keygen_result[0].1.public_key, keygen_result[1].1.public_key); + assert_eq!(keygen_result[1].1.public_key, keygen_result[2].1.public_key); + + let mut presign_result = run_presign(keygen_result, max_malicious); + presign_result.sort_by_key(|(p, _)| *p); + + let msg = b"hello world"; + + run_sign( + presign_result, + public_key.to_element().to_affine(), + msg, + sign_box, + ); + Ok(()) +} diff --git a/src/ecdsa/sign.rs b/src/ecdsa/sign.rs deleted file mode 100644 index 38399411..00000000 --- a/src/ecdsa/sign.rs +++ /dev/null @@ -1,354 +0,0 @@ -use elliptic_curve::{ops::Invert, scalar::IsHigh, Field, Group, ScalarPrimitive}; -use subtle::ConditionallySelectable; - -use crate::protocol::internal::Comms; -use crate::{ - compat::{self, CSCurve}, - ecdsa::presign::PresignOutput, - participants::{ParticipantCounter, ParticipantList}, - protocol::{ - internal::{make_protocol, SharedChannel}, - InitializationError, Participant, Protocol, ProtocolError, - }, -}; - -/// Represents a signature with extra information, to support different variants of ECDSA. -/// -/// An ECDSA signature is usually two scalars. The first scalar is derived from -/// a point on the curve, and because this process is lossy, some other variants -/// of ECDSA also include some extra information in order to recover this point. -/// -/// Furthermore, some signature formats may disagree on how precisely to serialize -/// different values as bytes. -/// -/// To support these variants, this simply gives you a normal signature, along with the entire -/// first point. -#[derive(Clone)] -pub struct FullSignature { - /// This is the entire first point. - pub big_r: C::AffinePoint, - /// This is the second scalar, normalized to be in the lower range. - pub s: C::Scalar, -} - -impl FullSignature { - #[must_use] - pub fn verify(&self, public_key: &C::AffinePoint, msg_hash: &C::Scalar) -> bool { - let r: C::Scalar = compat::x_coordinate::(&self.big_r); - if r.is_zero().into() || self.s.is_zero().into() { - return false; - } - let s_inv = self.s.invert_vartime().unwrap(); - let reproduced = (C::ProjectivePoint::generator() * (*msg_hash * s_inv)) - + (C::ProjectivePoint::from(*public_key) * (r * s_inv)); - compat::x_coordinate::(&reproduced.into()) == r - } -} - -async fn do_sign( - mut chan: SharedChannel, - participants: ParticipantList, - me: Participant, - public_key: C::AffinePoint, - presignature: PresignOutput, - msg_hash: C::Scalar, -) -> Result, ProtocolError> { - // Spec 1.1 - let lambda = participants.lagrange::(me); - let k_i = lambda * presignature.k; - - // Spec 1.2 - let sigma_i = lambda * presignature.sigma; - - // Spec 1.3 - let r = compat::x_coordinate::(&presignature.big_r); - let s_i: C::Scalar = msg_hash * k_i + r * sigma_i; - - // Spec 1.4 - let wait0 = chan.next_waitpoint(); - { - let s_i: ScalarPrimitive = s_i.into(); - chan.send_many(wait0, &s_i); - } - - // Spec 2.1 + 2.2 - let mut seen = ParticipantCounter::new(&participants); - let mut s: C::Scalar = s_i; - seen.put(me); - while !seen.full() { - let (from, s_j): (_, ScalarPrimitive) = chan.recv(wait0).await?; - if !seen.put(from) { - continue; - } - s += C::Scalar::from(s_j) - } - - // Spec 2.3 - // Optionally, normalize s - s.conditional_assign(&(-s), s.is_high()); - let sig = FullSignature { - big_r: presignature.big_r, - s, - }; - if !sig.verify(&public_key, &msg_hash) { - return Err(ProtocolError::AssertionFailed( - "signature failed to verify".to_string(), - )); - } - - // Spec 2.4 - Ok(sig) -} - -pub fn signature_share( - participants: Vec, - me: Participant, - // public_key: C::AffinePoint, - presignature: PresignOutput, - msg_hash: C::Scalar, -) -> Result { - let p_list = ParticipantList::new(&participants).unwrap(); - // Spec 1.1 - let lambda = p_list.lagrange::(me); - let k_i = lambda * presignature.k; - - // Spec 1.2 - let sigma_i = lambda * presignature.sigma; - - // Spec 1.3 - let r = compat::x_coordinate::(&presignature.big_r); - let s_i: C::Scalar = msg_hash * k_i + r * sigma_i; - - Ok(s_i) -} - -pub async fn combine_signature_shares( - shares: Vec, - public_key: C::AffinePoint, - // presignature: PresignOutput, - presignature_big_r: C::AffinePoint, - msg_hash: C::Scalar, -) -> Result, ProtocolError> { - let mut s: C::Scalar = shares[0]; - for s_j in shares.iter().skip(1) { - s += *s_j - } - - // Spec 2.3 - // Optionally, normalize s - s.conditional_assign(&(-s), s.is_high()); - let sig = FullSignature { - // big_r: presignature.big_r, - big_r: presignature_big_r, - s, - }; - if !sig.verify(&public_key, &msg_hash) { - return Err(ProtocolError::AssertionFailed( - "signature failed to verify".to_string(), - )); - } - - // Spec 2.4 - Ok(sig) -} - -/// The signature protocol, allowing us to use a presignature to sign a message. -/// -/// **WARNING** You must absolutely hash an actual message before passing it to -/// this function. Allowing the signing of arbitrary scalars *is* a security risk, -/// and this function only tolerates this risk to allow for genericity. -pub fn sign( - participants: &[Participant], - me: Participant, - public_key: C::AffinePoint, - presignature: PresignOutput, - msg_hash: C::Scalar, -) -> Result>, InitializationError> { - if participants.len() < 2 { - return Err(InitializationError::BadParameters(format!( - "participant count cannot be < 2, found: {}", - participants.len() - ))); - }; - - let participants = ParticipantList::new(participants).ok_or_else(|| { - InitializationError::BadParameters("participant list cannot contain duplicates".to_string()) - })?; - - let ctx = Comms::new(); - let fut = do_sign( - ctx.shared_channel(), - participants, - me, - public_key, - presignature, - msg_hash, - ); - Ok(make_protocol(ctx, fut)) -} - -#[cfg(test)] -mod test { - use std::error::Error; - - use ecdsa::Signature; - use k256::{ - ecdsa::signature::Verifier, ecdsa::VerifyingKey, ProjectivePoint, PublicKey, Scalar, - Secp256k1, - }; - use rand_core::OsRng; - - use super::*; - use crate::ecdsa::test::{ - assert_public_key_invariant, run_keygen, run_presign, run_reshare, run_sign, - }; - use crate::ecdsa::triples::deal; - use crate::{compat::scalar_hash, ecdsa::math::Polynomial, protocol::run_protocol}; - - #[test] - fn test_sign() -> Result<(), Box> { - let threshold = 2; - let msg = b"hello?"; - - // Run 4 times for flakiness reasons - for _ in 0..4 { - let f = Polynomial::::random(&mut OsRng, threshold); - let x = f.evaluate_zero(); - let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); - - let g = Polynomial::::random(&mut OsRng, threshold); - - let k: Scalar = g.evaluate_zero(); - let big_k = (ProjectivePoint::GENERATOR * k.invert().unwrap()).to_affine(); - - let sigma = k * x; - - let h = Polynomial::::extend_random(&mut OsRng, threshold, &sigma); - - let participants = vec![Participant::from(0u32), Participant::from(1u32)]; - #[allow(clippy::type_complexity)] - let mut protocols: Vec<( - Participant, - Box>>, - )> = Vec::with_capacity(participants.len()); - for p in &participants { - let p_scalar = p.scalar::(); - let presignature = PresignOutput { - big_r: big_k, - k: g.evaluate(&p_scalar), - sigma: h.evaluate(&p_scalar), - }; - let protocol = sign( - &participants, - *p, - public_key, - presignature, - scalar_hash(msg), - )?; - protocols.push((*p, Box::new(protocol))); - } - - let result = run_protocol(protocols)?; - let sig = result[0].1.clone(); - let sig = - Signature::from_scalars(compat::x_coordinate::(&sig.big_r), sig.s)?; - VerifyingKey::from(&PublicKey::from_affine(public_key).unwrap()) - .verify(&msg[..], &sig)?; - } - Ok(()) - } - - #[test] - fn test_reshare_sign_more_participants() -> Result<(), Box> { - let participants = vec![ - Participant::from(0u32), - Participant::from(1u32), - Participant::from(2u32), - Participant::from(3u32), - Participant::from(4u32), - ]; - let threshold = 3; - let result0 = run_keygen(&participants, threshold)?; - assert_public_key_invariant(&result0)?; - - let pub_key = result0[2].1.public_key; - - // Run heavy reshare - let new_threshold = 5; - let mut new_participant = participants.clone(); - new_participant.push(Participant::from(31u32)); - new_participant.push(Participant::from(32u32)); - new_participant.push(Participant::from(33u32)); - let mut key_packages = run_reshare( - &participants, - &pub_key, - result0, - threshold, - new_threshold, - new_participant.clone(), - )?; - assert_public_key_invariant(&key_packages)?; - key_packages.sort_by_key(|(p, _)| *p); - - let public_key = key_packages[0].1.public_key; - // Prepare triples - let (pub0, shares0) = deal(&mut OsRng, &new_participant, new_threshold); - let (pub1, shares1) = deal(&mut OsRng, &new_participant, new_threshold); - - // Presign - let mut presign_result = - run_presign(key_packages, shares0, shares1, &pub0, &pub1, new_threshold); - presign_result.sort_by_key(|(p, _)| *p); - - let msg = b"hello world"; - - run_sign(presign_result, public_key.to_element().to_affine(), msg); - Ok(()) - } - - #[test] - fn test_reshare_sign_less_participants() -> Result<(), Box> { - let participants = vec![ - Participant::from(0u32), - Participant::from(1u32), - Participant::from(2u32), - Participant::from(3u32), - Participant::from(4u32), - ]; - let threshold = 4; - let result0 = run_keygen(&participants, threshold)?; - assert_public_key_invariant(&result0)?; - - let pub_key = result0[2].1.public_key; - - // Run heavy reshare - let new_threshold = 3; - let mut new_participant = participants.clone(); - new_participant.pop(); - let mut key_packages = run_reshare( - &participants, - &pub_key, - result0, - threshold, - new_threshold, - new_participant.clone(), - )?; - assert_public_key_invariant(&key_packages)?; - key_packages.sort_by_key(|(p, _)| *p); - - let public_key = key_packages[0].1.public_key; - // Prepare triples - let (pub0, shares0) = deal(&mut OsRng, &new_participant, new_threshold); - let (pub1, shares1) = deal(&mut OsRng, &new_participant, new_threshold); - - // Presign - let mut presign_result = - run_presign(key_packages, shares0, shares1, &pub0, &pub1, new_threshold); - presign_result.sort_by_key(|(p, _)| *p); - - let msg = b"hello world"; - - run_sign(presign_result, public_key.to_element().to_affine(), msg); - Ok(()) - } -} diff --git a/src/ecdsa/test.rs b/src/ecdsa/test.rs index c393b3a1..fa74fbf3 100644 --- a/src/ecdsa/test.rs +++ b/src/ecdsa/test.rs @@ -1,19 +1,13 @@ -use k256::{AffinePoint, Secp256k1}; +use frost_secp256k1::VerifyingKey; +use k256::{AffinePoint, Scalar}; use std::error::Error; -use crate::compat::scalar_hash; - -use crate::ecdsa::dkg_ecdsa::{keygen, refresh, reshare}; +use crate::crypto::hash::scalar_hash; use crate::ecdsa::{ - presign::{presign, PresignArguments, PresignOutput}, - sign::{sign, FullSignature}, - triples::{self, TriplePub, TripleShare}, - KeygenOutput, + dkg_ecdsa::{keygen, refresh, reshare}, + FullSignature, KeygenOutput, }; -use crate::protocol::{run_protocol, Participant, Protocol}; - -use frost_secp256k1::VerifyingKey; -use rand_core::OsRng; +use crate::protocol::{run_protocol, InitializationError, Participant, Protocol}; /// runs distributed keygen pub(crate) fn run_keygen( @@ -23,7 +17,7 @@ pub(crate) fn run_keygen( let mut protocols: Vec<(Participant, Box>)> = Vec::with_capacity(participants.len()); - for p in participants.iter() { + for p in participants { let protocol = keygen(participants, *p, threshold)?; protocols.push((*p, Box::new(protocol))); } @@ -118,66 +112,30 @@ pub(crate) fn assert_public_key_invariant( Ok(()) } -pub fn run_presign( - participants: Vec<(Participant, KeygenOutput)>, - shares0: Vec>, - shares1: Vec>, - pub0: &TriplePub, - pub1: &TriplePub, - threshold: usize, -) -> Vec<(Participant, PresignOutput)> { - assert!(participants.len() == shares0.len()); - assert!(participants.len() == shares1.len()); - - #[allow(clippy::type_complexity)] - let mut protocols: Vec<( - Participant, - Box>>, - )> = Vec::with_capacity(participants.len()); - - let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); - - for (((p, keygen_out), share0), share1) in participants - .into_iter() - .zip(shares0.into_iter()) - .zip(shares1.into_iter()) - { - let protocol = presign( - &participant_list, - p, - &participant_list, - p, - PresignArguments { - triple0: (share0, pub0.clone()), - triple1: (share1, pub1.clone()), - keygen_out, - threshold, - }, - ); - assert!(protocol.is_ok()); - let protocol = protocol.unwrap(); - protocols.push((p, Box::new(protocol))); - } - - run_protocol(protocols).unwrap() -} - #[allow(clippy::type_complexity)] -pub fn run_sign( - participants: Vec<(Participant, PresignOutput)>, +pub fn run_sign( + participants_outs: Vec<(Participant, PresignOutput)>, public_key: AffinePoint, msg: &[u8], -) -> Vec<(Participant, FullSignature)> { - let mut protocols: Vec<( + sign_box: F, +) -> Vec<(Participant, FullSignature)> +where + F: Fn( + &[Participant], Participant, - Box>>, - )> = Vec::with_capacity(participants.len()); - - let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); - - for (p, presign_out) in participants.into_iter() { - let protocol = sign( - &participant_list, + AffinePoint, + PresignOutput, + Scalar, + ) -> Result>, InitializationError>, +{ + let mut protocols: Vec<(Participant, Box>)> = + Vec::with_capacity(participants_outs.len()); + + let participant_list: Vec = participants_outs.iter().map(|(p, _)| *p).collect(); + let participant_list = participant_list.as_slice(); + for (p, presign_out) in participants_outs.into_iter() { + let protocol = sign_box( + participant_list, p, public_key, presign_out, @@ -185,64 +143,8 @@ pub fn run_sign( ); assert!(protocol.is_ok()); let protocol = protocol.unwrap(); - protocols.push((p, Box::new(protocol))); + protocols.push((p, protocol)); } run_protocol(protocols).unwrap() } - -#[test] -fn test_e2e() -> Result<(), Box> { - let participants = vec![ - Participant::from(0u32), - Participant::from(1u32), - Participant::from(2u32), - ]; - let threshold = 3; - - let mut keygen_result = run_keygen(&participants.clone(), threshold)?; - keygen_result.sort_by_key(|(p, _)| *p); - - let public_key = keygen_result[0].1.public_key; - assert_eq!(keygen_result[0].1.public_key, keygen_result[1].1.public_key); - assert_eq!(keygen_result[1].1.public_key, keygen_result[2].1.public_key); - - let (pub0, shares0) = triples::deal(&mut OsRng, &participants, threshold); - let (pub1, shares1) = triples::deal(&mut OsRng, &participants, threshold); - - let mut presign_result = run_presign(keygen_result, shares0, shares1, &pub0, &pub1, threshold); - presign_result.sort_by_key(|(p, _)| *p); - - let msg = b"hello world"; - - run_sign(presign_result, public_key.to_element().to_affine(), msg); - Ok(()) -} - -#[test] -fn test_e2e_random_identifiers() -> Result<(), Box> { - let participants_count = 3; - let mut participants: Vec<_> = (0..participants_count) - .map(|_| Participant::from(rand::random::())) - .collect(); - participants.sort(); - let threshold = 3; - - let mut keygen_result = run_keygen(&participants.clone(), threshold)?; - keygen_result.sort_by_key(|(p, _)| *p); - - let public_key = keygen_result[0].1.public_key; - assert_eq!(keygen_result[0].1.public_key, keygen_result[1].1.public_key); - assert_eq!(keygen_result[1].1.public_key, keygen_result[2].1.public_key); - - let (pub0, shares0) = triples::deal(&mut OsRng, &participants, threshold); - let (pub1, shares1) = triples::deal(&mut OsRng, &participants, threshold); - - let mut presign_result = run_presign(keygen_result, shares0, shares1, &pub0, &pub1, threshold); - presign_result.sort_by_key(|(p, _)| *p); - - let msg = b"hello world"; - - run_sign(presign_result, public_key.to_element().to_affine(), msg); - Ok(()) -} diff --git a/src/ecdsa/triples/mod.rs b/src/ecdsa/triples/mod.rs deleted file mode 100644 index 090410dd..00000000 --- a/src/ecdsa/triples/mod.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! This module contains the types and protocols related to triple generation. -//! -//! The cait-sith signing protocol makes use of *committed* Beaver Triples. -//! A triple is a value of the form `(a, b, c), (A, B, C)`, such that -//! `c = a * b`, and `A = a * G`, `B = b * G`, `C = c * G`. This is a beaver -//! triple along with commitments to its values in the form of group elements. -//! -//! The signing protocols make use of a triple where the scalar values `(a, b, c)` -//! are secret-shared, and the commitments are public. Each signature requires -//! two triples. These triples can be generated in advance without knowledge -//! of the secret key used to sign. It's important that the value of the underlying -//! scalars in the triple is kept secret, otherwise the private key used to create -//! a signature with that triple could be recovered. -//! -//! There are two ways of generating these triples. -//! -//! One way is to have -//! a trusted third party generate them. This is supported by the [deal] function. -//! -//! The other way is to run a protocol generating a secret shared triple without any party -//! learning the secret values. This is better because no party learns the value of the -//! triple, which needs to be kept secret. This method is supported by the [generate_triple] -//! protocol. -//! -//! This protocol requires a setup protocol to be one once beforehand. -//! After this setup protocol has been run, an arbitarary number of triples can -//! be generated. -use elliptic_curve::{Field, Group}; -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; - -use crate::{compat::CSCurve, ecdsa::math::Polynomial, protocol::Participant}; - -/// Represents the public part of a triple. -/// -/// This contains commitments to each part of the triple. -/// -/// We also record who participated in the protocol, -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct TriplePub { - pub big_a: C::AffinePoint, - pub big_b: C::AffinePoint, - pub big_c: C::AffinePoint, - /// The participants in generating this triple. - pub participants: Vec, - /// The threshold which will be able to reconstruct it. - pub threshold: usize, -} - -/// Represents a share of a triple. -/// -/// This consists of shares of each individual part. -/// -/// i.e. we have a share of a, b, and c such that a * b = c. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct TripleShare { - pub a: C::Scalar, - pub b: C::Scalar, - pub c: C::Scalar, -} - -/// Create a new triple from scratch. -/// -/// This can be used to generate a triple if you then trust the person running -/// this code to forget about the values they generated. -pub fn deal( - rng: &mut impl CryptoRngCore, - participants: &[Participant], - threshold: usize, -) -> (TriplePub, Vec>) { - let a = C::Scalar::random(&mut *rng); - let b = C::Scalar::random(&mut *rng); - let c = a * b; - - let f_a = Polynomial::::extend_random(rng, threshold, &a); - let f_b = Polynomial::::extend_random(rng, threshold, &b); - let f_c = Polynomial::::extend_random(rng, threshold, &c); - - let mut shares = Vec::with_capacity(participants.len()); - let mut participants_owned = Vec::with_capacity(participants.len()); - - for p in participants { - participants_owned.push(*p); - let p_scalar = p.scalar::(); - shares.push(TripleShare { - a: f_a.evaluate(&p_scalar), - b: f_b.evaluate(&p_scalar), - c: f_c.evaluate(&p_scalar), - }); - } - - let triple_pub = TriplePub { - big_a: (C::ProjectivePoint::generator() * a).into(), - big_b: (C::ProjectivePoint::generator() * b).into(), - big_c: (C::ProjectivePoint::generator() * c).into(), - participants: participants_owned, - threshold, - }; - - (triple_pub, shares) -} - -/// Create a new batch of triples from scratch. -/// -/// This can be used to generate a triple if you then trust the person running -/// this code to forget about the values they generated. -pub fn deal_many( - rng: &mut impl CryptoRngCore, - participants: &[Participant], - threshold: usize, -) -> Vec<(TriplePub, Vec>)> { - let mut batch = Vec::with_capacity(1); - for _ in 0..N { - let a = C::Scalar::random(&mut *rng); - let b = C::Scalar::random(&mut *rng); - let c = a * b; - - let f_a = Polynomial::::extend_random(rng, threshold, &a); - let f_b = Polynomial::::extend_random(rng, threshold, &b); - let f_c = Polynomial::::extend_random(rng, threshold, &c); - - let mut shares = Vec::with_capacity(participants.len()); - let mut participants_owned = Vec::with_capacity(participants.len()); - - for p in participants { - participants_owned.push(*p); - let p_scalar = p.scalar::(); - shares.push(TripleShare { - a: f_a.evaluate(&p_scalar), - b: f_b.evaluate(&p_scalar), - c: f_c.evaluate(&p_scalar), - }); - } - - let triple_pub = TriplePub { - big_a: (C::ProjectivePoint::generator() * a).into(), - big_b: (C::ProjectivePoint::generator() * b).into(), - big_c: (C::ProjectivePoint::generator() * c).into(), - participants: participants_owned, - threshold, - }; - - batch.push((triple_pub, shares)) - } - batch -} - -mod batch_random_ot; -mod bits; -mod correlated_ot_extension; -mod generation; -mod mta; -mod multiplication; -mod random_ot_extension; - -pub use generation::{generate_triple, generate_triple_many, TripleGenerationOutput}; diff --git a/src/ecdsa/triples/mta.rs b/src/ecdsa/triples/mta.rs deleted file mode 100644 index 5b9c3db2..00000000 --- a/src/ecdsa/triples/mta.rs +++ /dev/null @@ -1,210 +0,0 @@ -use elliptic_curve::{Field, ScalarPrimitive}; -use rand_core::{OsRng, RngCore}; -use serde::{Deserialize, Serialize}; -use std::slice::Iter; -use subtle::{Choice, ConditionallySelectable}; - -use crate::protocol::internal::Comms; -use crate::{ - compat::CSCurve, - proofs::strobe_transcript::TranscriptRng, - protocol::{ - internal::{make_protocol, PrivateChannel}, - run_two_party_protocol, Participant, ProtocolError, - }, -}; - -struct MTAScalars(Vec<(ScalarPrimitive, ScalarPrimitive)>); - -impl MTAScalars { - const SCALAR_LEN: usize = (C::BITS + 7) >> 3; - - fn len(&self) -> usize { - self.0.len() - } - - fn iter(&self) -> Iter<'_, (ScalarPrimitive, ScalarPrimitive)> { - self.0.iter() - } -} - -impl Serialize for MTAScalars { - fn serialize(&self, s: S) -> Result { - let mut out = Vec::with_capacity(self.len() * Self::SCALAR_LEN * 2); - for (s0, s1) in self.iter() { - out.extend_from_slice(s0.to_bytes().as_ref()); - out.extend_from_slice(s1.to_bytes().as_ref()); - } - out.serialize(s) - } -} - -impl<'de, C: CSCurve> Deserialize<'de> for MTAScalars { - fn deserialize>(d: D) -> Result { - let bytes = Vec::::deserialize(d)?; - if bytes.len() % (Self::SCALAR_LEN * 2) != 0 { - return Err(serde::de::Error::custom("invalid length")); - } - let mut out = Vec::with_capacity(bytes.len() / (Self::SCALAR_LEN * 2)); - for chunk in bytes.chunks_exact(Self::SCALAR_LEN * 2) { - let s0 = ScalarPrimitive::from_slice(&chunk[..Self::SCALAR_LEN]) - .map_err(serde::de::Error::custom)?; - let s1 = ScalarPrimitive::from_slice(&chunk[Self::SCALAR_LEN..]) - .map_err(serde::de::Error::custom)?; - out.push((s0, s1)); - } - Ok(Self(out)) - } -} - -/// The sender for multiplicative to additive conversion. -pub async fn mta_sender( - mut chan: PrivateChannel, - v: Vec<(C::Scalar, C::Scalar)>, - a: C::Scalar, -) -> Result { - let size = v.len(); - - // Step 1 - let delta: Vec<_> = (0..size).map(|_| C::Scalar::random(&mut OsRng)).collect(); - - // Step 2 - let c: MTAScalars = MTAScalars( - delta - .iter() - .zip(v.iter()) - .map(|(delta_i, (v0_i, v1_i))| { - ((*v0_i + delta_i + a).into(), (*v1_i + delta_i - a).into()) - }) - .collect(), - ); - let wait0 = chan.next_waitpoint(); - chan.send(wait0, &c); - - // Step 7 - let wait1 = chan.next_waitpoint(); - let (chi1, seed): (ScalarPrimitive, [u8; 32]) = chan.recv(wait1).await?; - - let mut alpha = delta[0] * C::Scalar::from(chi1); - - let mut prng = TranscriptRng::new(&seed); - for &delta_i in &delta[1..] { - let chi_i = C::Scalar::random(&mut prng); - alpha += delta_i * chi_i; - } - - Ok(-alpha) -} - -/// The receiver for multiplicative to additive conversion. -pub async fn mta_receiver( - mut chan: PrivateChannel, - tv: Vec<(Choice, C::Scalar)>, - b: C::Scalar, -) -> Result { - let size = tv.len(); - - // Step 3 - let wait0 = chan.next_waitpoint(); - let c: MTAScalars = chan.recv(wait0).await?; - if c.len() != tv.len() { - return Err(ProtocolError::AssertionFailed( - "length of c was incorrect".to_owned(), - )); - } - let mut m = tv.iter().zip(c.iter()).map(|((t_i, v_i), (c0_i, c1_i))| { - C::Scalar::conditional_select(&(*c0_i).into(), &(*c1_i).into(), *t_i) - v_i - }); - - // Step 4 - let mut seed = [0u8; 32]; - OsRng.fill_bytes(&mut seed); - let mut prng = TranscriptRng::new(&seed); - let chi: Vec = (1..size).map(|_| C::Scalar::random(&mut prng)).collect(); - - let mut chi1 = C::Scalar::ZERO; - for ((t_i, _), &chi_i) in tv.iter().skip(1).zip(chi.iter()) { - chi1 += C::Scalar::conditional_select(&chi_i, &(-chi_i), *t_i); - } - chi1 = b - chi1; - chi1.conditional_assign(&(-chi1), tv[0].0); - //chi1.conditional_negate(tv[0].0); - - // Step 5 - let mut beta = chi1 * m.next().unwrap(); - for (&chi_i, m_i) in chi.iter().zip(m) { - beta += chi_i * m_i; - } - - // Step 6 - let wait1 = chan.next_waitpoint(); - let chi1: ScalarPrimitive = chi1.into(); - chan.send(wait1, &(chi1, seed)); - - Ok(beta) -} - -/// Run the multiplicative to additive protocol -#[allow(dead_code, clippy::type_complexity)] -fn run_mta( - (v, a): (Vec<(C::Scalar, C::Scalar)>, C::Scalar), - (tv, b): (Vec<(Choice, C::Scalar)>, C::Scalar), -) -> Result<(C::Scalar, C::Scalar), ProtocolError> { - let s = Participant::from(0u32); - let r = Participant::from(1u32); - let ctx_s = Comms::new(); - let ctx_r = Comms::new(); - - run_two_party_protocol( - s, - r, - &mut make_protocol( - ctx_s.clone(), - mta_sender::(ctx_s.private_channel(s, r), v, a), - ), - &mut make_protocol( - ctx_r.clone(), - mta_receiver::(ctx_r.private_channel(r, s), tv, b), - ), - ) -} - -#[cfg(test)] -mod test { - use ecdsa::elliptic_curve::{bigint::Bounded, Curve}; - use k256::{Scalar, Secp256k1}; - use rand_core::RngCore; - - use crate::constants::SECURITY_PARAMETER; - - use super::*; - - #[test] - fn test_mta() -> Result<(), ProtocolError> { - let batch_size = <::Uint as Bounded>::BITS + SECURITY_PARAMETER; - - let v: Vec<_> = (0..batch_size) - .map(|_| { - ( - Scalar::generate_biased(&mut OsRng), - Scalar::generate_biased(&mut OsRng), - ) - }) - .collect(); - let tv: Vec<_> = v - .iter() - .map(|(v0, v1)| { - let c = Choice::from((OsRng.next_u64() & 1) as u8); - (c, Scalar::conditional_select(v0, v1, c)) - }) - .collect(); - - let a = Scalar::generate_biased(&mut OsRng); - let b = Scalar::generate_biased(&mut OsRng); - let (alpha, beta) = run_mta::((v, a), (tv, b))?; - - assert_eq!(a * b, alpha + beta); - - Ok(()) - } -} diff --git a/src/eddsa/dkg_ed25519.rs b/src/eddsa/dkg_ed25519.rs index c634bcef..e4d6efe1 100644 --- a/src/eddsa/dkg_ed25519.rs +++ b/src/eddsa/dkg_ed25519.rs @@ -127,9 +127,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(()) } @@ -158,9 +158,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(()) } @@ -200,10 +200,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/eddsa/mod.rs b/src/eddsa/mod.rs index 372d3b1d..18b6a78a 100644 --- a/src/eddsa/mod.rs +++ b/src/eddsa/mod.rs @@ -1,5 +1,5 @@ -//! This module serves as a wrapper for Frost protocol. -use crate::generic_dkg::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; +//! This module serves as a wrapper for Ed25519 scheme. +use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; use frost_ed25519::keys::SigningShare; use frost_ed25519::{Ed25519Sha512, VerifyingKey}; diff --git a/src/eddsa/sign.rs b/src/eddsa/sign.rs index 0690a745..8b32221d 100644 --- a/src/eddsa/sign.rs +++ b/src/eddsa/sign.rs @@ -266,8 +266,8 @@ async fn fut_wrapper( } #[cfg(test)] -mod tests { - use crate::crypto::hash; +mod test { + use crate::crypto::hash::hash; use crate::participants::ParticipantList; use frost_core::{Field, Group}; use frost_ed25519::{Ed25519Group, Ed25519ScalarField, Ed25519Sha512, Signature}; @@ -475,7 +475,7 @@ mod tests { let p_list = ParticipantList::new(&participants).unwrap(); let mut x = Ed25519ScalarField::zero(); for (p, share) in participants.iter().zip(shares.iter()) { - x += p_list.generic_lagrange::(*p) * share; + x += p_list.lagrange::(*p) * share; } assert_eq!(::generator() * x, pub_key.to_element()); @@ -547,7 +547,7 @@ mod tests { let p_list = ParticipantList::new(&participants).unwrap(); let mut x = Ed25519ScalarField::zero(); for (p, share) in participants.iter().zip(shares.iter()) { - x += p_list.generic_lagrange::(*p) * share; + x += p_list.lagrange::(*p) * share; } assert_eq!(::generator() * x, pub_key.to_element()); diff --git a/src/eddsa/test.rs b/src/eddsa/test.rs index 33d9ae9d..302a2ae5 100644 --- a/src/eddsa/test.rs +++ b/src/eddsa/test.rs @@ -4,7 +4,7 @@ use crate::eddsa::KeygenOutput; use crate::participants::ParticipantList; use crate::protocol::{run_protocol, Participant, Protocol}; -use crate::crypto::HashOutput; +use crate::crypto::hash::HashOutput; use frost_ed25519::VerifyingKey; use rand_core::{OsRng, RngCore}; use std::error::Error; diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index 04641a5d..f29bdd38 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -1,8 +1,13 @@ -use crate::crypto::{hash, HashOutput}; -use crate::echo_broadcast::do_broadcast; +use crate::crypto::{ + ciphersuite::Ciphersuite, + hash::{domain_separate_hash, HashOutput}, + polynomials::{Polynomial, PolynomialCommitment}, +}; use crate::participants::{ParticipantCounter, ParticipantList, ParticipantMap}; -use crate::protocol::internal::SharedChannel; -use crate::protocol::{InitializationError, Participant, ProtocolError}; +use crate::protocol::{ + echo_broadcast::do_broadcast, internal::SharedChannel, InitializationError, Participant, + ProtocolError, +}; use frost_core::keys::{ CoefficientCommitment, SecretShare, SigningShare, VerifiableSecretSharingCommitment, @@ -11,20 +16,8 @@ use frost_core::{ Challenge, Element, Error, Field, Group, Scalar, Signature, SigningKey, VerifyingKey, }; use rand_core::{OsRng, RngCore}; -use serde::Serialize; use std::ops::Index; -pub enum BytesOrder { - BigEndian, - LittleEndian, -} - -pub trait ScalarSerializationFormat { - fn bytes_order() -> BytesOrder; -} - -pub trait Ciphersuite: frost_core::Ciphersuite + ScalarSerializationFormat {} - /// This function prevents calling keyshare function with inproper inputs fn assert_keyshare_inputs( me: Participant, @@ -60,46 +53,18 @@ fn assert_keyshare_inputs( } } -/// Hashes using a domain separator -/// The domain separator has to be manually incremented after the use of this function -fn domain_separate_hash(domain_separator: u32, data: &T) -> HashOutput { - let preimage = (domain_separator, data); - hash(&preimage) -} - -/// Creates a polynomial p of degree threshold - 1 -/// and sets p(0) = secret -fn generate_secret_polynomial( - secret: Scalar, - threshold: usize, - rng: &mut OsRng, -) -> Vec> { - let mut coefficients = Vec::with_capacity(threshold); - // insert the secret share - coefficients.push(secret); - for _ in 1..threshold { - coefficients.push(::Field::random(rng)); - } - coefficients -} - /// Creates a commitment vector of coefficients * G /// If the first coefficient is set to zero then skip it fn generate_coefficient_commitment( - secret_coefficients: &[Scalar], -) -> Vec> { + secret_coefficients: &Polynomial, +) -> Result, ProtocolError> { + let mut secret_coefficients = secret_coefficients.get_coefficients(); // we skip the zero share as neither zero scalar // nor identity group element are serializable - let coeff_iter = secret_coefficients - .iter() - .skip((secret_coefficients.first() == Some(&::Field::zero())) as usize); - - // Compute the multiplication of every coefficient of p with the generator G - let coefficient_commitment: Vec> = coeff_iter - .map(|c| CoefficientCommitment::new(::generator() * *c)) - .collect(); - - coefficient_commitment + if secret_coefficients.first() == Some(&::Field::zero()) { + secret_coefficients.remove(0); + }; + Ok(Polynomial::new(secret_coefficients)?.commit_polynomial()) } /// Generates the challenge for the proof of knowledge @@ -151,20 +116,20 @@ fn proof_of_knowledge( session_id: &HashOutput, domain_separator: u32, me: Participant, - coefficients: &[Scalar], - coefficient_commitment: &[CoefficientCommitment], + coefficients: &Polynomial, + coefficient_commitment: &PolynomialCommitment, rng: &mut OsRng, ) -> Result, ProtocolError> { // creates an identifier for the participant - let id = me.generic_scalar::(); - let vk_share = coefficient_commitment[0]; + let id = me.scalar::(); + let vk_share = coefficient_commitment.eval_on_zero(); // pick a random k_i and compute R_id = g^{k_id}, let (k, big_r) = ::generate_nonce(rng); // compute H(id, context_string, g^{a_0} , R_id) as a scalar let hash = challenge::(session_id, domain_separator, id, &vk_share, &big_r)?; - let a_0 = coefficients[0]; + let a_0 = coefficients.eval_on_zero().0; let mu = k + a_0 * hash.to_scalar(); Ok(Signature::new(big_r, mu)) } @@ -177,8 +142,8 @@ fn compute_proof_of_knowledge( domain_separator: u32, me: Participant, old_participants: Option, - coefficients: &[Scalar], - coefficient_commitment: &[CoefficientCommitment], + coefficients: &Polynomial, + coefficient_commitment: &PolynomialCommitment, rng: &mut OsRng, ) -> Result>, ProtocolError> { // I am allowed to send none only if I am a new participant @@ -207,7 +172,7 @@ fn internal_verify_proof_of_knowledge( proof_of_knowledge: &Signature, ) -> Result<(), ProtocolError> { // creates an identifier for the participant - let id = participant.generic_scalar::(); + let id = participant.scalar::(); let vk_share = commitment.coefficients().first().unwrap(); let big_r = proof_of_knowledge.R(); @@ -305,15 +270,6 @@ fn insert_identity_if_missing( commitment_i } -// evaluates a polynomial on the identifier of the participant -fn evaluate_polynomial( - coefficients: &[Scalar], - participant: Participant, -) -> Result, ProtocolError> { - let id = participant.to_identifier::(); - Ok(SigningShare::from_coefficients(coefficients, id)) -} - // creates a signing share structure using my identifier, the received // signing share and the received commitment fn validate_received_share( @@ -403,7 +359,7 @@ async fn do_keyshare( old_reshare_package: Option<(VerifyingKey, ParticipantList)>, mut rng: OsRng, ) -> Result, ProtocolError> { - let mut all_commitments = ParticipantMap::new(&participants); + let mut all_full_commitments = ParticipantMap::new(&participants); let mut domain_separator = 0; // Make sure you do not call do_keyshare with zero as secret on an old participant let (old_verification_key, old_participants) = @@ -411,7 +367,7 @@ async fn do_keyshare( // Start Round 0 let mut my_session_id = [0u8; 32]; // 256 bits - OsRng.fill_bytes(&mut my_session_id); + rng.fill_bytes(&mut my_session_id); let session_ids = do_broadcast(&mut chan, &participants, &me, my_session_id).await?; // Start Round 1 @@ -421,10 +377,13 @@ async fn do_keyshare( // this function does not add the zero coefficient let session_id = domain_separate_hash(domain_separator, &session_ids); domain_separator += 1; - let secret_coefficients = generate_secret_polynomial::(secret, threshold, &mut rng); + // the degree of the polynomial is threshold - 1 + let secret_coefficients = + Polynomial::::generate_polynomial(Some(secret), threshold - 1, &mut rng)?; // Compute the multiplication of every coefficient of p with the generator G - let coefficient_commitment = generate_coefficient_commitment::(&secret_coefficients); + let coefficient_commitment = generate_coefficient_commitment::(&secret_coefficients) + .expect("expects non constant polynomial (i.e. threshold strictly larger than 1)"); // generate a proof of knowledge if the participant me is not holding a secret that is zero let proof_domain_separator = domain_separator; let proof_of_knowledge = compute_proof_of_knowledge( @@ -439,7 +398,8 @@ async fn do_keyshare( domain_separator += 1; // Create the public polynomial = secret coefficients times G - let commitment = VerifiableSecretSharingCommitment::new(coefficient_commitment); + let commitment = + VerifiableSecretSharingCommitment::new(coefficient_commitment.get_coefficients()); // hash commitment and send it let commit_domain_separator = domain_separator; @@ -447,18 +407,24 @@ async fn do_keyshare( let wait_round_1 = chan.next_waitpoint(); chan.send_many(wait_round_1, &commitment_hash); // receive commitment_hash + let mut seen = ParticipantCounter::new(&participants); let mut all_hash_commitments = ParticipantMap::new(&participants); all_hash_commitments.put(me, commitment_hash); - while !all_hash_commitments.full() { + seen.put(me); + while !seen.full() { let (from, their_commitment_hash) = chan.recv(wait_round_1).await?; + if !seen.put(from) { + continue; + } all_hash_commitments.put(from, their_commitment_hash); } // Start Round 2 - // add my commitment and proof to the map - all_commitments.put(me, commitment.clone()); + // add my commitment to the map with the proper commitment sizes = threshold + let my_full_commitment = insert_identity_if_missing(threshold, &commitment); + all_full_commitments.put(me, my_full_commitment); - // Broadcast to all the commitment and the proof of knowledge + // Broadcast the commitment and the proof of knowledge let commitments_and_proofs_map = do_broadcast( &mut chan, &participants, @@ -494,27 +460,44 @@ async fn do_keyshare( &all_hash_commitments, )?; - // add received commitment and proof to the map - all_commitments.put(p, commitment_i.clone()); + // in case the participant was new and it sent a polynomial of length + // threshold -1 (because the zero term is not serializable) + let full_commitment_i = insert_identity_if_missing(threshold, commitment_i); + + // add received full commitment + all_full_commitments.put(p, full_commitment_i); + } + + // Verify vk asap + // cannot fail as all_commitments at least contains my commitment + let all_commitments_refs = all_full_commitments.to_refs_or_none().unwrap(); + let verifying_key = public_key_from_commitments(all_commitments_refs)?; + + // In the case of Resharing, check if the old public key is the same as the new one + if let Some(old_vk) = old_verification_key { + // check the equality between the old key and the new key without failing the unwrap + if old_vk != verifying_key { + return Err(ProtocolError::AssertionFailed( + "new public key does not match old public key".to_string(), + )); + } + }; - // Securely send to each other participant a secret share + for p in participants.others(me) { + // securely send to each other participant a secret share // using the evaluation secret polynomial on the identifier of the recipient - let signing_share_to_p = evaluate_polynomial::(&secret_coefficients, p)?; + // should not panic as secret_coefficients are created internally + let signing_share_to_p = secret_coefficients.eval_on_participant(p); // send the evaluation privately to participant p chan.send_private(wait_round_3, p, &signing_share_to_p); } - // compute the my secret evaluation of my private polynomial - let mut my_signing_share = evaluate_polynomial::(&secret_coefficients, me)?.to_scalar(); - - // recreate the commitments map with the proper commitment sizes = threshold - let mut all_full_commitments = ParticipantMap::new(&participants); - let my_full_commitment = insert_identity_if_missing(threshold, all_commitments.index(me)); - all_full_commitments.put(me, my_full_commitment); - // Start Round 4 + // compute my secret evaluation of my private polynomial + // should not panic as secret_coefficients are created internally + let mut my_signing_share = secret_coefficients.eval_on_participant(me).0; // receive evaluations from all participants - let mut seen = ParticipantCounter::new(&participants); + seen.clear(); seen.put(me); while !seen.full() { let (from, signing_share_from): (Participant, SigningShare) = @@ -523,46 +506,20 @@ async fn do_keyshare( continue; } - let commitment_from = all_commitments.index(from); - - // in case the participant was new and it sent a polynomial of length - // threshold -1 (because the zero term is not serializable) - let full_commitment_from = insert_identity_if_missing(threshold, commitment_from); - // Verify the share // this deviates from the original FROST DKG paper // however it matches the FROST implementation of ZCash - validate_received_share::(&me, &from, &signing_share_from, &full_commitment_from)?; - - // add full commitment - all_full_commitments.put(from, full_commitment_from); + let full_commitment_from = all_full_commitments.index(from); + validate_received_share::(&me, &from, &signing_share_from, full_commitment_from)?; // Compute the sum of all the owned secret shares // At the end of this loop, I will be owning a valid secret signing share my_signing_share = my_signing_share + signing_share_from.to_scalar(); } - // cannot fail as all_commitments at least contains my commitment - let all_commitments_vec = all_full_commitments.into_vec_or_none().unwrap(); - let all_commitments_refs = all_commitments_vec.iter().collect(); - - let verifying_key = public_key_from_commitments(all_commitments_refs)?; - - // In the case of Resharing, check if the old public key is the same as the new one - if let Some(old_vk) = old_verification_key { - // check the equality between the old key and the new key without failing the unwrap - if old_vk != verifying_key { - return Err(ProtocolError::AssertionFailed( - "new public key does not match old public key".to_string(), - )); - } - }; - - // Start Round 5 broadcast_success(&mut chan, &participants, &me, session_id).await?; - // will never panic as broadcast_success_failure would panic before it - // unwrap cannot fail as round 4 ensures failing if verification_key is None + // Return the key pair Ok(KeygenOutput { private_share: SigningShare::new(my_signing_share), public_key: verifying_key, @@ -645,7 +602,7 @@ pub(crate) async fn do_reshare( let intersection = old_participants.intersection(&participants); // either extract the share and linearize it or set it to zero let secret = old_signing_key - .map(|x_i| intersection.generic_lagrange::(me) * x_i.to_scalar()) + .map(|x_i| intersection.lagrange::(me) * x_i.to_scalar()) .unwrap_or(::Field::zero()); let old_reshare_package = Some((old_public_key, old_participants)); diff --git a/src/lib.rs b/src/lib.rs index a2011850..65bd9847 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,138 +1,9 @@ -//! Cait-Sith is a novel threshold ECDSA protocol (and implementation), -//! which is both simpler and substantially more performant than -//! popular alternatives. -//! -//! The protocol supports arbitrary numbers of parties and thresholds. -//! -//! # Warning -//! -//! This is experimental cryptographic software, unless you're a cat with -//! a megaphone on top of a giant Moogle I would exercise caution. -//! -//! - The protocol does not have a formal proof of security. -//! - This library has not undergone any form of audit. -//! -//! # Design -//! -//! The main design principle of Cait-Sith is offloading as much work -//! to a key-independent preprocessing phase as possible. -//! The advantage of this approach is that this preprocessing phase can be conducted -//! in advance, before a signature is needed, and the results of this phase -//! can even be peformed before the key that you need to sign with is decided. -//! -//! One potential scenario where this is useful is when running a threshold -//! custody service over many keys, where these preprocessing results -//! can be performed, and then used on demand regardless of which keys -//! end up being used more often. -//! -//! A detailed specification is available [in this repo](./docs), -//! but we'll also give a bit of detail here. -//! -//! The core of Cait-Sith's design involves a *committed* Beaver triple. -//! These are of the form: -//! ```ignore -//! ([a], [b], [c]), (A = a * G, B = b * G, C = c * G) -//! ``` -//! where `a, b, c` are scalars such that `a * b = c`, and are -//! secret shared among several participants, so that no one knows their actual value. -//! Furthermore, unlike standard Beaver triples, we also have a public commitment -//! to the these secret values, which helps the online protocol. -//! -//! The flow of the protocol is first that the parties need a way to generate triples: -//! -//! - A setup protocol is run once, allowing parties to efficiently generate triples. -//! - The parties can now generate an arbitrary number triples through a distributed protocol. -//! -//! Then, the parties need to generate a key pair so that they can sign messages: -//! -//! - The parties run a distributed key generation protocol to setup a new key pair, -//! which can be used for many signatures. -//! -//! When the parties want to sign using a given key: -//! -//! - Using their shares of a private key, the parties can create a *presignature*, -//! before knowing the message to sign. -//! - Once they know this message, they can use the presignature to create a complete signature. -//! -//! It's important that presignatures and triples are **never** reused. -//! -//! ## API Design -//! -//! Internally, the API tries to be as simple as possible abstracting away -//! as many details as possible into a simple interface. -//! -//! This interface just has two methods: -//! ```ignore -//! pub trait Protocol { -//! type Output; -//! -//! fn poke(&mut self) -> Result, ProtocolError>; -//! fn message(&mut self, from: Participant, data: MessageData); -//! } -//! ``` -//! Given an instance of this trait, which represents a single party -//! participating in a protocol, you can do two things: -//! - You can provide a new message received from some other party. -//! - You can "poke" the protocol to see if it has some kind of action it wants you to perform, or if an error happened. -//! -//! This action is either: -//! - The protocol telling you it has finished, with a return value of type `Output`. -//! - The protocol asking you to send a message to all other parties. -//! - The protocol asking you to *privately* send a message to one party. -//! - The protocol informing you that no more progress can be made until it receives new messages. -//! -//! In particular, details about rounds and message serialization are abstracted -//! away, and all performed internally. -//! In fact, the protocols aren't designed around "rounds", and can even have parallel -//! threads of execution internally for some of the more complicated ones. -//! # Generic Curves -//! -//! The library has support for generic curves and hashes. -//! -//! The support for generic curves is done through a custom `CSCurve` trait, -//! which can be easily implemented for any curve from the -//! RustCrypto [elliptic-curves](https://github.com/RustCrypto/elliptic-curves) -//! suite of libraries. -//! -//! This crate also provides implementations of some existing curves behind features, -//! as per the following table: -//! -//! | Curve | Feature | -//! |-------|---------| -//! |Secp256k1|`k256`| -//! -//! For supporting any message hash, the API requires the user to supply -//! the hash of a message when signing as a scalar directly. -//! -//! # Shortcomings -//! -//! The protocol and its implementation do have a few known disadvantages at the moment: -//! -//! - The protocol does require generating triples in advance, but these can be generated without knowledge of the private key. -//! - The protocol does not attempt to provide identifiable aborts. -//! -//! We also don't really intend to add identifiable aborts to Cait-Sith itself. -//! While these can be desirable in certain situations, we aren't satisfied -//! with the way the property of identifiable aborts is modeled currently, -//! and are working on improvements to this model. -mod compat; -mod constants; mod crypto; -mod echo_broadcast; - mod generic_dkg; mod participants; -mod proofs; pub mod protocol; -mod serde; - -pub use compat::CSCurve; pub mod ecdsa; pub mod eddsa; - -pub use frost_core; -pub use frost_ed25519; -pub use frost_secp256k1; diff --git a/src/participants.rs b/src/participants.rs index 46a212d0..de563d8a 100644 --- a/src/participants.rs +++ b/src/participants.rs @@ -6,11 +6,11 @@ use std::{collections::HashMap, mem, ops::Index}; -use frost_core::{Group, Scalar}; +use frost_core::Scalar; use serde::Serialize; -use crate::generic_dkg::Ciphersuite; -use crate::{compat::CSCurve, protocol::Participant}; +use crate::crypto::{ciphersuite::Ciphersuite, polynomials::compute_lagrange_coefficient}; +use crate::protocol::Participant; /// Represents a sorted list of participants. /// @@ -83,44 +83,18 @@ impl ParticipantList { } /// Get the lagrange coefficient for a participant, relative to this list. - /// Use cait-sith library curve type - pub fn lagrange(&self, p: Participant) -> C::Scalar { - use elliptic_curve::Field; - - let p_scalar = p.scalar::(); - - let mut top = C::Scalar::ONE; - let mut bot = C::Scalar::ONE; - for q in &self.participants { - if p == *q { - continue; - } - let q_scalar = q.scalar::(); - top *= q_scalar; - bot *= q_scalar - p_scalar; - } - - top * bot.invert().unwrap() - } - - /// Get the lagrange coefficient for a participant, relative to this list. + /// The lagrange coefficient are evaluated to zero /// Use generic frost library types - pub fn generic_lagrange(&self, p: Participant) -> Scalar { - use frost_core::Field; - let p_scalar = p.generic_scalar::(); - - let mut top = ::Field::one(); - let mut bot = ::Field::one(); - for q in &self.participants { - if p == *q { - continue; - } - let q_scalar = q.generic_scalar::(); - top = top * q_scalar; - bot = bot * (q_scalar - p_scalar); - } - let inverted = ::Field::invert(&bot).unwrap(); - top * inverted + pub fn lagrange(&self, p: Participant) -> Scalar { + let p = p.scalar::(); + let identifiers: Vec> = self + .participants() + .iter() + .map(|p| p.scalar::()) + .collect(); + compute_lagrange_coefficient::(&identifiers, &p, None) + .unwrap() + .0 } /// Return the intersection of this list with another list. @@ -136,8 +110,8 @@ impl ParticipantList { } // Returns all the participants in the list - pub fn participants(&self) -> Vec { - self.participants.clone() + pub fn participants(&self) -> &[Participant] { + self.participants.as_slice() } } @@ -204,16 +178,22 @@ impl<'a, T> ParticipantMap<'a, T> { // Consumes the Map returning only the vector of the unwrapped data // If one of the data is still none, then return None pub fn into_vec_or_none(self) -> Option> { - let mut vec_data: Vec = Vec::new(); - for d in self.data { - let data = d?; - vec_data.push(data) - } - Some(vec_data) + self.data.into_iter().collect() + } + + // Does not consume the map returning only the vector of the unwrapped data + // If one of the data is still none, then return None + pub fn to_refs_or_none(&self) -> Option> { + self.data.iter().map(|opt| opt.as_ref()).collect() + } + + // Returns the set of included participants + pub fn participants(&self) -> &[Participant] { + self.participants.participants() } } -impl<'a, T> Index for ParticipantMap<'a, T> { +impl Index for ParticipantMap<'_, T> { type Output = T; fn index(&self, index: Participant) -> &Self::Output { diff --git a/src/proofs/dlog.rs b/src/proofs/dlog.rs deleted file mode 100644 index 345667e3..00000000 --- a/src/proofs/dlog.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::{ - compat::{CSCurve, SerializablePoint}, - proofs::strobe_transcript::Transcript, - serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, -}; -use elliptic_curve::{Field, Group}; -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; - -/// 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(Debug, Clone, Copy, Serialize)] -pub struct Statement<'a, C: CSCurve> { - #[serde(serialize_with = "serialize_projective_point::")] - pub public: &'a C::ProjectivePoint, -} - -impl<'a, C: CSCurve> Statement<'a, C> { - /// Calculate the homomorphism we want to prove things about. - fn phi(&self, x: &C::Scalar) -> C::ProjectivePoint { - C::ProjectivePoint::generator() * x - } -} - -/// The private witness for this proof. -/// -/// This holds the scalar the prover needs to know. -#[derive(Clone, Copy)] -pub struct Witness<'a, C: CSCurve> { - pub x: &'a C::Scalar, -} - -/// Represents a proof of the statement. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Proof { - #[serde( - serialize_with = "serialize_scalar::", - deserialize_with = "deserialize_scalar::" - )] - e: C::Scalar, - #[serde( - serialize_with = "serialize_scalar::", - deserialize_with = "deserialize_scalar::" - )] - s: C::Scalar, -} - -/// 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<'a, C: CSCurve>( - rng: &mut impl CryptoRngCore, - transcript: &mut Transcript, - statement: Statement<'a, C>, - witness: Witness<'a, C>, -) -> Proof { - transcript.message(STATEMENT_LABEL, &encode(&statement)); - - let k = C::Scalar::random(rng); - let big_k = statement.phi(&k); - - transcript.message( - COMMITMENT_LABEL, - &encode(&SerializablePoint::::from_projective(&big_k)), - ); - let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); - let e = C::Scalar::random(&mut rng); - - let s = k + e * witness.x; - Proof { e, 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 { - let statement_data = encode(&statement); - transcript.message(STATEMENT_LABEL, &statement_data); - - let big_k: C::ProjectivePoint = statement.phi(&proof.s) - *statement.public * proof.e; - - transcript.message( - COMMITMENT_LABEL, - &encode(&SerializablePoint::::from_projective(&big_k)), - ); - let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); - let e = C::Scalar::random(&mut rng); - - e == proof.e -} - -#[cfg(test)] -mod test { - use rand_core::OsRng; - - use super::*; - use k256::{ProjectivePoint, Scalar, Secp256k1}; - - #[test] - fn test_valid_proof_verifies() { - let x = Scalar::generate_biased(&mut OsRng); - - let statement = Statement:: { - public: &(ProjectivePoint::GENERATOR * x), - }; - let witness = Witness { x: &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/dlogeq.rs b/src/proofs/dlogeq.rs deleted file mode 100644 index 360d102c..00000000 --- a/src/proofs/dlogeq.rs +++ /dev/null @@ -1,153 +0,0 @@ -use elliptic_curve::{Field, Group}; -use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; - -use crate::{ - compat::{CSCurve, SerializablePoint}, - proofs::strobe_transcript::Transcript, - serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, -}; - -/// 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(Debug, Clone, Copy, Serialize)] -pub struct Statement<'a, C: CSCurve> { - #[serde(serialize_with = "serialize_projective_point::")] - pub public0: &'a C::ProjectivePoint, - #[serde(serialize_with = "serialize_projective_point::")] - pub generator1: &'a C::ProjectivePoint, - #[serde(serialize_with = "serialize_projective_point::")] - pub public1: &'a C::ProjectivePoint, -} - -impl<'a, C: CSCurve> Statement<'a, C> { - /// Calculate the homomorphism we want to prove things about. - fn phi(&self, x: &C::Scalar) -> (C::ProjectivePoint, C::ProjectivePoint) { - (C::ProjectivePoint::generator() * x, *self.generator1 * x) - } -} - -/// The private witness for this proof. -/// -/// This holds the scalar the prover needs to know. -#[derive(Clone, Copy)] -pub struct Witness<'a, C: CSCurve> { - pub x: &'a C::Scalar, -} - -/// Represents a proof of the statement. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Proof { - #[serde( - serialize_with = "serialize_scalar::", - deserialize_with = "deserialize_scalar::" - )] - e: C::Scalar, - #[serde( - serialize_with = "serialize_scalar::", - deserialize_with = "deserialize_scalar::" - )] - s: C::Scalar, -} - -/// 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<'a, C: CSCurve>( - rng: &mut impl CryptoRngCore, - transcript: &mut Transcript, - statement: Statement<'a, C>, - witness: Witness<'a, C>, -) -> Proof { - transcript.message(STATEMENT_LABEL, &encode(&statement)); - - let k = C::Scalar::random(rng); - let big_k = statement.phi(&k); - - transcript.message( - COMMITMENT_LABEL, - &encode(&( - SerializablePoint::::from_projective(&big_k.0), - SerializablePoint::::from_projective(&big_k.1), - )), - ); - let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); - let e = C::Scalar::random(&mut rng); - - let s = k + e * witness.x; - Proof { e, 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 { - let statement_data = encode(&statement); - transcript.message(STATEMENT_LABEL, &statement_data); - - let (phi0, phi1) = statement.phi(&proof.s); - let big_k0 = phi0 - *statement.public0 * proof.e; - let big_k1 = phi1 - *statement.public1 * proof.e; - - transcript.message( - COMMITMENT_LABEL, - &encode(&( - SerializablePoint::::from_projective(&big_k0), - SerializablePoint::::from_projective(&big_k1), - )), - ); - let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); - let e = C::Scalar::random(&mut rng); - - e == proof.e -} - -#[cfg(test)] -mod test { - use rand_core::OsRng; - - use super::*; - - use k256::{ProjectivePoint, Scalar, Secp256k1}; - - #[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: &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/echo_broadcast.rs b/src/protocol/echo_broadcast.rs similarity index 100% rename from src/echo_broadcast.rs rename to src/protocol/echo_broadcast.rs diff --git a/src/protocol/internal.rs b/src/protocol/internal.rs index bf2b8921..465ec0f3 100644 --- a/src/protocol/internal.rs +++ b/src/protocol/internal.rs @@ -42,7 +42,6 @@ //! This is why we have to take great care that the identifiers a protocol will produce //! are deterministic, even in the presence of concurrent tasks. use super::{Action, MessageData, Participant, Protocol, ProtocolError}; -use crate::serde::{decode, encode_with_tag}; use futures::future::BoxFuture; use futures::task::noop_waker; use futures::{FutureExt, StreamExt}; @@ -56,6 +55,15 @@ use std::{collections::HashMap, error, future::Future, sync::Arc}; /// The domain for our use of sha here. const DOMAIN: &[u8] = b"Near threshold signatures channel tags"; +/// Encode an arbitrary serializable with a tag. +fn encode_with_tag(tag: &[u8], val: &T) -> Vec { + // Matches rmp_serde's internal default. + let mut out = Vec::with_capacity(128); + out.extend_from_slice(tag); + rmp_serde::encode::write(&mut out, val).expect("failed to encode value"); + out +} + /// Represents a unique tag for a channel. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)] struct ChannelTag([u8; Self::SIZE]); @@ -314,7 +322,7 @@ impl Comms { ) -> Result<(Participant, T), ProtocolError> { let (from, data) = self.incoming.pop(header).await; let decoded: Result> = - decode(&data[MessageHeader::LEN..]).map_err(|e| e.into()); + rmp_serde::decode::from_slice(&data[MessageHeader::LEN..]).map_err(|e| e.into()); Ok((from, decoded?)) } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index ea7beff4..23a0ef2c 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -7,10 +7,9 @@ //! to serialize the emssages it produces. use std::{collections::HashMap, error, fmt}; -use crate::compat::CSCurve; use ::serde::{Deserialize, Serialize}; -use crate::generic_dkg::{BytesOrder, Ciphersuite}; +use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite}; use frost_core::serialization::SerializableScalar; use frost_core::{Identifier, Scalar}; @@ -21,10 +20,18 @@ pub enum ProtocolError { AssertionFailed(String), /// The ciphersuite does not support DKG. DKGNotSupported, + /// When a Polynomial or PolynomialCommitment is Empty + EmptyOrZeroCoefficients, /// Could not extract the verification Key from a commitment. ErrorExtractVerificationKey, + /// Error in reducing bytes to scalar + ErrorReducingBytesToScalar, + /// Encounter the Identity EC point when not supposed to + IdentityElement, /// The sent commitment hash does not equal the hash of the sent commitment InvalidCommitmentHash, + /// The number of arguments are not valid for the polynomial interpolation + InvalidInterpolationArguments, /// Incorrect number of commitments. IncorrectNumberOfCommitments, /// The identifier of the signer whose share validation failed. @@ -39,6 +46,8 @@ pub enum ProtocolError { MalformedSigningKey, /// Error in serializing point PointSerialization, + /// Encounter Zero Scalar when not supposed to + ZeroScalar, /// Some generic error happened. Other(Box), } @@ -49,16 +58,32 @@ impl fmt::Display for ProtocolError { ProtocolError::Other(e) => write!(f, "{e}"), ProtocolError::AssertionFailed(e) => write!(f, "assertion failed {e}"), ProtocolError::DKGNotSupported => write!(f, "the ciphersuite does not support DKG"), + ProtocolError::EmptyOrZeroCoefficients => { + write!(f, "Found empty polynomials or zero polynomial.") + } ProtocolError::ErrorExtractVerificationKey => write!( f, "could not extract the verification Key from the commitment." ), + ProtocolError::ErrorReducingBytesToScalar => write!( + f, + "the given bytes are not mappable to a scalar without modular reduction." + ), + ProtocolError::IdentityElement => { + write!(f, "encoutered the identity element (identity point).") + } ProtocolError::InvalidCommitmentHash => { write!( f, "the sent commitment_hash does not equals the hash of the commitment" ) } + ProtocolError::InvalidInterpolationArguments => { + write!( + f, + "the provided elements are invalid for polynomial interpolation" + ) + } ProtocolError::IncorrectNumberOfCommitments => { write!(f, "incorrect number of commitments") } @@ -76,6 +101,7 @@ impl fmt::Display for ProtocolError { write!(f, "detected a malicious participant {p:?}.") } ProtocolError::MalformedSigningKey => write!(f, "the constructed signing key is null."), + ProtocolError::ZeroScalar => write!(f, "encountered a zero scalar."), ProtocolError::PointSerialization => { write!(f, "The group element could not be serialized.") } @@ -127,13 +153,7 @@ impl Participant { } /// Return the scalar associated with this participant. - /// The implementation follows the original cait-sith library - pub fn scalar(&self) -> C::Scalar { - C::Scalar::from(self.0 as u64 + 1) - } - - /// Return the scalar associated with this participant. - pub fn generic_scalar(&self) -> Scalar { + pub fn scalar(&self) -> Scalar { let mut bytes = [0u8; 32]; let id = (self.0 as u64) + 1; @@ -150,7 +170,7 @@ impl Participant { /// Returns a Frost identifier used in the frost library pub fn to_identifier(&self) -> Identifier { - let id = self.generic_scalar::(); + let id = self.scalar::(); // creating an identifier as required by the syntax of frost_core // cannot panic as the previous line ensures id is neq zero Identifier::new(id).unwrap() @@ -315,4 +335,5 @@ pub(crate) fn run_two_party_protocol( Ok((out0.unwrap(), out1.unwrap())) } +pub mod echo_broadcast; pub(crate) mod internal; diff --git a/src/serde.rs b/src/serde.rs deleted file mode 100644 index 16d1ba8a..00000000 --- a/src/serde.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::io::Write; - -use crate::compat::{CSCurve, SerializablePoint}; -use ecdsa::elliptic_curve::ScalarPrimitive; -use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; - -/// Encode an arbitrary serializable value into a vec. -pub fn encode(val: &T) -> Vec { - rmp_serde::encode::to_vec(val).expect("failed to encode value") -} - -/// Encode an arbitrary serializable value into a writer. -pub fn encode_writer(w: &mut W, val: &T) { - rmp_serde::encode::write(w, val).expect("failed to encode value"); -} - -/// Encode an arbitrary serializable with a tag. -pub fn encode_with_tag(tag: &[u8], val: &T) -> Vec { - // Matches rmp_serde's internal default. - let mut out = Vec::with_capacity(128); - out.extend_from_slice(tag); - rmp_serde::encode::write(&mut out, val).expect("failed to encode value"); - out -} - -/// Serialize a list of projective points. -pub fn serialize_projective_points( - data: &[C::ProjectivePoint], - serializer: S, -) -> Result { - serializer.collect_seq(data.iter().map(SerializablePoint::::from_projective)) -} - -/// Deserialize projective points. -pub fn deserialize_projective_points<'de, C, D>( - deserializer: D, -) -> Result, D::Error> -where - C: CSCurve, - D: Deserializer<'de>, -{ - let points: Vec> = Deserialize::deserialize(deserializer)?; - Ok(points.into_iter().map(|p| p.to_projective()).collect()) -} - -/// Serialize a single projective point. -pub fn serialize_projective_point( - data: &C::ProjectivePoint, - serializer: S, -) -> Result { - SerializablePoint::::from_projective(data).serialize(serializer) -} - -/// Serialize an arbitrary scalar. -pub fn serialize_scalar( - data: &C::Scalar, - serializer: S, -) -> Result { - let data: ScalarPrimitive = (*data).into(); - data.serialize(serializer) -} - -/// Deserialize an arbitrary scalar. -pub fn deserialize_scalar<'de, C, D>(deserializer: D) -> Result -where - C: CSCurve, - D: Deserializer<'de>, -{ - let out: ScalarPrimitive = ScalarPrimitive::deserialize(deserializer)?; - Ok(out.into()) -} - -/// Decode an arbitrary value from a slice of bytes. -pub fn decode(input: &[u8]) -> Result { - rmp_serde::decode::from_slice(input) -}