From 5b56e18f2f8edfcd378d9cd150f06e02f2bc919c Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Sun, 27 Apr 2025 21:02:27 +0200 Subject: [PATCH 01/52] Getting rid of meow hash -- still the meow Rng togo --- Cargo.lock | 13 ++++- Cargo.toml | 3 +- src/crypto.rs | 60 +++++++----------------- src/ecdsa/triples/batch_random_ot.rs | 23 +++++---- src/ecdsa/triples/bits.rs | 28 ++++++----- src/ecdsa/triples/generation.rs | 6 +-- src/ecdsa/triples/multiplication.rs | 6 +-- src/ecdsa/triples/random_ot_extension.rs | 16 ++++--- src/eddsa/test.rs | 4 +- src/generic_dkg.rs | 20 ++++---- src/protocol/internal.rs | 48 ++++++++++--------- 11 files changed, 111 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b95578..d45f074 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,7 +286,6 @@ name = "cait-sith" version = "0.8.0" dependencies = [ "auto_ops", - "ck-meow", "criterion", "digest", "easy-parallel", @@ -305,6 +304,8 @@ dependencies = [ "rand_core 0.6.4", "rmp-serde", "serde", + "sha2", + "sha3", "smol", "structopt", "subtle", @@ -1666,6 +1667,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" diff --git a/Cargo.toml b/Cargo.toml index a03b517..72b36a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ license = "MIT" [dependencies] auto_ops = "0.3.0" -ck-meow = "0.1.0" digest = "0.10.7" ecdsa = { version = "0.16.8", features = ["digest", "hazmat"] } elliptic-curve = { version = "0.13.5", features = ["serde"] } @@ -24,6 +23,8 @@ rand = "0.9.0" rand_core = { version = "0.6.4", features = ["getrandom"] } rmp-serde = "1.1.2" serde = { version = "1.0.175", features = ["derive"] } +sha2 = "0.10.8" +sha3 = "0.10.8" smol = "1.3.0" subtle = "2.5.0" diff --git a/src/crypto.rs b/src/crypto.rs index c27fd5a..3b7b45f 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,36 +1,16 @@ -use std::io::Write; +use sha2::{Sha256, Digest}; -use ck_meow::Meow; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use crate::serde::encode_writer; -const COMMIT_LABEL: &[u8] = b"cait-sith v0.8.0 commitment"; +const COMMIT_LABEL: &[u8] = b"near threshold signature commitment"; const COMMIT_LEN: usize = 32; const RANDOMIZER_LEN: usize = 32; -const HASH_LABEL: &[u8] = b"cait-sith v0.8.0 generic hash"; +const HASH_LABEL: &[u8] = b"near threshold signature generic hash"; const HASH_LEN: usize = 32; -struct MeowWriter<'a>(&'a mut Meow); - -impl<'a> MeowWriter<'a> { - fn init(meow: &'a mut Meow) -> Self { - meow.ad(&[], false); - Self(meow) - } -} - -impl Write for MeowWriter<'_> { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.0.ad(buf, true); - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} /// Represents the randomizer used to make a commit hiding. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -61,16 +41,12 @@ pub struct Commitment([u8; COMMIT_LEN]); impl Commitment { fn compute(val: &T, r: &Randomizer) -> Self { - let mut meow = Meow::new(COMMIT_LABEL); - - meow.ad(r.as_ref(), false); - meow.meta_ad(b"start data", false); - encode_writer(&mut MeowWriter::init(&mut meow), val); - - let mut out = [0u8; COMMIT_LEN]; - meow.prf(&mut out, false); - - Commitment(out) + 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. @@ -94,23 +70,21 @@ pub fn commit(rng: &mut R, val: &T) -> (Commitme (c, r) } + /// The output of a generic hash function. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct Digest([u8; HASH_LEN]); +pub struct HashOutput([u8; HASH_LEN]); -impl AsRef<[u8]> for Digest { +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) -> Digest { - let mut meow = Meow::new(HASH_LABEL); - encode_writer(&mut MeowWriter::init(&mut meow), val); - - let mut out = [0u8; HASH_LEN]; - meow.prf(&mut out, false); - - Digest(out) +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/ecdsa/triples/batch_random_ot.rs b/src/ecdsa/triples/batch_random_ot.rs index 338f9ef..5799998 100644 --- a/src/ecdsa/triples/batch_random_ot.rs +++ b/src/ecdsa/triples/batch_random_ot.rs @@ -1,8 +1,8 @@ -use ck_meow::Meow; use elliptic_curve::{Field, Group}; use rand_core::OsRng; use smol::stream::{self, StreamExt}; use std::sync::Arc; +use sha2::{Sha256, Digest}; use subtle::ConditionallySelectable; use crate::{ @@ -17,7 +17,7 @@ use crate::{ use super::bits::{BitMatrix, BitVector, SquareBitMatrix, SEC_PARAM_8}; -const BATCH_RANDOM_OT_HASH: &[u8] = b"cait-sith v0.8.0 batch ROT"; +const BATCH_RANDOM_OT_HASH: &[u8] = b"NEAR threshold signatures batch ROT"; fn hash( i: usize, @@ -25,14 +25,17 @@ fn hash( big_y: &SerializablePoint, p: &C::ProjectivePoint, ) -> BitVector { - let mut meow = Meow::new(BATCH_RANDOM_OT_HASH); - meow.ad(&(i as u64).to_le_bytes(), false); - meow.ad(&encode(&big_x_i), false); - meow.ad(&encode(&big_y), false); - meow.ad(&encode(&SerializablePoint::::from_projective(p)), false); - - let mut bytes = [0u8; SEC_PARAM_8]; - meow.prf(&mut bytes, false); + 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))); + + 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) } diff --git a/src/ecdsa/triples/bits.rs b/src/ecdsa/triples/bits.rs index 02cb72d..2897635 100644 --- a/src/ecdsa/triples/bits.rs +++ b/src/ecdsa/triples/bits.rs @@ -1,8 +1,8 @@ use auto_ops::impl_op_ex; -use ck_meow::Meow; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use sha3::{Shake256, digest::{Update, ExtendableOutput, XofReader}}; use crate::constants::SECURITY_PARAMETER; @@ -197,7 +197,7 @@ impl_op_ex!(^ |u: &DoubleBitVector, v: &DoubleBitVector| -> DoubleBitVector { u. impl_op_ex!(^= |u: &mut DoubleBitVector, v: &DoubleBitVector| { u.xor_mut(v) }); /// The context string for our PRG. -const PRG_CTX: &[u8] = b"cait-sith v0.8.0 correlated OT PRG"; +const PRG_CTX: &[u8] = b"near one threshold signatures correlated OT PRG"; /// Represents a matrix of bits. /// @@ -303,32 +303,34 @@ impl SquareBitMatrix { pub fn expand_transpose(&self, sid: &[u8], rows: usize) -> BitMatrix { assert!(rows % SECURITY_PARAMETER == 0); - let mut meow = Meow::new(PRG_CTX); - meow.meta_ad(b"sid", false); - meow.ad(sid, false); + let mut hasher = Shake256::default(); + hasher.update(PRG_CTX); + hasher.update(b"sid"); + hasher.update(sid); let mut out = BitMatrix(vec![BitVector::zero(); rows]); // How many bytes to get rows bits? let row8 = (rows + 7) / 8; + hasher.update(b"row"); + for (j, row) in self.matrix.0.iter().enumerate() { - // Expand the row - let mut expanded = vec![0u8; row8]; // We need to clone to make each row use the same prefix. - let mut meow = meow.clone(); - meow.meta_ad(b"row", false); - meow.ad(b"", false); + let mut hasher_row = hasher.clone(); for u in row.0 { - meow.ad(&u.to_le_bytes(), true); + hasher_row.update(&u.to_le_bytes()); } - meow.prf(&mut expanded, false); + let hasher = hasher.clone(); + let mut reader = hasher.finalize_xof(); + // Expand the row + let mut expanded = vec![0u8; row8]; + reader.read(&mut expanded); // Now, write into the correct column for i in 0..rows { out.0[i].0[j / 64] |= u64::from((expanded[i / 8] >> (i % 8)) & 1) << (j % 64); } } - out } } diff --git a/src/ecdsa/triples/generation.rs b/src/ecdsa/triples/generation.rs index 6acbfc9..dde6149 100644 --- a/src/ecdsa/triples/generation.rs +++ b/src/ecdsa/triples/generation.rs @@ -6,7 +6,7 @@ use crate::crypto::{Commitment, Randomizer}; use crate::ecdsa::triples::multiplication::multiplication_many; use crate::{ compat::{CSCurve, SerializablePoint}, - crypto::{commit, hash, Digest}, + crypto::{commit, hash, HashOutput}, ecdsa::math::{GroupPolynomial, Polynomial}, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, proofs::{dlog, dlogeq}, @@ -149,7 +149,7 @@ async fn do_generation( let mut seen = ParticipantCounter::new(&participants); seen.put(me); while !seen.full() { - let (from, confirmation): (_, Digest) = chan.recv(wait1).await?; + let (from, confirmation): (_, HashOutput) = chan.recv(wait1).await?; if !seen.put(from) { continue; } @@ -639,7 +639,7 @@ async fn do_generation_many( let mut seen = ParticipantCounter::new(&participants); seen.put(me); while !seen.full() { - let (from, confirmation): (_, Vec) = chan.recv(wait1).await?; + let (from, confirmation): (_, Vec) = chan.recv(wait1).await?; if !seen.put(from) { continue; } diff --git a/src/ecdsa/triples/multiplication.rs b/src/ecdsa/triples/multiplication.rs index b028854..75db78e 100644 --- a/src/ecdsa/triples/multiplication.rs +++ b/src/ecdsa/triples/multiplication.rs @@ -1,7 +1,7 @@ use crate::{ compat::CSCurve, constants::SECURITY_PARAMETER, - crypto::Digest, + crypto::HashOutput, participants::ParticipantList, protocol::{ internal::{Context, PrivateChannel}, @@ -89,7 +89,7 @@ pub async fn multiplication_receiver<'a, C: CSCurve>( pub async fn multiplication( ctx: Context<'_>, - sid: Digest, + sid: HashOutput, participants: ParticipantList, me: Participant, a_i: C::Scalar, @@ -119,7 +119,7 @@ pub async fn multiplication( pub async fn multiplication_many( ctx: Context<'_>, - sid: Vec, + sid: Vec, participants: ParticipantList, me: Participant, av_iv: Vec, diff --git a/src/ecdsa/triples/random_ot_extension.rs b/src/ecdsa/triples/random_ot_extension.rs index bf0440a..867d09e 100644 --- a/src/ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/triples/random_ot_extension.rs @@ -1,8 +1,8 @@ -use ck_meow::Meow; use elliptic_curve::CurveArithmetic; use magikitten::MeowRng; use rand_core::{OsRng, RngCore}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +use sha2::{Sha256, Digest}; use crate::{ compat::CSCurve, @@ -18,15 +18,17 @@ use super::{ correlated_ot_extension::{correlated_ot_receiver, correlated_ot_sender, CorrelatedOtParams}, }; -const MEOW_CTX: &[u8] = b"Random OT Extension Hash"; +const CTX: &[u8] = b"Random OT Extension Hash"; fn hash_to_scalar(i: usize, v: &BitVector) -> C::Scalar { - let mut meow = Meow::new(MEOW_CTX); + let mut hasher = Sha256::new(); let i64 = u64::try_from(i).expect("failed to convert usize to u64"); - meow.meta_ad(&i64.to_le_bytes(), false); - meow.ad(&v.bytes(), false); - let mut seed = [0u8; 32]; - meow.prf(&mut seed, false); + + hasher.update(CTX); + hasher.update(&i64.to_le_bytes()); + 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 MeowRng::new(&seed)) diff --git a/src/eddsa/test.rs b/src/eddsa/test.rs index 1925de3..6038211 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::Digest; +use crate::crypto::HashOutput; use frost_ed25519::VerifyingKey; use rand_core::{OsRng, RngCore}; use std::error::Error; @@ -146,7 +146,7 @@ pub(crate) fn test_run_signature_protocols( actual_signers: usize, coordinators: &[Participant], threshold: usize, - msg_hash: Digest, + msg_hash: HashOutput, ) -> Result, Box> { let mut protocols: Vec<(Participant, Box>)> = Vec::with_capacity(participants.len()); diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index f8d6d52..c34b95d 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -1,4 +1,4 @@ -use crate::crypto::{hash, Digest}; +use crate::crypto::{hash, HashOutput}; use crate::echo_broadcast::do_broadcast; use crate::participants::{ParticipantCounter, ParticipantList, ParticipantMap}; use crate::protocol::internal::SharedChannel; @@ -63,7 +63,7 @@ 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) -> Digest { +fn domain_separate_hash(domain_separator: u32, data: &T) -> HashOutput { let preimage = (domain_separator, data); hash(&preimage) } @@ -106,7 +106,7 @@ fn generate_coefficient_commitment( /// Generates the challenge for the proof of knowledge /// H(id, context_string, g^{secret} , R) fn challenge( - session_id: &Digest, + session_id: &HashOutput, domain_separator: u32, id: Scalar, vk_share: &CoefficientCommitment, @@ -149,7 +149,7 @@ fn challenge( /// Compute mu = k + a_0 * H(id, context_string, g^{a_0} , R) /// Output (R, mu) fn proof_of_knowledge( - session_id: &Digest, + session_id: &HashOutput, domain_separator: u32, me: Participant, coefficients: &[Scalar], @@ -174,7 +174,7 @@ fn proof_of_knowledge( /// The proof of knowledge could be set to None in case the participant is new /// and thus its secret share is known (set to zero) fn compute_proof_of_knowledge( - session_id: &Digest, + session_id: &HashOutput, domain_separator: u32, me: Participant, old_participants: Option, @@ -201,7 +201,7 @@ fn compute_proof_of_knowledge( /// Verifies the proof of knowledge of the secret coefficients used to generate the /// public secret sharing commitment. fn internal_verify_proof_of_knowledge( - session_id: &Digest, + session_id: &HashOutput, domain_separator: u32, participant: Participant, commitment: &VerifiableSecretSharingCommitment, @@ -225,7 +225,7 @@ fn internal_verify_proof_of_knowledge( /// if the proof of knowledge is none then make sure that the participant is /// performing reshare and does not exist in the set of old participants fn verify_proof_of_knowledge( - session_id: &Digest, + session_id: &HashOutput, domain_separator: u32, threshold: usize, participant: Participant, @@ -272,11 +272,11 @@ fn verify_proof_of_knowledge( /// Takes a commitment and a commitment hash and checks that /// H(commitment) = commitment_hash fn verify_commitment_hash( - session_id: &Digest, + session_id: &HashOutput, participant: Participant, domain_separator: u32, commitment: &VerifiableSecretSharingCommitment, - all_hash_commitments: &ParticipantMap<'_, Digest>, + all_hash_commitments: &ParticipantMap<'_, HashOutput>, ) -> Result<(), ProtocolError> { let actual_commitment_hash = all_hash_commitments.index(participant); let commitment_hash = @@ -367,7 +367,7 @@ async fn broadcast_success( chan: &mut SharedChannel, participants: &ParticipantList, me: &Participant, - session_id: Digest, + session_id: HashOutput, ) -> Result<(), ProtocolError> { // broadcast node me succeded let vote_list = do_broadcast(chan, participants, me, (true, session_id)).await?; diff --git a/src/protocol/internal.rs b/src/protocol/internal.rs index 9a3199a..c471320 100644 --- a/src/protocol/internal.rs +++ b/src/protocol/internal.rs @@ -41,7 +41,6 @@ //! agree on what the identifier for the channels in each part of the protocol is. //! 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 ck_meow::Meow; use event_listener::Event; use serde::{de::DeserializeOwned, Serialize}; use smol::{ @@ -56,25 +55,26 @@ use std::{collections::HashMap, error, future::Future, sync::Arc}; use crate::serde::{decode, encode_with_tag}; use super::{Action, MessageData, Participant, Protocol, ProtocolError}; +use sha2::{Sha256, Digest}; /// The domain for our use of meow here. -const MEOW_DOMAIN: &[u8] = b"cait-sith channel tags"; +const DOMAIN: &[u8] = b"near one threshold signature channel tags"; /// Represents a unique tag for a channel. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)] struct ChannelTag([u8; Self::SIZE]); impl ChannelTag { - /// 160 bit tags, enough for 80 bits of collision security, which should be ample. - const SIZE: usize = 20; + /// 256 bit tags, enough for 128 bits of collision security, which should be ample. + const SIZE: usize = 32; /// The channel tag for a shared channel. /// /// This will always yield the same tag, and is intended to be the root for shared channels. fn root_shared() -> Self { - let mut out = [0u8; Self::SIZE]; - let mut meow = Meow::new(MEOW_DOMAIN); - meow.meta_ad(b"root shared", false); - meow.prf(&mut out, false); + let mut hasher = Sha256::new(); + hasher.update(DOMAIN); + hasher.update(b"root shared"); + let out = hasher.finalize().into(); Self(out) } @@ -87,14 +87,16 @@ impl ChannelTag { fn root_private(p0: Participant, p1: Participant) -> Self { // Sort participants, for uniqueness. let (p0, p1) = (p0.min(p1), p0.max(p1)); - let mut meow = Meow::new(MEOW_DOMAIN); - meow.meta_ad(b"root private", false); - meow.meta_ad(b"p0", false); - meow.ad(&p0.bytes(), false); - meow.meta_ad(b"p1", false); - meow.ad(&p1.bytes(), false); - let mut out = [0u8; Self::SIZE]; - meow.prf(&mut out, false); + + let mut hasher = Sha256::new(); + hasher.update(DOMAIN); + hasher.update(b"root private"); + hasher.update(b"p0"); + hasher.update(&p0.bytes()); + hasher.update(b"p1"); + hasher.update(&p1.bytes()); + + let out = hasher.finalize().into(); Self(out) } @@ -104,13 +106,13 @@ impl ChannelTag { /// /// Indexed children have a separate namespace from named children. fn child(&self, i: u64) -> Self { - let mut meow = Meow::new(MEOW_DOMAIN); - meow.meta_ad(b"parent", false); - meow.ad(&self.0, false); - meow.meta_ad(b"i", false); - meow.ad(&i.to_le_bytes(), false); - let mut out = [0u8; Self::SIZE]; - meow.prf(&mut out, false); + let mut hasher = Sha256::new(); + hasher.update(DOMAIN); + hasher.update(b"parent"); + hasher.update(&self.0); + hasher.update(b"i"); + hasher.update(&i.to_le_bytes()); + let out = hasher.finalize().into(); Self(out) } } From 52970f6224a35a41a76be5c9751a20216b77805f Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Tue, 29 Apr 2025 12:40:30 +0200 Subject: [PATCH 02/52] Trying to get rid of Magikitten --- Cargo.lock | 117 +++++++++++------------ Cargo.toml | 3 +- src/crypto.rs | 4 +- src/ecdsa/triples/batch_random_ot.rs | 2 +- src/ecdsa/triples/bits.rs | 2 +- src/ecdsa/triples/generation.rs | 1 - src/ecdsa/triples/mta.rs | 1 - src/ecdsa/triples/random_ot_extension.rs | 1 - src/proofs/dlog.rs | 18 +++- src/proofs/dlogeq.rs | 1 - src/protocol/internal.rs | 2 +- 11 files changed, 74 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d45f074..f942c76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,8 +299,9 @@ dependencies = [ "haisou-chan", "itertools 0.14.0", "k256", - "magikitten", + "merlin", "rand", + "rand_chacha", "rand_core 0.6.4", "rmp-serde", "serde", @@ -350,17 +351,6 @@ dependencies = [ "half", ] -[[package]] -name = "ck-meow" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20351a318fc4110699260ffde844f0302bc3b34e528b9c48ea429fcd54d81323" -dependencies = [ - "keccak", - "subtle", - "zeroize", -] - [[package]] name = "clap" version = "2.34.0" @@ -552,7 +542,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -563,9 +553,9 @@ checksum = "f400d0750c0c069e8493f2256cb4da6f604b6d2eeb69a0ca8863acde352f8400" [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "zeroize", @@ -579,7 +569,7 @@ checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -664,9 +654,9 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -702,9 +692,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener 5.4.0", "pin-project-lite", @@ -889,7 +879,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -935,9 +925,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -977,9 +967,9 @@ dependencies = [ [[package]] name = "half" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -1155,9 +1145,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "linux-raw-sys" @@ -1189,19 +1179,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" - -[[package]] -name = "magikitten" -version = "0.2.0" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "277c454ad884bdcf0a283beaa7a22e0791fe0475791d01cf8becd1bd4549f4ba" -dependencies = [ - "ck-meow", - "rand_core 0.6.4", -] +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -1209,6 +1189,18 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1220,9 +1212,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -1388,9 +1380,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -1412,13 +1404,12 @@ checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha", "rand_core 0.9.3", - "zerocopy", ] [[package]] @@ -1437,7 +1428,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -1631,7 +1622,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1679,9 +1670,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -1806,9 +1797,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -1847,7 +1838,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1867,7 +1858,7 @@ checksum = "585e5ef40a784ce60b49c67d762110688d211d395d39e096be204535cf64590e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1940,7 +1931,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1996,7 +1987,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -2018,7 +2009,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2223,22 +2214,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.23" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.23" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2258,5 +2249,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] diff --git a/Cargo.toml b/Cargo.toml index 72b36a9..2cfa08f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,9 @@ frost-secp256k1 = { version = "2.1.0", default-features = false, features = ["se futures = "0.3.31" itertools = "0.14.0" k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"], optional = true } -magikitten = "0.2.0" +merlin = "3.0.0" rand = "0.9.0" +rand_chacha = "0.9.0" rand_core = { version = "0.6.4", features = ["getrandom"] } rmp-serde = "1.1.2" serde = { version = "1.0.175", features = ["derive"] } diff --git a/src/crypto.rs b/src/crypto.rs index 3b7b45f..2eb2488 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -5,10 +5,10 @@ use serde::{Deserialize, Serialize}; use crate::serde::encode_writer; -const COMMIT_LABEL: &[u8] = b"near threshold signature commitment"; +const COMMIT_LABEL: &[u8] = b"Near One 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_LABEL: &[u8] = b"Near One threshold signature generic hash"; const HASH_LEN: usize = 32; diff --git a/src/ecdsa/triples/batch_random_ot.rs b/src/ecdsa/triples/batch_random_ot.rs index 5799998..19d39fa 100644 --- a/src/ecdsa/triples/batch_random_ot.rs +++ b/src/ecdsa/triples/batch_random_ot.rs @@ -17,7 +17,7 @@ use crate::{ use super::bits::{BitMatrix, BitVector, SquareBitMatrix, SEC_PARAM_8}; -const BATCH_RANDOM_OT_HASH: &[u8] = b"NEAR threshold signatures batch ROT"; +const BATCH_RANDOM_OT_HASH: &[u8] = b"Near One threshold signatures batch ROT"; fn hash( i: usize, diff --git a/src/ecdsa/triples/bits.rs b/src/ecdsa/triples/bits.rs index 2897635..45f1acd 100644 --- a/src/ecdsa/triples/bits.rs +++ b/src/ecdsa/triples/bits.rs @@ -197,7 +197,7 @@ impl_op_ex!(^ |u: &DoubleBitVector, v: &DoubleBitVector| -> DoubleBitVector { u. impl_op_ex!(^= |u: &mut DoubleBitVector, v: &DoubleBitVector| { u.xor_mut(v) }); /// The context string for our PRG. -const PRG_CTX: &[u8] = b"near one threshold signatures correlated OT PRG"; +const PRG_CTX: &[u8] = b"Near One threshold signatures correlated OT PRG"; /// Represents a matrix of bits. /// diff --git a/src/ecdsa/triples/generation.rs b/src/ecdsa/triples/generation.rs index dde6149..622422a 100644 --- a/src/ecdsa/triples/generation.rs +++ b/src/ecdsa/triples/generation.rs @@ -1,5 +1,4 @@ use elliptic_curve::{Field, Group, ScalarPrimitive}; -use magikitten::Transcript; use rand_core::OsRng; use crate::crypto::{Commitment, Randomizer}; diff --git a/src/ecdsa/triples/mta.rs b/src/ecdsa/triples/mta.rs index 432e871..b3f6a6e 100644 --- a/src/ecdsa/triples/mta.rs +++ b/src/ecdsa/triples/mta.rs @@ -1,5 +1,4 @@ use elliptic_curve::{Field, ScalarPrimitive}; -use magikitten::MeowRng; use rand_core::{OsRng, RngCore}; use serde::{Deserialize, Serialize}; use std::slice::Iter; diff --git a/src/ecdsa/triples/random_ot_extension.rs b/src/ecdsa/triples/random_ot_extension.rs index 867d09e..a0abaff 100644 --- a/src/ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/triples/random_ot_extension.rs @@ -1,5 +1,4 @@ use elliptic_curve::CurveArithmetic; -use magikitten::MeowRng; use rand_core::{OsRng, RngCore}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use sha2::{Sha256, Digest}; diff --git a/src/proofs/dlog.rs b/src/proofs/dlog.rs index 1837e0f..bac2c85 100644 --- a/src/proofs/dlog.rs +++ b/src/proofs/dlog.rs @@ -1,8 +1,9 @@ use elliptic_curve::{Field, Group}; -use magikitten::Transcript; use rand_core::CryptoRngCore; +// use rand::prelude::SeedableRng; +// use rand_chacha::ChaCha20Rng; use serde::{Deserialize, Serialize}; - +use merlin::{Transcript, TranscriptRngBuilder, TranscriptRng}; use crate::{ compat::{CSCurve, SerializablePoint}, serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, @@ -64,17 +65,24 @@ pub fn prove<'a, C: CSCurve>( statement: Statement<'a, C>, witness: Witness<'a, C>, ) -> Proof { - transcript.message(STATEMENT_LABEL, &encode(&statement)); + transcript.append_message(STATEMENT_LABEL, &encode(&statement)); let k = C::Scalar::random(rng); let big_k = statement.phi(&k); - transcript.message( + transcript.append_message( COMMITMENT_LABEL, &encode(&SerializablePoint::::from_projective(&big_k)), ); - let e = C::Scalar::random(&mut transcript.challenge(CHALLENGE_LABEL)); + let mut seed = [0u8; 32]; + transcript.challenge_bytes(CHALLENGE_LABEL, &mut seed); + let rng = transcript.build_rng(); + let pub_rng = rng.rekey_with_witness_bytes( + "Rekeying with public data", + witness); + + let e = C::Scalar::random(&mut pub_rng); let s = k + e * witness.x; Proof { e, s } diff --git a/src/proofs/dlogeq.rs b/src/proofs/dlogeq.rs index 17ec73d..d36951d 100644 --- a/src/proofs/dlogeq.rs +++ b/src/proofs/dlogeq.rs @@ -1,5 +1,4 @@ use elliptic_curve::{Field, Group}; -use magikitten::Transcript; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; diff --git a/src/protocol/internal.rs b/src/protocol/internal.rs index c471320..b9c792c 100644 --- a/src/protocol/internal.rs +++ b/src/protocol/internal.rs @@ -57,7 +57,7 @@ use crate::serde::{decode, encode_with_tag}; use super::{Action, MessageData, Participant, Protocol, ProtocolError}; use sha2::{Sha256, Digest}; -/// The domain for our use of meow here. +/// The domain for our use of sha here. const DOMAIN: &[u8] = b"near one threshold signature channel tags"; /// Represents a unique tag for a channel. From b59de508a475b0c372f3ab70792c305cafd42852 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:45:23 +0200 Subject: [PATCH 03/52] Magikitten is now replaced by A Merlin variant --- Cargo.lock | 19 +++---------- Cargo.toml | 5 ++-- src/ecdsa/triples/generation.rs | 36 ++++++++++++------------ src/ecdsa/triples/mta.rs | 5 ++-- src/ecdsa/triples/random_ot_extension.rs | 12 ++++---- src/proofs/dlog.rs | 25 ++++++++-------- src/proofs/dlogeq.rs | 18 +++++++++--- src/proofs/mod.rs | 2 ++ src/protocol/internal.rs | 2 +- 9 files changed, 63 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f942c76..d9a1219 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -286,6 +286,7 @@ name = "cait-sith" version = "0.8.0" dependencies = [ "auto_ops", + "byteorder", "criterion", "digest", "easy-parallel", @@ -299,9 +300,8 @@ dependencies = [ "haisou-chan", "itertools 0.14.0", "k256", - "merlin", + "keccak", "rand", - "rand_chacha", "rand_core 0.6.4", "rmp-serde", "serde", @@ -310,6 +310,7 @@ dependencies = [ "smol", "structopt", "subtle", + "zeroize", ] [[package]] @@ -1189,18 +1190,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "merlin" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.6.4", - "zeroize", -] - [[package]] name = "num-traits" version = "0.2.19" diff --git a/Cargo.toml b/Cargo.toml index 2cfa08f..4375005 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT" [dependencies] auto_ops = "0.3.0" +byteorder = "1.5.0" digest = "0.10.7" ecdsa = { version = "0.16.8", features = ["digest", "hazmat"] } elliptic-curve = { version = "0.13.5", features = ["serde"] } @@ -18,9 +19,8 @@ frost-secp256k1 = { version = "2.1.0", default-features = false, features = ["se futures = "0.3.31" itertools = "0.14.0" k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"], optional = true } -merlin = "3.0.0" +keccak = "0.1.5" rand = "0.9.0" -rand_chacha = "0.9.0" rand_core = { version = "0.6.4", features = ["getrandom"] } rmp-serde = "1.1.2" serde = { version = "1.0.175", features = ["derive"] } @@ -28,6 +28,7 @@ sha2 = "0.10.8" sha3 = "0.10.8" smol = "1.3.0" subtle = "2.5.0" +zeroize = "1.8.1" [dev-dependencies] criterion = "0.4" diff --git a/src/ecdsa/triples/generation.rs b/src/ecdsa/triples/generation.rs index 622422a..d70f608 100644 --- a/src/ecdsa/triples/generation.rs +++ b/src/ecdsa/triples/generation.rs @@ -8,7 +8,7 @@ use crate::{ crypto::{commit, hash, HashOutput}, ecdsa::math::{GroupPolynomial, Polynomial}, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, - proofs::{dlog, dlogeq}, + proofs::{dlog, dlogeq, strobe_transcript::Transcript}, protocol::{ internal::{make_protocol, Context}, InitializationError, Participant, Protocol, ProtocolError, @@ -23,7 +23,7 @@ pub type TripleGenerationOutput = (TripleShare, TriplePub); pub type TripleGenerationOutputMany = Vec<(TripleShare, TriplePub)>; -const LABEL: &[u8] = b"cait-sith v0.8.0 triple generation"; +const LABEL: &[u8] = b"Near One threshold signatures triple generation"; async fn do_generation( ctx: Context<'_>, @@ -100,7 +100,7 @@ async fn do_generation( }; let my_phi_proof0 = dlog::prove( &mut rng, - &mut transcript.forked(b"dlog0", &me.bytes()), + &mut transcript.fork(b"dlog0", &me.bytes()), statement0, witness0, ); @@ -112,7 +112,7 @@ async fn do_generation( }; let my_phi_proof1 = dlog::prove( &mut rng, - &mut transcript.forked(b"dlog1", &me.bytes()), + &mut transcript.fork(b"dlog1", &me.bytes()), statement1, witness1, ); @@ -220,7 +220,7 @@ async fn do_generation( public: &their_big_e.evaluate_zero(), }; if !dlog::verify( - &mut transcript.forked(b"dlog0", &from.bytes()), + &mut transcript.fork(b"dlog0", &from.bytes()), statement0, &their_phi_proof0, ) { @@ -233,7 +233,7 @@ async fn do_generation( public: &their_big_f.evaluate_zero(), }; if !dlog::verify( - &mut transcript.forked(b"dlog1", &from.bytes()), + &mut transcript.fork(b"dlog1", &from.bytes()), statement1, &their_phi_proof1, ) { @@ -284,7 +284,7 @@ async fn do_generation( }; let my_phi_proof = dlogeq::prove( &mut rng, - &mut transcript.forked(b"dlogeq0", &me.bytes()), + &mut transcript.fork(b"dlogeq0", &me.bytes()), statement, witness, ); @@ -319,7 +319,7 @@ async fn do_generation( }; if !dlogeq::verify( - &mut transcript.forked(b"dlogeq0", &from.bytes()), + &mut transcript.fork(b"dlogeq0", &from.bytes()), statement, &their_phi_proof, ) { @@ -344,7 +344,7 @@ async fn do_generation( let witness = dlog::Witness:: { x: &l0 }; let my_phi_proof = dlog::prove( &mut rng, - &mut transcript.forked(b"dlog2", &me.bytes()), + &mut transcript.fork(b"dlog2", &me.bytes()), statement, witness, ); @@ -385,7 +385,7 @@ async fn do_generation( public: &their_hat_big_c, }; if !dlog::verify( - &mut transcript.forked(b"dlog2", &from.bytes()), + &mut transcript.fork(b"dlog2", &from.bytes()), statement, &their_phi_proof, ) { @@ -571,7 +571,7 @@ async fn do_generation_many( }; let my_phi_proof0 = dlog::prove( &mut rng, - &mut transcript.forked(b"dlog0", &me.bytes()), + &mut transcript.fork(b"dlog0", &me.bytes()), statement0, witness0, ); @@ -583,7 +583,7 @@ async fn do_generation_many( }; let my_phi_proof1 = dlog::prove( &mut rng, - &mut transcript.forked(b"dlog1", &me.bytes()), + &mut transcript.fork(b"dlog1", &me.bytes()), statement1, witness1, ); @@ -721,7 +721,7 @@ async fn do_generation_many( public: &their_big_e.evaluate_zero(), }; if !dlog::verify( - &mut transcript.forked(b"dlog0", &from.bytes()), + &mut transcript.fork(b"dlog0", &from.bytes()), statement0, their_phi_proof0, ) { @@ -734,7 +734,7 @@ async fn do_generation_many( public: &their_big_f.evaluate_zero(), }; if !dlog::verify( - &mut transcript.forked(b"dlog1", &from.bytes()), + &mut transcript.fork(b"dlog1", &from.bytes()), statement1, their_phi_proof1, ) { @@ -799,7 +799,7 @@ async fn do_generation_many( }; let my_phi_proof = dlogeq::prove( &mut rng, - &mut transcript.forked(b"dlogeq0", &me.bytes()), + &mut transcript.fork(b"dlogeq0", &me.bytes()), statement, witness, ); @@ -842,7 +842,7 @@ async fn do_generation_many( }; if !dlogeq::verify( - &mut transcript.forked(b"dlogeq0", &from.bytes()), + &mut transcript.fork(b"dlogeq0", &from.bytes()), statement, their_phi_proof, ) { @@ -872,7 +872,7 @@ async fn do_generation_many( let witness = dlog::Witness:: { x: &l0 }; let my_phi_proof = dlog::prove( &mut rng, - &mut transcript.forked(b"dlog2", &me.bytes()), + &mut transcript.fork(b"dlog2", &me.bytes()), statement, witness, ); @@ -933,7 +933,7 @@ async fn do_generation_many( public: &their_hat_big_c, }; if !dlog::verify( - &mut transcript.forked(b"dlog2", &from.bytes()), + &mut transcript.fork(b"dlog2", &from.bytes()), statement, their_phi_proof, ) { diff --git a/src/ecdsa/triples/mta.rs b/src/ecdsa/triples/mta.rs index b3f6a6e..ce94c99 100644 --- a/src/ecdsa/triples/mta.rs +++ b/src/ecdsa/triples/mta.rs @@ -6,6 +6,7 @@ use subtle::{Choice, ConditionallySelectable}; use crate::{ compat::CSCurve, + proofs::strobe_transcript::TranscriptRng, protocol::{ internal::{make_protocol, Context, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, @@ -85,7 +86,7 @@ pub async fn mta_sender( let mut alpha = delta[0] * C::Scalar::from(chi1); - let mut prng = MeowRng::new(&seed); + 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; @@ -117,7 +118,7 @@ pub async fn mta_receiver( // Step 4 let mut seed = [0u8; 32]; OsRng.fill_bytes(&mut seed); - let mut prng = MeowRng::new(&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; diff --git a/src/ecdsa/triples/random_ot_extension.rs b/src/ecdsa/triples/random_ot_extension.rs index a0abaff..1a8d762 100644 --- a/src/ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/triples/random_ot_extension.rs @@ -4,12 +4,10 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use sha2::{Sha256, Digest}; use crate::{ - compat::CSCurve, - constants::SECURITY_PARAMETER, - protocol::{ + compat::CSCurve, constants::SECURITY_PARAMETER, proofs::strobe_transcript::TranscriptRng, protocol::{ internal::{make_protocol, Context, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, - }, + } }; use super::{ @@ -30,7 +28,7 @@ fn hash_to_scalar(i: usize, v: &BitVector) -> C::Scalar { // 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 MeowRng::new(&seed)) + C::sample_scalar_constant_time(&mut TranscriptRng::new(&seed)) } fn adjust_size(size: usize) -> usize { @@ -88,7 +86,7 @@ pub async fn random_ot_extension_sender( let mu = adjusted_size / SECURITY_PARAMETER; // Step 7 - let mut prng = MeowRng::new(&seed); + let mut prng = TranscriptRng::new(&seed); let chi: Vec = (0..mu).map(|_| BitVector::random(&mut prng)).collect(); // Step 11 @@ -165,7 +163,7 @@ pub async fn random_ot_extension_receiver( let mu = adjusted_size / SECURITY_PARAMETER; // Step 7 - let mut prng = MeowRng::new(&seed); + let mut prng = TranscriptRng::new(&seed); let chi: Vec = (0..mu).map(|_| BitVector::random(&mut prng)).collect(); // Step 8 diff --git a/src/proofs/dlog.rs b/src/proofs/dlog.rs index bac2c85..6f97eec 100644 --- a/src/proofs/dlog.rs +++ b/src/proofs/dlog.rs @@ -3,9 +3,9 @@ use rand_core::CryptoRngCore; // use rand::prelude::SeedableRng; // use rand_chacha::ChaCha20Rng; use serde::{Deserialize, Serialize}; -use merlin::{Transcript, TranscriptRngBuilder, TranscriptRng}; use crate::{ compat::{CSCurve, SerializablePoint}, + proofs::strobe_transcript::Transcript, serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, }; @@ -65,24 +65,21 @@ pub fn prove<'a, C: CSCurve>( statement: Statement<'a, C>, witness: Witness<'a, C>, ) -> Proof { - transcript.append_message(STATEMENT_LABEL, &encode(&statement)); + transcript.message(STATEMENT_LABEL, &encode(&statement)); let k = C::Scalar::random(rng); let big_k = statement.phi(&k); - transcript.append_message( + transcript.message( COMMITMENT_LABEL, &encode(&SerializablePoint::::from_projective(&big_k)), ); let mut seed = [0u8; 32]; - transcript.challenge_bytes(CHALLENGE_LABEL, &mut seed); - let rng = transcript.build_rng(); - let pub_rng = rng.rekey_with_witness_bytes( - "Rekeying with public data", - witness); + transcript.challenge(CHALLENGE_LABEL, &mut seed); + let mut rng = transcript.build_rng(&seed); - let e = C::Scalar::random(&mut pub_rng); + let e = C::Scalar::random(&mut rng); let s = k + e * witness.x; Proof { e, s } @@ -107,7 +104,11 @@ pub fn verify( &encode(&SerializablePoint::::from_projective(&big_k)), ); - let e = C::Scalar::random(&mut transcript.challenge(CHALLENGE_LABEL)); + let mut seed = [0u8; 32]; + transcript.challenge(CHALLENGE_LABEL, &mut seed); + let mut rng = transcript.build_rng(&seed); + + let e = C::Scalar::random(&mut rng); e == proof.e } @@ -132,12 +133,12 @@ mod test { let proof = prove( &mut OsRng, - &mut transcript.forked(b"party", &[1]), + &mut transcript.fork(b"party", &[1]), statement, witness, ); - let ok = verify(&mut transcript.forked(b"party", &[1]), statement, &proof); + 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 index d36951d..6364530 100644 --- a/src/proofs/dlogeq.rs +++ b/src/proofs/dlogeq.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{ compat::{CSCurve, SerializablePoint}, + proofs::strobe_transcript::Transcript, serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, }; @@ -81,7 +82,11 @@ pub fn prove<'a, C: CSCurve>( )), ); - let e = C::Scalar::random(&mut transcript.challenge(CHALLENGE_LABEL)); + let mut seed = [0u8; 32]; + transcript.challenge(CHALLENGE_LABEL, &mut seed); + let mut rng = transcript.build_rng(&seed); + + let e = C::Scalar::random(&mut rng); let s = k + e * witness.x; Proof { e, s } @@ -111,7 +116,12 @@ pub fn verify( )), ); - let e = C::Scalar::random(&mut transcript.challenge(CHALLENGE_LABEL)); + + let mut seed = [0u8; 32]; + transcript.challenge(CHALLENGE_LABEL, &mut seed); + let mut rng = transcript.build_rng(&seed); + + let e = C::Scalar::random(&mut rng); e == proof.e } @@ -140,12 +150,12 @@ mod test { let proof = prove( &mut OsRng, - &mut transcript.forked(b"party", &[1]), + &mut transcript.fork(b"party", &[1]), statement, witness, ); - let ok = verify(&mut transcript.forked(b"party", &[1]), statement, &proof); + let ok = verify(&mut transcript.fork(b"party", &[1]), statement, &proof); assert!(ok); } diff --git a/src/proofs/mod.rs b/src/proofs/mod.rs index 5140f80..177bfde 100644 --- a/src/proofs/mod.rs +++ b/src/proofs/mod.rs @@ -1,2 +1,4 @@ pub mod dlog; pub mod dlogeq; +mod strobe; +pub mod strobe_transcript; diff --git a/src/protocol/internal.rs b/src/protocol/internal.rs index b9c792c..30ceabd 100644 --- a/src/protocol/internal.rs +++ b/src/protocol/internal.rs @@ -58,7 +58,7 @@ use super::{Action, MessageData, Participant, Protocol, ProtocolError}; use sha2::{Sha256, Digest}; /// The domain for our use of sha here. -const DOMAIN: &[u8] = b"near one threshold signature channel tags"; +const DOMAIN: &[u8] = b"Near One threshold signatures channel tags"; /// Represents a unique tag for a channel. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)] From 224ad2de44e1282993d506649d32934cf9327bcc Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:49:35 +0200 Subject: [PATCH 04/52] Forked and amended strobe implementation --- src/proofs/strobe.rs | 182 ++++++++++++++++++++++++++++++++ src/proofs/strobe_transcript.rs | 131 +++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 src/proofs/strobe.rs create mode 100644 src/proofs/strobe_transcript.rs diff --git a/src/proofs/strobe.rs b/src/proofs/strobe.rs new file mode 100644 index 0000000..0eadaf0 --- /dev/null +++ b/src/proofs/strobe.rs @@ -0,0 +1,182 @@ +//! Fully imported from zkcrypto/merlin project specifically from +//! https://github.com/zkcrypto/merlin/blob/main/src/strobe.rs +//! except for the test cases to prevent importing strobe_rs crate into the project + +//! Minimal implementation of (parts of) Strobe. + +use core::ops::{Deref, DerefMut}; + +use keccak; +use zeroize::Zeroize; + +/// Strobe R value; security level 128 is hardcoded +const STROBE_R: u8 = 166; + +const FLAG_I: u8 = 1; +const FLAG_A: u8 = 1 << 1; +const FLAG_C: u8 = 1 << 2; +const FLAG_T: u8 = 1 << 3; +const FLAG_M: u8 = 1 << 4; +const FLAG_K: u8 = 1 << 5; + +fn transmute_state(st: &mut AlignedKeccakState) -> &mut [u64; 25] { + unsafe { &mut *(st as *mut AlignedKeccakState as *mut [u64; 25]) } +} + +/// This is a wrapper around 200-byte buffer that's always 8-byte aligned +/// to make pointers to it safely convertible to pointers to [u64; 25] +/// (since u64 words must be 8-byte aligned) +#[derive(Clone, Zeroize)] +#[zeroize(drop)] +#[repr(align(8))] +struct AlignedKeccakState([u8; 200]); + +/// A Strobe context for the 128-bit security level. +/// +/// Only `meta-AD`, `AD`, `KEY`, and `PRF` operations are supported. +#[derive(Clone, Zeroize)] +pub struct Strobe128 { + state: AlignedKeccakState, + pos: u8, + pos_begin: u8, + cur_flags: u8, +} + +impl ::core::fmt::Debug for Strobe128 { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + // Ensure that the Strobe state isn't accidentally logged + write!(f, "Strobe128: STATE OMITTED") + } +} + +impl Strobe128 { + pub fn new(protocol_label: &[u8]) -> Strobe128 { + let initial_state = { + let mut st = AlignedKeccakState([0u8; 200]); + st[0..6].copy_from_slice(&[1, STROBE_R + 2, 1, 0, 1, 96]); + st[6..18].copy_from_slice(b"STROBEv1.0.2"); + keccak::f1600(transmute_state(&mut st)); + + st + }; + + let mut strobe = Strobe128 { + state: initial_state, + pos: 0, + pos_begin: 0, + cur_flags: 0, + }; + + strobe.meta_ad(protocol_label, false); + + strobe + } + + pub fn meta_ad(&mut self, data: &[u8], more: bool) { + self.begin_op(FLAG_M | FLAG_A, more); + self.absorb(data); + } + + pub fn ad(&mut self, data: &[u8], more: bool) { + self.begin_op(FLAG_A, more); + self.absorb(data); + } + + pub fn prf(&mut self, data: &mut [u8], more: bool) { + self.begin_op(FLAG_I | FLAG_A | FLAG_C, more); + self.squeeze(data); + } + + pub fn key(&mut self, data: &[u8], more: bool) { + self.begin_op(FLAG_A | FLAG_C, more); + self.overwrite(data); + } +} + +impl Strobe128 { + fn run_f(&mut self) { + self.state[self.pos as usize] ^= self.pos_begin; + self.state[(self.pos + 1) as usize] ^= 0x04; + self.state[(STROBE_R + 1) as usize] ^= 0x80; + keccak::f1600(transmute_state(&mut self.state)); + self.pos = 0; + self.pos_begin = 0; + } + + fn absorb(&mut self, data: &[u8]) { + for byte in data { + self.state[self.pos as usize] ^= byte; + self.pos += 1; + if self.pos == STROBE_R { + self.run_f(); + } + } + } + + fn overwrite(&mut self, data: &[u8]) { + for byte in data { + self.state[self.pos as usize] = *byte; + self.pos += 1; + if self.pos == STROBE_R { + self.run_f(); + } + } + } + + fn squeeze(&mut self, data: &mut [u8]) { + for byte in data { + *byte = self.state[self.pos as usize]; + self.state[self.pos as usize] = 0; + self.pos += 1; + if self.pos == STROBE_R { + self.run_f(); + } + } + } + + fn begin_op(&mut self, flags: u8, more: bool) { + // Check if we're continuing an operation + if more { + assert_eq!( + self.cur_flags, flags, + "You tried to continue op {:#b} but changed flags to {:#b}", + self.cur_flags, flags, + ); + return; + } + + // Skip adjusting direction information (we just use AD, PRF) + assert_eq!( + flags & FLAG_T, + 0u8, + "You used the T flag, which this implementation doesn't support" + ); + + let old_begin = self.pos_begin; + self.pos_begin = self.pos + 1; + self.cur_flags = flags; + + self.absorb(&[old_begin, flags]); + + // Force running F if C or K is set + let force_f = 0 != (flags & (FLAG_C | FLAG_K)); + + if force_f && self.pos != 0 { + self.run_f(); + } + } +} + +impl Deref for AlignedKeccakState { + type Target = [u8; 200]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for AlignedKeccakState { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} \ No newline at end of file diff --git a/src/proofs/strobe_transcript.rs b/src/proofs/strobe_transcript.rs new file mode 100644 index 0000000..83282f9 --- /dev/null +++ b/src/proofs/strobe_transcript.rs @@ -0,0 +1,131 @@ +use zeroize::Zeroize; + +use crate::proofs::strobe::Strobe128; + +pub const MERLIN_PROTOCOL_LABEL: &[u8] = b"Mini-Merlin"; + +fn encode_usize_as_u32(x: usize) -> [u8; 4] { + use byteorder::{ByteOrder, LittleEndian}; + + assert!(x <= (u32::max_value() as usize)); + + let mut buf = [0; 4]; + LittleEndian::write_u32(&mut buf, x as u32); + buf +} + + +#[derive(Clone, Zeroize)] +pub struct Transcript { + strobe: Strobe128, +} + + +impl Transcript { + /// Initialize a new transcript with the supplied `label`, which + /// is used as a domain separator. + /// + /// # Note + /// + /// This function should be called by a proof library's API + /// consumer (i.e., the application using the proof library), and + /// **not by the proof implementation**. See the [Passing + /// Transcripts](https://merlin.cool/use/passing.html) section of + /// the Merlin website for more details on why. + pub fn new(label: &'static [u8]) -> Transcript { + let mut transcript = Transcript { + strobe: Strobe128::new(MERLIN_PROTOCOL_LABEL), + }; + transcript.message(b"dom-sep", label); + + transcript + } + + /// Append a prover's `message` to the transcript. + /// + /// The `label` parameter is metadata about the message, and is + /// also appended to the transcript. See the [Transcript + /// Protocols](https://merlin.cool/use/protocol.html) section of + /// the Merlin website for details on labels. + pub fn message(&mut self, label: &'static [u8], message: &[u8]) { + let data_len = encode_usize_as_u32(message.len()); + self.strobe.meta_ad(label, false); + self.strobe.meta_ad(&data_len, true); + self.strobe.ad(message, false); + } + + /// Fill the supplied buffer with the verifier's challenge bytes. + /// + /// The `label` parameter is metadata about the challenge, and is + /// also appended to the transcript. See the [Transcript + /// Protocols](https://merlin.cool/use/protocol.html) section of + /// the Merlin website for details on labels. + pub fn challenge(&mut self, label: &'static [u8], dest: &mut [u8]) { + let data_len = encode_usize_as_u32(dest.len()); + self.strobe.meta_ad(label, false); + self.strobe.meta_ad(&data_len, true); + self.strobe.prf(dest, false); + } + + /// Create a forked version of this transcript. + /// + /// This is often useful in the context of cryptographic protocols. You + /// might want to verify multiple proofs generated at the some point + /// in the transcript, but by different people. You can use this primitive + /// to fork the transcript to check those proofs, with some domain separation + /// identifying each person. + /// + /// Forking without domain separation is intentionally not possible, to prevent + /// potential misuse where the same randomness is generated in different contexts. + pub fn fork(&self, label: &'static [u8], data: &[u8]) -> Self { + let mut out = self.clone(); + out.message(label, data); + out + } + + + /// Consumes the Transcript to build an RNG + pub fn build_rng(&mut self, seed: &[u8;32]) -> TranscriptRng { + self.strobe.meta_ad(b"rng from seed", false); + self.strobe.key(seed, false); + + + TranscriptRng { + strobe: self.strobe.clone(), + } + } +} + +pub struct TranscriptRng { + strobe: Strobe128, +} + +impl TranscriptRng{ + pub fn new(seed: &[u8;32]) -> TranscriptRng { + let mut t = Transcript::new(b"direct RNG from seed"); + t.build_rng(seed) + } +} + +impl rand_core::RngCore for TranscriptRng { + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + let dest_len = encode_usize_as_u32(dest.len()); + self.strobe.meta_ad(&dest_len, false); + self.strobe.prf(dest, false); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +impl rand_core::CryptoRng for TranscriptRng {} \ No newline at end of file From 6d11e650ad80bc1b92dd1b8770fcdefd969a1503 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <43679791+SimonRastikian@users.noreply.github.com> Date: Mon, 5 May 2025 16:33:19 +0200 Subject: [PATCH 05/52] Update Comment using Near instead of Near One Co-authored-by: Bowen Wang --- src/ecdsa/triples/generation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/triples/generation.rs b/src/ecdsa/triples/generation.rs index d70f608..8439d9a 100644 --- a/src/ecdsa/triples/generation.rs +++ b/src/ecdsa/triples/generation.rs @@ -23,7 +23,7 @@ pub type TripleGenerationOutput = (TripleShare, TriplePub); pub type TripleGenerationOutputMany = Vec<(TripleShare, TriplePub)>; -const LABEL: &[u8] = b"Near One threshold signatures triple generation"; +const LABEL: &[u8] = b"Near threshold signatures triple generation"; async fn do_generation( ctx: Context<'_>, From 1e7b335f54fe24f8fa1e7de4883e1d2e7cfef3f9 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Wed, 7 May 2025 12:01:26 +0200 Subject: [PATCH 06/52] Code Simplification and cargo fmt --- src/crypto.rs | 6 +- src/ecdsa/triples/batch_random_ot.rs | 2 +- src/ecdsa/triples/bits.rs | 5 +- src/ecdsa/triples/random_ot_extension.rs | 9 +- src/generic_dkg.rs | 3 +- src/proofs/dlog.rs | 20 +-- src/proofs/dlogeq.rs | 13 +- src/proofs/strobe.rs | 2 +- src/proofs/strobe_transcript.rs | 199 ++++++++++++----------- src/protocol/internal.rs | 2 +- 10 files changed, 124 insertions(+), 137 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index 2eb2488..5563ddc 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,4 +1,4 @@ -use sha2::{Sha256, Digest}; +use sha2::{Digest, Sha256}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -11,7 +11,6 @@ const RANDOMIZER_LEN: usize = 32; const HASH_LABEL: &[u8] = b"Near One 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]); @@ -70,7 +69,6 @@ pub fn commit(rng: &mut R, val: &T) -> (Commitme (c, r) } - /// The output of a generic hash function. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct HashOutput([u8; HASH_LEN]); @@ -82,7 +80,7 @@ impl AsRef<[u8]> for HashOutput { } /// Hash some value to produce a short digest. -pub fn hash(val: &T) -> HashOutput { +pub fn hash(val: &T) -> HashOutput { let mut hasher = Sha256::new(); hasher.update(HASH_LABEL); encode_writer(&mut hasher, val); diff --git a/src/ecdsa/triples/batch_random_ot.rs b/src/ecdsa/triples/batch_random_ot.rs index 19d39fa..ece14ae 100644 --- a/src/ecdsa/triples/batch_random_ot.rs +++ b/src/ecdsa/triples/batch_random_ot.rs @@ -1,8 +1,8 @@ use elliptic_curve::{Field, Group}; use rand_core::OsRng; +use sha2::{Digest, Sha256}; use smol::stream::{self, StreamExt}; use std::sync::Arc; -use sha2::{Sha256, Digest}; use subtle::ConditionallySelectable; use crate::{ diff --git a/src/ecdsa/triples/bits.rs b/src/ecdsa/triples/bits.rs index 45f1acd..9c448c6 100644 --- a/src/ecdsa/triples/bits.rs +++ b/src/ecdsa/triples/bits.rs @@ -1,8 +1,11 @@ use auto_ops::impl_op_ex; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; +use sha3::{ + digest::{ExtendableOutput, Update, XofReader}, + Shake256, +}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; -use sha3::{Shake256, digest::{Update, ExtendableOutput, XofReader}}; use crate::constants::SECURITY_PARAMETER; diff --git a/src/ecdsa/triples/random_ot_extension.rs b/src/ecdsa/triples/random_ot_extension.rs index 1a8d762..a98d99d 100644 --- a/src/ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/triples/random_ot_extension.rs @@ -1,13 +1,16 @@ use elliptic_curve::CurveArithmetic; use rand_core::{OsRng, RngCore}; +use sha2::{Digest, Sha256}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; -use sha2::{Sha256, Digest}; use crate::{ - compat::CSCurve, constants::SECURITY_PARAMETER, proofs::strobe_transcript::TranscriptRng, protocol::{ + compat::CSCurve, + constants::SECURITY_PARAMETER, + proofs::strobe_transcript::TranscriptRng, + protocol::{ internal::{make_protocol, Context, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, - } + }, }; use super::{ diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index c34b95d..11340e8 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -5,8 +5,7 @@ use crate::protocol::internal::SharedChannel; use crate::protocol::{InitializationError, Participant, ProtocolError}; use frost_core::keys::{ - CoefficientCommitment, SecretShare, SigningShare, - VerifiableSecretSharingCommitment, + CoefficientCommitment, SecretShare, SigningShare, VerifiableSecretSharingCommitment, }; use frost_core::{ Challenge, Element, Error, Field, Group, Scalar, Signature, SigningKey, VerifyingKey, diff --git a/src/proofs/dlog.rs b/src/proofs/dlog.rs index 6f97eec..345667e 100644 --- a/src/proofs/dlog.rs +++ b/src/proofs/dlog.rs @@ -1,13 +1,11 @@ -use elliptic_curve::{Field, Group}; -use rand_core::CryptoRngCore; -// use rand::prelude::SeedableRng; -// use rand_chacha::ChaCha20Rng; -use serde::{Deserialize, Serialize}; 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"; @@ -74,11 +72,7 @@ pub fn prove<'a, C: CSCurve>( COMMITMENT_LABEL, &encode(&SerializablePoint::::from_projective(&big_k)), ); - - let mut seed = [0u8; 32]; - transcript.challenge(CHALLENGE_LABEL, &mut seed); - let mut rng = transcript.build_rng(&seed); - + let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); let e = C::Scalar::random(&mut rng); let s = k + e * witness.x; @@ -103,11 +97,7 @@ pub fn verify( COMMITMENT_LABEL, &encode(&SerializablePoint::::from_projective(&big_k)), ); - - let mut seed = [0u8; 32]; - transcript.challenge(CHALLENGE_LABEL, &mut seed); - let mut rng = transcript.build_rng(&seed); - + let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); let e = C::Scalar::random(&mut rng); e == proof.e diff --git a/src/proofs/dlogeq.rs b/src/proofs/dlogeq.rs index 6364530..360d102 100644 --- a/src/proofs/dlogeq.rs +++ b/src/proofs/dlogeq.rs @@ -81,11 +81,7 @@ pub fn prove<'a, C: CSCurve>( SerializablePoint::::from_projective(&big_k.1), )), ); - - let mut seed = [0u8; 32]; - transcript.challenge(CHALLENGE_LABEL, &mut seed); - let mut rng = transcript.build_rng(&seed); - + let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); let e = C::Scalar::random(&mut rng); let s = k + e * witness.x; @@ -115,12 +111,7 @@ pub fn verify( SerializablePoint::::from_projective(&big_k1), )), ); - - - let mut seed = [0u8; 32]; - transcript.challenge(CHALLENGE_LABEL, &mut seed); - let mut rng = transcript.build_rng(&seed); - + let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); let e = C::Scalar::random(&mut rng); e == proof.e diff --git a/src/proofs/strobe.rs b/src/proofs/strobe.rs index 0eadaf0..bad4a24 100644 --- a/src/proofs/strobe.rs +++ b/src/proofs/strobe.rs @@ -179,4 +179,4 @@ impl DerefMut for AlignedKeccakState { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } -} \ No newline at end of file +} diff --git a/src/proofs/strobe_transcript.rs b/src/proofs/strobe_transcript.rs index 83282f9..11fce98 100644 --- a/src/proofs/strobe_transcript.rs +++ b/src/proofs/strobe_transcript.rs @@ -14,118 +14,121 @@ fn encode_usize_as_u32(x: usize) -> [u8; 4] { buf } - #[derive(Clone, Zeroize)] pub struct Transcript { strobe: Strobe128, } - impl Transcript { - /// Initialize a new transcript with the supplied `label`, which - /// is used as a domain separator. - /// - /// # Note - /// - /// This function should be called by a proof library's API - /// consumer (i.e., the application using the proof library), and - /// **not by the proof implementation**. See the [Passing - /// Transcripts](https://merlin.cool/use/passing.html) section of - /// the Merlin website for more details on why. - pub fn new(label: &'static [u8]) -> Transcript { - let mut transcript = Transcript { - strobe: Strobe128::new(MERLIN_PROTOCOL_LABEL), - }; - transcript.message(b"dom-sep", label); - - transcript - } - - /// Append a prover's `message` to the transcript. - /// - /// The `label` parameter is metadata about the message, and is - /// also appended to the transcript. See the [Transcript - /// Protocols](https://merlin.cool/use/protocol.html) section of - /// the Merlin website for details on labels. - pub fn message(&mut self, label: &'static [u8], message: &[u8]) { - let data_len = encode_usize_as_u32(message.len()); - self.strobe.meta_ad(label, false); - self.strobe.meta_ad(&data_len, true); - self.strobe.ad(message, false); - } - - /// Fill the supplied buffer with the verifier's challenge bytes. - /// - /// The `label` parameter is metadata about the challenge, and is - /// also appended to the transcript. See the [Transcript - /// Protocols](https://merlin.cool/use/protocol.html) section of - /// the Merlin website for details on labels. - pub fn challenge(&mut self, label: &'static [u8], dest: &mut [u8]) { - let data_len = encode_usize_as_u32(dest.len()); - self.strobe.meta_ad(label, false); - self.strobe.meta_ad(&data_len, true); - self.strobe.prf(dest, false); - } - - /// Create a forked version of this transcript. - /// - /// This is often useful in the context of cryptographic protocols. You - /// might want to verify multiple proofs generated at the some point - /// in the transcript, but by different people. You can use this primitive - /// to fork the transcript to check those proofs, with some domain separation - /// identifying each person. - /// - /// Forking without domain separation is intentionally not possible, to prevent - /// potential misuse where the same randomness is generated in different contexts. - pub fn fork(&self, label: &'static [u8], data: &[u8]) -> Self { - let mut out = self.clone(); - out.message(label, data); - out - } - - - /// Consumes the Transcript to build an RNG - pub fn build_rng(&mut self, seed: &[u8;32]) -> TranscriptRng { - self.strobe.meta_ad(b"rng from seed", false); - self.strobe.key(seed, false); - - - TranscriptRng { - strobe: self.strobe.clone(), + /// Initialize a new transcript with the supplied `label`, which + /// is used as a domain separator. + /// + /// # Note + /// + /// This function should be called by a proof library's API + /// consumer (i.e., the application using the proof library), and + /// **not by the proof implementation**. See the [Passing + /// Transcripts](https://merlin.cool/use/passing.html) section of + /// the Merlin website for more details on why. + pub fn new(label: &'static [u8]) -> Transcript { + let mut transcript = Transcript { + strobe: Strobe128::new(MERLIN_PROTOCOL_LABEL), + }; + transcript.message(b"dom-sep", label); + + transcript + } + + /// Append a prover's `message` to the transcript. + /// + /// The `label` parameter is metadata about the message, and is + /// also appended to the transcript. See the [Transcript + /// Protocols](https://merlin.cool/use/protocol.html) section of + /// the Merlin website for details on labels. + pub fn message(&mut self, label: &'static [u8], message: &[u8]) { + let data_len = encode_usize_as_u32(message.len()); + self.strobe.meta_ad(label, false); + self.strobe.meta_ad(&data_len, true); + self.strobe.ad(message, false); + } + + /// Fill the supplied buffer with the verifier's challenge bytes. + /// + /// The `label` parameter is metadata about the challenge, and is + /// also appended to the transcript. See the [Transcript + /// Protocols](https://merlin.cool/use/protocol.html) section of + /// the Merlin website for details on labels. + pub fn challenge(&mut self, label: &'static [u8], dest: &mut [u8]) { + let data_len = encode_usize_as_u32(dest.len()); + self.strobe.meta_ad(label, false); + self.strobe.meta_ad(&data_len, true); + self.strobe.prf(dest, false); + } + + /// Create a forked version of this transcript. + /// + /// This is often useful in the context of cryptographic protocols. You + /// might want to verify multiple proofs generated at the some point + /// in the transcript, but by different people. You can use this primitive + /// to fork the transcript to check those proofs, with some domain separation + /// identifying each person. + /// + /// Forking without domain separation is intentionally not possible, to prevent + /// potential misuse where the same randomness is generated in different contexts. + pub fn fork(&self, label: &'static [u8], data: &[u8]) -> Self { + let mut out = self.clone(); + out.message(label, data); + out + } + + /// Consumes the Transcript to build an RNG + pub fn build_rng(&mut self, seed: &[u8; 32]) -> TranscriptRng { + self.strobe.meta_ad(b"rng from seed", false); + self.strobe.key(seed, false); + + TranscriptRng { + strobe: self.strobe.clone(), + } + } + + /// Runs a challenge and then builds an rng from it + pub fn challenge_then_build_rng(&mut self, challenge_label: &'static [u8]) -> TranscriptRng { + let mut seed = [0u8; 32]; + self.challenge(challenge_label, &mut seed); + self.build_rng(&seed) } - } } pub struct TranscriptRng { - strobe: Strobe128, + strobe: Strobe128, } -impl TranscriptRng{ - pub fn new(seed: &[u8;32]) -> TranscriptRng { - let mut t = Transcript::new(b"direct RNG from seed"); - t.build_rng(seed) - } +impl TranscriptRng { + pub fn new(seed: &[u8; 32]) -> TranscriptRng { + let mut t = Transcript::new(b"direct RNG from seed"); + t.build_rng(seed) + } } impl rand_core::RngCore for TranscriptRng { - fn next_u32(&mut self) -> u32 { - rand_core::impls::next_u32_via_fill(self) - } - - fn next_u64(&mut self) -> u64 { - rand_core::impls::next_u64_via_fill(self) - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - let dest_len = encode_usize_as_u32(dest.len()); - self.strobe.meta_ad(&dest_len, false); - self.strobe.prf(dest, false); - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { - self.fill_bytes(dest); - Ok(()) - } + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + let dest_len = encode_usize_as_u32(dest.len()); + self.strobe.meta_ad(&dest_len, false); + self.strobe.prf(dest, false); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } } -impl rand_core::CryptoRng for TranscriptRng {} \ No newline at end of file +impl rand_core::CryptoRng for TranscriptRng {} diff --git a/src/protocol/internal.rs b/src/protocol/internal.rs index 30ceabd..6bf7b4d 100644 --- a/src/protocol/internal.rs +++ b/src/protocol/internal.rs @@ -55,7 +55,7 @@ use std::{collections::HashMap, error, future::Future, sync::Arc}; use crate::serde::{decode, encode_with_tag}; use super::{Action, MessageData, Participant, Protocol, ProtocolError}; -use sha2::{Sha256, Digest}; +use sha2::{Digest, Sha256}; /// The domain for our use of sha here. const DOMAIN: &[u8] = b"Near One threshold signatures channel tags"; From 2ca957ef57ea1ef6332dc7030354d3c68f56953e Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Wed, 7 May 2025 12:46:35 +0200 Subject: [PATCH 07/52] Near in the Label instead of Near One --- src/protocol/internal.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol/internal.rs b/src/protocol/internal.rs index 6bf7b4d..07aeea4 100644 --- a/src/protocol/internal.rs +++ b/src/protocol/internal.rs @@ -58,7 +58,7 @@ use super::{Action, MessageData, Participant, Protocol, ProtocolError}; use sha2::{Digest, Sha256}; /// The domain for our use of sha here. -const DOMAIN: &[u8] = b"Near One threshold signatures channel tags"; +const DOMAIN: &[u8] = b"Near threshold signatures channel tags"; /// Represents a unique tag for a channel. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Hash)] From 50e6d5489c3679c2edd894feab5dfcda9717fa7f Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Mon, 2 Jun 2025 16:29:43 +0200 Subject: [PATCH 08/52] Unused (unecessary) function --- src/ecdsa/triples/mod.rs | 45 ---------------------------------------- 1 file changed, 45 deletions(-) diff --git a/src/ecdsa/triples/mod.rs b/src/ecdsa/triples/mod.rs index 090410d..ad7dca5 100644 --- a/src/ecdsa/triples/mod.rs +++ b/src/ecdsa/triples/mod.rs @@ -100,51 +100,6 @@ pub fn deal( (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; From 355e4096fa058c707ecd8c3ff04c1d1be52cbef2 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Mon, 2 Jun 2025 16:30:00 +0200 Subject: [PATCH 09/52] Unused import --- src/ecdsa/triples/batch_random_ot.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ecdsa/triples/batch_random_ot.rs b/src/ecdsa/triples/batch_random_ot.rs index 0216187..c7277b8 100644 --- a/src/ecdsa/triples/batch_random_ot.rs +++ b/src/ecdsa/triples/batch_random_ot.rs @@ -1,7 +1,6 @@ use elliptic_curve::{Field, Group}; use rand_core::OsRng; use sha2::{Digest, Sha256}; -use smol::stream::{self, StreamExt}; use std::sync::Arc; use subtle::ConditionallySelectable; From c000d54074f023d44fc25e4f00bb0a6b318f550b Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Tue, 3 Jun 2025 11:37:34 +0200 Subject: [PATCH 10/52] Earlier verification of the correctness of pk --- src/generic_dkg.rs | 72 +++++++++++++++++++++------------------------ src/participants.rs | 14 +++++---- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index 30a5a91..fd2bea8 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -403,7 +403,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) = @@ -455,10 +455,11 @@ async fn do_keyshare( } // 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,9 +495,32 @@ 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.into_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(), + )); + } + }; + + 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)?; @@ -504,15 +528,10 @@ async fn do_keyshare( 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 + let mut my_signing_share = evaluate_polynomial::(&secret_coefficients, me)?.to_scalar(); // receive evaluations from all participants let mut seen = ParticipantCounter::new(&participants); seen.put(me); @@ -523,40 +542,17 @@ 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?; diff --git a/src/participants.rs b/src/participants.rs index 46a212d..8c83e75 100644 --- a/src/participants.rs +++ b/src/participants.rs @@ -204,13 +204,15 @@ 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 into_refs_or_none(&self) -> Option> { + self.data.iter().map(|opt| opt.as_ref()).collect() + } + } impl<'a, T> Index for ParticipantMap<'a, T> { From 0b2fe112acf533dd332d0b5b4863391cbf84b399 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Tue, 3 Jun 2025 16:14:52 +0200 Subject: [PATCH 11/52] Documentation rework version #1 --- CHANGELOG.md | 27 -- LICENSE | 19 -- README.md | 284 ++++-------------- docs/{ => ecdsa}/images/dark/dependencies.svg | 0 .../{ => ecdsa}/images/light/dependencies.svg | 0 docs/{ => ecdsa}/intro.md | 0 docs/{ => ecdsa}/key-generation.md | 0 docs/{ => ecdsa}/orchestration.md | 0 docs/{ => ecdsa}/proofs.md | 0 docs/{ => ecdsa}/signing.md | 0 docs/{ => ecdsa}/triples.md | 0 src/compat/mod.rs | 2 - src/lib.rs | 117 -------- 13 files changed, 53 insertions(+), 396 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 LICENSE rename docs/{ => ecdsa}/images/dark/dependencies.svg (100%) rename docs/{ => ecdsa}/images/light/dependencies.svg (100%) rename docs/{ => ecdsa}/intro.md (100%) rename docs/{ => ecdsa}/key-generation.md (100%) rename docs/{ => ecdsa}/orchestration.md (100%) rename docs/{ => ecdsa}/proofs.md (100%) rename docs/{ => ecdsa}/signing.md (100%) rename docs/{ => ecdsa}/triples.md (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 9829d60..0000000 --- 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/LICENSE b/LICENSE deleted file mode 100644 index cf935f9..0000000 --- a/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2023 Lúcás C. Meier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -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 diff --git a/README.md b/README.md index 208bc58..21dc513 100644 --- a/README.md +++ b/README.md @@ -1,248 +1,70 @@ -# Cait-Sith [![](https://img.shields.io/crates/v/cait-sith.svg)](https://crates.io/crates/cait-sith) [![](https://docs.rs/cait-sith/badge.svg)](https://docs.rs/cait-sith) +# Threshold Signing +This repository offers a cryptographic implementation of **threshold ECDSA** and **threshold EdDSA**. The implementation has undergone professional audit and supports arbitrary numbers +of signing parties and thresholds. -Cait-Sith is a novel threshold ECDSA protocol (and implementation), -which is both simpler and substantially more performant than -popular alternatives. +The former implementation is 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 protocol supports arbitrary numbers of parties and thresholds. +The latter implementation is mainly a wrapper of the [Frost](https://github.com/ZcashFoundation/frost) signing functions instantiated with Curve25519. - +# Hierarchical File System -# Warning +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/compat` implements a curve trait used only for ecdsa, `src/proofs` implements \[[Mau09](https://crypto.ethz.ch/publications/files/Maurer09.pdf)\] proofs for discrete logarithms, and `src/protocol` allows defining participants and asynchronous functions that run and test the protocol.\ +Some additional files are found in `src`. `src/serde.rs` provides functions to serialize messages, participants and elliptic curve points, `src/participants.rs` provides more complex structures related to participants mainly based on hash maps and `src/crypto.rs` implements a wrapper for Sha256 hash function. -This is experimental cryptographic software, unless you're a cat with -a megaphone on top of a giant Moogle I would exercise caution. +# 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 -- The protocol does not have a formal proof of security. -- This library has not undergone any form of audit. +### 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. -# Design +### General Notifications -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. +* 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. -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. +* Both implemented ECDSA and Ed25519 schemes do not currently provide **Robustness** i.e. recovery in case a participants drops out during presigning/signing. -A detailed specification is available [in this repo](./docs), -but we'll also give a bit of detail here. +* 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). -The core of Cait-Sith's design involves a *committed* Beaver triple. -These are of the form: +* 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). -$$ -([a], [b], [c]), (A = a \cdot G, B = b \cdot G, C = c \cdot G) -$$ +* **🚨 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. -where $a, b, c$ are scalars such that $a \cdot 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. +# Build and Test +Building the crate is fairly simple using +``cargo build``. -The flow of the protocol is first that the parties need a way to generate triples: +Run ``cargo test`` to run all the built-in test cases. Some the tests might take some time to run as they require running multiple participants at once. -- 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. - -### Refresh and Resharing - -In addition to key generation, cait-sith also supports key *refresh*, -and key *resharing*. - -Key refresh generates new shares for each party, while keeping the same list -of participants and threshold. - -Key resharing does the same, but also allows changing the threshold, -and the list of participants (as long as enough old participants are present -to meet the old threshold). - -## 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: -```rust -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. +The repository contains no mock implementation of the whole running protocol in a ``main.rs`` file but we believe that the main functions are fairly simple to call. # Benchmarks - -Here are some benchmarks, for the `Secp256k1` curve, performed on an Intel Core i5-4690K CPU. - -``` -> cargo bench -F k256 - -setup 3 -time: [94.689 ms 95.057 ms 95.449 ms] - -triple generation (3, 3) -time: [36.610 ms 36.682 ms 36.757 ms] - -keygen (3,3) -time: [3.0901 ms 3.1095 ms 3.1297 ms] - -presign (3,3) -time: [2.5531 ms 2.5640 ms 2.5761 ms] - -sign (3,3) -time: [446.79 µs 447.89 µs 449.02 µs] -``` - -These were performed with 3 parties running on the same machine, -with no communication cost. - -Note that triple generation needs to be performed *twice* for each signature. -Also, triple generation is relatively bandwidth intensive compared to other -protocols, which isn't reflected in these benchmarks, since network speed -isn't constrained. -Nonetheless, this cost isn't all that important, because it can be performed -in advance, and independent of the key. - -Thus, the cost of presigning + signing should be considered instead. -This cost is low enough to be bottlenecked by network performance, most likely. - -## Networked Benchmarks - -The library also has an example which runs a benchmark simulating -network latency and bandwidth constraints. -Note that in these examples, multiple threads are used, so better -reflect the fact that computation is parallelized across each node. -However, the CPU I ran these benchmarks on only had 4 cores, -so take the large party benchmarks with a grain of salt. - -Here's an example with 3 parties, with 100ms latency between them, -and a 10 MB/s outgoing link each. - -``` -> cargo run --release -F k256 --example network-benches -- 3 100 10000000 - -Triple Setup 3 [100 ms, 10000000 B/S] -time: 304.884093ms -up: 10322 B -down: 10322 B - -Triple Gen 3 [100 ms, 10000000 B/S] -time: 740.041888ms -up: 106202 B -down: 106202 B - -Keygen (3, 3) [100 ms, 10000000 B/S] -time: 207.137969ms -up: 1068 B -down: 1068 B - -Presign (3, 3) [100 ms, 10000000 B/S] -time: 104.090877ms -up: 961 B -down: 961 B - -Sign (3, 3) [100 ms, 10000000 B/S] -time: 100.606562ms -up: 151 B -down: 151 B -``` - -Here's an extreme case, with 100 parties, 300ms of latency between them, -and an outgoing 1 MB/s link each: - -``` -> cargo run --release --example network-benches -- 100 300 1000000 - -Triple Setup 100 [300 ms, 1000000 B/S] -time: 51.269278194s -up: 510843 B -down: 510843 B - -Triple Gen 100 [300 ms, 1000000 B/S] -time: 32.959644915s -up: 6765025 B -down: 6765025 B - -Keygen (100, 100) [300 ms, 1000000 B/S] -time: 5.871460998s -up: 551527 B -down: 551527 B - -Presign (100, 100) [300 ms, 1000000 B/S] -time: 2.891458487s -up: 546835 B -down: 546835 B - -Sign (100, 100) [300 ms, 1000000 B/S] -time: 359.795393ms -up: 7859 B -down: 7859 B -``` - -# 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. +* Benchmarks with 8 nodes -- TODO + +# Acknowledgements +This implementation relies on +[Cait-Sith](https://github.com/cronokirby/cait-sith) and +[Frost](https://github.com/ZcashFoundation/frost) and was possible to contributors that actively put this togethers. We thus thank: +
+ Robin Cheng
+ 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/src/compat/mod.rs b/src/compat/mod.rs index 33503bc..79cd84f 100644 --- a/src/compat/mod.rs +++ b/src/compat/mod.rs @@ -2,8 +2,6 @@ use elliptic_curve::{ops::Reduce, point::AffineCoordinates, Curve, CurveArithmet 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. diff --git a/src/lib.rs b/src/lib.rs index a201185..81f9ac5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,120 +1,3 @@ -//! 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; From 9a5764399ea171b07e842b0b1c1a30c293a01562 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Thu, 5 Jun 2025 13:51:02 +0200 Subject: [PATCH 12/52] Extra Cleanups --- README.md | 2 +- logo.png | Bin 95015 -> 0 bytes src/ecdsa/mod.rs | 5 +++-- src/eddsa/mod.rs | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 logo.png diff --git a/README.md b/README.md index 21dc513..2d7729f 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ The repository contains no mock implementation of the whole running protocol in # Acknowledgements This implementation relies on [Cait-Sith](https://github.com/cronokirby/cait-sith) and -[Frost](https://github.com/ZcashFoundation/frost) and was possible to contributors that actively put this togethers. We thus thank: +[Frost](https://github.com/ZcashFoundation/frost) and was possible thanks to contributors that actively put this togethers:
Robin Cheng
Chelsea Komlo
diff --git a/logo.png b/logo.png deleted file mode 100644 index 33b8c3b325aa7d926f036293dee29a8f7f3cabe3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95015 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 N002ovPDHLkV1f}k6Tkof diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 5ec5969..6cdf922 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -1,5 +1,4 @@ -//! This module serves as a wrapper for Frost protocol. - +//! This module serves as a wrapper for ECDSA scheme. use crate::generic_dkg::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; use frost_secp256k1::keys::SigningShare; use frost_secp256k1::{Secp256K1Sha256, VerifyingKey}; @@ -34,3 +33,5 @@ pub mod sign; #[cfg(test)] mod test; pub mod triples; + +pub mod robust_ecdsa; \ No newline at end of file diff --git a/src/eddsa/mod.rs b/src/eddsa/mod.rs index 372d3b1..14f240c 100644 --- a/src/eddsa/mod.rs +++ b/src/eddsa/mod.rs @@ -1,4 +1,4 @@ -//! This module serves as a wrapper for Frost protocol. +//! This module serves as a wrapper for Ed25519 scheme. use crate::generic_dkg::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; use frost_ed25519::keys::SigningShare; use frost_ed25519::{Ed25519Sha512, VerifyingKey}; From 19d37a302d418a7e021c26ebf0fa0b0f478d4d6a Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Thu, 5 Jun 2025 13:52:59 +0200 Subject: [PATCH 13/52] Calling mod that does not exist yet --- src/ecdsa/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 6cdf922..9cea03d 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -32,6 +32,4 @@ pub mod presign; pub mod sign; #[cfg(test)] mod test; -pub mod triples; - -pub mod robust_ecdsa; \ No newline at end of file +pub mod triples; \ No newline at end of file From f59592720a0d364b5e049f340f9876fa6df7f866 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Thu, 5 Jun 2025 14:56:16 +0200 Subject: [PATCH 14/52] constants file only for ecdsa triple generations --- src/ecdsa/mod.rs | 4 +++- src/ecdsa/triples/batch_random_ot.rs | 5 ++--- src/ecdsa/triples/bits.rs | 2 +- src/{ => ecdsa/triples}/constants.rs | 0 src/ecdsa/triples/mod.rs | 1 + src/ecdsa/triples/mta.rs | 4 +--- src/ecdsa/triples/multiplication.rs | 8 +++++--- src/ecdsa/triples/random_ot_extension.rs | 5 ++--- src/lib.rs | 1 - 9 files changed, 15 insertions(+), 15 deletions(-) rename src/{ => ecdsa/triples}/constants.rs (100%) diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 9cea03d..6cdf922 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -32,4 +32,6 @@ pub mod presign; pub mod sign; #[cfg(test)] mod test; -pub mod triples; \ No newline at end of file +pub mod triples; + +pub mod robust_ecdsa; \ No newline at end of file diff --git a/src/ecdsa/triples/batch_random_ot.rs b/src/ecdsa/triples/batch_random_ot.rs index c7277b8..9f6f5d4 100644 --- a/src/ecdsa/triples/batch_random_ot.rs +++ b/src/ecdsa/triples/batch_random_ot.rs @@ -6,16 +6,15 @@ use subtle::ConditionallySelectable; use crate::{ compat::{CSCurve, SerializablePoint}, - constants::SECURITY_PARAMETER, protocol::{ - internal::{make_protocol, PrivateChannel}, + internal::{make_protocol, Comms, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, }, serde::encode, }; +use super::constants::SECURITY_PARAMETER; use super::bits::{BitMatrix, BitVector, SquareBitMatrix, SEC_PARAM_8}; -use crate::protocol::internal::Comms; const BATCH_RANDOM_OT_HASH: &[u8] = b"Near threshold signatures batch ROT"; diff --git a/src/ecdsa/triples/bits.rs b/src/ecdsa/triples/bits.rs index 06e6b2a..50a5e1c 100644 --- a/src/ecdsa/triples/bits.rs +++ b/src/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 + 64 - 1) / 64; pub const SEC_PARAM_8: usize = (SECURITY_PARAMETER + 8 - 1) / 8; diff --git a/src/constants.rs b/src/ecdsa/triples/constants.rs similarity index 100% rename from src/constants.rs rename to src/ecdsa/triples/constants.rs diff --git a/src/ecdsa/triples/mod.rs b/src/ecdsa/triples/mod.rs index ad7dca5..ef0ef71 100644 --- a/src/ecdsa/triples/mod.rs +++ b/src/ecdsa/triples/mod.rs @@ -107,5 +107,6 @@ mod generation; mod mta; mod multiplication; mod random_ot_extension; +mod constants; pub use generation::{generate_triple, generate_triple_many, TripleGenerationOutput}; diff --git a/src/ecdsa/triples/mta.rs b/src/ecdsa/triples/mta.rs index 5b9c3db..270329d 100644 --- a/src/ecdsa/triples/mta.rs +++ b/src/ecdsa/triples/mta.rs @@ -174,9 +174,7 @@ mod test { use ecdsa::elliptic_curve::{bigint::Bounded, Curve}; use k256::{Scalar, Secp256k1}; use rand_core::RngCore; - - use crate::constants::SECURITY_PARAMETER; - + use crate::ecdsa::triples::constants::SECURITY_PARAMETER; use super::*; #[test] diff --git a/src/ecdsa/triples/multiplication.rs b/src/ecdsa/triples/multiplication.rs index cceada5..0638560 100644 --- a/src/ecdsa/triples/multiplication.rs +++ b/src/ecdsa/triples/multiplication.rs @@ -1,20 +1,22 @@ use crate::{ compat::CSCurve, - constants::SECURITY_PARAMETER, crypto::HashOutput, 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::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<'a, C: CSCurve>( diff --git a/src/ecdsa/triples/random_ot_extension.rs b/src/ecdsa/triples/random_ot_extension.rs index 46d1a59..843a836 100644 --- a/src/ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/triples/random_ot_extension.rs @@ -5,19 +5,18 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::{ compat::CSCurve, - constants::SECURITY_PARAMETER, proofs::strobe_transcript::TranscriptRng, 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; const CTX: &[u8] = b"Random OT Extension Hash"; diff --git a/src/lib.rs b/src/lib.rs index 81f9ac5..ce6bbe4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,4 @@ mod compat; -mod constants; mod crypto; mod echo_broadcast; From 008f1db5161e5b8ec90c20175ef5ea1a7d3cc6b2 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Thu, 5 Jun 2025 15:55:10 +0200 Subject: [PATCH 15/52] Generalizations of the cryptography primitives to be used in more than one scheme --- src/crypto/README.md | 1 + src/crypto/ciphersuite.rs | 10 +++++ src/{crypto.rs => crypto/commit.rs} | 45 ++------------------ src/crypto/hash.rs | 32 ++++++++++++++ src/crypto/mod.rs | 6 +++ src/crypto/polynomials.rs | 21 +++++++++ src/{ => crypto}/proofs/dlog.rs | 2 +- src/{ => crypto}/proofs/dlogeq.rs | 3 +- src/{ => crypto}/proofs/mod.rs | 0 src/{ => crypto}/proofs/strobe.rs | 0 src/{ => crypto}/proofs/strobe_transcript.rs | 2 +- src/crypto/random.rs | 23 ++++++++++ src/ecdsa/mod.rs | 2 +- src/ecdsa/robust_ecdsa/README.md | 4 ++ src/ecdsa/robust_ecdsa/mod.rs | 1 + src/ecdsa/robust_ecdsa/presign.rs | 34 +++++++++++++++ src/ecdsa/triples/generation.rs | 7 ++- src/ecdsa/triples/multiplication.rs | 8 ++-- src/eddsa/mod.rs | 2 +- src/eddsa/sign.rs | 2 +- src/eddsa/test.rs | 2 +- src/generic_dkg.rs | 41 +++--------------- src/lib.rs | 2 +- src/participants.rs | 2 +- src/protocol/mod.rs | 2 +- 25 files changed, 160 insertions(+), 94 deletions(-) create mode 100644 src/crypto/README.md create mode 100644 src/crypto/ciphersuite.rs rename src/{crypto.rs => crypto/commit.rs} (59%) create mode 100644 src/crypto/hash.rs create mode 100644 src/crypto/mod.rs create mode 100644 src/crypto/polynomials.rs rename src/{ => crypto}/proofs/dlog.rs (98%) rename src/{ => crypto}/proofs/dlogeq.rs (99%) rename src/{ => crypto}/proofs/mod.rs (100%) rename src/{ => crypto}/proofs/strobe.rs (100%) rename src/{ => crypto}/proofs/strobe_transcript.rs (99%) create mode 100644 src/crypto/random.rs create mode 100644 src/ecdsa/robust_ecdsa/README.md create mode 100644 src/ecdsa/robust_ecdsa/mod.rs create mode 100644 src/ecdsa/robust_ecdsa/presign.rs diff --git a/src/crypto/README.md b/src/crypto/README.md new file mode 100644 index 0000000..f1d1513 --- /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 0000000..28865ee --- /dev/null +++ b/src/crypto/ciphersuite.rs @@ -0,0 +1,10 @@ +// +++++++++ Generic Ciphersuite +++++++++ +pub enum BytesOrder { + BigEndian, + LittleEndian, +} + +pub trait ScalarSerializationFormat { + fn bytes_order() -> BytesOrder; +} +pub trait Ciphersuite: frost_core::Ciphersuite + ScalarSerializationFormat {} diff --git a/src/crypto.rs b/src/crypto/commit.rs similarity index 59% rename from src/crypto.rs rename to src/crypto/commit.rs index f892377..ef8a66b 100644 --- a/src/crypto.rs +++ b/src/crypto/commit.rs @@ -1,34 +1,13 @@ use sha2::{Digest, Sha256}; - -use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; +use rand_core::CryptoRngCore; use crate::serde::encode_writer; +use super::random::Randomizer; + 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. /// @@ -68,21 +47,3 @@ pub fn commit(rng: &mut R, val: &T) -> (Commitme 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/hash.rs b/src/crypto/hash.rs new file mode 100644 index 0000000..ca79129 --- /dev/null +++ b/src/crypto/hash.rs @@ -0,0 +1,32 @@ +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use crate::serde::encode_writer; + +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); + encode_writer(&mut hasher, val); + 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) +} \ No newline at end of file diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..d2391b8 --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,6 @@ +pub mod commit; +pub mod ciphersuite; +pub mod hash; +pub mod polynomials; +pub mod random; +pub mod proofs; \ No newline at end of file diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs new file mode 100644 index 0000000..749c74e --- /dev/null +++ b/src/crypto/polynomials.rs @@ -0,0 +1,21 @@ +// +++++++++ Polynomial manipulations +++++++++ +use frost_core::{Scalar,Group, Field}; +use rand_core::OsRng; + +use super::ciphersuite::Ciphersuite; + +/// Creates a polynomial p of degree threshold - 1 +/// and sets p(0) = secret +pub 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 +} \ No newline at end of file diff --git a/src/proofs/dlog.rs b/src/crypto/proofs/dlog.rs similarity index 98% rename from src/proofs/dlog.rs rename to src/crypto/proofs/dlog.rs index 345667e..6659c18 100644 --- a/src/proofs/dlog.rs +++ b/src/crypto/proofs/dlog.rs @@ -1,8 +1,8 @@ use crate::{ compat::{CSCurve, SerializablePoint}, - proofs::strobe_transcript::Transcript, serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, }; +use super::strobe_transcript::Transcript; use elliptic_curve::{Field, Group}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; diff --git a/src/proofs/dlogeq.rs b/src/crypto/proofs/dlogeq.rs similarity index 99% rename from src/proofs/dlogeq.rs rename to src/crypto/proofs/dlogeq.rs index 360d102..2cdf531 100644 --- a/src/proofs/dlogeq.rs +++ b/src/crypto/proofs/dlogeq.rs @@ -2,9 +2,10 @@ use elliptic_curve::{Field, Group}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; +use super::strobe_transcript::Transcript; + use crate::{ compat::{CSCurve, SerializablePoint}, - proofs::strobe_transcript::Transcript, serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, }; 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 11fce98..fa91818 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 0000000..3a4bf13 --- /dev/null +++ b/src/crypto/random.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; +use rand_core::CryptoRngCore; + +const RANDOMIZER_LEN: usize = 32; + +// +++++++++ Randomizers +++++++++ + +/// 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/mod.rs b/src/ecdsa/mod.rs index 6cdf922..b46c345 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -1,5 +1,5 @@ //! This module serves as a wrapper for ECDSA scheme. -use crate::generic_dkg::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; +use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; use frost_secp256k1::keys::SigningShare; use frost_secp256k1::{Secp256K1Sha256, VerifyingKey}; diff --git a/src/ecdsa/robust_ecdsa/README.md b/src/ecdsa/robust_ecdsa/README.md new file mode 100644 index 0000000..a2082f9 --- /dev/null +++ b/src/ecdsa/robust_ecdsa/README.md @@ -0,0 +1,4 @@ +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. \ No newline at end of file diff --git a/src/ecdsa/robust_ecdsa/mod.rs b/src/ecdsa/robust_ecdsa/mod.rs new file mode 100644 index 0000000..d021ca2 --- /dev/null +++ b/src/ecdsa/robust_ecdsa/mod.rs @@ -0,0 +1 @@ +pub mod presign; \ No newline at end of file diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs new file mode 100644 index 0000000..e94199d --- /dev/null +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -0,0 +1,34 @@ +use crate::compat::CSCurve; +use crate::{ + participants::ParticipantList, + protocol::{ + internal::SharedChannel, + Participant, + ProtocolError + }, +}; +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 nonces value. + pub h_i: C::Scalar, + pub d_i: C::Scalar, + pub e_i: C::Scalar, +} + + + +async fn do_presign( + mut chan: SharedChannel, + participants: ParticipantList, + me: Participant, +) -> Result, ProtocolError> { + todo!("TODO") +} \ No newline at end of file diff --git a/src/ecdsa/triples/generation.rs b/src/ecdsa/triples/generation.rs index d373230..8fe4b71 100644 --- a/src/ecdsa/triples/generation.rs +++ b/src/ecdsa/triples/generation.rs @@ -1,11 +1,14 @@ use elliptic_curve::{Field, Group, ScalarPrimitive}; use rand_core::OsRng; -use crate::crypto::{Commitment, Randomizer}; use crate::ecdsa::triples::multiplication::multiplication_many; use crate::{ compat::{CSCurve, SerializablePoint}, - crypto::{commit, hash, HashOutput}, + crypto::{ + commit::{Commitment,commit}, + hash::{hash, HashOutput}, + random::Randomizer, + }, ecdsa::math::{GroupPolynomial, Polynomial}, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, proofs::{dlog, dlogeq, strobe_transcript::Transcript}, diff --git a/src/ecdsa/triples/multiplication.rs b/src/ecdsa/triples/multiplication.rs index 0638560..89f6b49 100644 --- a/src/ecdsa/triples/multiplication.rs +++ b/src/ecdsa/triples/multiplication.rs @@ -1,6 +1,6 @@ use crate::{ compat::CSCurve, - crypto::HashOutput, + crypto::hash::{HashOutput, hash}, participants::ParticipantList, protocol::{ internal::{Comms, PrivateChannel}, @@ -128,14 +128,14 @@ pub async fn multiplication_many( 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 @@ -193,7 +193,7 @@ mod test { use rand_core::OsRng; use crate::{ - crypto::hash, + crypto::hash::hash, participants::ParticipantList, protocol::{internal::make_protocol, run_protocol, Participant, Protocol, ProtocolError}, }; diff --git a/src/eddsa/mod.rs b/src/eddsa/mod.rs index 14f240c..18b6a78 100644 --- a/src/eddsa/mod.rs +++ b/src/eddsa/mod.rs @@ -1,5 +1,5 @@ //! This module serves as a wrapper for Ed25519 scheme. -use crate::generic_dkg::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; +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 de0126b..629274c 100644 --- a/src/eddsa/sign.rs +++ b/src/eddsa/sign.rs @@ -267,7 +267,7 @@ async fn fut_wrapper( #[cfg(test)] mod tests { - use crate::crypto::hash; + use crate::crypto::hash::hash; use crate::participants::ParticipantList; use frost_core::{Field, Group}; use frost_ed25519::{Ed25519Group, Ed25519ScalarField, Ed25519Sha512, Signature}; diff --git a/src/eddsa/test.rs b/src/eddsa/test.rs index 6038211..d59d4bc 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 fd2bea8..60f6bbc 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -1,4 +1,8 @@ -use crate::crypto::{hash, HashOutput}; +use crate::crypto::{ + ciphersuite::Ciphersuite, + hash::{HashOutput, domain_separate_hash}, + polynomials::generate_secret_polynomial, +}; use crate::echo_broadcast::do_broadcast; use crate::participants::{ParticipantCounter, ParticipantList, ParticipantMap}; use crate::protocol::internal::SharedChannel; @@ -11,20 +15,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,29 +52,6 @@ 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( diff --git a/src/lib.rs b/src/lib.rs index ce6bbe4..5a4aab9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,12 @@ mod compat; mod crypto; +use crypto::*; mod echo_broadcast; mod generic_dkg; mod participants; -mod proofs; pub mod protocol; mod serde; diff --git a/src/participants.rs b/src/participants.rs index 8c83e75..f2ceecf 100644 --- a/src/participants.rs +++ b/src/participants.rs @@ -9,7 +9,7 @@ use std::{collections::HashMap, mem, ops::Index}; use frost_core::{Group, Scalar}; use serde::Serialize; -use crate::generic_dkg::Ciphersuite; +use crate::crypto::ciphersuite::Ciphersuite; use crate::{compat::CSCurve, protocol::Participant}; /// Represents a sorted list of participants. diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 47bef7c..65ec93d 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -10,7 +10,7 @@ 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}; From acf3e97e9bdaf5a5ccc45ebb180be678312eae05 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Fri, 6 Jun 2025 11:00:22 +0200 Subject: [PATCH 16/52] zero_secret_sharing of degree 2t, serialization need to be handled --- src/ecdsa/robust_ecdsa/presign.rs | 39 +++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index e94199d..e657b9b 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -1,5 +1,9 @@ -use crate::compat::CSCurve; +use serde::{Deserialize, Serialize}; +use rand_core::OsRng; + use crate::{ + compat::CSCurve, + crypto::polynomials::generate_secret_polynomial, participants::ParticipantList, protocol::{ internal::SharedChannel, @@ -7,20 +11,35 @@ use crate::{ ProtocolError }, }; -use serde::{Deserialize, Serialize}; + +use frost_secp256k1::*; +type C = Secp256K1Sha256; +type Element = ::Element; +type Scalar = ::Scalar; /// 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 { +pub struct PresignOutput { /// The public nonce commitment. - pub big_r: C::AffinePoint, - /// Our share of the nonces value. - pub h_i: C::Scalar, - pub d_i: C::Scalar, - pub e_i: C::Scalar, + pub big_r: Element, + + /// Our secret shares of the nonces. + pub h_i: Scalar, + pub d_i: Scalar, + pub e_i: Scalar, +} + + +fn zero_secret_sharing_2t( + threshold: usize, + rng: &mut OsRng, +)-> Vec { + let secret = Secp256K1ScalarField::zero(); + let threshold = 2 * threshold; + generate_secret_polynomial::(secret, threshold, rng) } @@ -29,6 +48,6 @@ async fn do_presign( mut chan: SharedChannel, participants: ParticipantList, me: Participant, -) -> Result, ProtocolError> { - todo!("TODO") +) -> Result { + unimplemented!("TODO") } \ No newline at end of file From bde3edef05c45c7911aa16871cacbb39a9695abb Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:37:18 +0200 Subject: [PATCH 17/52] Separation of polynomial functions into the shared crypto folder --- src/crypto/polynomials.rs | 47 ++++++++++++++++++++++++++++++++++----- src/generic_dkg.rs | 18 ++++----------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs index 749c74e..1d6c88d 100644 --- a/src/crypto/polynomials.rs +++ b/src/crypto/polynomials.rs @@ -1,21 +1,58 @@ -// +++++++++ Polynomial manipulations +++++++++ -use frost_core::{Scalar,Group, Field}; use rand_core::OsRng; +use frost_core::{ + Scalar, + Group, + Field, + keys::SigningShare, +}; use super::ciphersuite::Ciphersuite; +use crate::protocol::{Participant, ProtocolError}; /// Creates a polynomial p of degree threshold - 1 /// and sets p(0) = secret pub fn generate_secret_polynomial( secret: Scalar, - threshold: usize, + degree: usize, rng: &mut OsRng, ) -> Vec> { - let mut coefficients = Vec::with_capacity(threshold); + let poly_size = degree+1; + let mut coefficients = Vec::with_capacity(poly_size); // insert the secret share coefficients.push(secret); - for _ in 1..threshold { + for _ in 1..poly_size { coefficients.push(::Field::random(rng)); } coefficients +} + +/// Evaluates a polynomial on the identifier of a participant +/// Evaluate the polynomial with the given coefficients (constant term first) +/// at the point x=identifier using Horner's method. +/// Implements [`polynomial_evaluate`] from the spec. +/// [`polynomial_evaluate`]: https://datatracker.ietf.org/doc/html/rfc9591#name-additional-polynomial-opera +pub fn evaluate_polynomial( + coefficients: &[Scalar], + participant: Participant, +) -> Result, ProtocolError> { + let id = participant.to_identifier::(); + Ok(SigningShare::from_coefficients(coefficients, id)) +} + + +/// Evaluates multiple polynomials of the same type on the same identifier +pub fn evaluate_multi_polynomials( + polynomials: [&[Scalar]; N], + participant: Participant, +) -> Result<[SigningShare; N], ProtocolError> { + let mut result_vec = Vec::with_capacity(N); + + for poly in polynomials.iter() { + let eval = evaluate_polynomial::(poly, participant)?; + result_vec.push(eval); + } + Ok(result_vec + .try_into() + .expect("Internal error: Vec did not match expected array size") + ) } \ No newline at end of file diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index 60f6bbc..526a867 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -1,7 +1,7 @@ use crate::crypto::{ ciphersuite::Ciphersuite, hash::{HashOutput, domain_separate_hash}, - polynomials::generate_secret_polynomial, + polynomials::{evaluate_polynomial, generate_secret_polynomial}, }; use crate::echo_broadcast::do_broadcast; use crate::participants::{ParticipantCounter, ParticipantList, ParticipantMap}; @@ -274,15 +274,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( @@ -390,7 +381,8 @@ 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 = generate_secret_polynomial::(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); @@ -523,11 +515,9 @@ async fn do_keyshare( } - // 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, From bcf9df1e24afebeee8e44fde68967abe1f9ba82d Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:38:18 +0200 Subject: [PATCH 18/52] IMPORTANT: Notification that distinguishes the threshold language of different papers --- src/ecdsa/robust_ecdsa/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/robust_ecdsa/README.md b/src/ecdsa/robust_ecdsa/README.md index a2082f9..e125ae1 100644 --- a/src/ecdsa/robust_ecdsa/README.md +++ b/src/ecdsa/robust_ecdsa/README.md @@ -1,4 +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. \ No newline at end of file +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! From b13312369b67a96eefeb7f2edfde8d57d34e6956 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Fri, 6 Jun 2025 16:38:50 +0200 Subject: [PATCH 19/52] Round 0 and 1 almost done: left to solve the serialization problem --- src/ecdsa/robust_ecdsa/presign.rs | 136 +++++++++++++++++++++++++----- 1 file changed, 114 insertions(+), 22 deletions(-) diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index e657b9b..4593fbf 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -1,27 +1,38 @@ -use serde::{Deserialize, Serialize}; use rand_core::OsRng; +use frost_secp256k1::{ + Secp256K1Sha256, Secp256K1Group, Secp256K1ScalarField, + Group, Field, +}; use crate::{ - compat::CSCurve, - crypto::polynomials::generate_secret_polynomial, - participants::ParticipantList, - protocol::{ - internal::SharedChannel, - Participant, - ProtocolError + crypto::polynomials::{ + evaluate_multi_polynomials, + generate_secret_polynomial }, + ecdsa::KeygenOutput, + participants::{ParticipantCounter, ParticipantList}, + protocol::{internal::SharedChannel, Participant, ProtocolError}, }; -use frost_secp256k1::*; type C = Secp256K1Sha256; type Element = ::Element; type Scalar = ::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. -/// -/// This output is basically all the parts of the signature that we can perform -/// without knowing the message. -#[derive(Debug, Clone, Serialize, Deserialize)] +/// Contains the signature precomputed parts performed +/// independently of the message +#[derive(Debug, Clone)] pub struct PresignOutput { /// The public nonce commitment. pub big_r: Element, @@ -32,22 +43,103 @@ pub struct PresignOutput { pub e_i: Scalar, } - -fn zero_secret_sharing_2t( - threshold: usize, +/// Generates a secret polynomial where the comstant term is zero +fn zero_secret_polynomial( + degree: usize, rng: &mut OsRng, )-> Vec { let secret = Secp256K1ScalarField::zero(); - let threshold = 2 * threshold; - generate_secret_polynomial::(secret, threshold, rng) + generate_secret_polynomial::(secret, degree, rng) } +/// Generates a secret polynomial where the comstant term is random +fn random_secret_polynomial( + degree: usize, + rng: &mut OsRng, +)-> Vec { + let secret = Secp256K1ScalarField::random(rng); + generate_secret_polynomial::(secret, degree, rng) +} + +/// Evaluate five polynomials at once +fn evaluate_five_polynomials( + polynomials: [&[Scalar]; 5], + participant: Participant, +)-> Result<[Scalar; 5], ProtocolError> { + let package = evaluate_multi_polynomials::(polynomials, participant)?; + let output: [Scalar; 5] = package + .iter() + .map( |signing_share| signing_share.to_scalar()) + .collect::>() + .try_into() + .expect("Package must contain exactly N elements"); + Ok(output) +} -async fn do_presign( +/// /!\ Warning: the threshold in this scheme is the same as +/// the max number of malicious parties. +async fn do_presign( mut chan: SharedChannel, participants: ParticipantList, me: Participant, -) -> Result { - unimplemented!("TODO") -} \ No newline at end of file + threshold: usize, +) -> Result<(), ProtocolError> { +// ) -> Result { + + // Round 0 + let mut rng = OsRng; + // degree t random secret shares where t is the max number of malicious parties + let my_fk = random_secret_polynomial(threshold, &mut rng); + let my_fa = random_secret_polynomial(threshold, &mut rng); + + // degree 2t zero secret shares where t is the max number of malicious parties + let my_fb = zero_secret_polynomial(2*threshold, &mut rng); + let my_fd = zero_secret_polynomial(2*threshold, &mut rng); + let my_fe = zero_secret_polynomial(2*threshold, &mut rng); + + // 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 = evaluate_five_polynomials([&my_fk, &my_fa, &my_fb, &my_fd, &my_fe], p)?; + // send the evaluation privately to participant p + chan.send_private(wait_round_0, p, &package); + } + + // Evaluate my secret shares for my polynomials + let mut shares = evaluate_five_polynomials([&my_fk, &my_fa, &my_fb, &my_fd, &my_fe], me)?; + + // Round 1 + // Receive evaluations from all participants + let mut seen = ParticipantCounter::new(&participants); + seen.put(me); + while !seen.full() { + let (from, package): (_, [Scalar; 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]; + } + } + + // Compute R_me = g^{k_me} + let big_r_me = Secp256K1Group::generator() * shares[0]; + let serialize_big_r_me = Secp256K1Group::serialize(&big_r_me) + .map_err(|_| {ProtocolError::AssertionFailed( + "The group element R could not be serialized as it is the identity. + Please retry the presigning".to_string())})?; + + // Compute w_me = a_me * k_me + b_me + let w_me = shares[1] * shares[0] + shares[2]; + + let wait_round_1 = chan.next_waitpoint(); + chan.send_many(wait_round_1, &(&serialize_big_r_me, &w_me)); + + Ok(()) +} + From 21ce1c3d42dc4c851fc5d496576b8686f00ee9e6 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Fri, 6 Jun 2025 17:29:40 +0200 Subject: [PATCH 20/52] Serialization is done through AffinePoint --- src/ecdsa/robust_ecdsa/presign.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 4593fbf..25a33d0 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -4,6 +4,7 @@ use frost_secp256k1::{ Group, Field, }; + use crate::{ crypto::polynomials::{ evaluate_multi_polynomials, @@ -129,16 +130,26 @@ async fn do_presign( // Compute R_me = g^{k_me} let big_r_me = Secp256K1Group::generator() * shares[0]; - let serialize_big_r_me = Secp256K1Group::serialize(&big_r_me) - .map_err(|_| {ProtocolError::AssertionFailed( - "The group element R could not be serialized as it is the identity. - Please retry the presigning".to_string())})?; - // Compute w_me = a_me * k_me + b_me let w_me = shares[1] * shares[0] + shares[2]; - let wait_round_1 = chan.next_waitpoint(); - chan.send_many(wait_round_1, &(&serialize_big_r_me, &w_me)); + chan.send_many(wait_round_1, &(&big_r_me.to_affine(), &w_me)); + + // Receive and interpolate + seen.clear(); + seen.put(me); + while !seen.full() { + let (from, (big_r_p, w_p)): (_ (AffinePoint, Scalar)) = chan.recv(wait_round_1).await?; + if !seen.put(from) { + continue; + } + // collect big_r_p in a map + // collect w_p in a map + + } + // transform both maps into sorted vectors + // exponent interpolation of big R + // exponent interpolation of w Ok(()) } From 708b73246db8304b409c72db551d18266ca12ce7 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Fri, 6 Jun 2025 21:52:05 +0200 Subject: [PATCH 21/52] Switching between Scalar and SigningShare for serialization --- Cargo.lock | 94 +++++++++++++++++++------------ src/ecdsa/robust_ecdsa/presign.rs | 26 +++++---- src/ecdsa/robust_ecdsa/test.rs | 0 3 files changed, 71 insertions(+), 49 deletions(-) create mode 100644 src/ecdsa/robust_ecdsa/test.rs diff --git a/Cargo.lock b/Cargo.lock index d9a1219..2133264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,14 +51,15 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" dependencies = [ "async-task", "concurrent-queue", "fastrand 2.3.0", "futures-lite 2.6.0", + "pin-project-lite", "slab", ] @@ -96,9 +97,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" dependencies = [ "async-lock 3.4.0", "cfg-if", @@ -106,8 +107,8 @@ dependencies = [ "futures-io", "futures-lite 2.6.0", "parking", - "polling 3.7.4", - "rustix 0.38.44", + "polling 3.8.0", + "rustix 1.0.7", "slab", "tracing", "windows-sys 0.59.0", @@ -163,17 +164,17 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" dependencies = [ - "async-io 2.4.0", + "async-io 2.4.1", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.44", + "rustix 1.0.7", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -231,9 +232,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bitflags" @@ -243,9 +244,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block-buffer" @@ -271,9 +272,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byteorder" @@ -655,9 +656,9 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", "windows-sys 0.59.0", @@ -937,9 +938,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", @@ -1031,9 +1032,9 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -1162,6 +1163,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "litrs" version = "0.4.1" @@ -1170,9 +1177,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1308,15 +1315,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi 0.5.1", "pin-project-lite", - "rustix 0.38.44", + "rustix 1.0.7", "tracing", "windows-sys 0.59.0", ] @@ -1426,7 +1433,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.2", + "getrandom 0.3.3", ] [[package]] @@ -1539,18 +1546,31 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -1638,9 +1658,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -1872,9 +1892,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" [[package]] name = "typenum" @@ -2198,7 +2218,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 25a33d0..1d7b453 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -2,6 +2,7 @@ use rand_core::OsRng; use frost_secp256k1::{ Secp256K1Sha256, Secp256K1Group, Secp256K1ScalarField, Group, Field, + keys::SigningShare, }; @@ -66,15 +67,9 @@ fn random_secret_polynomial( fn evaluate_five_polynomials( polynomials: [&[Scalar]; 5], participant: Participant, -)-> Result<[Scalar; 5], ProtocolError> { +)-> Result<[SigningShare; 5], ProtocolError> { let package = evaluate_multi_polynomials::(polynomials, participant)?; - let output: [Scalar; 5] = package - .iter() - .map( |signing_share| signing_share.to_scalar()) - .collect::>() - .try_into() - .expect("Package must contain exactly N elements"); - Ok(output) + Ok(package) } @@ -110,21 +105,25 @@ async fn do_presign( } // Evaluate my secret shares for my polynomials - let mut shares = evaluate_five_polynomials([&my_fk, &my_fa, &my_fb, &my_fd, &my_fe], me)?; + let shares = evaluate_five_polynomials([&my_fk, &my_fa, &my_fb, &my_fd, &my_fe], me)?; + // Extract the shares into a vec of scalars + let mut shares = shares.iter() + .map( |signing_share| signing_share.to_scalar()) + .collect::>(); // Round 1 // Receive evaluations from all participants let mut seen = ParticipantCounter::new(&participants); seen.put(me); while !seen.full() { - let (from, package): (_, [Scalar; 5]) = chan.recv(wait_round_0).await?; + let (from, package): (_, [SigningShare; 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]; + shares[i] += package[i].to_scalar(); } } @@ -132,6 +131,9 @@ async fn do_presign( let big_r_me = Secp256K1Group::generator() * shares[0]; // Compute w_me = a_me * k_me + b_me let w_me = shares[1] * shares[0] + shares[2]; + let w_me = SigningShare::new(w_me); + + // Send and receive let wait_round_1 = chan.next_waitpoint(); chan.send_many(wait_round_1, &(&big_r_me.to_affine(), &w_me)); @@ -139,7 +141,7 @@ async fn do_presign( seen.clear(); seen.put(me); while !seen.full() { - let (from, (big_r_p, w_p)): (_ (AffinePoint, Scalar)) = chan.recv(wait_round_1).await?; + let (from, (big_r_p, w_p)): (_ , (AffinePoint, SigningShare)) = chan.recv(wait_round_1).await?; if !seen.put(from) { continue; } diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs new file mode 100644 index 0000000..e69de29 From ad917a49c172dcfbe382d29086ce7fb0d15715ef Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:02:59 +0200 Subject: [PATCH 22/52] Preventing the double commitment --- src/generic_dkg.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index 526a867..e886789 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -408,10 +408,15 @@ 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); } @@ -494,7 +499,7 @@ async fn do_keyshare( // compute my secret evaluation of my private polynomial let mut my_signing_share = evaluate_polynomial::(&secret_coefficients, me)?.to_scalar(); // 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) = From 65cbf8d786f4ebbca21518c1ce1a4b4d3fb08ba0 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:24:54 +0200 Subject: [PATCH 23/52] Adding polynomial interpolation --- src/crypto/ciphersuite.rs | 2 +- src/crypto/polynomials.rs | 78 +++++++++++++++++++++++++++++++++++++-- src/crypto/random.rs | 2 - src/participants.rs | 40 ++++++++++---------- src/protocol/mod.rs | 12 +++++- 5 files changed, 107 insertions(+), 27 deletions(-) diff --git a/src/crypto/ciphersuite.rs b/src/crypto/ciphersuite.rs index 28865ee..4eb1d36 100644 --- a/src/crypto/ciphersuite.rs +++ b/src/crypto/ciphersuite.rs @@ -1,4 +1,4 @@ -// +++++++++ Generic Ciphersuite +++++++++ +// Generic Ciphersuite Trait pub enum BytesOrder { BigEndian, LittleEndian, diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs index 1d6c88d..3769d4a 100644 --- a/src/crypto/polynomials.rs +++ b/src/crypto/polynomials.rs @@ -1,13 +1,15 @@ use rand_core::OsRng; use frost_core::{ Scalar, - Group, - Field, + Group, Field, keys::SigningShare, }; use super::ciphersuite::Ciphersuite; -use crate::protocol::{Participant, ProtocolError}; +use crate::{ + protocol::{Participant, ProtocolError}, + participants::ParticipantMap, +}; /// Creates a polynomial p of degree threshold - 1 /// and sets p(0) = secret @@ -55,4 +57,74 @@ pub fn evaluate_multi_polynomials( .try_into() .expect("Internal error: Vec did not match expected array size") ) +} + +// Computes polynomial interpolation on a specific point +// using a sequence of sorted elements +pub fn eval_interpolation( + signingshares_map: &ParticipantMap<'_, SigningShare>, + point: Option<&Scalar>, +)-> Result, ProtocolError>{ + let mut secret = <::Field>::zero(); + let identifiers: Vec> = signingshares_map + .participants() + .iter() + .map(|p| p.generic_scalar::()) + .collect(); + let shares = signingshares_map.into_refs_or_none() + .ok_or(ProtocolError::InvalidInterpolationArguments)?; + + + // Compute the Lagrange coefficients + for (id, share) in identifiers.iter().zip(shares) { + // would raise error if not enough shares or identifiers + let lagrange_coefficient = + compute_lagrange_coefficient::(&identifiers, id, point)?; + + // Compute y = f(0) via polynomial interpolation of these t-of-n solutions ('points) of f + secret = secret + (lagrange_coefficient * share.to_scalar()); + } + + Ok(SigningShare::new(secret)) +} + + +/// Computes the lagrange coefficient lamda_i(x) using a set of coefficients +pub fn compute_lagrange_coefficient( + points_set: &Vec>, + 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(num * den) } \ No newline at end of file diff --git a/src/crypto/random.rs b/src/crypto/random.rs index 3a4bf13..98c1fb0 100644 --- a/src/crypto/random.rs +++ b/src/crypto/random.rs @@ -3,8 +3,6 @@ use rand_core::CryptoRngCore; const RANDOMIZER_LEN: usize = 32; -// +++++++++ Randomizers +++++++++ - /// Represents the randomizer used to make a commit hiding. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Randomizer([u8; RANDOMIZER_LEN]); diff --git a/src/participants.rs b/src/participants.rs index f2ceecf..b88709a 100644 --- a/src/participants.rs +++ b/src/participants.rs @@ -6,10 +6,13 @@ use std::{collections::HashMap, mem, ops::Index}; -use frost_core::{Group, Scalar}; +use frost_core::Scalar; use serde::Serialize; -use crate::crypto::ciphersuite::Ciphersuite; +use crate::crypto::{ + ciphersuite::Ciphersuite, + polynomials::compute_lagrange_coefficient, +}; use crate::{compat::CSCurve, protocol::Participant}; /// Represents a sorted list of participants. @@ -83,6 +86,7 @@ impl ParticipantList { } /// Get the lagrange coefficient for a participant, relative to this list. + /// The lagrange coefficient are evaluated to zero /// Use cait-sith library curve type pub fn lagrange(&self, p: Participant) -> C::Scalar { use elliptic_curve::Field; @@ -104,23 +108,16 @@ impl ParticipantList { } /// 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 + let p = p.generic_scalar::(); + let identifiers: Vec> = self + .participants() + .iter() + .map(|p| p.generic_scalar::()) + .collect(); + compute_lagrange_coefficient::(&identifiers, &p, None).unwrap() } /// Return the intersection of this list with another list. @@ -136,8 +133,8 @@ impl ParticipantList { } // Returns all the participants in the list - pub fn participants(&self) -> Vec { - self.participants.clone() + pub fn participants(&self) -> &Vec { + &self.participants } } @@ -213,6 +210,11 @@ impl<'a, T> ParticipantMap<'a, T> { self.data.iter().map(|opt| opt.as_ref()).collect() } + // Returns the set of included participants + pub fn participants(&self) -> &Vec { + self.participants.participants() + } + } impl<'a, T> Index for ParticipantMap<'a, T> { diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 65ec93d..b7caa54 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -25,6 +25,8 @@ pub enum ProtocolError { ErrorExtractVerificationKey, /// 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. @@ -58,10 +60,16 @@ impl fmt::Display for ProtocolError { 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") - } + }, ProtocolError::InvalidProofOfKnowledge(p) => write!( f, "the proof of knowledge of participant {p:?} is not valid." From b64c186a9fd699aa02553d097d9825fe6e28a962 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:26:52 +0200 Subject: [PATCH 24/52] Serialization problem solved and can interpolate a the polynomial fw(0) to share w --- src/ecdsa/robust_ecdsa/presign.rs | 41 ++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 1d7b453..0fdbf7e 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -2,17 +2,20 @@ use rand_core::OsRng; use frost_secp256k1::{ Secp256K1Sha256, Secp256K1Group, Secp256K1ScalarField, Group, Field, - keys::SigningShare, + keys::{ + SigningShare, + VerifyingShare + }, }; - use crate::{ crypto::polynomials::{ evaluate_multi_polynomials, - generate_secret_polynomial + generate_secret_polynomial, + eval_interpolation, }, ecdsa::KeygenOutput, - participants::{ParticipantCounter, ParticipantList}, + participants::{ParticipantCounter, ParticipantList, ParticipantMap}, protocol::{internal::SharedChannel, Participant, ProtocolError}, }; @@ -107,9 +110,9 @@ async fn do_presign( // Evaluate my secret shares for my polynomials let shares = evaluate_five_polynomials([&my_fk, &my_fa, &my_fb, &my_fd, &my_fe], me)?; // Extract the shares into a vec of scalars - let mut shares = shares.iter() + let mut shares: Vec = shares.iter() .map( |signing_share| signing_share.to_scalar()) - .collect::>(); + .collect(); // Round 1 // Receive evaluations from all participants @@ -129,29 +132,39 @@ async fn do_presign( // Compute R_me = g^{k_me} let big_r_me = Secp256K1Group::generator() * shares[0]; + let big_r_me = VerifyingShare::new(big_r_me); + // Compute w_me = a_me * k_me + b_me let w_me = shares[1] * shares[0] + shares[2]; let w_me = SigningShare::new(w_me); // Send and receive let wait_round_1 = chan.next_waitpoint(); - chan.send_many(wait_round_1, &(&big_r_me.to_affine(), &w_me)); + chan.send_many(wait_round_1, &(&big_r_me, &w_me)); + + // Store the sent items + let mut signingshares_map = ParticipantMap::new(&participants); + let mut verifyingshares_map = ParticipantMap::new(&participants); + signingshares_map.put(me, 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)): (_ , (AffinePoint, SigningShare)) = chan.recv(wait_round_1).await?; + let (from, (big_r_p, w_p)): (_ , (VerifyingShare, SigningShare)) = chan.recv(wait_round_1).await?; if !seen.put(from) { continue; } - // collect big_r_p in a map - // collect w_p in a map - + // collect big_r_p and w_p in maps that will be later ordered + signingshares_map.put(from, w_p); + verifyingshares_map.put(from, big_r_p); } - // transform both maps into sorted vectors - // exponent interpolation of big R - // exponent interpolation of w + + // polynomial interpolation of w + let w = eval_interpolation(&signingshares_map, None); + // exponent interpolation of big R + // CAREFUL NOT TO INTERPOLATE MYSELF? Ok(()) } From f8b19b18a459f58f094e04ae5e4b232b3ad7786b Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:08:24 +0200 Subject: [PATCH 25/52] Polynomial interpolation on the exponent --- src/crypto/polynomials.rs | 94 ++++++++++++++++++++----------- src/ecdsa/robust_ecdsa/presign.rs | 8 +++ 2 files changed, 70 insertions(+), 32 deletions(-) diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs index 3769d4a..3437126 100644 --- a/src/crypto/polynomials.rs +++ b/src/crypto/polynomials.rs @@ -2,7 +2,10 @@ use rand_core::OsRng; use frost_core::{ Scalar, Group, Field, - keys::SigningShare, + keys::{ + SigningShare, + VerifyingShare, + } }; use super::ciphersuite::Ciphersuite; @@ -59,36 +62,6 @@ pub fn evaluate_multi_polynomials( ) } -// Computes polynomial interpolation on a specific point -// using a sequence of sorted elements -pub fn eval_interpolation( - signingshares_map: &ParticipantMap<'_, SigningShare>, - point: Option<&Scalar>, -)-> Result, ProtocolError>{ - let mut secret = <::Field>::zero(); - let identifiers: Vec> = signingshares_map - .participants() - .iter() - .map(|p| p.generic_scalar::()) - .collect(); - let shares = signingshares_map.into_refs_or_none() - .ok_or(ProtocolError::InvalidInterpolationArguments)?; - - - // Compute the Lagrange coefficients - for (id, share) in identifiers.iter().zip(shares) { - // would raise error if not enough shares or identifiers - let lagrange_coefficient = - compute_lagrange_coefficient::(&identifiers, id, point)?; - - // Compute y = f(0) via polynomial interpolation of these t-of-n solutions ('points) of f - secret = secret + (lagrange_coefficient * share.to_scalar()); - } - - Ok(SigningShare::new(secret)) -} - - /// Computes the lagrange coefficient lamda_i(x) using a set of coefficients pub fn compute_lagrange_coefficient( points_set: &Vec>, @@ -127,4 +100,61 @@ pub fn compute_lagrange_coefficient( let den = <::Field>::invert(&den) .map_err(|_| ProtocolError::InvalidInterpolationArguments)?; Ok(num * den) -} \ No newline at end of file +} + +// Computes polynomial interpolation on a specific point +// using a sequence of sorted elements +pub fn eval_interpolation( + signingshares_map: &ParticipantMap<'_, SigningShare>, + point: Option<&Scalar>, +)-> Result, ProtocolError>{ + let mut interpolation = <::Field>::zero(); + let identifiers: Vec> = signingshares_map + .participants() + .iter() + .map(|p| p.generic_scalar::()) + .collect(); + let shares = signingshares_map.into_refs_or_none() + .ok_or(ProtocolError::InvalidInterpolationArguments)?; + + // Compute the Lagrange coefficients + for (id, share) in identifiers.iter().zip(shares) { + // would raise error if not enough shares or identifiers + 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 * share.to_scalar()); + } + + Ok(SigningShare::new(interpolation)) +} + +// Computes polynomial interpolation on the exponent on a specific point +// using a sequence of sorted elements +pub fn eval_exponent_interpolation( + verifyingshares_map: ParticipantMap<'_, VerifyingShare>, + point: Option<&Scalar>, +) -> Result, ProtocolError>{ + let mut interpolation = ::identity(); + let identifiers: Vec> = verifyingshares_map + .participants() + .iter() + .map(|p| p.generic_scalar::()) + .collect(); + let shares = verifyingshares_map.into_refs_or_none() + .ok_or(ProtocolError::InvalidInterpolationArguments)?; + + // Compute the Lagrange coefficients + for (id, share) in identifiers.iter().zip(shares) { + // would raise error if not enough shares or identifiers + 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.to_element() * lagrange_coefficient); + } + + Ok(VerifyingShare::new(interpolation)) +} + diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 0fdbf7e..0199524 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -13,6 +13,7 @@ use crate::{ evaluate_multi_polynomials, generate_secret_polynomial, eval_interpolation, + eval_exponent_interpolation, }, ecdsa::KeygenOutput, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, @@ -144,6 +145,9 @@ async fn do_presign( // Store the sent items let mut signingshares_map = ParticipantMap::new(&participants); + + // ONLY FOR PASSIVE: Disregard t points for the verifying shares + todo!("Find a way to disregard t participants for the passive security of the verifying shares"); let mut verifyingshares_map = ParticipantMap::new(&participants); signingshares_map.put(me, w_me); verifyingshares_map.put(me, big_r_me); @@ -158,12 +162,16 @@ async fn do_presign( } // collect big_r_p and w_p in maps that will be later ordered signingshares_map.put(from, w_p); + + // ONLY FOR PASSIVE: Disregard t points verifyingshares_map.put(from, big_r_p); } // polynomial interpolation of w let w = eval_interpolation(&signingshares_map, None); // exponent interpolation of big R + + let big_r = eval_exponent_interpolation(verifyingshares_map, None); // CAREFUL NOT TO INTERPOLATE MYSELF? Ok(()) From c99b31abc2d453b7eec4bcb4a683026156878832 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Tue, 10 Jun 2025 13:13:31 +0200 Subject: [PATCH 26/52] Presigning done: Still to be tested --- src/crypto/polynomials.rs | 13 +++--- src/ecdsa/robust_ecdsa/presign.rs | 67 ++++++++++++++++++++++--------- src/protocol/mod.rs | 9 +++++ 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs index 3437126..fbd95a8 100644 --- a/src/crypto/polynomials.rs +++ b/src/crypto/polynomials.rs @@ -133,17 +133,14 @@ pub fn eval_interpolation( // Computes polynomial interpolation on the exponent on a specific point // using a sequence of sorted elements pub fn eval_exponent_interpolation( - verifyingshares_map: ParticipantMap<'_, VerifyingShare>, + identifiers: &Vec>, + shares: &Vec<&VerifyingShare>, point: Option<&Scalar>, ) -> Result, ProtocolError>{ let mut interpolation = ::identity(); - let identifiers: Vec> = verifyingshares_map - .participants() - .iter() - .map(|p| p.generic_scalar::()) - .collect(); - let shares = verifyingshares_map.into_refs_or_none() - .ok_or(ProtocolError::InvalidInterpolationArguments)?; + if identifiers.len() != shares.len(){ + return Err(ProtocolError::InvalidInterpolationArguments) + }; // Compute the Lagrange coefficients for (id, share) in identifiers.iter().zip(shares) { diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 0199524..f04f3ce 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -7,6 +7,7 @@ use frost_secp256k1::{ VerifyingShare }, }; +use serde::{Deserialize, Serialize}; use crate::{ crypto::polynomials::{ @@ -21,7 +22,6 @@ use crate::{ }; type C = Secp256K1Sha256; -type Element = ::Element; type Scalar = ::Scalar; @@ -38,15 +38,15 @@ pub struct PresignArguments { /// The output of the presigning protocol. /// Contains the signature precomputed parts performed /// independently of the message -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct PresignOutput { /// The public nonce commitment. - pub big_r: Element, + pub big_r: VerifyingShare, /// Our secret shares of the nonces. - pub h_i: Scalar, - pub d_i: Scalar, - pub e_i: Scalar, + pub h_i: SigningShare, + pub d_i: SigningShare, + pub e_i: SigningShare, } /// Generates a secret polynomial where the comstant term is zero @@ -77,39 +77,43 @@ fn evaluate_five_polynomials( } -/// /!\ Warning: the threshold in this scheme is the same as -/// the max number of malicious parties. +/// /!\ 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, threshold: usize, -) -> Result<(), ProtocolError> { -// ) -> Result { +) -> Result { // Round 0 let mut rng = OsRng; // degree t random secret shares where t is the max number of malicious parties let my_fk = random_secret_polynomial(threshold, &mut rng); + let my_fk = my_fk.as_slice(); let my_fa = random_secret_polynomial(threshold, &mut rng); + let my_fa = my_fa.as_slice(); // degree 2t zero secret shares where t is the max number of malicious parties let my_fb = zero_secret_polynomial(2*threshold, &mut rng); + let my_fb = my_fb.as_slice(); let my_fd = zero_secret_polynomial(2*threshold, &mut rng); + let my_fd = my_fd.as_slice(); let my_fe = zero_secret_polynomial(2*threshold, &mut rng); + let my_fe = my_fe.as_slice(); // 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 = evaluate_five_polynomials([&my_fk, &my_fa, &my_fb, &my_fd, &my_fe], p)?; + let package = evaluate_five_polynomials([my_fk, my_fa, my_fb, my_fd, my_fe], p)?; // send the evaluation privately to participant p chan.send_private(wait_round_0, p, &package); } // Evaluate my secret shares for my polynomials - let shares = evaluate_five_polynomials([&my_fk, &my_fa, &my_fb, &my_fd, &my_fe], me)?; + let shares = evaluate_five_polynomials([my_fk, my_fa, my_fb, my_fd, my_fe], me)?; // Extract the shares into a vec of scalars let mut shares: Vec = shares.iter() .map( |signing_share| signing_share.to_scalar()) @@ -145,9 +149,6 @@ async fn do_presign( // Store the sent items let mut signingshares_map = ParticipantMap::new(&participants); - - // ONLY FOR PASSIVE: Disregard t points for the verifying shares - todo!("Find a way to disregard t participants for the passive security of the verifying shares"); let mut verifyingshares_map = ParticipantMap::new(&participants); signingshares_map.put(me, w_me); verifyingshares_map.put(me, big_r_me); @@ -168,12 +169,40 @@ async fn do_presign( } // polynomial interpolation of w - let w = eval_interpolation(&signingshares_map, None); + let w = eval_interpolation(&signingshares_map, None)?; + // exponent interpolation of big R + let identifiers: Vec = verifyingshares_map + .participants() + .iter() + .map(|p| p.generic_scalar::()) + .collect(); + let verifying_shares = verifyingshares_map.into_refs_or_none() + .ok_or(ProtocolError::InvalidInterpolationArguments)?; + + // get only the first t+1 elements to interpolate + // we know that identifiers.len()>threshold+1 + let first_tplus1_id = identifiers[..threshold+1].to_vec(); + let first_tplus1_vfyshares = verifying_shares[..threshold+1].to_vec(); + // evaluate the exponent interpolation on zero + let big_r = eval_exponent_interpolation(&first_tplus1_id, &first_tplus1_vfyshares, None)?; + + // check w is non-zero and that R is not the identity + if w.to_scalar().is_zero().into(){ + return Err(ProtocolError::ZeroScalar) + } + if big_r.to_element().eq(&::identity()){ + return Err(ProtocolError::IdentityElement) + } - let big_r = eval_exponent_interpolation(verifyingshares_map, None); - // CAREFUL NOT TO INTERPOLATE MYSELF? + // w is non-zero due to previous check and so I can unwrap safely + let h_me = w.to_scalar().invert().unwrap() * shares[1]; - Ok(()) + Ok(PresignOutput{ + big_r, + h_i: SigningShare::new(h_me), + d_i: SigningShare::new(shares[3]), + e_i: SigningShare::new(shares[4]), + }) } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index b7caa54..67a482a 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -23,6 +23,8 @@ pub enum ProtocolError { DKGNotSupported, /// Could not extract the verification Key from a commitment. ErrorExtractVerificationKey, + /// 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 @@ -41,6 +43,8 @@ pub enum ProtocolError { MalformedSigningKey, /// Error in serializing point PointSerialization, + /// Encounter Zero Scalar when not supposed to + ZeroScalar, /// Some generic error happened. Other(Box), } @@ -55,6 +59,10 @@ impl fmt::Display for ProtocolError { f, "could not extract the verification Key from the commitment." ), + ProtocolError::IdentityElement => write!( + f, + "encoutered the identity element (identity point)." + ), ProtocolError::InvalidCommitmentHash => { write!( f, @@ -84,6 +92,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.") } From c783ff2a66ff0462d23c038edac50d49802c0c8c Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:17:48 +0200 Subject: [PATCH 27/52] Presign protocol function --- src/ecdsa/robust_ecdsa/presign.rs | 59 ++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index f04f3ce..1749710 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -18,7 +18,13 @@ use crate::{ }, ecdsa::KeygenOutput, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, - protocol::{internal::SharedChannel, Participant, ProtocolError}, + protocol::{ + internal::{make_protocol, Comms, SharedChannel}, + Participant, + ProtocolError, + InitializationError, + Protocol + }, }; type C = Secp256K1Sha256; @@ -206,3 +212,54 @@ async fn do_presign( }) } + +/// 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.threshold, + ); + Ok(make_protocol(ctx, fut)) +} From b1c97558f96d4a0aad15fb1e16efa910fe79332a Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 00:57:56 +0200 Subject: [PATCH 28/52] Adjusting how to run the implementation --- Cargo.lock | 36 ++++++++++++++++++------------------ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2133264..935ab0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,9 +322,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "ciborium" @@ -544,7 +544,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -571,7 +571,7 @@ checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -881,7 +881,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -933,7 +933,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] @@ -1631,7 +1631,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -1806,9 +1806,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462" dependencies = [ "proc-macro2", "quote", @@ -1847,7 +1847,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -1867,7 +1867,7 @@ checksum = "585e5ef40a784ce60b49c67d762110688d211d395d39e096be204535cf64590e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -1940,7 +1940,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -1961,9 +1961,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -1996,7 +1996,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "wasm-bindgen-shared", ] @@ -2018,7 +2018,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2238,7 +2238,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] [[package]] @@ -2258,5 +2258,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.102", ] diff --git a/Cargo.toml b/Cargo.toml index 4375005..721d883 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,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"], optional = true} keccak = "0.1.5" rand = "0.9.0" rand_core = { version = "0.6.4", features = ["getrandom"] } diff --git a/README.md b/README.md index 2d7729f..dc0ce5f 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ The following functionalities are provided: # Build and Test Building the crate is fairly simple using -``cargo build``. +``cargo build --features k256``. Run ``cargo test`` to run all the built-in test cases. Some the tests might take some time to run as they require running multiple participants at once. From 81be1c99c3a128f7cb2e25e6bcb54da5fc9b91d7 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 00:58:30 +0200 Subject: [PATCH 29/52] Optimizing the online phase even further --- src/ecdsa/robust_ecdsa/presign.rs | 32 +++++++++++++++++++++++-------- src/protocol/mod.rs | 6 ++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 1749710..5ed01b0 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -1,4 +1,7 @@ use rand_core::OsRng; +use elliptic_curve::{ + point::AffineCoordinates, +}; use frost_secp256k1::{ Secp256K1Sha256, Secp256K1Group, Secp256K1ScalarField, Group, Field, @@ -27,6 +30,7 @@ use crate::{ }, }; + type C = Secp256K1Sha256; type Scalar = ::Scalar; @@ -50,9 +54,8 @@ pub struct PresignOutput { pub big_r: VerifyingShare, /// Our secret shares of the nonces. - pub h_i: SigningShare, - pub d_i: SigningShare, - pub e_i: SigningShare, + pub alpha_i: SigningShare, + pub beta_i: SigningShare, } /// Generates a secret polynomial where the comstant term is zero @@ -89,9 +92,10 @@ async fn do_presign( mut chan: SharedChannel, participants: ParticipantList, me: Participant, - threshold: usize, + args: PresignArguments, ) -> Result { + let threshold = args.threshold; // Round 0 let mut rng = OsRng; // degree t random secret shares where t is the max number of malicious parties @@ -204,11 +208,23 @@ async fn do_presign( // w is non-zero due to previous check and so I can unwrap safely let h_me = w.to_scalar().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.to_element() + .to_affine() + .x() + .try_into() + .expect("Slice is not 32 bytes long"); + 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, - h_i: SigningShare::new(h_me), - d_i: SigningShare::new(shares[3]), - e_i: SigningShare::new(shares[4]), + alpha_i: SigningShare::new(alpha_me), + beta_i: SigningShare::new(beta_me), }) } @@ -259,7 +275,7 @@ pub fn presign( ctx.shared_channel(), participants, me, - args.threshold, + args, ); Ok(make_protocol(ctx, fut)) } diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 67a482a..67ab688 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -23,6 +23,8 @@ pub enum ProtocolError { DKGNotSupported, /// 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 @@ -59,6 +61,10 @@ impl fmt::Display for ProtocolError { 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)." From fe2b19b75c9dac497e67f32d8f875f547b533b18 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 01:18:10 +0200 Subject: [PATCH 30/52] Signing: still the verification and potential normalization --- src/ecdsa/robust_ecdsa/mod.rs | 3 +- src/ecdsa/robust_ecdsa/sign.rs | 88 ++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/ecdsa/robust_ecdsa/sign.rs diff --git a/src/ecdsa/robust_ecdsa/mod.rs b/src/ecdsa/robust_ecdsa/mod.rs index d021ca2..788c3cd 100644 --- a/src/ecdsa/robust_ecdsa/mod.rs +++ b/src/ecdsa/robust_ecdsa/mod.rs @@ -1 +1,2 @@ -pub mod presign; \ No newline at end of file +pub mod presign; +pub mod sign; \ No newline at end of file diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs new file mode 100644 index 0000000..179aefc --- /dev/null +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -0,0 +1,88 @@ +use frost_secp256k1::{ + Secp256K1Sha256, Secp256K1ScalarField, + Field, + keys::SigningShare, +}; + +use super::presign::PresignOutput; +use k256::AffinePoint; + +use crate::{ + crypto::polynomials::eval_interpolation, + participants::{ParticipantCounter, ParticipantList, ParticipantMap}, + protocol::{ + internal::{make_protocol, Comms, SharedChannel}, + Participant, + ProtocolError, + InitializationError, + Protocol + }, +} + +type C = Secp256K1Sha256; +type Scalar = ::Scalar; + +/// 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: AffinePoint, + /// This is the second scalar, normalized to be in the lower range. + pub s: Scalar, +} + + +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.to_scalar() + presignature.beta_i.to_scalar(); + let s_me = SigningShare::new(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): (_, SigningShare) = chan.recv(wait_round).await?; + if !seen.put(from) { + continue; + } + s_map.put(from, s_i); + } + + let s = eval_interpolation(&s_map, None)?; + + // // Optionally, normalize s + // s.conditional_assign(&(-s), s.is_high()); + let sig = FullSignature { + big_r: presignature.big_r.to_element().to_affine(), + s: s.to_scalar(), + }; + + if !sig.verify(&public_key, &msg_hash) { + return Err(ProtocolError::AssertionFailed( + "signature failed to verify".to_string(), + )); + } + + Ok(sig) +} \ No newline at end of file From b9e0752760ccdda95d3227926e80fc892d5eb435 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 12:07:20 +0200 Subject: [PATCH 31/52] Todo tasks Post testing --- TODO.txt | 6 ++++++ src/ecdsa/robust_ecdsa/presign.rs | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 TODO.txt diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..2ab464f --- /dev/null +++ b/TODO.txt @@ -0,0 +1,6 @@ +Improve doc simon/cleanups, +Separate and generalize robust_ecdsa with ot_based_ecdsa +Unify interface (CSCurve vs frost curve) + This implies getting rid of the from from_secp256k1sha256_to_cscurve_vk and from_secp256k1sha256_to_cscurve_sk +Add the active adversary functionality for robust ecdsa +make sure k256 is always activated without --feature k256 build in cargo diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 5ed01b0..9b3bc30 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -94,7 +94,6 @@ async fn do_presign( me: Participant, args: PresignArguments, ) -> Result { - let threshold = args.threshold; // Round 0 let mut rng = OsRng; From 135aa8ecf97daf06880d2be746c6fd4bf6b45997 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 12:08:12 +0200 Subject: [PATCH 32/52] Signing done but not too cleanly (involves CSCurve and Frost library) --- src/ecdsa/presign.rs | 4 +- src/ecdsa/robust_ecdsa/sign.rs | 75 ++++++++++++++++++++-------------- 2 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/ecdsa/presign.rs b/src/ecdsa/presign.rs index 05b45db..22d0b2e 100644 --- a/src/ecdsa/presign.rs +++ b/src/ecdsa/presign.rs @@ -59,7 +59,7 @@ fn from_secp256k1sha256_to_cscurve_vk( } /// Transforms a secret key of type Secp256k1Sha256 to CSCurve of cait-sith -fn from_secp256k1sha256_to_cscurve_sk(private_share: &SigningShare) -> C::Scalar { +fn from_secp256k1sha256_to_cscurve_scalar(private_share: &SigningShare) -> C::Scalar { let bytes = private_share.to_scalar().to_bytes(); let bytes: [u8; 32] = bytes.try_into().expect("Slice is not 32 bytes long"); C::from_bytes_to_scalar(bytes).unwrap() @@ -97,7 +97,7 @@ async fn do_presign( 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 private_share = from_secp256k1sha256_to_cscurve_scalar::(&args.keygen_out.private_share); let x_prime_i = sk_lambda * private_share; // Spec 1.4 diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 179aefc..33b5024 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -1,11 +1,15 @@ +use elliptic_curve::group::Curve; use frost_secp256k1::{ - Secp256K1Sha256, Secp256K1ScalarField, + Secp256K1ScalarField, Field, - keys::SigningShare, + keys::{ + SigningShare, + VerifyingShare, + } }; use super::presign::PresignOutput; -use k256::AffinePoint; +use elliptic_curve::CurveArithmetic; use crate::{ crypto::polynomials::eval_interpolation, @@ -17,39 +21,46 @@ use crate::{ InitializationError, Protocol }, -} + // TODO: The following crates need to be done away with + compat::CSCurve, + ecdsa::sign::FullSignature, +}; -type C = Secp256K1Sha256; type Scalar = ::Scalar; -/// 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: AffinePoint, - /// This is the second scalar, normalized to be in the lower range. - pub s: Scalar, + +/// Transforms a verification key of type Secp256k1SHA256 to CSCurve of cait-sith +fn from_secp256k1sha256_to_cscurve_point( + vshare: &VerifyingShare, +) -> Result<::AffinePoint, ProtocolError> { + // serializes into a canonical byte array buf of length 33 bytes using the affine point representation + let bytes = vshare + .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.to_affine()) } +/// Transforms a secret key of type Secp256k1Sha256 to CSCurve of cait-sith +fn from_secp256k1sha256_to_cscurve_scalar(private_share: &SigningShare) -> C::Scalar { + let bytes = private_share.to_scalar().to_bytes(); + let bytes: [u8; 32] = bytes.try_into().expect("Slice is not 32 bytes long"); + C::from_bytes_to_scalar(bytes).unwrap() +} -async fn do_sign( +async fn do_sign( mut chan: SharedChannel, participants: ParticipantList, me: Participant, - public_key: AffinePoint, + public_key: C::AffinePoint, presignature: PresignOutput, msg_hash: Scalar, -) -> Result { +) -> Result, ProtocolError> { let s_me = msg_hash * presignature.alpha_i.to_scalar() + presignature.beta_i.to_scalar(); let s_me = SigningShare::new(s_me); @@ -70,19 +81,23 @@ async fn do_sign( } let s = eval_interpolation(&s_map, None)?; - - // // Optionally, normalize s + // Only for formatting + let s = from_secp256k1sha256_to_cscurve_scalar::(&s); + let big_r = from_secp256k1sha256_to_cscurve_point::(&presignature.big_r)?; + // TODO: + // Normalize s // s.conditional_assign(&(-s), s.is_high()); let sig = FullSignature { - big_r: presignature.big_r.to_element().to_affine(), - s: s.to_scalar(), + big_r, + s, }; + let msg_hash = from_secp256k1sha256_to_cscurve_scalar::(&SigningShare::new(msg_hash)); if !sig.verify(&public_key, &msg_hash) { return Err(ProtocolError::AssertionFailed( "signature failed to verify".to_string(), )); - } + }; Ok(sig) } \ No newline at end of file From bd74e577c47dd8c2981bfc49d84b3fc6a4ac9235 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:03:30 +0200 Subject: [PATCH 33/52] Unused functions. Unecessary legacy implementations --- src/ecdsa/sign.rs | 52 ----------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/src/ecdsa/sign.rs b/src/ecdsa/sign.rs index 597142e..6cdf45d 100644 --- a/src/ecdsa/sign.rs +++ b/src/ecdsa/sign.rs @@ -100,58 +100,6 @@ async fn do_sign( 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 From bd6ec43fe60780a507bb0e3eeb6490f266f3da07 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:07:15 +0200 Subject: [PATCH 34/52] Check that me belongs to the participant list --- src/ecdsa/presign.rs | 5 +++++ src/ecdsa/sign.rs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/ecdsa/presign.rs b/src/ecdsa/presign.rs index 22d0b2e..646ebcd 100644 --- a/src/ecdsa/presign.rs +++ b/src/ecdsa/presign.rs @@ -212,6 +212,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. @@ -233,6 +234,10 @@ 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(), diff --git a/src/ecdsa/sign.rs b/src/ecdsa/sign.rs index 6cdf45d..5153a3a 100644 --- a/src/ecdsa/sign.rs +++ b/src/ecdsa/sign.rs @@ -123,6 +123,10 @@ pub fn sign( 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(), From 12ebd2beacfaa4f748c2d4d476ca5ed19c35e70e Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:10:55 +0200 Subject: [PATCH 35/52] Sign function --- src/ecdsa/robust_ecdsa/sign.rs | 39 +++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 33b5024..f2ed850 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -100,4 +100,41 @@ async fn do_sign( }; Ok(sig) -} \ No newline at end of file +} + + +// TODO: try to unify both sign functions in robust ecdsa and in ot_based_ecdsa +pub fn sign( + participants: &[Participant], + me: Participant, + public_key: C::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)) +} From b9cafaf60dd09d22a12e2aa9ca79573f6cd009cb Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 16:33:51 +0200 Subject: [PATCH 36/52] Presign testing --- src/ecdsa/robust_ecdsa/presign.rs | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 9b3bc30..336a35e 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -278,3 +278,74 @@ pub fn presign( ); Ok(make_protocol(ctx, fut)) } + + + +#[cfg(test)] +mod test { + use super::*; + use rand_core::OsRng; + + use crate::{ecdsa::math::Polynomial, protocol::run_protocol}; + use frost_secp256k1::keys::PublicKeyPackage; + use frost_secp256k1::VerifyingKey; + use std::collections::BTreeMap; + + use k256::{ProjectivePoint, Secp256k1}; + + #[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::::random(&mut OsRng, max_malicious+1); + let big_x = ProjectivePoint::GENERATOR * f.evaluate_zero(); + + + #[allow(clippy::type_complexity)] + let mut protocols: Vec<( + Participant, + Box>, + )> = Vec::with_capacity(participants.len()); + + for p in participants.iter(){ + // simulating the key packages for each participant + let private_share = f.evaluate(&p.scalar::()); + 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), + 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); + } +} \ No newline at end of file From 8af0716d7154e4035d681992a90317fc1c862434 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 16:35:41 +0200 Subject: [PATCH 37/52] thinking of set changes --- TODO.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.txt b/TODO.txt index 2ab464f..0c4d995 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,3 +4,4 @@ Unify interface (CSCurve vs frost curve) This implies getting rid of the from from_secp256k1sha256_to_cscurve_vk and from_secp256k1sha256_to_cscurve_sk Add the active adversary functionality for robust ecdsa make sure k256 is always activated without --feature k256 build in cargo +Try adding (if possible) reducing the set of participant signers wrt set of presigners (just like in ot-based ecdsa implementation where you linearize the shares) \ No newline at end of file From ac7081c331b62b76848755b04d4ecaa61c728c16 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 17:47:04 +0200 Subject: [PATCH 38/52] update todo --- TODO.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO.txt b/TODO.txt index 0c4d995..cb5fde7 100644 --- a/TODO.txt +++ b/TODO.txt @@ -4,4 +4,6 @@ Unify interface (CSCurve vs frost curve) This implies getting rid of the from from_secp256k1sha256_to_cscurve_vk and from_secp256k1sha256_to_cscurve_sk Add the active adversary functionality for robust ecdsa make sure k256 is always activated without --feature k256 build in cargo -Try adding (if possible) reducing the set of participant signers wrt set of presigners (just like in ot-based ecdsa implementation where you linearize the shares) \ No newline at end of file +Try adding (if possible) reducing the set of participant signers wrt set of presigners (just like in ot-based ecdsa implementation where you linearize the shares) + +Unify the test cases between the different ecdsa implementations \ No newline at end of file From 54914f48659eaa33a8f3efb0eb807e9a36820d58 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 17:49:20 +0200 Subject: [PATCH 39/52] Sign test --- src/ecdsa/robust_ecdsa/mod.rs | 4 +- src/ecdsa/robust_ecdsa/sign.rs | 118 ++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 4 deletions(-) diff --git a/src/ecdsa/robust_ecdsa/mod.rs b/src/ecdsa/robust_ecdsa/mod.rs index 788c3cd..468a104 100644 --- a/src/ecdsa/robust_ecdsa/mod.rs +++ b/src/ecdsa/robust_ecdsa/mod.rs @@ -1,2 +1,4 @@ pub mod presign; -pub mod sign; \ No newline at end of file +pub mod sign; +#[cfg(test)] +pub mod test; \ No newline at end of file diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index f2ed850..9e2f7f7 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -1,4 +1,8 @@ -use elliptic_curve::group::Curve; +use elliptic_curve::{ + scalar::IsHigh, + group::Curve, +}; + use frost_secp256k1::{ Secp256K1ScalarField, Field, @@ -84,9 +88,15 @@ async fn do_sign( // Only for formatting let s = from_secp256k1sha256_to_cscurve_scalar::(&s); let big_r = from_secp256k1sha256_to_cscurve_point::(&presignature.big_r)?; - // TODO: + // Normalize s - // s.conditional_assign(&(-s), s.is_high()); + let minus_s = -s; + let s = if s.is_high().into() { + minus_s + }else{ + s + }; + let sig = FullSignature { big_r, s, @@ -138,3 +148,105 @@ pub fn sign( ); Ok(make_protocol(ctx, fut)) } + + + + +#[cfg(test)] +mod test { + use std::error::Error; + + use ecdsa::Signature; + use frost_core::keys::{SigningShare, VerifyingShare}; + 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::{compat::scalar_hash, ecdsa::math::Polynomial, protocol::run_protocol}; + use crate::compat::x_coordinate; + + #[test] + fn test_sign() -> Result<(), Box> { + let max_malicious = 2; + let threshold = max_malicious + 1; + let msg = b"hello?"; + + // Run 4 times to test randomness + for _ in 0..4 { + let fx = Polynomial::::random(&mut OsRng, threshold); + // master secret key + let x = fx.evaluate_zero(); + // master public key + let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); + + + + let fa = Polynomial::::random(&mut OsRng, threshold); + let fk = Polynomial::::random(&mut OsRng, threshold); + + let fd = Polynomial::::extend_random(&mut OsRng, 2*max_malicious+1, &Scalar::ZERO); + let fe = Polynomial::::extend_random(&mut OsRng, 2*max_malicious+1, &Scalar::ZERO); + + let k = fk.evaluate_zero(); + let big_r = ProjectivePoint::GENERATOR * k; + let big_r_x_coordinate = x_coordinate::(&big_r.to_affine()); + + let big_r = VerifyingShare::new(big_r); + + let w = fa.evaluate_zero()* 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 p_scalar = p.scalar::(); + let h_i = fa.evaluate(&p_scalar) *w_invert; + let alpha_i = h_i + fd.evaluate(&p_scalar); + let beta_i = h_i * big_r_x_coordinate * fx.evaluate(&p_scalar) + fe.evaluate(&p_scalar); + + let alpha_i = SigningShare::new(alpha_i); + let beta_i = SigningShare::new(beta_i); + + let presignature = PresignOutput { + big_r, + 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(()) + } +} From c57a64ce7214cfd6ea5cc5abce56825a3e84a301 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 18:08:19 +0200 Subject: [PATCH 40/52] Testing in end to end with keygen presignature and sign --- src/ecdsa/robust_ecdsa/test.rs | 130 +++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs index e69de29..1b02ff4 100644 --- a/src/ecdsa/robust_ecdsa/test.rs +++ b/src/ecdsa/robust_ecdsa/test.rs @@ -0,0 +1,130 @@ +use k256::{AffinePoint, Secp256k1}; +use std::error::Error; + +use crate::compat::scalar_hash; + +use super::{ + presign::{presign, PresignArguments, PresignOutput}, + sign::sign, +}; + +use crate::protocol::{run_protocol, Participant, Protocol}; +use crate::ecdsa::{ + test::run_keygen, + KeygenOutput, + sign::FullSignature, +}; + +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() +} + +#[allow(clippy::type_complexity)] +pub fn run_sign( + participants: Vec<(Participant, PresignOutput)>, + public_key: AffinePoint, + msg: &[u8], +) -> Vec<(Participant, FullSignature)> { + let mut protocols: Vec<( + 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, + p, + public_key, + presign_out, + scalar_hash(msg), + ); + assert!(protocol.is_ok()); + let protocol = protocol.unwrap(); + protocols.push((p, Box::new(protocol))); + } + + run_protocol(protocols).unwrap() +} + +#[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.clone(); + 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); + 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.clone(); + 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); + Ok(()) +} From 33b68855e81614284c65dd20a3e6ef629690b21d Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Wed, 11 Jun 2025 18:18:14 +0200 Subject: [PATCH 41/52] Testing resharing for robust ecdsa signing --- src/ecdsa/robust_ecdsa/sign.rs | 103 ++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 9e2f7f7..ef1305b 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -165,8 +165,10 @@ mod test { use rand_core::OsRng; use super::*; + use crate::ecdsa::robust_ecdsa::test::{run_presign, run_sign}; + use crate::ecdsa::test::{ - assert_public_key_invariant, run_keygen, run_presign, run_reshare, run_sign, + assert_public_key_invariant, run_keygen, run_reshare }; use crate::{compat::scalar_hash, ecdsa::math::Polynomial, protocol::run_protocol}; use crate::compat::x_coordinate; @@ -249,4 +251,103 @@ mod test { } 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), + 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.clone(); + + // 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.clone(); + + // 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); + 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.clone(); + + // 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.clone(); + + // 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); + Ok(()) + } } From f96eb3b075e65e73ad4af2db91a733e248b47773 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Sat, 21 Jun 2025 14:06:35 +0200 Subject: [PATCH 42/52] Unifying test cases for both signature schemes: TODO: adapt for ot_based_ecdsa and unify signature functions --- TODO.txt | 5 +- src/ecdsa/mod.rs | 50 +++++- src/ecdsa/ot_based_ecdsa/mod.rs | 6 + src/ecdsa/{ => ot_based_ecdsa}/presign.rs | 40 +++-- src/ecdsa/{ => ot_based_ecdsa}/sign.rs | 34 +--- src/ecdsa/ot_based_ecdsa/test.rs | 103 ++++++++++++ .../triples/batch_random_ot.rs | 0 .../{ => ot_based_ecdsa}/triples/bits.rs | 0 .../{ => ot_based_ecdsa}/triples/constants.rs | 0 .../triples/correlated_ot_extension.rs | 0 .../triples/generation.rs | 0 src/ecdsa/{ => ot_based_ecdsa}/triples/mod.rs | 0 src/ecdsa/{ => ot_based_ecdsa}/triples/mta.rs | 0 .../triples/multiplication.rs | 0 .../triples/random_ot_extension.rs | 0 src/ecdsa/robust_ecdsa/sign.rs | 35 ++-- src/ecdsa/robust_ecdsa/test.rs | 40 +---- src/ecdsa/test.rs | 152 ++++-------------- 18 files changed, 249 insertions(+), 216 deletions(-) create mode 100644 src/ecdsa/ot_based_ecdsa/mod.rs rename src/ecdsa/{ => ot_based_ecdsa}/presign.rs (93%) rename src/ecdsa/{ => ot_based_ecdsa}/sign.rs (89%) create mode 100644 src/ecdsa/ot_based_ecdsa/test.rs rename src/ecdsa/{ => ot_based_ecdsa}/triples/batch_random_ot.rs (100%) rename src/ecdsa/{ => ot_based_ecdsa}/triples/bits.rs (100%) rename src/ecdsa/{ => ot_based_ecdsa}/triples/constants.rs (100%) rename src/ecdsa/{ => ot_based_ecdsa}/triples/correlated_ot_extension.rs (100%) rename src/ecdsa/{ => ot_based_ecdsa}/triples/generation.rs (100%) rename src/ecdsa/{ => ot_based_ecdsa}/triples/mod.rs (100%) rename src/ecdsa/{ => ot_based_ecdsa}/triples/mta.rs (100%) rename src/ecdsa/{ => ot_based_ecdsa}/triples/multiplication.rs (100%) rename src/ecdsa/{ => ot_based_ecdsa}/triples/random_ot_extension.rs (100%) diff --git a/TODO.txt b/TODO.txt index cb5fde7..6494931 100644 --- a/TODO.txt +++ b/TODO.txt @@ -6,4 +6,7 @@ Add the active adversary functionality for robust ecdsa make sure k256 is always activated without --feature k256 build in cargo Try adding (if possible) reducing the set of participant signers wrt set of presigners (just like in ot-based ecdsa implementation where you linearize the shares) -Unify the test cases between the different ecdsa implementations \ No newline at end of file +Unify the test cases between the different ecdsa implementations + + +Add the unified test to both ecdsa files \ No newline at end of file diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index b46c345..3a11600 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -1,7 +1,14 @@ //! This module serves as a wrapper for ECDSA scheme. +use elliptic_curve::{ops::Invert, Field, Group}; + +use crate::compat::{CSCurve, x_coordinate}; + use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; -use frost_secp256k1::keys::SigningShare; -use frost_secp256k1::{Secp256K1Sha256, VerifyingKey}; +use frost_secp256k1::{ + keys::SigningShare, + Secp256K1Sha256, + VerifyingKey, +}; #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Eq, PartialEq)] pub struct KeygenOutput { @@ -9,6 +16,39 @@ pub struct KeygenOutput { pub public_key: VerifyingKey, } + +/// 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: 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 = 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)); + x_coordinate::(&reproduced.into()) == r + } +} + + impl From> for KeygenOutput { fn from(value: crate::generic_dkg::KeygenOutput) -> Self { Self { @@ -28,10 +68,8 @@ impl Ciphersuite for Secp256K1Sha256 {} pub mod dkg_ecdsa; pub mod math; -pub mod presign; -pub mod sign; #[cfg(test)] mod test; -pub mod triples; -pub mod robust_ecdsa; \ No newline at end of file +pub mod robust_ecdsa; +// pub mod ot_based_ecdsa; \ No newline at end of file diff --git a/src/ecdsa/ot_based_ecdsa/mod.rs b/src/ecdsa/ot_based_ecdsa/mod.rs new file mode 100644 index 0000000..d523b5a --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/mod.rs @@ -0,0 +1,6 @@ +pub mod triples; +pub mod presign; +pub mod sign; + +#[cfg(test)] +mod test; \ No newline at end of file diff --git a/src/ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs similarity index 93% rename from src/ecdsa/presign.rs rename to src/ecdsa/ot_based_ecdsa/presign.rs index 646ebcd..aee2ab6 100644 --- a/src/ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -1,18 +1,22 @@ -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 elliptic_curve::{Field, Group, ScalarPrimitive}; -use frost_secp256k1::keys::SigningShare; -use frost_secp256k1::VerifyingKey; +use frost_secp256k1::{ + VerifyingKey, + keys::SigningShare, +}; use serde::{Deserialize, Serialize}; +use super::triples::{TriplePub, TripleShare}; + +use crate::ecdsa::KeygenOutput; +use crate::compat::CSCurve; +use crate::protocol::{ + Participant, + InitializationError, Protocol, ProtocolError, + internal::{make_protocol, Comms, SharedChannel} +}; +use crate::participants::{ParticipantList, ParticipantCounter}; + + /// The output of the presigning protocol. /// /// This output is basically all the parts of the signature that we can perform @@ -255,9 +259,12 @@ mod test { use super::*; 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 crate::ecdsa::{ + ot_based_ecdsa::triples, + math::Polynomial + }; + use crate::protocol::run_protocol; + use frost_secp256k1::keys::PublicKeyPackage; use std::collections::BTreeMap; use k256::{ProjectivePoint, Secp256k1}; @@ -294,9 +301,8 @@ mod test { .zip(triple1_shares.into_iter()) { let private_share = f.evaluate(&p.scalar::()); - let dummy_tree: BTreeMap = BTreeMap::new(); 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(), diff --git a/src/ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs similarity index 89% rename from src/ecdsa/sign.rs rename to src/ecdsa/ot_based_ecdsa/sign.rs index 5153a3a..e519079 100644 --- a/src/ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -1,36 +1,17 @@ use elliptic_curve::{ops::Invert, scalar::IsHigh, Field, Group, ScalarPrimitive}; use subtle::ConditionallySelectable; -use crate::protocol::internal::Comms; +use super::presign::PresignOutput; use crate::{ compat::{self, CSCurve}, - ecdsa::presign::PresignOutput, + ecdsa::FullSignature, participants::{ParticipantCounter, ParticipantList}, protocol::{ - internal::{make_protocol, SharedChannel}, + internal::{Comms, 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 { @@ -150,11 +131,12 @@ mod test { }; 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::ot_based_ecdsa::{ + test::{ + assert_public_key_invariant, run_keygen, run_presign, run_reshare, run_sign, + }, + triples::deal, }; - use crate::ecdsa::triples::deal; use crate::{compat::scalar_hash, ecdsa::math::Polynomial, protocol::run_protocol}; #[test] diff --git a/src/ecdsa/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs new file mode 100644 index 0000000..6b9bff9 --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -0,0 +1,103 @@ +use rand_core::OsRng; +use super::presign::{presign, PresignArguments, PresignOutput}; +use super::triples::{self, TriplePub, TripleShare}; + +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_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.clone(); + 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.clone(); + 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/batch_random_ot.rs b/src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs similarity index 100% rename from src/ecdsa/triples/batch_random_ot.rs rename to src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs diff --git a/src/ecdsa/triples/bits.rs b/src/ecdsa/ot_based_ecdsa/triples/bits.rs similarity index 100% rename from src/ecdsa/triples/bits.rs rename to src/ecdsa/ot_based_ecdsa/triples/bits.rs diff --git a/src/ecdsa/triples/constants.rs b/src/ecdsa/ot_based_ecdsa/triples/constants.rs similarity index 100% rename from src/ecdsa/triples/constants.rs rename to src/ecdsa/ot_based_ecdsa/triples/constants.rs diff --git a/src/ecdsa/triples/correlated_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs similarity index 100% rename from src/ecdsa/triples/correlated_ot_extension.rs rename to src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs diff --git a/src/ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs similarity index 100% rename from src/ecdsa/triples/generation.rs rename to src/ecdsa/ot_based_ecdsa/triples/generation.rs diff --git a/src/ecdsa/triples/mod.rs b/src/ecdsa/ot_based_ecdsa/triples/mod.rs similarity index 100% rename from src/ecdsa/triples/mod.rs rename to src/ecdsa/ot_based_ecdsa/triples/mod.rs diff --git a/src/ecdsa/triples/mta.rs b/src/ecdsa/ot_based_ecdsa/triples/mta.rs similarity index 100% rename from src/ecdsa/triples/mta.rs rename to src/ecdsa/ot_based_ecdsa/triples/mta.rs diff --git a/src/ecdsa/triples/multiplication.rs b/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs similarity index 100% rename from src/ecdsa/triples/multiplication.rs rename to src/ecdsa/ot_based_ecdsa/triples/multiplication.rs diff --git a/src/ecdsa/triples/random_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs similarity index 100% rename from src/ecdsa/triples/random_ot_extension.rs rename to src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index ef1305b..5a389c2 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -27,7 +27,7 @@ use crate::{ }, // TODO: The following crates need to be done away with compat::CSCurve, - ecdsa::sign::FullSignature, + ecdsa::FullSignature, }; type Scalar = ::Scalar; @@ -150,7 +150,16 @@ pub fn sign( } - +pub(crate) fn sign_box( + participants: &[Participant], + me: Participant, + public_key: C::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)] mod test { @@ -165,13 +174,21 @@ mod test { use rand_core::OsRng; use super::*; - use crate::ecdsa::robust_ecdsa::test::{run_presign, run_sign}; + use crate::ecdsa::{ + robust_ecdsa::test::run_presign, + test::{ + assert_public_key_invariant, + run_keygen, + run_reshare, + run_sign, + }, + math::Polynomial, + }; - use crate::ecdsa::test::{ - assert_public_key_invariant, run_keygen, run_reshare + use crate::{ + compat::{scalar_hash, x_coordinate}, + protocol::run_protocol }; - use crate::{compat::scalar_hash, ecdsa::math::Polynomial, protocol::run_protocol}; - use crate::compat::x_coordinate; #[test] fn test_sign() -> Result<(), Box> { @@ -302,7 +319,7 @@ mod test { let msg = b"hello world"; - run_sign(presign_result, public_key.to_element().to_affine(), msg); + run_sign(presign_result, public_key.to_element().to_affine(), msg, sign_box); Ok(()) } @@ -347,7 +364,7 @@ mod test { let msg = b"hello world"; - run_sign(presign_result, public_key.to_element().to_affine(), msg); + run_sign(presign_result, public_key.to_element().to_affine(), msg, sign_box); Ok(()) } } diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs index 1b02ff4..3f7236d 100644 --- a/src/ecdsa/robust_ecdsa/test.rs +++ b/src/ecdsa/robust_ecdsa/test.rs @@ -1,18 +1,15 @@ -use k256::{AffinePoint, Secp256k1}; use std::error::Error; -use crate::compat::scalar_hash; - use super::{ presign::{presign, PresignArguments, PresignOutput}, - sign::sign, + sign::sign_box, }; +use crate::ecdsa::test::run_sign; use crate::protocol::{run_protocol, Participant, Protocol}; use crate::ecdsa::{ test::run_keygen, KeygenOutput, - sign::FullSignature, }; pub fn run_presign( @@ -45,35 +42,6 @@ pub fn run_presign( run_protocol(protocols).unwrap() } -#[allow(clippy::type_complexity)] -pub fn run_sign( - participants: Vec<(Participant, PresignOutput)>, - public_key: AffinePoint, - msg: &[u8], -) -> Vec<(Participant, FullSignature)> { - let mut protocols: Vec<( - 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, - p, - public_key, - presign_out, - scalar_hash(msg), - ); - assert!(protocol.is_ok()); - let protocol = protocol.unwrap(); - protocols.push((p, Box::new(protocol))); - } - - run_protocol(protocols).unwrap() -} - #[test] fn test_e2e() -> Result<(), Box> { let participants = vec![ @@ -100,7 +68,7 @@ fn test_e2e() -> Result<(), Box> { let msg = b"hello world"; - run_sign(presign_result, public_key.to_element().to_affine(), msg); + run_sign(presign_result, public_key.to_element().to_affine(), msg, sign_box); Ok(()) } @@ -125,6 +93,6 @@ fn test_e2e_random_identifiers() -> Result<(), Box> { let msg = b"hello world"; - run_sign(presign_result, public_key.to_element().to_affine(), msg); + run_sign(presign_result, public_key.to_element().to_affine(), msg, sign_box); Ok(()) } diff --git a/src/ecdsa/test.rs b/src/ecdsa/test.rs index c7d3339..30de430 100644 --- a/src/ecdsa/test.rs +++ b/src/ecdsa/test.rs @@ -1,19 +1,19 @@ -use k256::{AffinePoint, Secp256k1}; +use k256::{AffinePoint, Secp256k1, Scalar}; use std::error::Error; +use frost_secp256k1::VerifyingKey; use crate::compat::scalar_hash; - -use crate::ecdsa::dkg_ecdsa::{keygen, refresh, reshare}; use crate::ecdsa::{ - presign::{presign, PresignArguments, PresignOutput}, - sign::{sign, FullSignature}, - triples::{self, TriplePub, TripleShare}, KeygenOutput, + FullSignature, + dkg_ecdsa::{keygen, refresh, reshare}, +}; +use crate::protocol::{ + InitializationError, + Participant, + Protocol, + run_protocol, }; -use crate::protocol::{run_protocol, Participant, Protocol}; - -use frost_secp256k1::VerifyingKey; -use rand_core::OsRng; /// runs distributed keygen pub(crate) fn run_keygen( @@ -124,66 +124,32 @@ 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)> { + sign_box: F, +) -> Vec<(Participant, FullSignature)> +where +F: Fn( + &[Participant], + Participant, + AffinePoint, + PresignOutput, + Scalar, +) -> Result>>, InitializationError> +{ let mut protocols: Vec<( Participant, Box>>, - )> = Vec::with_capacity(participants.len()); - - let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); + )> = Vec::with_capacity(participants_outs.len()); - for (p, presign_out) in participants.into_iter() { - let protocol = sign( - &participant_list, + 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, @@ -191,64 +157,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.clone(); - 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.clone(); - 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(()) -} +} \ No newline at end of file From a09bebc1e2561e805f452518e7b4803dc7223284 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Sun, 22 Jun 2025 12:21:21 +0200 Subject: [PATCH 43/52] Unifying some test cases --- src/ecdsa/mod.rs | 2 +- src/ecdsa/ot_based_ecdsa/sign.rs | 135 ++-------------- src/ecdsa/ot_based_ecdsa/test.rs | 152 +++++++++++++++++- .../triples/correlated_ot_extension.rs | 2 +- .../ot_based_ecdsa/triples/generation.rs | 9 +- src/ecdsa/ot_based_ecdsa/triples/mta.rs | 2 +- .../ot_based_ecdsa/triples/multiplication.rs | 2 +- .../triples/random_ot_extension.rs | 2 +- src/ecdsa/robust_ecdsa/sign.rs | 117 -------------- src/ecdsa/robust_ecdsa/test.rs | 129 ++++++++++++++- 10 files changed, 296 insertions(+), 256 deletions(-) diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 3a11600..b4a2760 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -72,4 +72,4 @@ pub mod math; mod test; pub mod robust_ecdsa; -// pub mod ot_based_ecdsa; \ No newline at end of file +pub mod ot_based_ecdsa; \ No newline at end of file diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs index e519079..d1525b3 100644 --- a/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -1,4 +1,7 @@ -use elliptic_curve::{ops::Invert, scalar::IsHigh, Field, Group, ScalarPrimitive}; +use elliptic_curve::{ + scalar::IsHigh, + ScalarPrimitive +}; use subtle::ConditionallySelectable; use super::presign::PresignOutput; @@ -12,20 +15,6 @@ use crate::{ }, }; -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, @@ -123,21 +112,25 @@ pub fn sign( #[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 crate::ecdsa::ot_based_ecdsa::{ - test::{ - assert_public_key_invariant, run_keygen, run_presign, run_reshare, run_sign, - }, - triples::deal, + use super::{ + PresignOutput, + FullSignature, + sign, + }; + use crate::{ + ecdsa::math::Polynomial, + protocol::{run_protocol, Participant,Protocol} + }; + use crate::compat::{ + x_coordinate, + scalar_hash, }; - use crate::{compat::scalar_hash, ecdsa::math::Polynomial, protocol::run_protocol}; #[test] fn test_sign() -> Result<(), Box> { @@ -185,104 +178,10 @@ mod test { let result = run_protocol(protocols)?; let sig = result[0].1.clone(); let sig = - Signature::from_scalars(compat::x_coordinate::(&sig.big_r), sig.s)?; + Signature::from_scalars(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.clone(); - - // 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.clone(); - // 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.clone(); - - // 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.clone(); - // 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/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs index 6b9bff9..b0b20b5 100644 --- a/src/ecdsa/ot_based_ecdsa/test.rs +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -1,6 +1,47 @@ +use std::error::Error; use rand_core::OsRng; -use super::presign::{presign, PresignArguments, PresignOutput}; -use super::triples::{self, TriplePub, TripleShare}; +use k256::Secp256k1; +use super::presign::{ + presign, + PresignArguments, + PresignOutput +}; +use super::triples::{ + deal, + TriplePub, + TripleShare +}; +use super::sign::sign; +use crate::protocol::{ + run_protocol, + Participant, + Protocol, + InitializationError, +}; +use crate::ecdsa::{ + test::{ + run_keygen, + run_reshare, + run_sign, + assert_public_key_invariant, + }, + KeygenOutput, + FullSignature, +}; +// TODO: The following crates need to be done away with +use crate::compat::CSCurve; + + +fn sign_box( + participants: &[Participant], + me: Participant, + public_key: C::AffinePoint, + presignature: PresignOutput, + msg_hash: C::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)>, @@ -46,6 +87,101 @@ pub fn run_presign( 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.clone(); + + // 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.clone(); + // 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, 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.clone(); + + // 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.clone(); + // 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, sign_box); + Ok(()) +} + #[test] fn test_e2e() -> Result<(), Box> { let participants = vec![ @@ -62,15 +198,15 @@ fn test_e2e() -> Result<(), Box> { 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 (pub0, shares0) = deal(&mut OsRng, &participants, threshold); + let (pub1, shares1) = 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); + run_sign(presign_result, public_key.to_element().to_affine(), msg, sign_box); Ok(()) } @@ -90,14 +226,14 @@ fn test_e2e_random_identifiers() -> Result<(), Box> { 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 (pub0, shares0) = deal(&mut OsRng, &participants, threshold); + let (pub1, shares1) = 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); + run_sign(presign_result, public_key.to_element().to_affine(), msg, sign_box); Ok(()) } diff --git a/src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs index ec9cbf6..5e9ee3d 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/correlated_ot_extension.rs @@ -59,7 +59,7 @@ 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; diff --git a/src/ecdsa/ot_based_ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs index 8fe4b71..634e873 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -1,7 +1,6 @@ use elliptic_curve::{Field, Group, ScalarPrimitive}; use rand_core::OsRng; -use crate::ecdsa::triples::multiplication::multiplication_many; use crate::{ compat::{CSCurve, SerializablePoint}, crypto::{ @@ -18,7 +17,11 @@ use crate::{ serde::encode, }; -use super::{multiplication::multiplication, TriplePub, TripleShare}; +use super::{ + multiplication::{multiplication, multiplication_many}, + TriplePub, + TripleShare +}; use crate::protocol::internal::Comms; /// The output of running the triple generation protocol. @@ -1146,7 +1149,7 @@ mod test { use k256::{ProjectivePoint, Secp256k1}; use crate::{ - ecdsa::triples::generate_triple, + ecdsa::ot_based_ecdsa::triples::generate_triple, participants::ParticipantList, protocol::{run_protocol, Participant, Protocol, ProtocolError}, }; diff --git a/src/ecdsa/ot_based_ecdsa/triples/mta.rs b/src/ecdsa/ot_based_ecdsa/triples/mta.rs index 270329d..e0e5478 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/mta.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/mta.rs @@ -174,7 +174,7 @@ mod test { use ecdsa::elliptic_curve::{bigint::Bounded, Curve}; use k256::{Scalar, Secp256k1}; use rand_core::RngCore; - use crate::ecdsa::triples::constants::SECURITY_PARAMETER; + use crate::ecdsa::ot_based_ecdsa::triples::constants::SECURITY_PARAMETER; use super::*; #[test] diff --git a/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs b/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs index 89f6b49..b57d98d 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs @@ -199,7 +199,7 @@ mod test { }; 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] diff --git a/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs index 843a836..16a95ce 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs @@ -243,7 +243,7 @@ fn run_random_ot( #[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::*; diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 5a389c2..0eb616f 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -150,17 +150,6 @@ pub fn sign( } -pub(crate) fn sign_box( - participants: &[Participant], - me: Participant, - public_key: C::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)] mod test { use std::error::Error; @@ -175,13 +164,6 @@ mod test { use super::*; use crate::ecdsa::{ - robust_ecdsa::test::run_presign, - test::{ - assert_public_key_invariant, - run_keygen, - run_reshare, - run_sign, - }, math::Polynomial, }; @@ -268,103 +250,4 @@ mod test { } 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), - 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.clone(); - - // 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.clone(); - - // 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.clone(); - - // 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.clone(); - - // 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(()) - } } diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs index 3f7236d..a5bef84 100644 --- a/src/ecdsa/robust_ecdsa/test.rs +++ b/src/ecdsa/robust_ecdsa/test.rs @@ -1,16 +1,35 @@ use std::error::Error; +use k256::Scalar; use super::{ presign::{presign, PresignArguments, PresignOutput}, - sign::sign_box, + sign::sign, }; -use crate::ecdsa::test::run_sign; -use crate::protocol::{run_protocol, Participant, Protocol}; +use crate::protocol::{run_protocol, Participant, Protocol, InitializationError}; use crate::ecdsa::{ - test::run_keygen, + test::{ + assert_public_key_invariant, + run_keygen, + run_reshare, + run_sign, + }, KeygenOutput, + FullSignature, }; +// TODO: The following crates need to be done away with +use crate::compat::CSCurve; + +fn sign_box( + participants: &[Participant], + me: Participant, + public_key: C::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)>, @@ -42,6 +61,106 @@ pub fn run_presign( 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.clone(); + + // 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.clone(); + + // 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.clone(); + + // 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.clone(); + + // 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![ @@ -95,4 +214,4 @@ fn test_e2e_random_identifiers() -> Result<(), Box> { run_sign(presign_result, public_key.to_element().to_affine(), msg, sign_box); Ok(()) -} +} \ No newline at end of file From b301e6dc5e6e3f2d17ed778296be0ddf38bcc751 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Sun, 29 Jun 2025 01:26:44 +0200 Subject: [PATCH 44/52] Unifying the Curves Traits #1: Fullsignature is made only for k256 tools and Secp256K1Sha256 implements some useful CSCurve functions --- Cargo.toml | 4 +- src/compat/mod.rs | 1 - src/ecdsa/mod.rs | 156 ++++++++++++++++++++++++------- src/ecdsa/ot_based_ecdsa/test.rs | 4 +- src/ecdsa/robust_ecdsa/sign.rs | 27 +++--- src/ecdsa/robust_ecdsa/test.rs | 13 ++- src/ecdsa/test.rs | 8 +- 7 files changed, 146 insertions(+), 67 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 721d883..96d8c8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,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" rand_core = { version = "0.6.4", features = ["getrandom"] } @@ -37,8 +37,6 @@ haisou-chan = { git = "https://github.com/cronokirby/haisou-chan", rev = "d28c46 structopt = "0.3.26" k256 = { version = "0.13.1", features = ["sha256", "ecdsa", "serde"], optional = false } -[features] -k256 = ["dep:k256"] [[example]] name = "network-benches" diff --git a/src/compat/mod.rs b/src/compat/mod.rs index 79cd84f..f7e4da6 100644 --- a/src/compat/mod.rs +++ b/src/compat/mod.rs @@ -39,7 +39,6 @@ pub trait CSCurve: PrimeCurve + CurveArithmetic { fn sample_scalar_constant_time(r: &mut R) -> Self::Scalar; } -#[cfg(any(feature = "k256", test))] mod k256_impl { use super::*; diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index b4a2760..b831ab9 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -1,21 +1,126 @@ //! This module serves as a wrapper for ECDSA scheme. -use elliptic_curve::{ops::Invert, Field, Group}; - -use crate::compat::{CSCurve, x_coordinate}; +use elliptic_curve::{ + bigint::{ArrayEncoding, U256, U512}, + sec1::FromEncodedPoint, + PrimeField, + ops::{Invert, Reduce}, + point::AffineCoordinates, +}; use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; + +use k256::{ProjectivePoint, AffinePoint}; + +use rand_core::CryptoRngCore; + use frost_secp256k1::{ keys::SigningShare, Secp256K1Sha256, + Secp256K1ScalarField, VerifyingKey, + Field, }; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Eq, PartialEq)] +#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] pub struct KeygenOutput { pub private_share: SigningShare, pub public_key: VerifyingKey, } +pub type Scalar = ::Scalar; +/// 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 PointScalarFunctions { + /// Serialize a point with serde. + fn serialize_point( + point: &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) -> Scalar; +} + +impl From> for KeygenOutput { + fn from(value: crate::generic_dkg::KeygenOutput) -> Self { + Self { + private_share: value.private_share, + public_key: value.public_key, + } + } +} + +impl ScalarSerializationFormat for Secp256K1Sha256 { + fn bytes_order() -> BytesOrder { + BytesOrder::BigEndian + } +} + +impl Ciphersuite for Secp256K1Sha256 {} +impl PointScalarFunctions for Secp256K1Sha256{ + fn serialize_point( + point: &AffinePoint, + serializer: S, + ) -> Result { + point.serialize(serializer) + } + + fn deserialize_point<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result { + AffinePoint::deserialize(deserializer) + } + + fn sample_scalar_constant_time(r: &mut R) -> 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, + }; + match Option::::from(AffinePoint::from_encoded_point( + &encoded_point, + )) { + Some(point) => Some(ProjectivePoint::from(point)), + None => None, + } + } +} + +/// 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. /// @@ -27,49 +132,32 @@ pub struct KeygenOutput { /// /// This signature supports all variants by containing big_r entirely #[derive(Clone)] -pub struct FullSignature { +pub struct FullSignature{ /// This is the entire first point. - pub big_r: C::AffinePoint, + pub big_r: AffinePoint, /// This is the second scalar, normalized to be in the lower range. - pub s: C::Scalar, + pub s: Scalar, } -impl FullSignature { +impl FullSignature{ #[must_use] - pub fn verify(&self, public_key: &C::AffinePoint, msg_hash: &C::Scalar) -> bool { - let r: C::Scalar = x_coordinate::(&self.big_r); + // 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 = (C::ProjectivePoint::generator() * (*msg_hash * s_inv)) - + (C::ProjectivePoint::from(*public_key) * (r * s_inv)); - x_coordinate::(&reproduced.into()) == r + let reproduced = (ProjectivePoint::GENERATOR * (*msg_hash * s_inv)) + + (ProjectivePoint::from(*public_key) * (r * s_inv)); + x_coordinate(&reproduced.into()) == r } } -impl From> for KeygenOutput { - fn from(value: crate::generic_dkg::KeygenOutput) -> Self { - Self { - private_share: value.private_share, - public_key: value.public_key, - } - } -} - -impl ScalarSerializationFormat for Secp256K1Sha256 { - fn bytes_order() -> BytesOrder { - BytesOrder::BigEndian - } -} - -impl Ciphersuite for Secp256K1Sha256 {} - pub mod dkg_ecdsa; pub mod math; -#[cfg(test)] -mod test; - pub mod robust_ecdsa; -pub mod ot_based_ecdsa; \ No newline at end of file +pub mod ot_based_ecdsa; +#[cfg(test)] +mod test; \ No newline at end of file diff --git a/src/ecdsa/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs index b0b20b5..679c398 100644 --- a/src/ecdsa/ot_based_ecdsa/test.rs +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -38,9 +38,9 @@ fn sign_box( public_key: C::AffinePoint, presignature: PresignOutput, msg_hash: C::Scalar, -) -> Result>>, InitializationError>{ +) -> Result, InitializationError>{ sign(participants, me, public_key, presignature, msg_hash) - .map(|sig| Box::new(sig) as Box>>) + .map(|sig| Box::new(sig) as Box>) } pub fn run_presign( diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 0eb616f..330cacd 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -4,15 +4,12 @@ use elliptic_curve::{ }; use frost_secp256k1::{ - Secp256K1ScalarField, - Field, keys::{ SigningShare, VerifyingShare, } }; -use super::presign::PresignOutput; use elliptic_curve::CurveArithmetic; use crate::{ @@ -27,11 +24,10 @@ use crate::{ }, // TODO: The following crates need to be done away with compat::CSCurve, - ecdsa::FullSignature, + ecdsa::{FullSignature, Scalar}, }; - -type Scalar = ::Scalar; - +use super::presign::PresignOutput; +use k256::AffinePoint; /// Transforms a verification key of type Secp256k1SHA256 to CSCurve of cait-sith fn from_secp256k1sha256_to_cscurve_point( @@ -57,14 +53,14 @@ fn from_secp256k1sha256_to_cscurve_scalar(private_share: &SigningSha C::from_bytes_to_scalar(bytes).unwrap() } -async fn do_sign( +async fn do_sign( mut chan: SharedChannel, participants: ParticipantList, me: Participant, - public_key: C::AffinePoint, + public_key: AffinePoint, presignature: PresignOutput, msg_hash: Scalar, -) -> Result, ProtocolError> { +) -> Result { let s_me = msg_hash * presignature.alpha_i.to_scalar() + presignature.beta_i.to_scalar(); let s_me = SigningShare::new(s_me); @@ -85,7 +81,7 @@ async fn do_sign( } let s = eval_interpolation(&s_map, None)?; - // Only for formatting + // // Only for formatting let s = from_secp256k1sha256_to_cscurve_scalar::(&s); let big_r = from_secp256k1sha256_to_cscurve_point::(&presignature.big_r)?; @@ -102,7 +98,6 @@ async fn do_sign( s, }; - let msg_hash = from_secp256k1sha256_to_cscurve_scalar::(&SigningShare::new(msg_hash)); if !sig.verify(&public_key, &msg_hash) { return Err(ProtocolError::AssertionFailed( "signature failed to verify".to_string(), @@ -114,13 +109,13 @@ async fn do_sign( // TODO: try to unify both sign functions in robust ecdsa and in ot_based_ecdsa -pub fn sign( +pub fn sign( participants: &[Participant], me: Participant, - public_key: C::AffinePoint, + public_key: AffinePoint, presignature: PresignOutput, msg_hash: Scalar, -) -> Result>, InitializationError> { +) -> Result, InitializationError> { if participants.len() < 2 { return Err(InitializationError::BadParameters(format!( @@ -214,7 +209,7 @@ mod test { #[allow(clippy::type_complexity)] let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants.len()); for p in &participants { let p_scalar = p.scalar::(); diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs index a5bef84..8f333fb 100644 --- a/src/ecdsa/robust_ecdsa/test.rs +++ b/src/ecdsa/robust_ecdsa/test.rs @@ -1,5 +1,4 @@ use std::error::Error; -use k256::Scalar; use super::{ presign::{presign, PresignArguments, PresignOutput}, @@ -16,19 +15,19 @@ use crate::ecdsa::{ }, KeygenOutput, FullSignature, + Scalar, }; -// TODO: The following crates need to be done away with -use crate::compat::CSCurve; +use k256::AffinePoint; -fn sign_box( +fn sign_box( participants: &[Participant], me: Participant, - public_key: C::AffinePoint, + public_key: AffinePoint, presignature: PresignOutput, msg_hash: Scalar, -) -> Result>>, InitializationError>{ +) -> Result>, InitializationError>{ sign(participants, me, public_key, presignature, msg_hash) - .map(|sig| Box::new(sig) as Box>>) + .map(|sig| Box::new(sig) as Box>) } pub fn run_presign( diff --git a/src/ecdsa/test.rs b/src/ecdsa/test.rs index 30de430..7e428c2 100644 --- a/src/ecdsa/test.rs +++ b/src/ecdsa/test.rs @@ -1,4 +1,4 @@ -use k256::{AffinePoint, Secp256k1, Scalar}; +use k256::{AffinePoint, Scalar}; use std::error::Error; use frost_secp256k1::VerifyingKey; @@ -130,7 +130,7 @@ pub fn run_sign ( public_key: AffinePoint, msg: &[u8], sign_box: F, -) -> Vec<(Participant, FullSignature)> +) -> Vec<(Participant, FullSignature)> where F: Fn( &[Participant], @@ -138,11 +138,11 @@ F: Fn( AffinePoint, PresignOutput, Scalar, -) -> Result>>, InitializationError> +) -> Result>, InitializationError> { let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants_outs.len()); let participant_list: Vec = participants_outs.iter().map(|(p, _)| *p).collect(); From a105c271651805f0a94dd0f11f916e716f60a9d1 Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Sun, 29 Jun 2025 07:16:09 +0200 Subject: [PATCH 45/52] Unifying the Curves Traits #2: Doing away (further) with CSCurve trait and integrating AffinePoint and Scalar into the mod as pub types --- src/ecdsa/mod.rs | 4 +- src/ecdsa/ot_based_ecdsa/presign.rs | 8 ++-- src/ecdsa/ot_based_ecdsa/sign.rs | 46 ++++++++++++----------- src/ecdsa/ot_based_ecdsa/test.rs | 28 +++++++------- src/ecdsa/robust_ecdsa/sign.rs | 57 ++++------------------------- src/ecdsa/robust_ecdsa/test.rs | 2 +- 6 files changed, 53 insertions(+), 92 deletions(-) diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index b831ab9..907e1a4 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -9,7 +9,7 @@ use elliptic_curve::{ use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; -use k256::{ProjectivePoint, AffinePoint}; +use k256::ProjectivePoint; use rand_core::CryptoRngCore; @@ -29,6 +29,8 @@ pub struct KeygenOutput { } pub type Scalar = ::Scalar; +pub type AffinePoint = k256::AffinePoint; + /// 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. diff --git a/src/ecdsa/ot_based_ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs index aee2ab6..4f24979 100644 --- a/src/ecdsa/ot_based_ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -22,13 +22,13 @@ use crate::participants::{ParticipantList, ParticipantCounter}; /// 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 { +pub struct PresignOutput { /// The public nonce commitment. - pub big_r: C::AffinePoint, + pub big_r: AffinePoint, /// Our share of the nonce value. - pub k: C::Scalar, + pub k: Scalar, /// Our share of the sigma value. - pub sigma: C::Scalar, + pub sigma: Scalar, } /// The arguments needed to create a presignature. diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs index d1525b3..73bacdf 100644 --- a/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -6,8 +6,12 @@ use subtle::ConditionallySelectable; use super::presign::PresignOutput; use crate::{ - compat::{self, CSCurve}, - ecdsa::FullSignature, + ecdsa::{ + FullSignature, + Scalar, + AffinePoint, + x_coordinate, + }, participants::{ParticipantCounter, ParticipantList}, protocol::{ internal::{Comms, make_protocol, SharedChannel}, @@ -15,14 +19,14 @@ use crate::{ }, }; -async fn do_sign( +async fn do_sign( mut chan: SharedChannel, participants: ParticipantList, me: Participant, - public_key: C::AffinePoint, - presignature: PresignOutput, - msg_hash: C::Scalar, -) -> Result, ProtocolError> { + public_key: AffinePoint, + presignature: PresignOutput, + msg_hash: Scalar, +) -> Result { // Spec 1.1 let lambda = participants.lagrange::(me); let k_i = lambda * presignature.k; @@ -31,31 +35,29 @@ async fn do_sign( 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; + 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(); - { - let s_i: ScalarPrimitive = s_i.into(); - chan.send_many(wait0, &s_i); - } + chan.send_many(wait0, &s_i); // Spec 2.1 + 2.2 let mut seen = ParticipantCounter::new(&participants); - let mut s: C::Scalar = s_i; + let mut s = s_i; seen.put(me); while !seen.full() { - let (from, s_j): (_, ScalarPrimitive) = chan.recv(wait0).await?; + let (from, s_j): (_, Scalar) = chan.recv(wait0).await?; if !seen.put(from) { continue; } - s += C::Scalar::from(s_j) + 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, @@ -75,13 +77,13 @@ async fn do_sign( /// **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( +pub fn sign( participants: &[Participant], me: Participant, - public_key: C::AffinePoint, - presignature: PresignOutput, - msg_hash: C::Scalar, -) -> Result>, InitializationError> { + 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: {}", @@ -156,7 +158,7 @@ mod test { #[allow(clippy::type_complexity)] let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants.len()); for p in &participants { let p_scalar = p.scalar::(); diff --git a/src/ecdsa/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs index 679c398..e4b5e6f 100644 --- a/src/ecdsa/ot_based_ecdsa/test.rs +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -1,6 +1,5 @@ use std::error::Error; use rand_core::OsRng; -use k256::Secp256k1; use super::presign::{ presign, PresignArguments, @@ -27,37 +26,38 @@ use crate::ecdsa::{ }, KeygenOutput, FullSignature, + Scalar, + AffinePoint, + Secp256K1Sha256 }; -// TODO: The following crates need to be done away with -use crate::compat::CSCurve; -fn sign_box( +fn sign_box( participants: &[Participant], me: Participant, - public_key: C::AffinePoint, - presignature: PresignOutput, - msg_hash: C::Scalar, -) -> Result, InitializationError>{ + 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, + shares0: Vec>, + shares1: Vec>, + pub0: &TriplePub, + pub1: &TriplePub, threshold: usize, -) -> Vec<(Participant, PresignOutput)> { +) -> Vec<(Participant, PresignOutput)> { assert!(participants.len() == shares0.len()); assert!(participants.len() == shares1.len()); #[allow(clippy::type_complexity)] let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants.len()); let participant_list: Vec = participants.iter().map(|(p, _)| *p).collect(); diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 330cacd..3033bcd 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -1,16 +1,7 @@ -use elliptic_curve::{ - scalar::IsHigh, - group::Curve, -}; +use elliptic_curve::scalar::IsHigh; -use frost_secp256k1::{ - keys::{ - SigningShare, - VerifyingShare, - } -}; - -use elliptic_curve::CurveArithmetic; +use frost_secp256k1::keys::SigningShare; +use subtle::ConditionallySelectable; use crate::{ crypto::polynomials::eval_interpolation, @@ -22,36 +13,9 @@ use crate::{ InitializationError, Protocol }, - // TODO: The following crates need to be done away with - compat::CSCurve, - ecdsa::{FullSignature, Scalar}, + ecdsa::{FullSignature, Scalar, AffinePoint}, }; use super::presign::PresignOutput; -use k256::AffinePoint; - -/// Transforms a verification key of type Secp256k1SHA256 to CSCurve of cait-sith -fn from_secp256k1sha256_to_cscurve_point( - vshare: &VerifyingShare, -) -> Result<::AffinePoint, ProtocolError> { - // serializes into a canonical byte array buf of length 33 bytes using the affine point representation - let bytes = vshare - .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.to_affine()) -} - -/// Transforms a secret key of type Secp256k1Sha256 to CSCurve of cait-sith -fn from_secp256k1sha256_to_cscurve_scalar(private_share: &SigningShare) -> C::Scalar { - let bytes = private_share.to_scalar().to_bytes(); - let bytes: [u8; 32] = bytes.try_into().expect("Slice is not 32 bytes long"); - C::from_bytes_to_scalar(bytes).unwrap() -} async fn do_sign( mut chan: SharedChannel, @@ -80,18 +44,11 @@ async fn do_sign( s_map.put(from, s_i); } - let s = eval_interpolation(&s_map, None)?; - // // Only for formatting - let s = from_secp256k1sha256_to_cscurve_scalar::(&s); - let big_r = from_secp256k1sha256_to_cscurve_point::(&presignature.big_r)?; + let mut s = eval_interpolation(&s_map, None)?.to_scalar(); + let big_r = presignature.big_r.to_element().to_affine(); // Normalize s - let minus_s = -s; - let s = if s.is_high().into() { - minus_s - }else{ - s - }; + s.conditional_assign(&(-s), s.is_high()); let sig = FullSignature { big_r, diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs index 8f333fb..f56c0be 100644 --- a/src/ecdsa/robust_ecdsa/test.rs +++ b/src/ecdsa/robust_ecdsa/test.rs @@ -16,8 +16,8 @@ use crate::ecdsa::{ KeygenOutput, FullSignature, Scalar, + AffinePoint, }; -use k256::AffinePoint; fn sign_box( participants: &[Participant], From eec657dbdda92b48094486349dcd75db83349fda Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:49:59 +0200 Subject: [PATCH 46/52] Starting to switch the triple to new trait --- TODO.txt | 6 +- src/ecdsa/mod.rs | 3 +- src/ecdsa/ot_based_ecdsa/presign.rs | 106 +++++++----------- src/ecdsa/ot_based_ecdsa/sign.rs | 8 +- src/ecdsa/ot_based_ecdsa/test.rs | 9 +- .../ot_based_ecdsa/triples/generation.rs | 14 +-- src/ecdsa/ot_based_ecdsa/triples/mod.rs | 41 ++++--- 7 files changed, 85 insertions(+), 102 deletions(-) diff --git a/TODO.txt b/TODO.txt index 6494931..930dfc7 100644 --- a/TODO.txt +++ b/TODO.txt @@ -9,4 +9,8 @@ Try adding (if possible) reducing the set of participant signers wrt set of pres Unify the test cases between the different ecdsa implementations -Add the unified test to both ecdsa files \ No newline at end of file +Add the unified test to both ecdsa files + + + +Do away with lagrange:: and rename generic lagrange to lagrange \ No newline at end of file diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 907e1a4..6cd16e7 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -21,6 +21,8 @@ use frost_secp256k1::{ Field, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use k256::AffinePoint; + #[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] pub struct KeygenOutput { @@ -29,7 +31,6 @@ pub struct KeygenOutput { } pub type Scalar = ::Scalar; -pub type AffinePoint = k256::AffinePoint; /// This is the trait that any curve usable in this library must implement. /// This library does provide a few feature-gated implementations for curves diff --git a/src/ecdsa/ot_based_ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs index 4f24979..fe933f9 100644 --- a/src/ecdsa/ot_based_ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -1,4 +1,4 @@ -use elliptic_curve::{Field, Group, ScalarPrimitive}; +use elliptic_curve::Field; use frost_secp256k1::{ VerifyingKey, keys::SigningShare, @@ -7,7 +7,13 @@ use serde::{Deserialize, Serialize}; use super::triples::{TriplePub, TripleShare}; -use crate::ecdsa::KeygenOutput; +use crate::ecdsa::{ + KeygenOutput, + AffinePoint, + Scalar, + ProjectivePoint, + Secp256K1Sha256 +}; use crate::compat::CSCurve; use crate::protocol::{ Participant, @@ -33,11 +39,11 @@ pub struct PresignOutput { /// The arguments needed to create a presignature. #[derive(Debug, Clone)] -pub struct PresignArguments { +pub struct PresignArguments { /// The first triple's public information, and our share. - pub triple0: (TripleShare, TriplePub), + pub triple0: (TripleShare, TriplePub), /// Ditto, for the second triple. - pub triple1: (TripleShare, TriplePub), + 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, @@ -45,53 +51,29 @@ pub struct PresignArguments { pub threshold: usize, } -/// 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_scalar(private_share: &SigningShare) -> C::Scalar { - let bytes = private_share.to_scalar().to_bytes(); - 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 sk_lambda = participants.generic_lagrange::(me); + let bt_lambda = bt_participants.generic_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; @@ -99,36 +81,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_scalar::(&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( @@ -139,11 +113,11 @@ async fn do_presign( if !seen.put(from) { continue; } - kd += C::Scalar::from(kd_j); + kd += Scalar::from(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(), )); @@ -155,18 +129,18 @@ async fn do_presign( seen.clear(); seen.put(me); while !seen.full() { - let (from, (ka_j, xb_j)): (_, (ScalarPrimitive, ScalarPrimitive)) = + 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 += Scalar::from(ka_j); + xb += Scalar::from(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(), @@ -174,10 +148,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 = (ProjectivePoint::from(big_d) * kd_inv).into(); // Spec 2.8 let lambda_diff = bt_lambda * sk_lambda.invert().expect("to invert sk_lambda"); @@ -197,13 +171,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: {}", @@ -291,7 +265,7 @@ mod test { #[allow(clippy::type_complexity)] let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants.len()); for ((p, triple0), triple1) in participants diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs index 73bacdf..5c10816 100644 --- a/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -1,7 +1,4 @@ -use elliptic_curve::{ - scalar::IsHigh, - ScalarPrimitive -}; +use elliptic_curve::scalar::IsHigh; use subtle::ConditionallySelectable; use super::presign::PresignOutput; @@ -11,6 +8,7 @@ use crate::{ Scalar, AffinePoint, x_coordinate, + Secp256K1Sha256, }, participants::{ParticipantCounter, ParticipantList}, protocol::{ @@ -28,7 +26,7 @@ async fn do_sign( msg_hash: Scalar, ) -> Result { // Spec 1.1 - let lambda = participants.lagrange::(me); + let lambda = participants.generic_lagrange::(me); let k_i = lambda * presignature.k; // Spec 1.2 diff --git a/src/ecdsa/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs index e4b5e6f..5c29e5b 100644 --- a/src/ecdsa/ot_based_ecdsa/test.rs +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -28,7 +28,6 @@ use crate::ecdsa::{ FullSignature, Scalar, AffinePoint, - Secp256K1Sha256 }; @@ -45,10 +44,10 @@ fn sign_box( pub fn run_presign( participants: Vec<(Participant, KeygenOutput)>, - shares0: Vec>, - shares1: Vec>, - pub0: &TriplePub, - pub1: &TriplePub, + shares0: Vec, + shares1: Vec, + pub0: &TriplePub, + pub1: &TriplePub, threshold: usize, ) -> Vec<(Participant, PresignOutput)> { assert!(participants.len() == shares0.len()); diff --git a/src/ecdsa/ot_based_ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs index 634e873..d830e2a 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -25,9 +25,9 @@ use super::{ use crate::protocol::internal::Comms; /// 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)>; const LABEL: &[u8] = b"Near threshold signatures triple generation"; @@ -36,7 +36,7 @@ async fn do_generation( 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); @@ -1093,7 +1093,7 @@ 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: {}", @@ -1121,7 +1121,7 @@ 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: {}", @@ -1168,7 +1168,7 @@ mod test { #[allow(clippy::type_complexity)] let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants.len()); for &p in &participants { @@ -1226,7 +1226,7 @@ mod test { #[allow(clippy::type_complexity)] let mut protocols: Vec<( Participant, - Box>>, + Box>, )> = Vec::with_capacity(participants.len()); for &p in &participants { diff --git a/src/ecdsa/ot_based_ecdsa/triples/mod.rs b/src/ecdsa/ot_based_ecdsa/triples/mod.rs index ef0ef71..25e31b5 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/mod.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/mod.rs @@ -25,11 +25,18 @@ //! 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}; +use crate::{ + ecdsa::math::Polynomial, + ecdsa::{ + AffinePoint, + Scalar, + ProjectivePoint, + }, + protocol::Participant +}; /// Represents the public part of a triple. /// @@ -37,10 +44,10 @@ use crate::{compat::CSCurve, ecdsa::math::Polynomial, protocol::Participant}; /// /// 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, +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. @@ -53,23 +60,23 @@ pub struct TriplePub { /// /// 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, +pub struct TripleShare { + pub a: Scalar, + pub b: Scalar, + pub 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( +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); +) -> (TriplePub, Vec) { + let a = Scalar::random(&mut *rng); + let b = Scalar::random(&mut *rng); let c = a * b; let f_a = Polynomial::::extend_random(rng, threshold, &a); @@ -90,9 +97,9 @@ pub fn deal( } 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(), + big_a: (ProjectivePoint::GENERATOR * a).into(), + big_b: (ProjectivePoint::GENERATOR * b).into(), + big_c: (ProjectivePoint::GENERATOR * c).into(), participants: participants_owned, threshold, }; From 362a76edc1e26d7d3f785ad651c69a8f92192630 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Tue, 1 Jul 2025 13:54:01 +0200 Subject: [PATCH 47/52] Polynomial using the polynomials.rs instead of maths.rs --- src/crypto/polynomials.rs | 10 ++-- src/ecdsa/mod.rs | 2 +- src/ecdsa/ot_based_ecdsa/presign.rs | 22 +++---- src/ecdsa/ot_based_ecdsa/sign.rs | 5 +- src/ecdsa/ot_based_ecdsa/test.rs | 20 +++---- .../ot_based_ecdsa/triples/generation.rs | 26 ++++---- src/ecdsa/ot_based_ecdsa/triples/mod.rs | 49 ++------------- src/ecdsa/ot_based_ecdsa/triples/test.rs | 60 +++++++++++++++++++ src/ecdsa/robust_ecdsa/presign.rs | 6 +- src/ecdsa/robust_ecdsa/sign.rs | 28 ++++----- src/generic_dkg.rs | 2 +- 11 files changed, 126 insertions(+), 104 deletions(-) create mode 100644 src/ecdsa/ot_based_ecdsa/triples/test.rs diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs index fbd95a8..d8ce1b8 100644 --- a/src/crypto/polynomials.rs +++ b/src/crypto/polynomials.rs @@ -1,4 +1,4 @@ -use rand_core::OsRng; +use rand_core::CryptoRngCore; use frost_core::{ Scalar, Group, Field, @@ -17,13 +17,15 @@ use crate::{ /// Creates a polynomial p of degree threshold - 1 /// and sets p(0) = secret pub fn generate_secret_polynomial( - secret: Scalar, + secret: Option>, degree: usize, - rng: &mut OsRng, + rng: &mut impl CryptoRngCore, ) -> Vec> { let poly_size = degree+1; let mut coefficients = Vec::with_capacity(poly_size); - // insert the secret share + // 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)); diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 6cd16e7..6791ddf 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -159,7 +159,7 @@ impl FullSignature{ pub mod dkg_ecdsa; -pub mod math; +// pub mod math; pub mod robust_ecdsa; pub mod ot_based_ecdsa; #[cfg(test)] diff --git a/src/ecdsa/ot_based_ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs index fe933f9..8dc216e 100644 --- a/src/ecdsa/ot_based_ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -1,8 +1,4 @@ -use elliptic_curve::Field; -use frost_secp256k1::{ - VerifyingKey, - keys::SigningShare, -}; + use serde::{Deserialize, Serialize}; use super::triples::{TriplePub, TripleShare}; @@ -14,7 +10,6 @@ use crate::ecdsa::{ ProjectivePoint, Secp256K1Sha256 }; -use crate::compat::CSCurve; use crate::protocol::{ Participant, InitializationError, Protocol, ProtocolError, @@ -231,17 +226,18 @@ pub fn presign( #[cfg(test)] mod test { use super::*; - use rand_core::OsRng; use crate::ecdsa::{ - ot_based_ecdsa::triples, - math::Polynomial + ot_based_ecdsa::triples::test::deal, }; use crate::protocol::run_protocol; - use frost_secp256k1::keys::PublicKeyPackage; use std::collections::BTreeMap; - + use rand_core::OsRng; use k256::{ProjectivePoint, Secp256k1}; + use frost_secp256k1::{ + VerifyingKey, + keys::{SigningShare, PublicKeyPackage} + }; #[test] fn test_presign() { @@ -258,9 +254,9 @@ mod test { 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<( diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs index 5c10816..9f93e3f 100644 --- a/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -124,7 +124,6 @@ mod test { sign, }; use crate::{ - ecdsa::math::Polynomial, protocol::{run_protocol, Participant,Protocol} }; use crate::compat::{ @@ -139,11 +138,11 @@ mod test { // Run 4 times for flakiness reasons for _ in 0..4 { - let f = Polynomial::::random(&mut OsRng, threshold); + let f = generate_secret_polynomial::(None, threshold-1, &mut OsRng);; let x = f.evaluate_zero(); let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); - let g = Polynomial::::random(&mut OsRng, threshold); + let g = generate_secret_polynomial::(None, threshold-1, &mut OsRng);; let k: Scalar = g.evaluate_zero(); let big_k = (ProjectivePoint::GENERATOR * k.invert().unwrap()).to_affine(); diff --git a/src/ecdsa/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs index 5c29e5b..ecb1e58 100644 --- a/src/ecdsa/ot_based_ecdsa/test.rs +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -6,9 +6,9 @@ use super::presign::{ PresignOutput }; use super::triples::{ - deal, TriplePub, - TripleShare + TripleShare, + test::deal, }; use super::sign::sign; use crate::protocol::{ @@ -121,8 +121,8 @@ fn test_reshare_sign_more_participants() -> Result<(), Box> { let public_key = key_packages[0].1.public_key.clone(); // Prepare triples - let (pub0, shares0) = deal(&mut OsRng, &new_participant, new_threshold); - let (pub1, shares1) = deal(&mut OsRng, &new_participant, new_threshold); + 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 = @@ -167,8 +167,8 @@ fn test_reshare_sign_less_participants() -> Result<(), Box> { let public_key = key_packages[0].1.public_key.clone(); // Prepare triples - let (pub0, shares0) = deal(&mut OsRng, &new_participant, new_threshold); - let (pub1, shares1) = deal(&mut OsRng, &new_participant, new_threshold); + 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 = @@ -197,8 +197,8 @@ fn test_e2e() -> Result<(), Box> { 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); - let (pub1, shares1) = deal(&mut OsRng, &participants, threshold); + 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); @@ -225,8 +225,8 @@ fn test_e2e_random_identifiers() -> Result<(), Box> { 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); - let (pub1, shares1) = deal(&mut OsRng, &participants, threshold); + 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); diff --git a/src/ecdsa/ot_based_ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs index d830e2a..bb303ef 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -1,4 +1,4 @@ -use elliptic_curve::{Field, Group, ScalarPrimitive}; +use elliptic_curve::{Group, ScalarPrimitive}; use rand_core::OsRng; use crate::{ @@ -7,14 +7,18 @@ use crate::{ commit::{Commitment,commit}, hash::{hash, HashOutput}, random::Randomizer, + polynomials::generate_secret_polynomial }, - ecdsa::math::{GroupPolynomial, Polynomial}, + // ecdsa::math::{GroupPolynomial, Polynomial}, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, proofs::{dlog, dlogeq, strobe_transcript::Transcript}, protocol::{ internal::make_protocol, InitializationError, Participant, Protocol, ProtocolError, }, serde::encode, + ecdsa::{ + Secp256K1Sha256 + } }; use super::{ @@ -28,10 +32,12 @@ use crate::protocol::internal::Comms; pub type TripleGenerationOutput = (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 LABEL: &[u8] = b"Near threshold signatures triple generation"; +const NAME: &[u8] = b"Secp256K1Sha256" +async fn do_generation( comms: Comms, participants: ParticipantList, me: Participant, @@ -42,7 +48,7 @@ async fn do_generation( 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( @@ -51,9 +57,9 @@ 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 = generate_secret_polynomial::(None, threshold-1, &mut rng); + let f = generate_secret_polynomial::(None, threshold-1, &mut rng); + let l = generate_secret_polynomial::(None, threshold-1, &mut rng); // Spec 1.3 l.set_zero(C::Scalar::ZERO); @@ -484,7 +490,7 @@ async fn do_generation( )) } -async fn do_generation_many( +async fn do_generation_many( comms: Comms, participants: ParticipantList, me: Participant, @@ -497,7 +503,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( diff --git a/src/ecdsa/ot_based_ecdsa/triples/mod.rs b/src/ecdsa/ot_based_ecdsa/triples/mod.rs index 25e31b5..f6e04e2 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/mod.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/mod.rs @@ -25,17 +25,14 @@ //! 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 rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use crate::{ - ecdsa::math::Polynomial, ecdsa::{ AffinePoint, Scalar, - ProjectivePoint, }, - protocol::Participant + protocol::Participant, }; /// Represents the public part of a triple. @@ -66,47 +63,6 @@ pub struct TripleShare { pub 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 = Scalar::random(&mut *rng); - let b = 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: (ProjectivePoint::GENERATOR * a).into(), - big_b: (ProjectivePoint::GENERATOR * b).into(), - big_c: (ProjectivePoint::GENERATOR * c).into(), - participants: participants_owned, - threshold, - }; - - (triple_pub, shares) -} - mod batch_random_ot; mod bits; mod correlated_ot_extension; @@ -117,3 +73,6 @@ mod random_ot_extension; mod constants; pub use generation::{generate_triple, generate_triple_many, TripleGenerationOutput}; + +#[cfg(test)] +pub mod test; \ No newline at end of file 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 0000000..79ab727 --- /dev/null +++ b/src/ecdsa/ot_based_ecdsa/triples/test.rs @@ -0,0 +1,60 @@ +use rand_core::CryptoRngCore; + +#[cfg(test)] +use crate::protocol::ProtocolError; +use crate::{ + ecdsa::{ + ProjectivePoint, + Secp256K1Sha256, + Secp256K1ScalarField, + Field, + }, + protocol::Participant, + crypto::polynomials::{ + generate_secret_polynomial, + evaluate_polynomial, + }, +}; +use super::{TriplePub, TripleShare}; +type C = Secp256K1Sha256; + +/// 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 = generate_secret_polynomial::(Some(a), threshold-1, rng); + let f_b = generate_secret_polynomial::(Some(b), threshold-1, rng); + let f_c = generate_secret_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: evaluate_polynomial::(&f_a, *p)?.to_scalar(), + b: evaluate_polynomial::(&f_b, *p)?.to_scalar(), + c: evaluate_polynomial::(&f_c, *p)?.to_scalar(), + }); + } + + 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/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 336a35e..b8e8ec6 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -64,7 +64,7 @@ fn zero_secret_polynomial( rng: &mut OsRng, )-> Vec { let secret = Secp256K1ScalarField::zero(); - generate_secret_polynomial::(secret, degree, rng) + generate_secret_polynomial::(Some(secret), degree, rng) } /// Generates a secret polynomial where the comstant term is random @@ -73,7 +73,7 @@ fn random_secret_polynomial( rng: &mut OsRng, )-> Vec { let secret = Secp256K1ScalarField::random(rng); - generate_secret_polynomial::(secret, degree, rng) + generate_secret_polynomial::(Some(secret), degree, rng) } /// Evaluate five polynomials at once @@ -286,7 +286,7 @@ mod test { use super::*; use rand_core::OsRng; - use crate::{ecdsa::math::Polynomial, protocol::run_protocol}; + use crate::{protocol::run_protocol}; use frost_secp256k1::keys::PublicKeyPackage; use frost_secp256k1::VerifyingKey; use std::collections::BTreeMap; diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 3033bcd..74c5eee 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -64,8 +64,6 @@ async fn do_sign( Ok(sig) } - -// TODO: try to unify both sign functions in robust ecdsa and in ot_based_ecdsa pub fn sign( participants: &[Participant], me: Participant, @@ -109,21 +107,25 @@ mod test { use ecdsa::Signature; use frost_core::keys::{SigningShare, VerifyingShare}; use k256::{ - ecdsa::signature::Verifier, ecdsa::VerifyingKey, ProjectivePoint, PublicKey, Scalar, - Secp256k1, + ecdsa::signature::Verifier, ecdsa::VerifyingKey, PublicKey, }; use rand_core::OsRng; - use super::*; use crate::ecdsa::{ - math::Polynomial, + Scalar, + ProjectivePoint, + Secp256K1Sha256, + x_coordinate, }; + use super::*; use crate::{ - compat::{scalar_hash, x_coordinate}, - protocol::run_protocol + compat::{scalar_hash}, + protocol::run_protocol, + crypto::polynomials::generate_secret_polynomial }; + type C = Secp256K1Sha256; #[test] fn test_sign() -> Result<(), Box> { let max_malicious = 2; @@ -132,16 +134,14 @@ mod test { // Run 4 times to test randomness for _ in 0..4 { - let fx = Polynomial::::random(&mut OsRng, threshold); + let fx = generate_secret_polynomial::(None, threshold-1, &mut OsRng); // master secret key let x = fx.evaluate_zero(); // master public key let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); - - - let fa = Polynomial::::random(&mut OsRng, threshold); - let fk = Polynomial::::random(&mut OsRng, threshold); + let fa = generate_secret_polynomial::(None, threshold-1, &mut OsRng); + let fk = generate_secret_polynomial::(None, threshold-1, &mut OsRng); let fd = Polynomial::::extend_random(&mut OsRng, 2*max_malicious+1, &Scalar::ZERO); let fe = Polynomial::::extend_random(&mut OsRng, 2*max_malicious+1, &Scalar::ZERO); @@ -196,7 +196,7 @@ mod test { let result = run_protocol(protocols)?; let sig = result[0].1.clone(); let sig = - Signature::from_scalars(x_coordinate::(&sig.big_r), sig.s)?; + Signature::from_scalars(x_coordinate(&sig.big_r), sig.s)?; VerifyingKey::from(&PublicKey::from_affine(public_key).unwrap()) .verify(&msg[..], &sig)?; } diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index e886789..a981031 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -382,7 +382,7 @@ async fn do_keyshare( let session_id = domain_separate_hash(domain_separator, &session_ids); domain_separator += 1; // the degree of the polynomial is threshold - 1 - let secret_coefficients = generate_secret_polynomial::(secret, threshold-1, &mut rng); + let secret_coefficients = generate_secret_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); From b318d01b58ac88f4cab1ddcf7ac6b355028637ae Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Tue, 1 Jul 2025 18:12:21 +0200 Subject: [PATCH 48/52] Extra polynomial functions --- src/crypto/polynomials.rs | 33 +++++++++++++-- src/ecdsa/ot_based_ecdsa/sign.rs | 22 ++++++---- .../ot_based_ecdsa/triples/generation.rs | 10 ++--- src/ecdsa/ot_based_ecdsa/triples/test.rs | 16 +++---- src/ecdsa/robust_ecdsa/presign.rs | 40 ++++++++---------- src/ecdsa/robust_ecdsa/sign.rs | 42 +++++++++++-------- src/ecdsa/test.rs | 2 +- src/generic_dkg.rs | 8 ++-- src/protocol/mod.rs | 7 ---- 9 files changed, 103 insertions(+), 77 deletions(-) diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs index d8ce1b8..6a01782 100644 --- a/src/crypto/polynomials.rs +++ b/src/crypto/polynomials.rs @@ -5,7 +5,8 @@ use frost_core::{ keys::{ SigningShare, VerifyingShare, - } + }, + Identifier, }; use super::ciphersuite::Ciphersuite; @@ -16,7 +17,8 @@ use crate::{ /// Creates a polynomial p of degree threshold - 1 /// and sets p(0) = secret -pub fn generate_secret_polynomial( +/// if the secret is not given then it is picked at random +pub fn generate_polynomial( secret: Option>, degree: usize, rng: &mut impl CryptoRngCore, @@ -33,12 +35,35 @@ pub fn generate_secret_polynomial( coefficients } -/// Evaluates a polynomial on the identifier of a participant +/// Basically returns the constant term +/// Evaluate the polynomial with the given coefficients (constant term first) +pub fn evaluate_polynomial_on_zero( + coefficients: &[Scalar], +) -> SigningShare { + SigningShare::new(coefficients[0]) +} + +/// Evaluates a polynomial on a certain scalar /// Evaluate the polynomial with the given coefficients (constant term first) /// at the point x=identifier using Horner's method. /// Implements [`polynomial_evaluate`] from the spec. /// [`polynomial_evaluate`]: https://datatracker.ietf.org/doc/html/rfc9591#name-additional-polynomial-opera pub fn evaluate_polynomial( + coefficients: &[Scalar], + point: Scalar, +) -> Result, ProtocolError> { + // creating this dummy id is only to be able to call the from_coefficients function + let point_id = Identifier::new(point); + if point_id.is_err(){ + evaluate_polynomial_on_zero(coefficients) + } else{ + SigningShare::from_coefficients(coefficients, point_id) + } +} + + +/// Evaluates a polynomial on the identifier of a participant +pub fn evaluate_polynomial_on_participant( coefficients: &[Scalar], participant: Participant, ) -> Result, ProtocolError> { @@ -55,7 +80,7 @@ pub fn evaluate_multi_polynomials( let mut result_vec = Vec::with_capacity(N); for poly in polynomials.iter() { - let eval = evaluate_polynomial::(poly, participant)?; + let eval = evaluate_polynomial_on_participant::(poly, participant)?; result_vec.push(eval); } Ok(result_vec diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs index 9f93e3f..084975e 100644 --- a/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -121,16 +121,22 @@ mod test { use super::{ PresignOutput, FullSignature, + Secp256K1Sha256, sign, + x_coordinate, }; use crate::{ - protocol::{run_protocol, Participant,Protocol} + protocol::{run_protocol, Participant,Protocol}, + crypto::polynomials::{ + generate_polynomial, + evaluate_polynomial_on_zero, + }, }; use crate::compat::{ - x_coordinate, scalar_hash, }; + type C = Secp256K1Sha256; #[test] fn test_sign() -> Result<(), Box> { let threshold = 2; @@ -138,18 +144,18 @@ mod test { // Run 4 times for flakiness reasons for _ in 0..4 { - let f = generate_secret_polynomial::(None, threshold-1, &mut OsRng);; - let x = f.evaluate_zero(); + let f = generate_polynomial::(None, threshold-1, &mut OsRng);; + let x = evaluate_polynomial_on_zero::(f); let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); - let g = generate_secret_polynomial::(None, threshold-1, &mut OsRng);; + let g = generate_polynomial::(None, threshold-1, &mut OsRng);; - let k: Scalar = g.evaluate_zero(); + let k= evaluate_polynomial_on_zero::(g); let big_k = (ProjectivePoint::GENERATOR * k.invert().unwrap()).to_affine(); let sigma = k * x; - let h = Polynomial::::extend_random(&mut OsRng, threshold, &sigma); + let h = generate_polynomial::(Some(sigma), threshold-1,&mut OsRng); let participants = vec![Participant::from(0u32), Participant::from(1u32)]; #[allow(clippy::type_complexity)] @@ -177,7 +183,7 @@ mod test { let result = run_protocol(protocols)?; let sig = result[0].1.clone(); let sig = - Signature::from_scalars(x_coordinate::(&sig.big_r), sig.s)?; + Signature::from_scalars(x_coordinate(&sig.big_r), sig.s)?; VerifyingKey::from(&PublicKey::from_affine(public_key).unwrap()) .verify(&msg[..], &sig)?; } diff --git a/src/ecdsa/ot_based_ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs index bb303ef..9c5474c 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -7,7 +7,7 @@ use crate::{ commit::{Commitment,commit}, hash::{hash, HashOutput}, random::Randomizer, - polynomials::generate_secret_polynomial + polynomials::generate_polynomial }, // ecdsa::math::{GroupPolynomial, Polynomial}, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, @@ -36,7 +36,7 @@ type C = Secp256K1Sha256; const LABEL: &[u8] = b"Near threshold signatures triple generation"; -const NAME: &[u8] = b"Secp256K1Sha256" +const NAME: &[u8] = b"Secp256K1Sha256"; async fn do_generation( comms: Comms, participants: ParticipantList, @@ -57,9 +57,9 @@ async fn do_generation( ); // Spec 1.2 - let e = generate_secret_polynomial::(None, threshold-1, &mut rng); - let f = generate_secret_polynomial::(None, threshold-1, &mut rng); - let l = generate_secret_polynomial::(None, threshold-1, &mut rng); + let e = generate_polynomial::(None, threshold-1, &mut rng); + let f = generate_polynomial::(None, threshold-1, &mut rng); + let l = generate_polynomial::(None, threshold-1, &mut rng); // Spec 1.3 l.set_zero(C::Scalar::ZERO); diff --git a/src/ecdsa/ot_based_ecdsa/triples/test.rs b/src/ecdsa/ot_based_ecdsa/triples/test.rs index 79ab727..8294997 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/test.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/test.rs @@ -11,8 +11,8 @@ use crate::{ }, protocol::Participant, crypto::polynomials::{ - generate_secret_polynomial, - evaluate_polynomial, + generate_polynomial, + evaluate_polynomial_on_participant, }, }; use super::{TriplePub, TripleShare}; @@ -33,9 +33,9 @@ pub fn deal( let b = Secp256K1ScalarField::random(&mut *rng); let c = a * b; - let f_a = generate_secret_polynomial::(Some(a), threshold-1, rng); - let f_b = generate_secret_polynomial::(Some(b), threshold-1, rng); - let f_c = generate_secret_polynomial::(Some(c), threshold-1, rng); + let f_a = generate_polynomial::(Some(a), threshold-1, rng); + let f_b = generate_polynomial::(Some(b), threshold-1, rng); + let f_c = generate_polynomial::(Some(c), threshold-1, rng); let mut shares = Vec::with_capacity(participants.len()); let mut participants_owned = Vec::with_capacity(participants.len()); @@ -43,9 +43,9 @@ pub fn deal( for p in participants { participants_owned.push(*p); shares.push(TripleShare { - a: evaluate_polynomial::(&f_a, *p)?.to_scalar(), - b: evaluate_polynomial::(&f_b, *p)?.to_scalar(), - c: evaluate_polynomial::(&f_c, *p)?.to_scalar(), + a: evaluate_polynomial_on_participant::(&f_a, *p)?.to_scalar(), + b: evaluate_polynomial_on_participant::(&f_b, *p)?.to_scalar(), + c: evaluate_polynomial_on_participant::(&f_c, *p)?.to_scalar(), }); } diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index b8e8ec6..9d65d44 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize}; use crate::{ crypto::polynomials::{ evaluate_multi_polynomials, - generate_secret_polynomial, + generate_polynomial, eval_interpolation, eval_exponent_interpolation, }, @@ -64,16 +64,7 @@ fn zero_secret_polynomial( rng: &mut OsRng, )-> Vec { let secret = Secp256K1ScalarField::zero(); - generate_secret_polynomial::(Some(secret), degree, rng) -} - -/// Generates a secret polynomial where the comstant term is random -fn random_secret_polynomial( - degree: usize, - rng: &mut OsRng, -)-> Vec { - let secret = Secp256K1ScalarField::random(rng); - generate_secret_polynomial::(Some(secret), degree, rng) + generate_polynomial::(Some(secret), degree, rng) } /// Evaluate five polynomials at once @@ -85,7 +76,6 @@ fn evaluate_five_polynomials( Ok(package) } - /// /!\ Warning: the threshold in this scheme is the exactly the /// same as the max number of malicious parties. async fn do_presign( @@ -98,9 +88,9 @@ async fn do_presign( // Round 0 let mut rng = OsRng; // degree t random secret shares where t is the max number of malicious parties - let my_fk = random_secret_polynomial(threshold, &mut rng); + let my_fk = generate_polynomial::(None, threshold, &mut rng); let my_fk = my_fk.as_slice(); - let my_fa = random_secret_polynomial(threshold, &mut rng); + let my_fa = generate_polynomial::(None, threshold, &mut rng); let my_fa = my_fa.as_slice(); // degree 2t zero secret shares where t is the max number of malicious parties @@ -286,12 +276,18 @@ mod test { use super::*; use rand_core::OsRng; - use crate::{protocol::run_protocol}; + use crate::{ + protocol::run_protocol, + crypto::polynomials::{ + generate_polynomial, + evaluate_polynomial_on_participant, + }, + }; use frost_secp256k1::keys::PublicKeyPackage; use frost_secp256k1::VerifyingKey; use std::collections::BTreeMap; - use k256::{ProjectivePoint, Secp256k1}; + use k256::{ProjectivePoint}; #[test] fn test_presign() { @@ -304,7 +300,7 @@ mod test { ]; let max_malicious = 2; - let f = Polynomial::::random(&mut OsRng, max_malicious+1); + let f = generate_polynomial::(None, max_malicious, &mut OsRng); let big_x = ProjectivePoint::GENERATOR * f.evaluate_zero(); @@ -314,19 +310,19 @@ mod test { Box>, )> = Vec::with_capacity(participants.len()); - for p in participants.iter(){ + for p in participants{ // simulating the key packages for each participant - let private_share = f.evaluate(&p.scalar::()); + let private_share = evaluate_polynomial_on_participant::(&f, p).unwrap(); 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), + private_share, public_key: *public_key_package.verifying_key(), }; let protocol = presign( &participants[..], - *p, + p, PresignArguments { keygen_out, threshold: max_malicious, @@ -334,7 +330,7 @@ mod test { ); assert!(protocol.is_ok()); let protocol = protocol.unwrap(); - protocols.push((*p, Box::new(protocol))); + protocols.push((p, Box::new(protocol))); } let result = run_protocol(protocols); diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 74c5eee..0647fcd 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -112,9 +112,10 @@ mod test { use rand_core::OsRng; use crate::ecdsa::{ - Scalar, ProjectivePoint, Secp256K1Sha256, + Secp256K1ScalarField, + Field, x_coordinate, }; use super::*; @@ -122,7 +123,11 @@ mod test { use crate::{ compat::{scalar_hash}, protocol::run_protocol, - crypto::polynomials::generate_secret_polynomial + crypto::polynomials::{ + generate_polynomial, + evaluate_polynomial_on_participant, + evaluate_polynomial_on_zero, + } }; type C = Secp256K1Sha256; @@ -134,25 +139,26 @@ mod test { // Run 4 times to test randomness for _ in 0..4 { - let fx = generate_secret_polynomial::(None, threshold-1, &mut OsRng); + let fx = generate_polynomial::(None, threshold-1, &mut OsRng); // master secret key - let x = fx.evaluate_zero(); + let x = evaluate_polynomial_on_zero(&fx).to_scalar(); // master public key - let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); + let public_key = ProjectivePoint::GENERATOR * x; + let public_key = public_key.to_affine(); - let fa = generate_secret_polynomial::(None, threshold-1, &mut OsRng); - let fk = generate_secret_polynomial::(None, threshold-1, &mut OsRng); + let fa = generate_polynomial::(None, threshold-1, &mut OsRng); + let fk = generate_polynomial::(None, threshold-1, &mut OsRng); - let fd = Polynomial::::extend_random(&mut OsRng, 2*max_malicious+1, &Scalar::ZERO); - let fe = Polynomial::::extend_random(&mut OsRng, 2*max_malicious+1, &Scalar::ZERO); + let fd = generate_polynomial::(Some(Secp256K1ScalarField::zero()), 2*max_malicious, &mut OsRng); + let fe = generate_polynomial::(Some(Secp256K1ScalarField::zero()), 2*max_malicious, &mut OsRng); - let k = fk.evaluate_zero(); - let big_r = ProjectivePoint::GENERATOR * k; - let big_r_x_coordinate = x_coordinate::(&big_r.to_affine()); + let k = evaluate_polynomial_on_zero(&fk).to_scalar(); + let big_r = ProjectivePoint::GENERATOR * k.clone(); + let big_r_x_coordinate = x_coordinate(&big_r.to_affine()); let big_r = VerifyingShare::new(big_r); - let w = fa.evaluate_zero()* k; + let w = evaluate_polynomial_on_zero(&fa).to_scalar() * k; let w_invert = w.invert().unwrap(); let participants = vec![ @@ -168,11 +174,11 @@ mod test { Participant, Box>, )> = Vec::with_capacity(participants.len()); - for p in &participants { - let p_scalar = p.scalar::(); - let h_i = fa.evaluate(&p_scalar) *w_invert; - let alpha_i = h_i + fd.evaluate(&p_scalar); - let beta_i = h_i * big_r_x_coordinate * fx.evaluate(&p_scalar) + fe.evaluate(&p_scalar); + for p in participants { + let h_i = evaluate_polynomial_on_participant(&fa, p).unwrap() *w_invert; + let alpha_i = h_i + evaluate_polynomial_on_participant(&fd, p).unwrap(); + let beta_i = h_i * big_r_x_coordinate * evaluate_polynomial_on_participant(&fx, p).unwrap() + + evaluate_polynomial_on_participant(&fe, p).unwrap(); let alpha_i = SigningShare::new(alpha_i); let beta_i = SigningShare::new(beta_i); diff --git a/src/ecdsa/test.rs b/src/ecdsa/test.rs index 7e428c2..0525f8f 100644 --- a/src/ecdsa/test.rs +++ b/src/ecdsa/test.rs @@ -23,7 +23,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))); } diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index a981031..2241655 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -1,7 +1,7 @@ use crate::crypto::{ ciphersuite::Ciphersuite, hash::{HashOutput, domain_separate_hash}, - polynomials::{evaluate_polynomial, generate_secret_polynomial}, + polynomials::{evaluate_polynomial_on_participant, generate_polynomial}, }; use crate::echo_broadcast::do_broadcast; use crate::participants::{ParticipantCounter, ParticipantList, ParticipantMap}; @@ -382,7 +382,7 @@ async fn do_keyshare( let session_id = domain_separate_hash(domain_separator, &session_ids); domain_separator += 1; // the degree of the polynomial is threshold - 1 - let secret_coefficients = generate_secret_polynomial::(Some(secret), threshold-1, &mut rng); + let secret_coefficients = 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); @@ -489,7 +489,7 @@ async fn do_keyshare( 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)?; + let signing_share_to_p = evaluate_polynomial_on_participant::(&secret_coefficients, p)?; // send the evaluation privately to participant p chan.send_private(wait_round_3, p, &signing_share_to_p); } @@ -497,7 +497,7 @@ async fn do_keyshare( // Start Round 4 // compute my secret evaluation of my private polynomial - let mut my_signing_share = evaluate_polynomial::(&secret_coefficients, me)?.to_scalar(); + let mut my_signing_share = evaluate_polynomial_on_participant::(&secret_coefficients, me)?.to_scalar(); // receive evaluations from all participants seen.clear(); seen.put(me); diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index 67ab688..7fc1272 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -7,7 +7,6 @@ //! to serialize the emssages it produces. use std::{collections::HashMap, error, fmt}; -use crate::compat::CSCurve; use ::serde::{Deserialize, Serialize}; use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite}; @@ -149,12 +148,6 @@ impl Participant { self.0.to_le_bytes() } - /// 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 { let mut bytes = [0u8; 32]; From 89a9b157f660875e0dc853e98740c08622512bbe Mon Sep 17 00:00:00 2001 From: Simon Rastikian <157731593+srastikian@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:41:58 +0200 Subject: [PATCH 49/52] CSCurve being removed from triple generation --- src/compat/mod.rs | 245 +++++++++--------- src/crypto/polynomials.rs | 5 +- src/crypto/proofs/dlog.rs | 11 +- src/crypto/proofs/dlogeq.rs | 1 - src/ecdsa/mod.rs | 1 - src/ecdsa/ot_based_ecdsa/presign.rs | 44 ++-- src/ecdsa/ot_based_ecdsa/sign.rs | 20 +- .../ot_based_ecdsa/triples/generation.rs | 76 +++--- src/ecdsa/ot_based_ecdsa/triples/mta.rs | 2 +- .../triples/random_ot_extension.rs | 2 +- src/ecdsa/robust_ecdsa/presign.rs | 11 +- src/ecdsa/robust_ecdsa/sign.rs | 22 +- src/lib.rs | 7 - src/participants.rs | 23 +- 14 files changed, 229 insertions(+), 241 deletions(-) diff --git a/src/compat/mod.rs b/src/compat/mod.rs index f7e4da6..bc1460d 100644 --- a/src/compat/mod.rs +++ b/src/compat/mod.rs @@ -2,95 +2,95 @@ use elliptic_curve::{ops::Reduce, point::AffineCoordinates, Curve, CurveArithmet use rand_core::CryptoRngCore; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -/// 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; -} - -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, - }; - match Option::::from(Self::AffinePoint::from_encoded_point( - &encoded_point, - )) { - Some(point) => Some(Self::ProjectivePoint::from(point)), - None => None, - } - } - } -} +// /// 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; +// } + +// 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, +// }; +// match Option::::from(Self::AffinePoint::from_encoded_point( +// &encoded_point, +// )) { +// Some(point) => Some(Self::ProjectivePoint::from(point)), +// None => None, +// } +// } +// } +// } #[cfg(test)] mod test_scalar_hash { @@ -112,39 +112,34 @@ mod test_scalar_hash { #[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()) -} +// #[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)) +// } +// } \ No newline at end of file diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs index 6a01782..448dfb0 100644 --- a/src/crypto/polynomials.rs +++ b/src/crypto/polynomials.rs @@ -51,12 +51,13 @@ pub fn evaluate_polynomial_on_zero( pub fn evaluate_polynomial( coefficients: &[Scalar], point: Scalar, -) -> Result, ProtocolError> { +) -> SigningShare { // creating this dummy id is only to be able to call the from_coefficients function let point_id = Identifier::new(point); if point_id.is_err(){ - evaluate_polynomial_on_zero(coefficients) + evaluate_polynomial_on_zero::(coefficients) } else{ + let point_id = point_id.unwrap(); SigningShare::from_coefficients(coefficients, point_id) } } diff --git a/src/crypto/proofs/dlog.rs b/src/crypto/proofs/dlog.rs index 6659c18..3349273 100644 --- a/src/crypto/proofs/dlog.rs +++ b/src/crypto/proofs/dlog.rs @@ -1,9 +1,10 @@ use crate::{ - compat::{CSCurve, SerializablePoint}, + compat::SerializablePoint, serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, + crypto::ciphersuite::Ciphersuite, + Group, }; use super::strobe_transcript::Transcript; -use elliptic_curve::{Field, Group}; use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; @@ -18,12 +19,12 @@ const CHALLENGE_LABEL: &[u8] = b"dlog proof challenge"; /// /// This statement claims knowledge of the discrete logarithm of some point. #[derive(Debug, Clone, Copy, Serialize)] -pub struct Statement<'a, C: CSCurve> { +pub struct Statement<'a, C: Ciphersuite> { #[serde(serialize_with = "serialize_projective_point::")] - pub public: &'a C::ProjectivePoint, + pub public: &'a Ciphersuite::Group::Element, } -impl<'a, C: CSCurve> Statement<'a, C> { +impl<'a, C: Ciphersuite> Statement<'a, C> { /// Calculate the homomorphism we want to prove things about. fn phi(&self, x: &C::Scalar) -> C::ProjectivePoint { C::ProjectivePoint::generator() * x diff --git a/src/crypto/proofs/dlogeq.rs b/src/crypto/proofs/dlogeq.rs index 2cdf531..323f17d 100644 --- a/src/crypto/proofs/dlogeq.rs +++ b/src/crypto/proofs/dlogeq.rs @@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize}; use super::strobe_transcript::Transcript; use crate::{ - compat::{CSCurve, SerializablePoint}, serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, }; diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 6791ddf..ec94412 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -159,7 +159,6 @@ impl FullSignature{ pub mod dkg_ecdsa; -// pub mod math; pub mod robust_ecdsa; pub mod ot_based_ecdsa; #[cfg(test)] diff --git a/src/ecdsa/ot_based_ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs index 8dc216e..bd45b77 100644 --- a/src/ecdsa/ot_based_ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -17,7 +17,6 @@ use crate::protocol::{ }; use crate::participants::{ParticipantList, ParticipantCounter}; - /// The output of the presigning protocol. /// /// This output is basically all the parts of the signature that we can perform @@ -108,7 +107,7 @@ async fn do_presign( if !seen.put(from) { continue; } - kd += Scalar::from(kd_j); + kd += kd_j; } // Spec 2.3 @@ -129,8 +128,8 @@ async fn do_presign( if !seen.put(from) { continue; } - ka += Scalar::from(ka_j); - xb += Scalar::from(xb_j); + ka += ka_j; + xb += xb_j; } // Spec 2.6 @@ -146,7 +145,7 @@ async fn do_presign( 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 = (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"); @@ -226,19 +225,27 @@ pub fn presign( #[cfg(test)] mod test { use super::*; - - use crate::ecdsa::{ - ot_based_ecdsa::triples::test::deal, + use crate::{ + ecdsa::{ + ot_based_ecdsa::triples::test::deal, + ProjectivePoint, + Secp256K1Sha256 + }, + protocol::run_protocol, + crypto::polynomials::{ + generate_polynomial, + evaluate_polynomial_on_zero, + evaluate_polynomial_on_participant, + }, }; - use crate::protocol::run_protocol; use std::collections::BTreeMap; use rand_core::OsRng; - use k256::{ProjectivePoint, Secp256k1}; use frost_secp256k1::{ VerifyingKey, keys::{SigningShare, PublicKeyPackage} }; + type C = Secp256K1Sha256; #[test] fn test_presign() { let participants = vec![ @@ -248,8 +255,8 @@ 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 = generate_polynomial::(None, original_threshold-1, &mut OsRng); + let big_x = ProjectivePoint::GENERATOR * evaluate_polynomial_on_zero::(&f).to_scalar(); let threshold = 2; @@ -270,7 +277,8 @@ mod test { .zip(triple0_shares.into_iter()) .zip(triple1_shares.into_iter()) { - let private_share = f.evaluate(&p.scalar::()); + let private_share = evaluate_polynomial_on_participant::(&f, *p).unwrap().to_scalar(); + // let private_share = f.evaluate(&p.scalar::()); let verifying_key = VerifyingKey::new(big_x); let public_key_package = PublicKeyPackage::new(BTreeMap::new(), verifying_key); let keygen_out = KeygenOutput { @@ -309,11 +317,11 @@ mod test { let k_shares = vec![result[0].1.k, result[1].1.k]; let sigma_shares = vec![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.generic_lagrange::(participants[0]) * k_shares[0] + + p_list.generic_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.generic_lagrange::(participants[0]) * sigma_shares[0] + + p_list.generic_lagrange::(participants[1]) * sigma_shares[1]; + assert_eq!(sigma, k * evaluate_polynomial_on_zero::(&f).to_scalar()); } } diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs index 084975e..45feb4b 100644 --- a/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -114,8 +114,10 @@ mod test { use std::error::Error; use ecdsa::Signature; use k256::{ - ecdsa::signature::Verifier, ecdsa::VerifyingKey, ProjectivePoint, PublicKey, Scalar, - Secp256k1, + ecdsa::signature::Verifier, + ecdsa::VerifyingKey, + ProjectivePoint, + PublicKey, }; use rand_core::OsRng; use super::{ @@ -130,6 +132,7 @@ mod test { crypto::polynomials::{ generate_polynomial, evaluate_polynomial_on_zero, + evaluate_polynomial_on_participant, }, }; use crate::compat::{ @@ -145,12 +148,12 @@ mod test { // Run 4 times for flakiness reasons for _ in 0..4 { let f = generate_polynomial::(None, threshold-1, &mut OsRng);; - let x = evaluate_polynomial_on_zero::(f); + let x = evaluate_polynomial_on_zero::(&f).to_scalar(); let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); let g = generate_polynomial::(None, threshold-1, &mut OsRng);; - let k= evaluate_polynomial_on_zero::(g); + let k = evaluate_polynomial_on_zero::(&g).to_scalar(); let big_k = (ProjectivePoint::GENERATOR * k.invert().unwrap()).to_affine(); let sigma = k * x; @@ -164,11 +167,14 @@ mod test { 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), + k: evaluate_polynomial_on_participant::(&g, *p) + .unwrap() + .to_scalar(), + sigma: evaluate_polynomial_on_participant::(&h, *p) + .unwrap() + .to_scalar(), }; let protocol = sign( &participants, diff --git a/src/ecdsa/ot_based_ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs index 9c5474c..03e2021 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -11,13 +11,16 @@ use crate::{ }, // ecdsa::math::{GroupPolynomial, Polynomial}, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, - proofs::{dlog, dlogeq, strobe_transcript::Transcript}, + crypto::proofs::{dlog, dlogeq, strobe_transcript::Transcript}, protocol::{ internal::make_protocol, InitializationError, Participant, Protocol, ProtocolError, }, serde::encode, ecdsa::{ - Secp256K1Sha256 + Secp256K1Sha256, + Secp256K1ScalarField, + Field, + Scalar, } }; @@ -59,10 +62,8 @@ async fn do_generation( // Spec 1.2 let e = generate_polynomial::(None, threshold-1, &mut rng); let f = generate_polynomial::(None, threshold-1, &mut rng); - let l = generate_polynomial::(None, threshold-1, &mut rng); - // Spec 1.3 - l.set_zero(C::Scalar::ZERO); + let l = generate_polynomial::(Some(Secp256K1ScalarField::zero()), threshold-1, &mut rng); // Spec 1.4 let big_e_i = e.commit(); @@ -163,8 +164,8 @@ 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: Scalar = e.evaluate(&p.scalar::()).into(); + let b_i_j: Scalar = f.evaluate(&p.scalar::()).into(); chan.send_private(wait3, p, &(a_i_j, b_i_j)); } let mut a_i = e.evaluate(&me.scalar::()); @@ -279,7 +280,7 @@ async fn do_generation( 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)): (_, (Scalar, Scalar)) = chan.recv(wait3).await?; if !seen.put(from) { continue; @@ -410,7 +411,7 @@ async fn do_generation( l.set_zero(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: Scalar = l.evaluate(&p.scalar::()).into(); chan.send_private(wait6, p, &c_i_j); } let mut c_i = l.evaluate(&me.scalar::()); @@ -456,7 +457,7 @@ 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): (_, Scalar) = chan.recv(wait6).await?; if !seen.put(from) { continue; } @@ -669,8 +670,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: Scalar = e.evaluate(&p.scalar::()).into(); + let b_i_j: Scalar = f.evaluate(&p.scalar::()).into(); a_i_j_v.push(a_i_j); b_i_j_v.push(b_i_j); } @@ -810,7 +811,7 @@ async fn do_generation_many( while !seen.full() { let (from, (a_j_i_v, b_j_i_v)): ( _, - (Vec>, Vec>), + (Vec, Vec), ) = chan.recv(wait3).await?; if !seen.put(from) { continue; @@ -972,7 +973,7 @@ async fn do_generation_many( let mut c_i_j_v = Vec::new(); for i in 0..N { let l = &mut l_v[i]; - let c_i_j: ScalarPrimitive = l.evaluate(&p.scalar::()).into(); + let c_i_j: Scalar = l.evaluate(&p.scalar::()).into(); c_i_j_v.push(c_i_j); } chan.send_private(wait6, p, &c_i_j_v); @@ -1039,7 +1040,7 @@ 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; } @@ -1152,16 +1153,19 @@ pub fn generate_triple_many( #[cfg(test)] mod test { - use k256::{ProjectivePoint, Secp256k1}; - use crate::{ - ecdsa::ot_based_ecdsa::triples::generate_triple, + ecdsa::{ + ot_based_ecdsa::triples::generate_triple, + ProjectivePoint, + Secp256K1Sha256 + }, participants::ParticipantList, protocol::{run_protocol, Participant, Protocol, ProtocolError}, }; use super::{generate_triple_many, TripleGenerationOutput, TripleGenerationOutputMany}; + type C = Secp256K1Sha256; #[test] fn test_triple_generation() -> Result<(), ProtocolError> { let participants = vec![ @@ -1200,19 +1204,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.generic_lagrange::(participants[0]) * triple_shares[0].a + + p_list.generic_lagrange::(participants[1]) * triple_shares[1].a + + p_list.generic_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.generic_lagrange::(participants[0]) * triple_shares[0].b + + p_list.generic_lagrange::(participants[1]) * triple_shares[1].b + + p_list.generic_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.generic_lagrange::(participants[0]) * triple_shares[0].c + + p_list.generic_lagrange::(participants[1]) * triple_shares[1].c + + p_list.generic_lagrange::(participants[2]) * triple_shares[2].c; assert_eq!(ProjectivePoint::GENERATOR * c, triple_pub.big_c); assert_eq!(a * b, c); @@ -1258,19 +1262,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.generic_lagrange::(participants[0]) * triple_shares[0].a + + p_list.generic_lagrange::(participants[1]) * triple_shares[1].a + + p_list.generic_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.generic_lagrange::(participants[0]) * triple_shares[0].b + + p_list.generic_lagrange::(participants[1]) * triple_shares[1].b + + p_list.generic_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.generic_lagrange::(participants[0]) * triple_shares[0].c + + p_list.generic_lagrange::(participants[1]) * triple_shares[1].c + + p_list.generic_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/mta.rs b/src/ecdsa/ot_based_ecdsa/triples/mta.rs index e0e5478..6153058 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/mta.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/mta.rs @@ -7,7 +7,7 @@ use subtle::{Choice, ConditionallySelectable}; use crate::protocol::internal::Comms; use crate::{ compat::CSCurve, - proofs::strobe_transcript::TranscriptRng, + crypto::proofs::strobe_transcript::TranscriptRng, protocol::{ internal::{make_protocol, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, diff --git a/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs index 16a95ce..a85a8fe 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs @@ -5,7 +5,7 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::{ compat::CSCurve, - proofs::strobe_transcript::TranscriptRng, + crypto::proofs::strobe_transcript::TranscriptRng, protocol::{ internal::{make_protocol, Comms, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 9d65d44..3b75784 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -281,6 +281,7 @@ mod test { crypto::polynomials::{ generate_polynomial, evaluate_polynomial_on_participant, + evaluate_polynomial_on_zero, }, }; use frost_secp256k1::keys::PublicKeyPackage; @@ -301,7 +302,7 @@ mod test { let max_malicious = 2; let f = generate_polynomial::(None, max_malicious, &mut OsRng); - let big_x = ProjectivePoint::GENERATOR * f.evaluate_zero(); + let big_x = ProjectivePoint::GENERATOR * evaluate_polynomial_on_zero::(&f).to_scalar(); #[allow(clippy::type_complexity)] @@ -310,9 +311,9 @@ mod test { Box>, )> = Vec::with_capacity(participants.len()); - for p in participants{ + for p in &participants{ // simulating the key packages for each participant - let private_share = evaluate_polynomial_on_participant::(&f, p).unwrap(); + let private_share = evaluate_polynomial_on_participant::(&f, *p).unwrap(); let verifying_key = VerifyingKey::new(big_x); let public_key_package = PublicKeyPackage::new(BTreeMap::new(), verifying_key); let keygen_out = KeygenOutput { @@ -322,7 +323,7 @@ mod test { let protocol = presign( &participants[..], - p, + *p, PresignArguments { keygen_out, threshold: max_malicious, @@ -330,7 +331,7 @@ mod test { ); assert!(protocol.is_ok()); let protocol = protocol.unwrap(); - protocols.push((p, Box::new(protocol))); + protocols.push((*p, Box::new(protocol))); } let result = run_protocol(protocols); diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index 0647fcd..df1bc7b 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -141,10 +141,9 @@ mod test { for _ in 0..4 { let fx = generate_polynomial::(None, threshold-1, &mut OsRng); // master secret key - let x = evaluate_polynomial_on_zero(&fx).to_scalar(); + let x = evaluate_polynomial_on_zero::(&fx).to_scalar(); // master public key - let public_key = ProjectivePoint::GENERATOR * x; - let public_key = public_key.to_affine(); + let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); let fa = generate_polynomial::(None, threshold-1, &mut OsRng); let fk = generate_polynomial::(None, threshold-1, &mut OsRng); @@ -152,13 +151,13 @@ mod test { let fd = generate_polynomial::(Some(Secp256K1ScalarField::zero()), 2*max_malicious, &mut OsRng); let fe = generate_polynomial::(Some(Secp256K1ScalarField::zero()), 2*max_malicious, &mut OsRng); - let k = evaluate_polynomial_on_zero(&fk).to_scalar(); + let k = evaluate_polynomial_on_zero::(&fk).to_scalar(); let big_r = ProjectivePoint::GENERATOR * k.clone(); let big_r_x_coordinate = x_coordinate(&big_r.to_affine()); let big_r = VerifyingShare::new(big_r); - let w = evaluate_polynomial_on_zero(&fa).to_scalar() * k; + let w = evaluate_polynomial_on_zero::(&fa).to_scalar() * k; let w_invert = w.invert().unwrap(); let participants = vec![ @@ -174,11 +173,14 @@ mod test { Participant, Box>, )> = Vec::with_capacity(participants.len()); - for p in participants { - let h_i = evaluate_polynomial_on_participant(&fa, p).unwrap() *w_invert; - let alpha_i = h_i + evaluate_polynomial_on_participant(&fd, p).unwrap(); - let beta_i = h_i * big_r_x_coordinate * evaluate_polynomial_on_participant(&fx, p).unwrap() - + evaluate_polynomial_on_participant(&fe, p).unwrap(); + for p in &participants { + let h_i = w_invert + * evaluate_polynomial_on_participant::(&fa, *p).unwrap().to_scalar(); + let alpha_i = h_i + + evaluate_polynomial_on_participant::(&fd, *p).unwrap().to_scalar(); + let beta_i = h_i * big_r_x_coordinate + * evaluate_polynomial_on_participant::(&fx, *p).unwrap().to_scalar() + + evaluate_polynomial_on_participant::(&fe, *p).unwrap().to_scalar(); let alpha_i = SigningShare::new(alpha_i); let beta_i = SigningShare::new(beta_i); diff --git a/src/lib.rs b/src/lib.rs index 5a4aab9..833a1a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ mod compat; mod crypto; -use crypto::*; mod echo_broadcast; @@ -10,11 +9,5 @@ mod participants; 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 b88709a..fe6fea3 100644 --- a/src/participants.rs +++ b/src/participants.rs @@ -13,7 +13,7 @@ use crate::crypto::{ ciphersuite::Ciphersuite, polynomials::compute_lagrange_coefficient, }; -use crate::{compat::CSCurve, protocol::Participant}; +use crate::protocol::Participant; /// Represents a sorted list of participants. /// @@ -85,27 +85,6 @@ impl ParticipantList { Some(self.participants[index]) } - /// Get the lagrange coefficient for a participant, relative to this list. - /// The lagrange coefficient are evaluated to zero - /// 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 From 9d1aeb2d23db2bdebd4fee3a3853644533f22ceb Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Mon, 7 Jul 2025 12:21:27 +0200 Subject: [PATCH 50/52] Fixing dlog proofs and generalizing them for any Ciphersuite --- src/crypto/ciphersuite.rs | 8 ++ src/crypto/proofs/dlog.rs | 97 ++++++++------ src/crypto/proofs/dlogeq.rs | 119 ++++++++++-------- src/crypto/proofs/mod.rs | 50 ++++++++ src/ecdsa/mod.rs | 2 +- .../ot_based_ecdsa/triples/generation.rs | 4 +- .../triples/random_ot_extension.rs | 1 - src/serde.rs | 94 +++++++------- 8 files changed, 233 insertions(+), 142 deletions(-) diff --git a/src/crypto/ciphersuite.rs b/src/crypto/ciphersuite.rs index 4eb1d36..246d7c7 100644 --- a/src/crypto/ciphersuite.rs +++ b/src/crypto/ciphersuite.rs @@ -1,4 +1,9 @@ // Generic Ciphersuite Trait +use frost_core::{ + Group, + Field, +}; + pub enum BytesOrder { BigEndian, LittleEndian, @@ -8,3 +13,6 @@ pub trait ScalarSerializationFormat { fn bytes_order() -> BytesOrder; } pub trait Ciphersuite: frost_core::Ciphersuite + ScalarSerializationFormat {} + +pub(crate) type Scalar = <<::Group as Group>::Field as Field>::Scalar; +pub(crate) type Element = <::Group as Group>::Element; diff --git a/src/crypto/proofs/dlog.rs b/src/crypto/proofs/dlog.rs index 3349273..99855d6 100644 --- a/src/crypto/proofs/dlog.rs +++ b/src/crypto/proofs/dlog.rs @@ -1,12 +1,20 @@ use crate::{ - compat::SerializablePoint, - serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, - crypto::ciphersuite::Ciphersuite, + crypto::ciphersuite::{ + Ciphersuite, + Element, + Scalar, + }, +}; +use frost_core::{ Group, + Field, +}; + +use super::{ + encode_point, + strobe_transcript::Transcript, }; -use super::strobe_transcript::Transcript; use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; /// The label we use for hashing the statement. const STATEMENT_LABEL: &[u8] = b"dlog proof statement"; @@ -18,65 +26,76 @@ 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)] +#[derive(Clone, Copy)] pub struct Statement<'a, C: Ciphersuite> { - #[serde(serialize_with = "serialize_projective_point::")] - pub public: &'a Ciphersuite::Group::Element, + pub public: &'a Element, } impl<'a, C: Ciphersuite> Statement<'a, C> { /// Calculate the homomorphism we want to prove things about. - fn phi(&self, x: &C::Scalar) -> C::ProjectivePoint { - C::ProjectivePoint::generator() * x + fn phi(&self, x: &Scalar) -> Element { + C::Group::generator() * *x + } + + /// Encode into Vec: some sort of serialization + pub 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().into()); + }, + _=> panic!("Expected non-identity element"), + }; + enc } } /// 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, +pub struct Witness<'a, C: Ciphersuite> { + pub x: &'a Scalar, +} + +impl<'a, C: Ciphersuite> Witness<'a, C> { + fn encode(&self) -> Vec{ + ::Field::serialize(self.x).as_ref().into() + } } /// 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, +#[derive(Clone)] +pub struct Proof { + e: Scalar, + s: 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>( +pub fn prove<'a, C: Ciphersuite>( rng: &mut impl CryptoRngCore, transcript: &mut Transcript, statement: Statement<'a, C>, witness: Witness<'a, C>, ) -> Proof { - transcript.message(STATEMENT_LABEL, &encode(&statement)); + transcript.message(STATEMENT_LABEL, &statement.encode()); - let k = C::Scalar::random(rng); + let k = ::Field::random(rng); let big_k = statement.phi(&k); transcript.message( COMMITMENT_LABEL, - &encode(&SerializablePoint::::from_projective(&big_k)), + &encode_point::(&big_k), ); let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); - let e = C::Scalar::random(&mut rng); + let e = ::Field::random(&mut rng); - let s = k + e * witness.x; + let s = k + e * *witness.x; Proof { e, s } } @@ -84,22 +103,21 @@ pub fn prove<'a, C: CSCurve>( /// /// We use a transcript in order to verify the Fiat-Shamir transformation. #[must_use] -pub fn verify( +pub fn verify( transcript: &mut Transcript, statement: Statement<'_, C>, proof: &Proof, ) -> bool { - let statement_data = encode(&statement); - transcript.message(STATEMENT_LABEL, &statement_data); + transcript.message(STATEMENT_LABEL, &statement.encode()); - let big_k: C::ProjectivePoint = statement.phi(&proof.s) - *statement.public * proof.e; + let big_k: Element = statement.phi(&proof.s) - *statement.public * proof.e; transcript.message( COMMITMENT_LABEL, - &encode(&SerializablePoint::::from_projective(&big_k)), + &encode_point::(&big_k), ); let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); - let e = C::Scalar::random(&mut rng); + let e = ::Field::random(&mut rng); e == proof.e } @@ -109,13 +127,14 @@ mod test { use rand_core::OsRng; use super::*; - use k256::{ProjectivePoint, Scalar, Secp256k1}; + use frost_secp256k1::Secp256K1Sha256; + use k256::{ProjectivePoint, Scalar}; #[test] fn test_valid_proof_verifies() { let x = Scalar::generate_biased(&mut OsRng); - let statement = Statement:: { + let statement = Statement:: { public: &(ProjectivePoint::GENERATOR * x), }; let witness = Witness { x: &x }; diff --git a/src/crypto/proofs/dlogeq.rs b/src/crypto/proofs/dlogeq.rs index 323f17d..81d34c3 100644 --- a/src/crypto/proofs/dlogeq.rs +++ b/src/crypto/proofs/dlogeq.rs @@ -1,13 +1,24 @@ -use elliptic_curve::{Field, Group}; use rand_core::CryptoRngCore; -use serde::{Deserialize, Serialize}; - -use super::strobe_transcript::Transcript; use crate::{ - serde::{deserialize_scalar, encode, serialize_projective_point, serialize_scalar}, + crypto::ciphersuite::{ + Ciphersuite, + Element, + Scalar, + }, +}; + +use frost_core::{ + Group, + Field, }; +use super::{ + encode_two_points, + 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. @@ -19,20 +30,43 @@ const CHALLENGE_LABEL: &[u8] = b"dlogeq proof challenge"; /// /// 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, +#[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().into()); + }, + _=> panic!("Expected non-identity element"), + }; + enc } -impl<'a, C: CSCurve> Statement<'a, C> { +impl<'a, C: Ciphersuite> 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) + fn phi(&self, x: &Scalar) -> (Element, Element) { + (C::Group::generator() * *x, *self.generator1 * *x) + } + + /// Encode into Vec: some sort of serialization + pub 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 } } @@ -40,51 +74,40 @@ impl<'a, C: CSCurve> Statement<'a, C> { /// /// This holds the scalar the prover needs to know. #[derive(Clone, Copy)] -pub struct Witness<'a, C: CSCurve> { - pub x: &'a C::Scalar, +pub struct Witness<'a, C: Ciphersuite> { + pub x: &'a 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, +#[derive(Clone)] +pub struct Proof { + e: Scalar, + s: 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>( +pub fn prove<'a, C: Ciphersuite>( rng: &mut impl CryptoRngCore, transcript: &mut Transcript, statement: Statement<'a, C>, witness: Witness<'a, C>, ) -> Proof { - transcript.message(STATEMENT_LABEL, &encode(&statement)); + transcript.message(STATEMENT_LABEL, &statement.encode()); - let k = C::Scalar::random(rng); + let k = ::Field::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), - )), + &encode_two_points::(&big_k.0,&big_k.1), ); let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); - let e = C::Scalar::random(&mut rng); + let e = ::Field::random(&mut rng); - let s = k + e * witness.x; + let s = k + e * *witness.x; Proof { e, s } } @@ -92,13 +115,12 @@ pub fn prove<'a, C: CSCurve>( /// /// We use a transcript in order to verify the Fiat-Shamir transformation. #[must_use] -pub fn verify( +pub fn verify( transcript: &mut Transcript, statement: Statement<'_, C>, proof: &Proof, ) -> bool { - let statement_data = encode(&statement); - transcript.message(STATEMENT_LABEL, &statement_data); + transcript.message(STATEMENT_LABEL, &statement.encode()); let (phi0, phi1) = statement.phi(&proof.s); let big_k0 = phi0 - *statement.public0 * proof.e; @@ -106,13 +128,10 @@ pub fn verify( transcript.message( COMMITMENT_LABEL, - &encode(&( - SerializablePoint::::from_projective(&big_k0), - SerializablePoint::::from_projective(&big_k1), - )), + &encode_two_points::(&big_k0,&big_k1) ); let mut rng = transcript.challenge_then_build_rng(CHALLENGE_LABEL); - let e = C::Scalar::random(&mut rng); + let e = ::Field::random(&mut rng); e == proof.e } @@ -122,15 +141,15 @@ mod test { use rand_core::OsRng; use super::*; - - use k256::{ProjectivePoint, Scalar, Secp256k1}; + 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:: { + let statement = Statement:: { public0: &(ProjectivePoint::GENERATOR * x), generator1: &big_h, public1: &(big_h * x), diff --git a/src/crypto/proofs/mod.rs b/src/crypto/proofs/mod.rs index 177bfde..854dbca 100644 --- a/src/crypto/proofs/mod.rs +++ b/src/crypto/proofs/mod.rs @@ -1,3 +1,53 @@ + +use crate::{crypto::ciphersuite::{ + Ciphersuite, Element +}}; +use serde::Serialize; +use frost_core::{ + Group, +}; + + +/// 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 +pub(crate) 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_else(|_| ser).as_ref().to_vec() +} + +/// 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 +pub(crate) 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_else(|_| 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_else(|_| ser).as_ref().to_vec(); + rmp_serde::encode::to_vec(&(ser_1,ser_2)).expect("failed to encode value") +} + + pub mod dlog; pub mod dlogeq; mod strobe; diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index ec94412..954f950 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -160,6 +160,6 @@ impl FullSignature{ pub mod dkg_ecdsa; pub mod robust_ecdsa; -pub mod ot_based_ecdsa; +// pub mod ot_based_ecdsa; #[cfg(test)] mod test; \ No newline at end of file diff --git a/src/ecdsa/ot_based_ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs index 03e2021..bc74a8a 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -1,8 +1,8 @@ -use elliptic_curve::{Group, ScalarPrimitive}; +use elliptic_curve::Group; use rand_core::OsRng; use crate::{ - compat::{CSCurve, SerializablePoint}, + compat::SerializablePoint, crypto::{ commit::{Commitment,commit}, hash::{hash, HashOutput}, diff --git a/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs index a85a8fe..129a985 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs @@ -4,7 +4,6 @@ use sha2::{Digest, Sha256}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::{ - compat::CSCurve, crypto::proofs::strobe_transcript::TranscriptRng, protocol::{ internal::{make_protocol, Comms, PrivateChannel}, diff --git a/src/serde.rs b/src/serde.rs index 16d1ba8..b598b8a 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,13 +1,9 @@ use std::io::Write; -use crate::compat::{CSCurve, SerializablePoint}; -use ecdsa::elliptic_curve::ScalarPrimitive; -use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{de::DeserializeOwned, Serialize}; +// 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) { @@ -23,52 +19,52 @@ pub fn encode_with_tag(tag: &[u8], val: &T) -> Vec { 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)) -} +// /// 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()) -} +// /// 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 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) -} +// /// 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()) -} +// /// 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 { From ab9f30b8f5ddc7dd3c2c8ba52cbd40f0a987c6df Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Thu, 10 Jul 2025 11:06:40 +0200 Subject: [PATCH 51/52] Repairing batch_random_ot and start using CoefficientCommitment instead of VerifyingShare --- src/crypto/polynomials.rs | 41 +++-- src/crypto/proofs/dlog.rs | 8 +- src/crypto/proofs/dlogeq.rs | 2 +- src/ecdsa/mod.rs | 11 +- src/ecdsa/ot_based_ecdsa/presign.rs | 11 +- src/ecdsa/ot_based_ecdsa/sign.rs | 12 +- .../ot_based_ecdsa/triples/batch_random_ot.rs | 150 +++++++++--------- .../ot_based_ecdsa/triples/generation.rs | 49 +++--- src/ecdsa/ot_based_ecdsa/triples/mta.rs | 13 +- .../ot_based_ecdsa/triples/multiplication.rs | 9 +- .../triples/random_ot_extension.rs | 8 +- src/ecdsa/robust_ecdsa/presign.rs | 54 +++---- src/ecdsa/robust_ecdsa/sign.rs | 18 +-- src/generic_dkg.rs | 28 ++-- 14 files changed, 210 insertions(+), 204 deletions(-) diff --git a/src/crypto/polynomials.rs b/src/crypto/polynomials.rs index 448dfb0..170eb2c 100644 --- a/src/crypto/polynomials.rs +++ b/src/crypto/polynomials.rs @@ -4,7 +4,7 @@ use frost_core::{ Group, Field, keys::{ SigningShare, - VerifyingShare, + CoefficientCommitment, }, Identifier, }; @@ -35,9 +35,8 @@ pub fn generate_polynomial( coefficients } -/// Basically returns the constant term -/// Evaluate the polynomial with the given coefficients (constant term first) -pub fn evaluate_polynomial_on_zero( +/// Returns the constant term (constant term is the first coefficient) +pub fn eval_polynomial_on_zero( coefficients: &[Scalar], ) -> SigningShare { SigningShare::new(coefficients[0]) @@ -48,14 +47,14 @@ pub fn evaluate_polynomial_on_zero( /// at the point x=identifier using Horner's method. /// Implements [`polynomial_evaluate`] from the spec. /// [`polynomial_evaluate`]: https://datatracker.ietf.org/doc/html/rfc9591#name-additional-polynomial-opera -pub fn evaluate_polynomial( +pub fn eval_polynomial( coefficients: &[Scalar], point: Scalar, ) -> SigningShare { // creating this dummy id is only to be able to call the from_coefficients function let point_id = Identifier::new(point); if point_id.is_err(){ - evaluate_polynomial_on_zero::(coefficients) + eval_polynomial_on_zero::(coefficients) } else{ let point_id = point_id.unwrap(); SigningShare::from_coefficients(coefficients, point_id) @@ -64,7 +63,7 @@ pub fn evaluate_polynomial( /// Evaluates a polynomial on the identifier of a participant -pub fn evaluate_polynomial_on_participant( +pub fn eval_polynomial_on_participant( coefficients: &[Scalar], participant: Participant, ) -> Result, ProtocolError> { @@ -74,14 +73,14 @@ pub fn evaluate_polynomial_on_participant( /// Evaluates multiple polynomials of the same type on the same identifier -pub fn evaluate_multi_polynomials( +pub fn eval_multi_polynomials( polynomials: [&[Scalar]; N], participant: Participant, ) -> Result<[SigningShare; N], ProtocolError> { let mut result_vec = Vec::with_capacity(N); for poly in polynomials.iter() { - let eval = evaluate_polynomial_on_participant::(poly, participant)?; + let eval = eval_polynomial_on_participant::(poly, participant)?; result_vec.push(eval); } Ok(result_vec @@ -130,8 +129,8 @@ pub fn compute_lagrange_coefficient( Ok(num * den) } -// Computes polynomial interpolation on a specific point -// using a sequence of sorted elements +/// Computes polynomial interpolation on a specific point +/// using a sequence of sorted elements pub fn eval_interpolation( signingshares_map: &ParticipantMap<'_, SigningShare>, point: Option<&Scalar>, @@ -158,13 +157,13 @@ pub fn eval_interpolation( Ok(SigningShare::new(interpolation)) } -// Computes polynomial interpolation on the exponent on a specific point -// using a sequence of sorted elements +/// Computes polynomial interpolation on the exponent on a specific point +/// using a sequence of sorted elements pub fn eval_exponent_interpolation( identifiers: &Vec>, - shares: &Vec<&VerifyingShare>, + shares: &Vec<&CoefficientCommitment>, point: Option<&Scalar>, -) -> Result, ProtocolError>{ +) -> Result, ProtocolError>{ let mut interpolation = ::identity(); if identifiers.len() != shares.len(){ return Err(ProtocolError::InvalidInterpolationArguments) @@ -180,6 +179,16 @@ pub fn eval_exponent_interpolation( interpolation = interpolation + (share.to_element() * lagrange_coefficient); } - Ok(VerifyingShare::new(interpolation)) + Ok(CoefficientCommitment::new(interpolation)) } +/// Commits to a polynomial returning a sequence of group coefficients +/// Creates a commitment vector of coefficients * G +pub fn commit_polynomial( + coefficients: &[Scalar], +) -> Vec> { + // Computes the multiplication of every coefficient of p with the generator G + coefficients.iter().map( + |c| CoefficientCommitment::new(::generator() * *c) + ).collect() +} diff --git a/src/crypto/proofs/dlog.rs b/src/crypto/proofs/dlog.rs index 99855d6..3c858ea 100644 --- a/src/crypto/proofs/dlog.rs +++ b/src/crypto/proofs/dlog.rs @@ -38,7 +38,7 @@ impl<'a, C: Ciphersuite> Statement<'a, C> { } /// Encode into Vec: some sort of serialization - pub fn encode(&self) -> Vec{ + fn encode(&self) -> Vec{ let mut enc = Vec::new(); enc.extend_from_slice(b"statement:"); @@ -60,12 +60,6 @@ pub struct Witness<'a, C: Ciphersuite> { pub x: &'a Scalar, } -impl<'a, C: Ciphersuite> Witness<'a, C> { - fn encode(&self) -> Vec{ - ::Field::serialize(self.x).as_ref().into() - } -} - /// Represents a proof of the statement. #[derive(Clone)] pub struct Proof { diff --git a/src/crypto/proofs/dlogeq.rs b/src/crypto/proofs/dlogeq.rs index 81d34c3..663cce8 100644 --- a/src/crypto/proofs/dlogeq.rs +++ b/src/crypto/proofs/dlogeq.rs @@ -56,7 +56,7 @@ impl<'a, C: Ciphersuite> Statement<'a, C> { } /// Encode into Vec: some sort of serialization - pub fn encode(&self) -> Vec{ + 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 diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 954f950..88476d5 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -7,10 +7,7 @@ use elliptic_curve::{ point::AffineCoordinates, }; -use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; - -use k256::ProjectivePoint; - +use frost_core::keys::CoefficientCommitment; use rand_core::CryptoRngCore; use frost_secp256k1::{ @@ -21,8 +18,9 @@ use frost_secp256k1::{ Field, }; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use k256::AffinePoint; +use k256::{AffinePoint, ProjectivePoint}; +use crate::crypto::ciphersuite::{BytesOrder, Ciphersuite, ScalarSerializationFormat}; #[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] pub struct KeygenOutput { @@ -31,6 +29,7 @@ pub struct KeygenOutput { } pub type Scalar = ::Scalar; +pub type CoefficientCommitment = CoefficientCommitment::; /// This is the trait that any curve usable in this library must implement. /// This library does provide a few feature-gated implementations for curves @@ -160,6 +159,6 @@ impl FullSignature{ pub mod dkg_ecdsa; pub mod robust_ecdsa; -// pub mod ot_based_ecdsa; +pub mod ot_based_ecdsa; #[cfg(test)] mod test; \ No newline at end of file diff --git a/src/ecdsa/ot_based_ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs index bd45b77..ac02df7 100644 --- a/src/ecdsa/ot_based_ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -234,8 +234,8 @@ mod test { protocol::run_protocol, crypto::polynomials::{ generate_polynomial, - evaluate_polynomial_on_zero, - evaluate_polynomial_on_participant, + eval_polynomial_on_zero, + eval_polynomial_on_participant, }, }; use std::collections::BTreeMap; @@ -256,7 +256,7 @@ mod test { ]; let original_threshold = 2; let f = generate_polynomial::(None, original_threshold-1, &mut OsRng); - let big_x = ProjectivePoint::GENERATOR * evaluate_polynomial_on_zero::(&f).to_scalar(); + let big_x = ProjectivePoint::GENERATOR * eval_polynomial_on_zero::(&f).to_scalar(); let threshold = 2; @@ -277,8 +277,7 @@ mod test { .zip(triple0_shares.into_iter()) .zip(triple1_shares.into_iter()) { - let private_share = evaluate_polynomial_on_participant::(&f, *p).unwrap().to_scalar(); - // let private_share = f.evaluate(&p.scalar::()); + let private_share = eval_polynomial_on_participant::(&f, *p).unwrap().to_scalar(); let verifying_key = VerifyingKey::new(big_x); let public_key_package = PublicKeyPackage::new(BTreeMap::new(), verifying_key); let keygen_out = KeygenOutput { @@ -322,6 +321,6 @@ mod test { assert_eq!(ProjectivePoint::GENERATOR * k.invert().unwrap(), big_k); let sigma = p_list.generic_lagrange::(participants[0]) * sigma_shares[0] + p_list.generic_lagrange::(participants[1]) * sigma_shares[1]; - assert_eq!(sigma, k * evaluate_polynomial_on_zero::(&f).to_scalar()); + assert_eq!(sigma, k * eval_polynomial_on_zero::(&f).to_scalar()); } } diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs index 45feb4b..64982ec 100644 --- a/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -131,8 +131,8 @@ mod test { protocol::{run_protocol, Participant,Protocol}, crypto::polynomials::{ generate_polynomial, - evaluate_polynomial_on_zero, - evaluate_polynomial_on_participant, + eval_polynomial_on_zero, + eval_polynomial_on_participant, }, }; use crate::compat::{ @@ -148,12 +148,12 @@ mod test { // Run 4 times for flakiness reasons for _ in 0..4 { let f = generate_polynomial::(None, threshold-1, &mut OsRng);; - let x = evaluate_polynomial_on_zero::(&f).to_scalar(); + let x = eval_polynomial_on_zero::(&f).to_scalar(); let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); let g = generate_polynomial::(None, threshold-1, &mut OsRng);; - let k = evaluate_polynomial_on_zero::(&g).to_scalar(); + let k = eval_polynomial_on_zero::(&g).to_scalar(); let big_k = (ProjectivePoint::GENERATOR * k.invert().unwrap()).to_affine(); let sigma = k * x; @@ -169,10 +169,10 @@ mod test { for p in &participants { let presignature = PresignOutput { big_r: big_k, - k: evaluate_polynomial_on_participant::(&g, *p) + k: eval_polynomial_on_participant::(&g, *p) .unwrap() .to_scalar(), - sigma: evaluate_polynomial_on_participant::(&h, *p) + sigma: eval_polynomial_on_participant::(&h, *p) .unwrap() .to_scalar(), }; diff --git a/src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs b/src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs index 9f6f5d4..acea13c 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/batch_random_ot.rs @@ -1,16 +1,19 @@ -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}, + ecdsa::{ + Field, + ProjectivePoint, + Secp256K1ScalarField, + VerifyingKey, + }, protocol::{ internal::{make_protocol, Comms, PrivateChannel}, run_two_party_protocol, Participant, ProtocolError, }, - serde::encode, }; use super::constants::SECURITY_PARAMETER; @@ -18,18 +21,18 @@ use super::bits::{BitMatrix, BitVector, SquareBitMatrix, SEC_PARAM_8}; 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, + big_x_i: &VerifyingKey, + big_y: &VerifyingKey, + p: &VerifyingKey, ) -> BitVector { 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()?); + hasher.update(&big_y.serialize()?); + hasher.update(&p.serialize()?); let bytes: [u8; 32] = hasher.finalize().into(); // the hash output is 256 bits @@ -41,28 +44,30 @@ fn hash( 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 = VerifyingKey::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 ser_big_x_i: VerifyingKey = chan.recv(wait0).await?; - let y_big_x_i = big_x_i_affine.to_projective() * y; + let y_big_x_i = ser_big_x_i.to_element() * 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 big_k0 = hash(i, &ser_big_x_i, &ser_big_y, &VerifyingKey::new(y_big_x_i)); + let big_k1 = hash(i, &ser_big_x_i, &ser_big_y, &VerifyingKey::new(y_big_x_i - big_z)); Ok::<_, ProtocolError>((big_k0, big_k1)) } @@ -74,7 +79,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); @@ -83,8 +88,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); @@ -92,34 +97,33 @@ pub async fn batch_random_ot_sender_many( } let wait0 = chan.next_waitpoint(); - let mut big_y_affine_v = vec![]; + let mut big_y_ser_v = vec![]; for i in 0..N { - let big_y = &big_y_v[i]; - let big_y_affine = SerializablePoint::::from_projective(&big_y); - big_y_affine_v.push(big_y_affine); + let big_y_verkey = VerifyingKey::new(big_y_v[i]); + big_y_ser_v.push(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 in 0..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].to_element() * *y; + let big_k0 = hash(i, &big_x_i_verkey_v[j], big_y_verkey, &VerifyingKey::new(y_big_x_i)); + let big_k1 = hash(i, &big_x_i_verkey_v[j], big_y_verkey, &VerifyingKey::new(y_big_x_i - big_z)); ret.push((big_k0, big_k1)); } @@ -151,19 +155,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: VerifyingKey = chan.recv(wait0).await?; + let big_y = big_y_verkey.to_element(); let delta = BitVector::random(&mut OsRng); let out = delta @@ -172,49 +171,44 @@ 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 = VerifyingKey::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, &VerifyingKey::new(big_y * x_i)) }) .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 i in 0..N { - let big_y_affine = big_y_affine_v[i]; - 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(), - )); - } - + let big_y_verkey = big_y_verkey_v[i]; + let big_y = big_y_verkey.to_element(); 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(); @@ -235,15 +229,15 @@ pub async fn batch_random_ot_receiver_many( // clone arcs let d_i_v = choices[i].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); @@ -251,21 +245,21 @@ pub async fn batch_random_ot_receiver_many( // Step 6 let wait0 = chan.next_waitpoint(); - let mut big_x_i_affine_v = Vec::new(); + let mut big_x_i_verkey_v = Vec::new(); for j in 0..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 big_x_i_verkey = VerifyingKey::new(big_x_i_v[j]); + big_x_i_verkey_v.push(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, &VerifyingKey::new(big_y * x_i))); } hashv }; @@ -296,7 +290,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); @@ -308,18 +302,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, @@ -336,11 +330,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)), ), ) } @@ -349,11 +343,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(); @@ -375,7 +367,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/ot_based_ecdsa/triples/generation.rs b/src/ecdsa/ot_based_ecdsa/triples/generation.rs index bc74a8a..7381f17 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -1,21 +1,28 @@ use elliptic_curve::Group; use rand_core::OsRng; +use serde::Serialize; use crate::{ - compat::SerializablePoint, crypto::{ commit::{Commitment,commit}, hash::{hash, HashOutput}, random::Randomizer, - polynomials::generate_polynomial + polynomials::{ + generate_polynomial, + commit_polynomial, + eval_polynomial_on_zero, + }, }, // ecdsa::math::{GroupPolynomial, Polynomial}, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, crypto::proofs::{dlog, dlogeq, strobe_transcript::Transcript}, protocol::{ - internal::make_protocol, InitializationError, Participant, Protocol, ProtocolError, + internal::{make_protocol, Comms}, + InitializationError, + Participant, + Protocol, + ProtocolError, }, - serde::encode, ecdsa::{ Secp256K1Sha256, Secp256K1ScalarField, @@ -29,7 +36,13 @@ use super::{ TriplePub, TripleShare }; -use crate::protocol::internal::Comms; + + +/// 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); @@ -66,9 +79,9 @@ async fn do_generation( let l = generate_polynomial::(Some(Secp256K1ScalarField::zero()), threshold-1, &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 = commit_polynomial::(&e); + let big_f_i = commit_polynomial::(&f); + let big_l_i = commit_polynomial::(&l); // Spec 1.5 let (my_commitment, my_randomizer) = commit(&mut rng, &(&big_e_i, &big_f_i, &big_l_i)); @@ -93,9 +106,9 @@ async fn do_generation( // Spec 2.4 let multiplication_task = { - let e0 = e.evaluate_zero(); - let f0 = f.evaluate_zero(); - multiplication::( + let e0 = eval_polynomial_on_zero::(&e); + let f0 = eval_polynomial_on_zero::(&f); + multiplication( comms.clone(), my_confirmation, participants.clone(), @@ -105,7 +118,7 @@ async fn do_generation( ) }; - struct ParallelToMultiplicationTaskOutput<'a, C: CSCurve> { + struct ParallelToMultiplicationTaskOutput<'a> { seen: ParticipantCounter<'a>, big_e: GroupPolynomial, big_f: GroupPolynomial, @@ -531,9 +544,9 @@ async fn do_generation_many( l.set_zero(C::Scalar::ZERO); // 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 = commit_polynomial::(&e); + let big_f_i = commit_polynomial::(&f); + let big_l_i = commit_polynomial::(&l); // Spec 1.5 let (my_commitment, my_randomizer) = commit(&mut rng, &(&big_e_i, &big_f_i, &big_l_i)); @@ -595,7 +608,7 @@ async fn do_generation_many( ) }; - struct ParallelToMultiplicationTaskOutput<'a, C: CSCurve> { + struct ParallelToMultiplicationTaskOutput<'a> { seen: ParticipantCounter<'a>, big_e_v: Vec>, big_f_v: Vec>, @@ -1096,7 +1109,7 @@ 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, @@ -1124,7 +1137,7 @@ 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, diff --git a/src/ecdsa/ot_based_ecdsa/triples/mta.rs b/src/ecdsa/ot_based_ecdsa/triples/mta.rs index 6153058..fe61f1d 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/mta.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/mta.rs @@ -6,7 +6,6 @@ use subtle::{Choice, ConditionallySelectable}; use crate::protocol::internal::Comms; use crate::{ - compat::CSCurve, crypto::proofs::strobe_transcript::TranscriptRng, protocol::{ internal::{make_protocol, PrivateChannel}, @@ -14,9 +13,9 @@ use crate::{ }, }; -struct MTAScalars(Vec<(ScalarPrimitive, ScalarPrimitive)>); +struct MTAScalars(Vec<(ScalarPrimitive, ScalarPrimitive)>); -impl MTAScalars { +impl MTAScalars { const SCALAR_LEN: usize = (C::BITS + 7) >> 3; fn len(&self) -> usize { @@ -28,7 +27,7 @@ impl MTAScalars { } } -impl Serialize for MTAScalars { +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() { @@ -39,7 +38,7 @@ impl Serialize for MTAScalars { } } -impl<'de, C: CSCurve> Deserialize<'de> for MTAScalars { +impl<'de> Deserialize<'de> for MTAScalars { fn deserialize>(d: D) -> Result { let bytes = Vec::::deserialize(d)?; if bytes.len() % (Self::SCALAR_LEN * 2) != 0 { @@ -58,7 +57,7 @@ impl<'de, C: CSCurve> Deserialize<'de> for MTAScalars { } /// The sender for multiplicative to additive conversion. -pub async fn mta_sender( +pub async fn mta_sender( mut chan: PrivateChannel, v: Vec<(C::Scalar, C::Scalar)>, a: C::Scalar, @@ -97,7 +96,7 @@ pub async fn mta_sender( } /// The receiver for multiplicative to additive conversion. -pub async fn mta_receiver( +pub async fn mta_receiver( mut chan: PrivateChannel, tv: Vec<(Choice, C::Scalar)>, b: C::Scalar, diff --git a/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs b/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs index b57d98d..24f9cdd 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/multiplication.rs @@ -1,5 +1,4 @@ use crate::{ - compat::CSCurve, crypto::hash::{HashOutput, hash}, participants::ParticipantList, protocol::{ @@ -19,7 +18,7 @@ use super::{ }; use std::collections::VecDeque; -pub async fn multiplication_sender<'a, C: CSCurve>( +pub async fn multiplication_sender<'a>( chan: PrivateChannel, sid: &[u8], a_i: &C::Scalar, @@ -52,7 +51,7 @@ pub async fn multiplication_sender<'a, C: CSCurve>( Ok(gamma0? + gamma1?) } -pub async fn multiplication_receiver<'a, C: CSCurve>( +pub async fn multiplication_receiver<'a>( chan: PrivateChannel, sid: &[u8], a_i: &C::Scalar, @@ -85,7 +84,7 @@ pub async fn multiplication_receiver<'a, C: CSCurve>( Ok(gamma0? + gamma1?) } -pub async fn multiplication( +pub async fn multiplication( comms: Comms, sid: HashOutput, participants: ParticipantList, @@ -114,7 +113,7 @@ pub async fn multiplication( Ok(out) } -pub async fn multiplication_many( +pub async fn multiplication_many( comms: Comms, sid: Vec, participants: ParticipantList, diff --git a/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs index 129a985..5567671 100644 --- a/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs +++ b/src/ecdsa/ot_based_ecdsa/triples/random_ot_extension.rs @@ -19,7 +19,7 @@ use super::{ 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) -> C::Scalar { let mut hasher = Sha256::new(); let i64 = u64::try_from(i).expect("failed to convert usize to u64"); @@ -59,7 +59,7 @@ pub type RandomOTExtensionSenderOut = Vec<( /// The result that the receiver gets. 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, @@ -129,7 +129,7 @@ pub async fn random_ot_extension_sender( Ok(out) } -pub async fn random_ot_extension_receiver( +pub async fn random_ot_extension_receiver( mut chan: PrivateChannel, params: RandomOtExtensionParams<'_>, k0: &SquareBitMatrix, @@ -200,7 +200,7 @@ 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, diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index 3b75784..a1b5a61 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -1,25 +1,27 @@ +use frost_core::keys::CoefficientCommitment; use rand_core::OsRng; use elliptic_curve::{ point::AffineCoordinates, }; -use frost_secp256k1::{ - Secp256K1Sha256, Secp256K1Group, Secp256K1ScalarField, - Group, Field, - keys::{ - SigningShare, - VerifyingShare - }, -}; +use frost_secp256k1::{Secp256K1Group,Group}; use serde::{Deserialize, Serialize}; use crate::{ crypto::polynomials::{ - evaluate_multi_polynomials, + eval_multi_polynomials, generate_polynomial, eval_interpolation, eval_exponent_interpolation, }, - ecdsa::KeygenOutput, + ecdsa::{ + KeygenOutput, + Scalar, + AffinePoint, + SigningShare, + Secp256K1Sha256, + Secp256K1ScalarField, + Field, + }, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, protocol::{ internal::{make_protocol, Comms, SharedChannel}, @@ -30,9 +32,7 @@ use crate::{ }, }; - type C = Secp256K1Sha256; -type Scalar = ::Scalar; /// The arguments needed to create a presignature. @@ -51,11 +51,11 @@ pub struct PresignArguments { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PresignOutput { /// The public nonce commitment. - pub big_r: VerifyingShare, + pub big_r: AffinePoint, /// Our secret shares of the nonces. - pub alpha_i: SigningShare, - pub beta_i: SigningShare, + pub alpha_i: Scalar, + pub beta_i: Scalar, } /// Generates a secret polynomial where the comstant term is zero @@ -68,11 +68,11 @@ fn zero_secret_polynomial( } /// Evaluate five polynomials at once -fn evaluate_five_polynomials( +fn eval_five_polynomials( polynomials: [&[Scalar]; 5], participant: Participant, )-> Result<[SigningShare; 5], ProtocolError> { - let package = evaluate_multi_polynomials::(polynomials, participant)?; + let package = eval_multi_polynomials::(polynomials, participant)?; Ok(package) } @@ -106,13 +106,13 @@ async fn do_presign( for p in participants.others(me) { // Securely send to each other participant a secret share - let package = evaluate_five_polynomials([my_fk, my_fa, my_fb, my_fd, my_fe], p)?; + let package = eval_five_polynomials([my_fk, my_fa, my_fb, my_fd, my_fe], p)?; // send the evaluation privately to participant p chan.send_private(wait_round_0, p, &package); } // Evaluate my secret shares for my polynomials - let shares = evaluate_five_polynomials([my_fk, my_fa, my_fb, my_fd, my_fe], me)?; + let shares = eval_five_polynomials([my_fk, my_fa, my_fb, my_fd, my_fe], me)?; // Extract the shares into a vec of scalars let mut shares: Vec = shares.iter() .map( |signing_share| signing_share.to_scalar()) @@ -136,7 +136,7 @@ async fn do_presign( // Compute R_me = g^{k_me} let big_r_me = Secp256K1Group::generator() * shares[0]; - let big_r_me = VerifyingShare::new(big_r_me); + 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]; @@ -156,7 +156,7 @@ async fn do_presign( seen.clear(); seen.put(me); while !seen.full() { - let (from, (big_r_p, w_p)): (_ , (VerifyingShare, SigningShare)) = chan.recv(wait_round_1).await?; + let (from, (big_r_p, w_p)): (_ , (CoefficientCommitment, SigningShare)) = chan.recv(wait_round_1).await?; if !seen.put(from) { continue; } @@ -190,7 +190,7 @@ async fn do_presign( if w.to_scalar().is_zero().into(){ return Err(ProtocolError::ZeroScalar) } - if big_r.to_element().eq(&::identity()){ + if big_r.value().eq(&::identity()){ return Err(ProtocolError::IdentityElement) } @@ -200,7 +200,7 @@ async fn do_presign( // 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.to_element() + let big_r_x_coordinate: [u8; 32] = big_r.value() .to_affine() .x() .try_into() @@ -280,8 +280,8 @@ mod test { protocol::run_protocol, crypto::polynomials::{ generate_polynomial, - evaluate_polynomial_on_participant, - evaluate_polynomial_on_zero, + eval_polynomial_on_participant, + eval_polynomial_on_zero, }, }; use frost_secp256k1::keys::PublicKeyPackage; @@ -302,7 +302,7 @@ mod test { let max_malicious = 2; let f = generate_polynomial::(None, max_malicious, &mut OsRng); - let big_x = ProjectivePoint::GENERATOR * evaluate_polynomial_on_zero::(&f).to_scalar(); + let big_x = ProjectivePoint::GENERATOR * eval_polynomial_on_zero::(&f).to_scalar(); #[allow(clippy::type_complexity)] @@ -313,7 +313,7 @@ mod test { for p in &participants{ // simulating the key packages for each participant - let private_share = evaluate_polynomial_on_participant::(&f, *p).unwrap(); + let private_share = eval_polynomial_on_participant::(&f, *p).unwrap(); let verifying_key = VerifyingKey::new(big_x); let public_key_package = PublicKeyPackage::new(BTreeMap::new(), verifying_key); let keygen_out = KeygenOutput { diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index df1bc7b..a703247 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -125,8 +125,8 @@ mod test { protocol::run_protocol, crypto::polynomials::{ generate_polynomial, - evaluate_polynomial_on_participant, - evaluate_polynomial_on_zero, + eval_polynomial_on_participant, + eval_polynomial_on_zero, } }; @@ -141,7 +141,7 @@ mod test { for _ in 0..4 { let fx = generate_polynomial::(None, threshold-1, &mut OsRng); // master secret key - let x = evaluate_polynomial_on_zero::(&fx).to_scalar(); + let x = eval_polynomial_on_zero::(&fx).to_scalar(); // master public key let public_key = (ProjectivePoint::GENERATOR * x).to_affine(); @@ -151,13 +151,13 @@ mod test { let fd = generate_polynomial::(Some(Secp256K1ScalarField::zero()), 2*max_malicious, &mut OsRng); let fe = generate_polynomial::(Some(Secp256K1ScalarField::zero()), 2*max_malicious, &mut OsRng); - let k = evaluate_polynomial_on_zero::(&fk).to_scalar(); + let k = eval_polynomial_on_zero::(&fk).to_scalar(); let big_r = ProjectivePoint::GENERATOR * k.clone(); let big_r_x_coordinate = x_coordinate(&big_r.to_affine()); let big_r = VerifyingShare::new(big_r); - let w = evaluate_polynomial_on_zero::(&fa).to_scalar() * k; + let w = eval_polynomial_on_zero::(&fa).to_scalar() * k; let w_invert = w.invert().unwrap(); let participants = vec![ @@ -175,12 +175,12 @@ mod test { )> = Vec::with_capacity(participants.len()); for p in &participants { let h_i = w_invert - * evaluate_polynomial_on_participant::(&fa, *p).unwrap().to_scalar(); + * eval_polynomial_on_participant::(&fa, *p).unwrap().to_scalar(); let alpha_i = h_i - + evaluate_polynomial_on_participant::(&fd, *p).unwrap().to_scalar(); + + eval_polynomial_on_participant::(&fd, *p).unwrap().to_scalar(); let beta_i = h_i * big_r_x_coordinate - * evaluate_polynomial_on_participant::(&fx, *p).unwrap().to_scalar() - + evaluate_polynomial_on_participant::(&fe, *p).unwrap().to_scalar(); + * eval_polynomial_on_participant::(&fx, *p).unwrap().to_scalar() + + eval_polynomial_on_participant::(&fe, *p).unwrap().to_scalar(); let alpha_i = SigningShare::new(alpha_i); let beta_i = SigningShare::new(beta_i); diff --git a/src/generic_dkg.rs b/src/generic_dkg.rs index 2241655..b348cc4 100644 --- a/src/generic_dkg.rs +++ b/src/generic_dkg.rs @@ -1,7 +1,11 @@ use crate::crypto::{ ciphersuite::Ciphersuite, hash::{HashOutput, domain_separate_hash}, - polynomials::{evaluate_polynomial_on_participant, generate_polynomial}, + polynomials::{ + eval_polynomial_on_participant, + generate_polynomial, + commit_polynomial, + }, }; use crate::echo_broadcast::do_broadcast; use crate::participants::{ParticipantCounter, ParticipantList, ParticipantMap}; @@ -59,16 +63,14 @@ fn generate_coefficient_commitment( ) -> Vec> { // 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 + let secret_coefficients = if secret_coefficients.first() + == Some(&::Field::zero()) + { + &secret_coefficients[1..] + } else { + secret_coefficients + }; + commit_polynomial(secret_coefficients) } /// Generates the challenge for the proof of knowledge @@ -489,7 +491,7 @@ async fn do_keyshare( 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_on_participant::(&secret_coefficients, p)?; + let signing_share_to_p = eval_polynomial_on_participant::(&secret_coefficients, p)?; // send the evaluation privately to participant p chan.send_private(wait_round_3, p, &signing_share_to_p); } @@ -497,7 +499,7 @@ async fn do_keyshare( // Start Round 4 // compute my secret evaluation of my private polynomial - let mut my_signing_share = evaluate_polynomial_on_participant::(&secret_coefficients, me)?.to_scalar(); + let mut my_signing_share = eval_polynomial_on_participant::(&secret_coefficients, me)?.to_scalar(); // receive evaluations from all participants seen.clear(); seen.put(me); From f27632ecf32031de67a8833fe4a9618251d80af7 Mon Sep 17 00:00:00 2001 From: SimonRastikian <> Date: Thu, 10 Jul 2025 11:21:44 +0200 Subject: [PATCH 52/52] Presign output and Presign arguments are now part of the mod --- src/ecdsa/mod.rs | 2 +- src/ecdsa/ot_based_ecdsa/mod.rs | 38 ++++++++++++++++++++++++ src/ecdsa/ot_based_ecdsa/presign.rs | 38 ++---------------------- src/ecdsa/ot_based_ecdsa/sign.rs | 2 +- src/ecdsa/ot_based_ecdsa/test.rs | 14 ++++----- src/ecdsa/robust_ecdsa/mod.rs | 33 +++++++++++++++++++++ src/ecdsa/robust_ecdsa/presign.rs | 45 +++++++---------------------- src/ecdsa/robust_ecdsa/sign.rs | 6 ++-- src/ecdsa/robust_ecdsa/test.rs | 4 ++- 9 files changed, 97 insertions(+), 85 deletions(-) diff --git a/src/ecdsa/mod.rs b/src/ecdsa/mod.rs index 88476d5..3458b7a 100644 --- a/src/ecdsa/mod.rs +++ b/src/ecdsa/mod.rs @@ -29,7 +29,7 @@ pub struct KeygenOutput { } pub type Scalar = ::Scalar; -pub type CoefficientCommitment = CoefficientCommitment::; +pub type Commitment = CoefficientCommitment::; /// This is the trait that any curve usable in this library must implement. /// This library does provide a few feature-gated implementations for curves diff --git a/src/ecdsa/ot_based_ecdsa/mod.rs b/src/ecdsa/ot_based_ecdsa/mod.rs index d523b5a..cb861cf 100644 --- a/src/ecdsa/ot_based_ecdsa/mod.rs +++ b/src/ecdsa/ot_based_ecdsa/mod.rs @@ -1,3 +1,41 @@ + +use serde::{Deserialize, Serialize}; +use triples::{TriplePub, TripleShare}; +use crate::ecdsa::{ + KeygenOutput, + AffinePoint, + Scalar, +}; + +/// 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 triples; pub mod presign; pub mod sign; diff --git a/src/ecdsa/ot_based_ecdsa/presign.rs b/src/ecdsa/ot_based_ecdsa/presign.rs index ac02df7..c62e2bd 100644 --- a/src/ecdsa/ot_based_ecdsa/presign.rs +++ b/src/ecdsa/ot_based_ecdsa/presign.rs @@ -1,11 +1,5 @@ -use serde::{Deserialize, Serialize}; - -use super::triples::{TriplePub, TripleShare}; - use crate::ecdsa::{ - KeygenOutput, - AffinePoint, Scalar, ProjectivePoint, Secp256K1Sha256 @@ -16,34 +10,7 @@ use crate::protocol::{ internal::{make_protocol, Comms, SharedChannel} }; use crate::participants::{ParticipantList, ParticipantCounter}; - -/// 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, -} +use super::{PresignArguments, PresignOutput}; async fn do_presign( mut chan: SharedChannel, @@ -229,7 +196,8 @@ mod test { ecdsa::{ ot_based_ecdsa::triples::test::deal, ProjectivePoint, - Secp256K1Sha256 + Secp256K1Sha256, + KeygenOutput, }, protocol::run_protocol, crypto::polynomials::{ diff --git a/src/ecdsa/ot_based_ecdsa/sign.rs b/src/ecdsa/ot_based_ecdsa/sign.rs index 64982ec..fdaa97a 100644 --- a/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/src/ecdsa/ot_based_ecdsa/sign.rs @@ -1,7 +1,7 @@ use elliptic_curve::scalar::IsHigh; use subtle::ConditionallySelectable; -use super::presign::PresignOutput; +use super::PresignOutput; use crate::{ ecdsa::{ FullSignature, diff --git a/src/ecdsa/ot_based_ecdsa/test.rs b/src/ecdsa/ot_based_ecdsa/test.rs index ecb1e58..274001a 100644 --- a/src/ecdsa/ot_based_ecdsa/test.rs +++ b/src/ecdsa/ot_based_ecdsa/test.rs @@ -1,16 +1,12 @@ use std::error::Error; use rand_core::OsRng; -use super::presign::{ - presign, +use super::{ + PresignOutput, PresignArguments, - PresignOutput + triples::{TriplePub, TripleShare, test::deal}, + presign::presign, + sign::sign, }; -use super::triples::{ - TriplePub, - TripleShare, - test::deal, -}; -use super::sign::sign; use crate::protocol::{ run_protocol, Participant, diff --git a/src/ecdsa/robust_ecdsa/mod.rs b/src/ecdsa/robust_ecdsa/mod.rs index 468a104..a5ddde2 100644 --- a/src/ecdsa/robust_ecdsa/mod.rs +++ b/src/ecdsa/robust_ecdsa/mod.rs @@ -1,3 +1,36 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + ecdsa::{ + Scalar, + AffinePoint, + KeygenOutput, + }, +}; + +/// 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)] diff --git a/src/ecdsa/robust_ecdsa/presign.rs b/src/ecdsa/robust_ecdsa/presign.rs index a1b5a61..934abe2 100644 --- a/src/ecdsa/robust_ecdsa/presign.rs +++ b/src/ecdsa/robust_ecdsa/presign.rs @@ -1,10 +1,8 @@ -use frost_core::keys::CoefficientCommitment; use rand_core::OsRng; use elliptic_curve::{ point::AffineCoordinates, }; use frost_secp256k1::{Secp256K1Group,Group}; -use serde::{Deserialize, Serialize}; use crate::{ crypto::polynomials::{ @@ -14,13 +12,12 @@ use crate::{ eval_exponent_interpolation, }, ecdsa::{ - KeygenOutput, - Scalar, - AffinePoint, SigningShare, Secp256K1Sha256, Secp256K1ScalarField, Field, + Commitment, + Scalar, }, participants::{ParticipantCounter, ParticipantList, ParticipantMap}, protocol::{ @@ -31,34 +28,11 @@ use crate::{ Protocol }, }; +use super::{PresignOutput, PresignArguments}; type C = Secp256K1Sha256; - -/// 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, -} - -/// Generates a secret polynomial where the comstant term is zero +/// Generates a secret polynomial where the constant term is zero fn zero_secret_polynomial( degree: usize, rng: &mut OsRng, @@ -136,7 +110,7 @@ async fn do_presign( // Compute R_me = g^{k_me} let big_r_me = Secp256K1Group::generator() * shares[0]; - let big_r_me = CoefficientCommitment::new(big_r_me); + let big_r_me = Commitment::new(big_r_me); // Compute w_me = a_me * k_me + b_me let w_me = shares[1] * shares[0] + shares[2]; @@ -156,7 +130,7 @@ async fn do_presign( seen.clear(); seen.put(me); while !seen.full() { - let (from, (big_r_p, w_p)): (_ , (CoefficientCommitment, SigningShare)) = chan.recv(wait_round_1).await?; + let (from, (big_r_p, w_p)): (_ , (Commitment, SigningShare)) = chan.recv(wait_round_1).await?; if !seen.put(from) { continue; } @@ -211,9 +185,9 @@ async fn do_presign( let beta_me = h_me * big_r_x_coordinate* x_me + shares[4]; Ok(PresignOutput{ - big_r, - alpha_i: SigningShare::new(alpha_me), - beta_i: SigningShare::new(beta_me), + big_r: big_r.value().to_affine(), + alpha_i: alpha_me, + beta_i: beta_me, }) } @@ -283,6 +257,7 @@ mod test { eval_polynomial_on_participant, eval_polynomial_on_zero, }, + ecdsa::KeygenOutput, }; use frost_secp256k1::keys::PublicKeyPackage; use frost_secp256k1::VerifyingKey; diff --git a/src/ecdsa/robust_ecdsa/sign.rs b/src/ecdsa/robust_ecdsa/sign.rs index a703247..3e46e5a 100644 --- a/src/ecdsa/robust_ecdsa/sign.rs +++ b/src/ecdsa/robust_ecdsa/sign.rs @@ -15,7 +15,7 @@ use crate::{ }, ecdsa::{FullSignature, Scalar, AffinePoint}, }; -use super::presign::PresignOutput; +use super::PresignOutput; async fn do_sign( mut chan: SharedChannel, @@ -25,7 +25,7 @@ async fn do_sign( presignature: PresignOutput, msg_hash: Scalar, ) -> Result { - let s_me = msg_hash * presignature.alpha_i.to_scalar() + presignature.beta_i.to_scalar(); + let s_me = msg_hash * presignature.alpha_i + presignature.beta_i; let s_me = SigningShare::new(s_me); let wait_round = chan.next_waitpoint(); @@ -45,7 +45,7 @@ async fn do_sign( } let mut s = eval_interpolation(&s_map, None)?.to_scalar(); - let big_r = presignature.big_r.to_element().to_affine(); + let big_r = presignature.big_r; // Normalize s s.conditional_assign(&(-s), s.is_high()); diff --git a/src/ecdsa/robust_ecdsa/test.rs b/src/ecdsa/robust_ecdsa/test.rs index f56c0be..ff9e897 100644 --- a/src/ecdsa/robust_ecdsa/test.rs +++ b/src/ecdsa/robust_ecdsa/test.rs @@ -1,7 +1,9 @@ use std::error::Error; use super::{ - presign::{presign, PresignArguments, PresignOutput}, + PresignOutput, + PresignArguments, + presign::presign, sign::sign, };