diff --git a/crates/node/src/assets/cleanup.rs b/crates/node/src/assets/cleanup.rs index 31e7b26a9c..76fc8ad466 100644 --- a/crates/node/src/assets/cleanup.rs +++ b/crates/node/src/assets/cleanup.rs @@ -4,9 +4,10 @@ use crate::db::{DBCol, EPOCH_ID_KEY, SecretDB}; use crate::primitives; use crate::providers::ecdsa::presign::PresignOutputWithParticipants; use crate::providers::ecdsa::triple::PairedTriple; -use mpc_primitives::{EpochId, ReconstructionThreshold, domain::DomainId}; +use mpc_primitives::{EpochId, domain::DomainId}; use serde::{self, Deserialize, Serialize}; use std::sync::Arc; +use threshold_signatures::ReconstructionThreshold; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct EpochData { diff --git a/crates/node/src/assets/test_utils.rs b/crates/node/src/assets/test_utils.rs index 2e63aee8f3..ae16f2b0b9 100644 --- a/crates/node/src/assets/test_utils.rs +++ b/crates/node/src/assets/test_utils.rs @@ -14,13 +14,14 @@ use ed25519_dalek::{SigningKey, VerifyingKey}; use k256::ProjectivePoint; use mpc_contract::primitives::test_utils::gen_participants; use mpc_contract::primitives::thresholds::{Threshold, ThresholdParameters}; -use mpc_primitives::{EpochId, ReconstructionThreshold, domain::DomainId}; +use mpc_primitives::{EpochId, domain::DomainId}; use near_time::FakeClock; use rand::RngCore; use rand::rngs::OsRng; use serde::Serialize; use serde::de::DeserializeOwned; use std::sync::{Arc, Mutex}; +use threshold_signatures::ReconstructionThreshold; use threshold_signatures::ecdsa::Polynomial; use threshold_signatures::ecdsa::ot_based_ecdsa::PresignOutput; use threshold_signatures::ecdsa::ot_based_ecdsa::triples::{ diff --git a/crates/node/src/coordinator.rs b/crates/node/src/coordinator.rs index d5ff81ebca..5c4ab1f002 100644 --- a/crates/node/src/coordinator.rs +++ b/crates/node/src/coordinator.rs @@ -32,13 +32,13 @@ use crate::web::DebugRequest; use futures::FutureExt; use futures::future::BoxFuture; use mpc_node_config::ConfigFile; +use mpc_primitives::EpochId; use mpc_primitives::domain::{Curve, DomainId, Protocol}; -use mpc_primitives::{EpochId, ReconstructionThreshold}; use near_time::Clock; use std::collections::HashMap; use std::future::Future; use std::sync::{Arc, Mutex}; -use threshold_signatures::ReconstructionThreshold as TSReconstructionThreshold; +use threshold_signatures::ReconstructionThreshold; use threshold_signatures::{confidential_key_derivation, ecdsa, frost::eddsa}; use tokio::select; use tokio::sync::mpsc::unbounded_channel; @@ -332,7 +332,7 @@ where let (network_client, channel_receiver, _handle) = run_network_client(Arc::new(sender), Box::new(receiver)); let threshold: usize = mpc_config.participants.threshold.try_into()?; - let threshold = TSReconstructionThreshold::from(threshold); + let threshold = ReconstructionThreshold::from(threshold); if mpc_config.is_leader_for_key_event() { keygen_leader( network_client, @@ -780,7 +780,7 @@ where let args = Arc::new(ResharingArgs { previous_keyset, existing_keyshares, - new_threshold: TSReconstructionThreshold::from(new_threshold), + new_threshold: ReconstructionThreshold::from(new_threshold), old_participants: current_running_state.participants, }); diff --git a/crates/node/src/key_events.rs b/crates/node/src/key_events.rs index b9d1d9cc6a..3c468068e5 100644 --- a/crates/node/src/key_events.rs +++ b/crates/node/src/key_events.rs @@ -727,11 +727,11 @@ mod tests { use mpc_primitives::domain::DomainId; use mpc_primitives::{AttemptId, EpochId, KeyEventId}; use near_mpc_contract_interface::types::{ - DomainConfig, DomainPurpose, Protocol, ReconstructionThreshold, + DomainConfig, DomainPurpose, Protocol, }; use std::collections::BTreeSet; use std::sync::atomic::{AtomicUsize, Ordering}; - use threshold_signatures::ReconstructionThreshold as TSReconstructionThreshold; + use threshold_signatures::ReconstructionThreshold; #[rstest::rstest] #[tokio::test(start_paused = true)] @@ -888,7 +888,7 @@ mod tests { domain: DomainConfig { id: key_event_id.domain_id, protocol: Protocol::CaitSith, - reconstruction_threshold: ReconstructionThreshold::new(2), + reconstruction_threshold: dtos::ReconstructionThreshold::new(2), purpose: DomainPurpose::Sign, }, started, @@ -901,7 +901,7 @@ mod tests { Arc::new(ResharingArgs { previous_keyset: Keyset::new(EpochId::new(5), vec![]), existing_keyshares: None, - new_threshold: TSReconstructionThreshold::from(3), + new_threshold: ReconstructionThreshold::from(3), old_participants: ParticipantsConfig { threshold: 3, participants: vec![], diff --git a/crates/node/src/providers/ckd/sign.rs b/crates/node/src/providers/ckd/sign.rs index 74182141e7..c7c83f537d 100644 --- a/crates/node/src/providers/ckd/sign.rs +++ b/crates/node/src/providers/ckd/sign.rs @@ -42,7 +42,7 @@ impl CKDProvider { let participants = self .client .select_random_active_participants_including_me( - threshold.value(), + threshold.try_as_usize()?, &running_participants, ) .context("Could not choose active participants for a ckd")?; diff --git a/crates/node/src/providers/ecdsa.rs b/crates/node/src/providers/ecdsa.rs index 2f749a49b0..35bc6ded20 100644 --- a/crates/node/src/providers/ecdsa.rs +++ b/crates/node/src/providers/ecdsa.rs @@ -22,11 +22,10 @@ use mpc_node_config::ConfigFile; use crate::types::SignatureId; use borsh::{BorshDeserialize, BorshSerialize}; -use mpc_primitives::ReconstructionThreshold; use mpc_primitives::domain::DomainId; use near_time::Clock; use std::sync::Arc; -use threshold_signatures::ReconstructionThreshold as TSReconstructionThreshold; +use threshold_signatures::ReconstructionThreshold; use threshold_signatures::ecdsa::KeygenOutput; use threshold_signatures::ecdsa::Signature; use threshold_signatures::frost_secp256k1::VerifyingKey; @@ -193,14 +192,14 @@ impl SignatureProvider for EcdsaSignatureProvider { } async fn run_key_generation_client( - threshold: TSReconstructionThreshold, + threshold: ReconstructionThreshold, channel: NetworkTaskChannel, ) -> anyhow::Result { EcdsaSignatureProvider::run_key_generation_client_internal(threshold, channel).await } async fn run_key_resharing_client( - new_threshold: TSReconstructionThreshold, + new_threshold: ReconstructionThreshold, my_share: Option, public_key: VerifyingKey, old_participants: &ParticipantsConfig, @@ -271,8 +270,6 @@ impl SignatureProvider for EcdsaSignatureProvider { // and source `t` from `domain.reconstruction_threshold` rather than the // network-wide threshold. let threshold = ReconstructionThreshold::new(self.mpc_config.participants.threshold); - let threshold_usize: usize = threshold.inner().try_into()?; - let threshold_bound = TSReconstructionThreshold::from(threshold_usize); let triple_store = self.triple_store_for_t(threshold)?; let generate_triples = tracking::spawn( @@ -282,7 +279,7 @@ impl SignatureProvider for EcdsaSignatureProvider { self.mpc_config.clone(), self.config.triple.clone().into(), triple_store.clone(), - threshold_bound, + threshold, ), ); @@ -294,7 +291,7 @@ impl SignatureProvider for EcdsaSignatureProvider { &format!("generate presignatures for domain {}", domain_id.0), Self::run_background_presignature_generation( self.client.clone(), - threshold_bound, + threshold, self.config.presignature.clone().into(), triple_store.clone(), *domain_id, diff --git a/crates/node/src/providers/ecdsa/presign.rs b/crates/node/src/providers/ecdsa/presign.rs index 6c8bf85fb6..913aee545f 100644 --- a/crates/node/src/providers/ecdsa/presign.rs +++ b/crates/node/src/providers/ecdsa/presign.rs @@ -12,14 +12,13 @@ use crate::providers::ecdsa::{EcdsaSignatureProvider, EcdsaTaskId, KeygenOutput, use crate::tracking::AutoAbortTaskCollection; use crate::{metrics, tracking}; use mpc_node_config::PresignatureConfig; -use mpc_primitives::ReconstructionThreshold; use mpc_primitives::domain::DomainId; use near_time::Clock; use serde::{Deserialize, Serialize}; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::time::Duration; -use threshold_signatures::ReconstructionThreshold as TSReconstructionThreshold; +use threshold_signatures::ReconstructionThreshold; use threshold_signatures::ecdsa::ot_based_ecdsa::triples::TripleGenerationOutput; use threshold_signatures::ecdsa::ot_based_ecdsa::{ PresignArguments, PresignOutput, presign::presign, @@ -66,7 +65,7 @@ impl EcdsaSignatureProvider { /// so that needs to be separately handled. pub(super) async fn run_background_presignature_generation( client: Arc, - threshold: TSReconstructionThreshold, + threshold: ReconstructionThreshold, config: Arc, triple_store: Arc, domain_id: DomainId, @@ -182,11 +181,10 @@ impl EcdsaSignatureProvider { // Triple store to consume from is keyed by the presign's `t`, which // equals the number of presign participants (same as triple // participants — the leader pairs them). - let threshold_usize: usize = channel.participants().len(); - let threshold = ReconstructionThreshold::new(threshold_usize.try_into()?); + let threshold = ReconstructionThreshold::from(channel.participants().len()); let triple_store = self.triple_store_for_t(threshold)?; FollowerPresignComputation { - threshold: TSReconstructionThreshold::from(threshold_usize), + threshold, keygen_out: domain_data.keyshare, triple_store, paired_triple_id, @@ -220,7 +218,7 @@ impl HasParticipants for PresignOutputWithParticipants { /// Performs an MPC presignature operation. This is shared for the initiator /// and for passive participants. pub struct PresignComputation { - threshold: TSReconstructionThreshold, + threshold: ReconstructionThreshold, triple0: TripleGenerationOutput, triple1: TripleGenerationOutput, keygen_out: KeygenOutput, @@ -260,7 +258,7 @@ impl MpcLeaderCentricComputation for PresignComputation { /// The difference is: we need to read the triples from the triple store (which may fail), /// and we need to write the presignature to the presignature store before completing. pub struct FollowerPresignComputation { - pub threshold: TSReconstructionThreshold, + pub threshold: ReconstructionThreshold, pub paired_triple_id: UniqueId, pub keygen_out: KeygenOutput, pub triple_store: Arc, diff --git a/crates/node/src/providers/ecdsa/triple.rs b/crates/node/src/providers/ecdsa/triple.rs index d810d24a87..6015f6194e 100644 --- a/crates/node/src/providers/ecdsa/triple.rs +++ b/crates/node/src/providers/ecdsa/triple.rs @@ -12,13 +12,12 @@ use crate::providers::HasParticipants; use crate::providers::ecdsa::{EcdsaSignatureProvider, EcdsaTaskId}; use crate::tracking::AutoAbortTaskCollection; use mpc_node_config::TripleConfig; -use mpc_primitives::ReconstructionThreshold; use near_time::Clock; use rand::rngs::OsRng; use std::ops::Deref; use std::sync::Arc; use std::time::Duration; -use threshold_signatures::ReconstructionThreshold as TSReconstructionThreshold; +use threshold_signatures::ReconstructionThreshold; use threshold_signatures::ecdsa::ot_based_ecdsa::triples::TripleGenerationOutput; use threshold_signatures::participants::Participant; @@ -74,11 +73,16 @@ impl EcdsaSignatureProvider { mpc_config: Arc, config: Arc, triple_store: Arc, - threshold: TSReconstructionThreshold, + threshold: ReconstructionThreshold, ) -> ! { let in_flight_generations = InFlightGenerationTracker::new(); let parallelism_limiter = Arc::new(tokio::sync::Semaphore::new(config.concurrency)); let mut tasks = AutoAbortTaskCollection::new(); + // Converted once: loop-invariant, and this `-> !` loop has no error + // channel. Unreachable on supported targets (count fits in `usize`). + let threshold_usize = threshold + .try_as_usize() + .expect("reconstruction threshold fits in usize"); let running_participants: Vec<_> = mpc_config .participants .participants @@ -107,7 +111,7 @@ impl EcdsaSignatureProvider { < config.concurrency * 2 * SUPPORTED_TRIPLE_GENERATION_BATCH_SIZE { let participants = match client.select_random_active_participants_including_me( - threshold.value(), + threshold_usize, &running_participants, ) { Ok(participants) => participants, @@ -208,11 +212,10 @@ impl EcdsaSignatureProvider { // Cait-sith triple generation runs with exactly `t` participants, so we // can derive the store's `t` from the channel's participant list // without a wire-format change to `EcdsaTaskId::ManyTriples`. - let threshold_usize: usize = channel.participants().len(); - let threshold = ReconstructionThreshold::new(threshold_usize.try_into()?); + let threshold = ReconstructionThreshold::from(channel.participants().len()); let triple_store = self.triple_store_for_t(threshold)?; FollowerManyTripleGenerationComputation:: { - threshold: TSReconstructionThreshold::from(threshold_usize), + threshold, out_triple_id_start: start, out_triple_store: triple_store, } @@ -240,7 +243,7 @@ impl HasParticipants for PairedTriple { /// Generates many cait-sith triples at once. This can significantly save the /// *number* of network messages. pub struct ManyTripleGenerationComputation { - pub threshold: TSReconstructionThreshold, + pub threshold: ReconstructionThreshold, } #[async_trait::async_trait] @@ -288,7 +291,7 @@ impl MpcLeaderCentricComputation> /// The follower version of the triple generation. The difference is that the follower will only /// complete the computation after successfully persisting the triples to storage. pub struct FollowerManyTripleGenerationComputation { - pub threshold: TSReconstructionThreshold, + pub threshold: ReconstructionThreshold, pub out_triple_store: Arc, pub out_triple_id_start: UniqueId, } @@ -348,7 +351,6 @@ mod tests { use futures::{FutureExt, StreamExt, stream}; use std::collections::HashMap; use std::sync::Arc; - use threshold_signatures::ReconstructionThreshold as TSReconstructionThreshold; use threshold_signatures::test_utils::generate_participants; use tokio::sync::mpsc; @@ -405,7 +407,7 @@ mod tests { panic!("Unexpected task id"); }; let triples = ManyTripleGenerationComputation:: { - threshold: TSReconstructionThreshold::from(THRESHOLD), + threshold: ReconstructionThreshold::from(THRESHOLD), } .perform_leader_centric_computation( channel, @@ -453,7 +455,7 @@ mod tests { let result = tracking::spawn( &format!("task {:?}", task_id), ManyTripleGenerationComputation:: { - threshold: TSReconstructionThreshold::from(THRESHOLD), + threshold: ReconstructionThreshold::from(THRESHOLD), } .perform_leader_centric_computation( channel, diff --git a/crates/node/src/providers/eddsa/sign.rs b/crates/node/src/providers/eddsa/sign.rs index 64c08208af..2eda2835a1 100644 --- a/crates/node/src/providers/eddsa/sign.rs +++ b/crates/node/src/providers/eddsa/sign.rs @@ -37,7 +37,7 @@ impl EddsaSignatureProvider { let participants = self .client .select_random_active_participants_including_me( - threshold.value(), + threshold.try_as_usize()?, &running_participants, ) .context("Can't choose active participants for a eddsa signature")?; diff --git a/crates/node/src/providers/robust_ecdsa.rs b/crates/node/src/providers/robust_ecdsa.rs index 338a464fd1..8162985f60 100644 --- a/crates/node/src/providers/robust_ecdsa.rs +++ b/crates/node/src/providers/robust_ecdsa.rs @@ -149,7 +149,7 @@ impl SignatureProvider for RobustEcdsaSignatureProvider { ) -> anyhow::Result { let number_of_participants = channel.participants().len(); let robust_ecdsa_threshold = - translate_threshold(threshold.value(), number_of_participants)?; + translate_threshold(threshold.try_as_usize()?, number_of_participants)?; EcdsaSignatureProvider::run_key_generation_client_internal( ReconstructionThreshold::try_from(robust_ecdsa_threshold)?, channel, @@ -166,7 +166,7 @@ impl SignatureProvider for RobustEcdsaSignatureProvider { ) -> anyhow::Result { let number_of_participants = channel.participants().len(); let new_robust_ecdsa_threshold = - translate_threshold(new_threshold.value(), number_of_participants)?; + translate_threshold(new_threshold.try_as_usize()?, number_of_participants)?; // This is a bad hack, but cannot think of a better way to solve it, as the struct // comes directly from generic implementations, so probably this is the best place @@ -176,9 +176,8 @@ impl SignatureProvider for RobustEcdsaSignatureProvider { old_participants.threshold.try_into()?, old_participants.participants.len(), )?; - old_participants_patched.threshold = ReconstructionThreshold::try_from(old_translated)? - .value() - .try_into()?; + old_participants_patched.threshold = + ReconstructionThreshold::try_from(old_translated)?.inner(); EcdsaSignatureProvider::run_key_resharing_client_internal( ReconstructionThreshold::try_from(new_robust_ecdsa_threshold)?, diff --git a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs index a4c462ba76..a748145b3f 100644 --- a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs +++ b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v1.rs @@ -22,7 +22,9 @@ type PreparedSimulatedSig = PreparedOutputs; /// Benches the signing protocol fn bench_sign(c: &mut Criterion) { - let num = RECONSTRUCTION_LOWER_BOUND.value(); + let num = RECONSTRUCTION_LOWER_BOUND + .try_as_usize() + .expect("reconstruction threshold fits in usize"); let max_malicious = *MAX_MALICIOUS; let setup = setup_sign_snapshot(*RECONSTRUCTION_LOWER_BOUND); diff --git a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs index 2b4a8df84d..363f916185 100644 --- a/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs +++ b/crates/threshold-signatures/benches/advanced_eddsa_frost_sign_v2.rs @@ -27,7 +27,9 @@ type PreparedSimulatedSig = PreparedOutputs; /// Benches the presigning protocol fn bench_presign(c: &mut Criterion) { - let num = RECONSTRUCTION_LOWER_BOUND.value(); + let num = RECONSTRUCTION_LOWER_BOUND + .try_as_usize() + .expect("reconstruction threshold fits in usize"); let max_malicious = *MAX_MALICIOUS; let setup = setup_presign_snapshot(num); @@ -50,7 +52,9 @@ fn bench_presign(c: &mut Criterion) { /// Benches the signing protocol fn bench_sign(c: &mut Criterion) { - let num = RECONSTRUCTION_LOWER_BOUND.value(); + let num = RECONSTRUCTION_LOWER_BOUND + .try_as_usize() + .expect("reconstruction threshold fits in usize"); let max_malicious = *MAX_MALICIOUS; let setup = setup_sign_snapshot(*RECONSTRUCTION_LOWER_BOUND); @@ -151,7 +155,12 @@ struct SignSetup { /// Expensive one-time setup for sign: runs the full N-party protocol to capture snapshots fn setup_sign_snapshot(threshold: ReconstructionThreshold) -> SignSetup { let mut rng = MockCryptoRng::seed_from_u64(41); - let preps = ed25519_prepare_presign(threshold.value(), &mut rng); + let preps = ed25519_prepare_presign( + threshold + .try_as_usize() + .expect("reconstruction threshold fits in usize"), + &mut rng, + ); let result = run_protocol(preps.protocols).expect("Prepare sign should not fail"); let preps = ed25519_prepare_sign_v2(&result, preps.key_packages, threshold, &mut rng); let (_, protocol_snapshot) = run_protocol_and_take_snapshots(preps.protocols) diff --git a/crates/threshold-signatures/benches/bench_utils/frost_eddsa.rs b/crates/threshold-signatures/benches/bench_utils/frost_eddsa.rs index 87160a27f7..97f149fb6a 100644 --- a/crates/threshold-signatures/benches/bench_utils/frost_eddsa.rs +++ b/crates/threshold-signatures/benches/bench_utils/frost_eddsa.rs @@ -66,7 +66,9 @@ pub fn ed25519_prepare_sign_v1( threshold: ReconstructionThreshold, rng: &mut R, ) -> FrostEd25519SigV1 { - let num_participants = threshold.value(); + let num_participants = threshold + .try_as_usize() + .expect("reconstruction threshold fits in usize"); let participants = generate_participants_with_random_ids(num_participants, rng); let key_packages = run_keygen(&participants, *MAX_MALICIOUS + 1, rng); @@ -110,7 +112,9 @@ pub fn ed25519_prepare_sign_v2( threshold: ReconstructionThreshold, rng: &mut R, ) -> FrostEd25519SigV2 { - let num_participants = threshold.value(); + let num_participants = threshold + .try_as_usize() + .expect("reconstruction threshold fits in usize"); // collect all participants let participants: Vec<_> = result.iter().map(|(participant, _)| *participant).collect(); @@ -175,7 +179,9 @@ pub fn prepare_ckd( threshold: ReconstructionThreshold, rng: &mut R, ) -> PreparedCkdPackage { - let num_participants = threshold.value(); + let num_participants = threshold + .try_as_usize() + .expect("reconstruction threshold fits in usize"); // collect all participants let participants = generate_participants_with_random_ids(num_participants, rng); let key_packages = run_keygen(&participants, *MAX_MALICIOUS + 1, rng); diff --git a/crates/threshold-signatures/benches/ckd.rs b/crates/threshold-signatures/benches/ckd.rs index 2e0d72c080..d8e91b0973 100644 --- a/crates/threshold-signatures/benches/ckd.rs +++ b/crates/threshold-signatures/benches/ckd.rs @@ -27,7 +27,9 @@ fn threshold() -> ReconstructionThreshold { /// Benches the ckd protocol fn bench_ckd(c: &mut Criterion) { - let num = threshold().value(); + let num = threshold() + .try_as_usize() + .expect("reconstruction threshold fits in usize"); let max_malicious = *MAX_MALICIOUS; let setup = setup_ckd_snapshot(threshold()); diff --git a/crates/threshold-signatures/src/dkg.rs b/crates/threshold-signatures/src/dkg.rs index ecda580f35..7f5621dcde 100644 --- a/crates/threshold-signatures/src/dkg.rs +++ b/crates/threshold-signatures/src/dkg.rs @@ -180,7 +180,7 @@ fn verify_proof_of_knowledge( commitment: &VerifiableSecretSharingCommitment, proof_of_knowledge: Option<&Signature>, ) -> Result<(), ProtocolError> { - let threshold = threshold.value(); + let threshold = threshold.try_as_usize()?; match proof_of_knowledge { // if participant did not send anything but he is actually an old participant None => { @@ -243,17 +243,17 @@ fn verify_commitment_hash( fn insert_identity_if_missing( threshold: ReconstructionThreshold, commitment_i: &VerifiableSecretSharingCommitment, -) -> VerifiableSecretSharingCommitment { +) -> Result, ProtocolError> { // in case the participant was new and it sent a polynomial of length // threshold -1 (because the zero term is not serializable) let mut commitment_i = commitment_i.clone(); let mut coefficients_i = commitment_i.coefficients().to_vec(); - if coefficients_i.len() == threshold.value() - 1 { + if coefficients_i.len() == threshold.try_as_usize()? - 1 { let identity = CoefficientCommitment::new(::identity()); coefficients_i.insert(0, identity); commitment_i = VerifiableSecretSharingCommitment::new(coefficients_i); } - commitment_i + Ok(commitment_i) } // creates a signing share structure using my identifier, the received @@ -373,7 +373,7 @@ async fn do_keyshare( // Step 2.3 // the degree of the polynomial is threshold - 1 let degree = threshold - .value() + .try_as_usize()? .checked_sub(1) .ok_or(ProtocolError::IntegerOverflow)?; let secret_coefficients = Polynomial::::generate_polynomial(Some(secret), degree, rng)?; @@ -429,7 +429,7 @@ async fn do_keyshare( // Start Round 3 // add my commitment to the map with the proper commitment sizes = threshold - let my_full_commitment = insert_identity_if_missing(threshold, &commitment); + let my_full_commitment = insert_identity_if_missing(threshold, &commitment)?; all_full_commitments.put(me, my_full_commitment); // Broadcast the commitment and the proof of knowledge @@ -472,7 +472,7 @@ async fn do_keyshare( // 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); + let full_commitment_i = insert_identity_if_missing(threshold, commitment_i)?; // add received full commitment all_full_commitments.put(p, full_commitment_i); @@ -566,7 +566,7 @@ pub fn assert_key_invariants( me: Participant, threshold: impl Into, ) -> Result { - let threshold = usize::from(threshold.into()); + let threshold = threshold.into().try_as_usize()?; // need enough participants if participants.len() < 2 { return Err(InitializationError::NotEnoughParticipants { @@ -649,8 +649,8 @@ pub fn assert_reshare_keys_invariants( old_threshold: impl Into, old_participants: &[Participant], ) -> Result<(ParticipantList, ParticipantList), InitializationError> { - let threshold = usize::from(threshold.into()); - let old_threshold = usize::from(old_threshold.into()); + let threshold = threshold.into(); + let old_threshold = old_threshold.into().try_as_usize()?; let participants = assert_key_invariants(participants, me, threshold)?; diff --git a/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/presign.rs b/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/presign.rs index 5ff172fda5..b4f49e90a2 100644 --- a/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/presign.rs +++ b/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/presign.rs @@ -31,9 +31,10 @@ pub fn presign( }); } // Spec 1.1 - if args.threshold.value() > participants.len() { + let threshold = args.threshold.try_as_usize()?; + if threshold > participants.len() { return Err(InitializationError::ThresholdTooLarge { - threshold: args.threshold.value(), + threshold, max: participants.len(), }); } diff --git a/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/sign.rs b/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/sign.rs index 76c17af785..44f3b20a9a 100644 --- a/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/sign.rs +++ b/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/sign.rs @@ -37,7 +37,7 @@ pub fn sign( where T: Into, { - let threshold = usize::from(threshold.into()); + let threshold = threshold.into().try_as_usize()?; if participants.len() < 2 { return Err(InitializationError::NotEnoughParticipants { participants: participants.len(), diff --git a/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/triples/generation.rs b/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/triples/generation.rs index e1cfd541f3..a5dd1ff514 100644 --- a/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/triples/generation.rs +++ b/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/triples/generation.rs @@ -41,12 +41,7 @@ fn create_transcript( let enc = rmp_serde::encode::to_vec(participants).map_err(|_| ProtocolError::ErrorEncoding)?; transcript.message(b"participants", &enc); // To allow interop between platforms where usize is different - transcript.message( - b"threshold", - &u64::try_from(threshold.value()) - .expect("threshold should always fit in u64") - .to_be_bytes(), - ); + transcript.message(b"threshold", &threshold.inner().to_be_bytes()); Ok(transcript) } @@ -149,11 +144,11 @@ async fn do_generation_many( let mut big_l_i_v = vec![]; let degree1 = threshold - .value() + .try_as_usize()? .checked_sub(1) .ok_or(ProtocolError::IntegerOverflow)?; let degree2 = threshold - .value() + .try_as_usize()? .checked_sub(2) .ok_or(ProtocolError::IntegerOverflow)?; @@ -376,10 +371,11 @@ async fn do_generation_many( big_f_v.iter_mut(), big_l_v.iter_mut(), )) { - if their_big_e.degree() != threshold.value() - 1 - || their_big_f.degree() != threshold.value() - 1 + let threshold_value = threshold.try_as_usize()?; + if their_big_e.degree() != threshold_value - 1 + || their_big_f.degree() != threshold_value - 1 // degree is threshold - 2 because the constant element identity is not serializable - || their_big_l.degree() != threshold.value() - 2 + || their_big_l.degree() != threshold_value - 2 { return Err(ProtocolError::AssertionFailed(format!( "polynomial from {from:?} has the wrong length" @@ -730,7 +726,7 @@ fn validate_triple_inputs( threshold: impl Into, ) -> Result<(ParticipantList, ReconstructionThreshold), InitializationError> { let threshold = threshold.into(); - let threshold_value = threshold.value(); + let threshold_value = threshold.try_as_usize()?; if participants.len() < 2 { return Err(InitializationError::NotEnoughParticipants { participants: participants.len(), diff --git a/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/triples/test.rs b/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/triples/test.rs index 88cd9ba2f5..5d89171beb 100644 --- a/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/triples/test.rs +++ b/crates/threshold-signatures/src/ecdsa/ot_based_ecdsa/triples/test.rs @@ -31,7 +31,7 @@ pub fn deal( let b = Secp256K1ScalarField::random(&mut *rng); let c = a * b; - let degree = threshold.value().checked_sub(1).unwrap(); + let degree = threshold.try_as_usize()?.checked_sub(1).unwrap(); let f_a = Polynomial::generate_polynomial(Some(a), degree, rng)?; let f_b = Polynomial::generate_polynomial(Some(b), degree, rng)?; let f_c = Polynomial::generate_polynomial(Some(c), degree, rng)?; diff --git a/crates/threshold-signatures/src/errors.rs b/crates/threshold-signatures/src/errors.rs index 61aecf6acf..acacc6ce5c 100644 --- a/crates/threshold-signatures/src/errors.rs +++ b/crates/threshold-signatures/src/errors.rs @@ -1,4 +1,5 @@ use crate::participants::Participant; +use crate::thresholds::ThresholdError; use std::error; use thiserror::Error; @@ -165,3 +166,15 @@ pub enum InitializationError { #[error("participant has an invalid index")] InvalidParticipantIndex, } + +impl From for ProtocolError { + fn from(_: ThresholdError) -> Self { + Self::IntegerOverflow + } +} + +impl From for InitializationError { + fn from(value: ThresholdError) -> Self { + Self::BadParameters(value.to_string()) + } +} diff --git a/crates/threshold-signatures/src/frost/eddsa/sign.rs b/crates/threshold-signatures/src/frost/eddsa/sign.rs index 9206e5bc72..6b1a6d56d1 100644 --- a/crates/threshold-signatures/src/frost/eddsa/sign.rs +++ b/crates/threshold-signatures/src/frost/eddsa/sign.rs @@ -406,7 +406,7 @@ fn construct_key_package( signing_share, verifying_share, *verifying_key, - u16::try_from(threshold.value()).map_err(|_| { + u16::try_from(threshold.inner()).map_err(|_| { ProtocolError::Other("threshold cannot be converted to u16".to_string()) })?, )) diff --git a/crates/threshold-signatures/src/frost/redjubjub/sign.rs b/crates/threshold-signatures/src/frost/redjubjub/sign.rs index 5823248684..7a29c2cbd7 100644 --- a/crates/threshold-signatures/src/frost/redjubjub/sign.rs +++ b/crates/threshold-signatures/src/frost/redjubjub/sign.rs @@ -280,7 +280,7 @@ fn construct_key_package( signing_share, verifying_share, verifying_key, - u16::try_from(threshold.value()).map_err(|_| { + u16::try_from(threshold.inner()).map_err(|_| { ProtocolError::Other("threshold cannot be converted to u16".to_string()) })?, ); diff --git a/crates/threshold-signatures/src/frost/sign_utils.rs b/crates/threshold-signatures/src/frost/sign_utils.rs index 94cbe558f2..1692a8b294 100644 --- a/crates/threshold-signatures/src/frost/sign_utils.rs +++ b/crates/threshold-signatures/src/frost/sign_utils.rs @@ -29,9 +29,10 @@ pub fn assert_participant_inputs( } // validate threshold - if threshold.value() > participants.len() { + let threshold = threshold.try_as_usize()?; + if threshold > participants.len() { return Err(InitializationError::ThresholdTooLarge { - threshold: threshold.value(), + threshold, max: participants.len(), }); } diff --git a/crates/threshold-signatures/src/lib.rs b/crates/threshold-signatures/src/lib.rs index 0834b1d6b5..0bbf6114d9 100644 --- a/crates/threshold-signatures/src/lib.rs +++ b/crates/threshold-signatures/src/lib.rs @@ -36,7 +36,7 @@ use crate::errors::InitializationError; use crate::participants::Participant; use crate::protocol::Protocol; use crate::protocol::internal::{Comms, make_protocol}; -pub use crate::thresholds::{MaxMalicious, ReconstructionThreshold}; +pub use crate::thresholds::{MaxMalicious, ReconstructionThreshold, ThresholdError}; use rand_core::CryptoRngCore; use std::marker::Send; diff --git a/crates/threshold-signatures/src/thresholds.rs b/crates/threshold-signatures/src/thresholds.rs index dd56f36dfc..9db498128e 100644 --- a/crates/threshold-signatures/src/thresholds.rs +++ b/crates/threshold-signatures/src/thresholds.rs @@ -7,10 +7,8 @@ use thiserror::Error; )] pub struct MaxMalicious(usize); -#[derive( - Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, From, Into, -)] -pub struct ReconstructionThreshold(usize); +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct ReconstructionThreshold(u64); // ----- MaxMalicious conversions ----- impl MaxMalicious { @@ -20,9 +18,34 @@ impl MaxMalicious { } impl ReconstructionThreshold { - pub fn value(self) -> usize { + pub const fn new(value: u64) -> Self { + Self(value) + } + + pub fn inner(self) -> u64 { self.0 } + + /// The threshold as a `usize`, for indexing and sizing collections. + /// + /// # Errors + /// + /// [`ThresholdError::IntegerOverflow`] if the threshold exceeds `usize::MAX` + /// — unreachable on supported (>= 32-bit) targets. + pub fn try_as_usize(self) -> Result { + usize::try_from(self.0).map_err(|_| ThresholdError::IntegerOverflow) + } +} + +/// Construct from a `usize` participant count. `usize` → `u64` is lossless on +/// all supported targets. Provided (instead of `derive_more::From`) so that +/// untyped integer literals like `ReconstructionThreshold::from(3)` infer cleanly +/// and the crypto crate keeps its `usize`-based construction ergonomics; the node +/// constructs the `u64`-backed value via [`ReconstructionThreshold::new`]. +impl From for ReconstructionThreshold { + fn from(value: usize) -> Self { + Self(value as u64) + } } /// Lower bound to reconstruct the secret is `MaxMalicious` + 1. @@ -30,7 +53,9 @@ impl TryFrom for ReconstructionThreshold { type Error = ThresholdError; fn try_from(m: MaxMalicious) -> Result { - m.0.checked_add(1) + u64::try_from(m.0) + .ok() + .and_then(|v| v.checked_add(1)) .map(Self) .ok_or(ThresholdError::IntegerOverflow) } @@ -41,3 +66,71 @@ pub enum ThresholdError { #[error("integer overflow")] IntegerOverflow, } + +#[cfg(test)] +#[allow(non_snake_case)] +mod tests { + use super::*; + + #[test] + fn reconstruction_threshold__should_round_trip_through_new_and_inner() { + // Given + let threshold = ReconstructionThreshold::new(3); + + // When + let inner = threshold.inner(); + + // Then + assert_eq!(inner, 3); + assert_eq!(ReconstructionThreshold::new(inner), threshold); + } + + #[test] + fn reconstruction_threshold__should_round_trip_through_from_usize_and_try_as_usize() { + // Given + let threshold = ReconstructionThreshold::from(5usize); + + // When + let as_usize = threshold.try_as_usize(); + + // Then + assert_eq!(as_usize, Ok(5usize)); + assert_eq!(ReconstructionThreshold::from(as_usize.unwrap()), threshold); + } + + #[test] + fn try_as_usize__should_return_ok_for_representable_value() { + // Given + let threshold = ReconstructionThreshold::new(7); + + // When + let result = threshold.try_as_usize(); + + // Then + assert_eq!(result, Ok(7usize)); + } + + #[test] + fn try_from_max_malicious__should_be_max_malicious_plus_one() { + // Given + let max_malicious = MaxMalicious::from(4usize); + + // When + let threshold = ReconstructionThreshold::try_from(max_malicious); + + // Then + assert_eq!(threshold, Ok(ReconstructionThreshold::new(5))); + } + + #[test] + fn try_from_max_malicious__should_overflow_when_at_usize_max() { + // Given + let max_malicious = MaxMalicious::from(usize::MAX); + + // When + let result = ReconstructionThreshold::try_from(max_malicious); + + // Then + assert_eq!(result, Err(ThresholdError::IntegerOverflow)); + } +}