Skip to content

Commit 391706f

Browse files
committed
feat: proper threshold handling for robust ecdsa, adapted tests
1 parent 67ccc3d commit 391706f

File tree

7 files changed

+103
-52
lines changed

7 files changed

+103
-52
lines changed

crates/contract/src/primitives/thresholds.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ impl ThresholdParameters {
6161
if k.value() < MIN_THRESHOLD_ABSOLUTE {
6262
return Err(InvalidThreshold::MinAbsRequirementFailed.into());
6363
}
64-
// TODO: this is just a temporary fix else I cannot make tests work for robust-ecdsa
65-
let percentage_bound = (2 * n_shares).div_ceil(5); // minimum 60%
64+
65+
let percentage_bound = (3 * n_shares).div_ceil(5); // minimum 60%
6666
if k.value() < percentage_bound {
6767
return Err(InvalidThreshold::MinRelRequirementFailed.message(format!(
6868
"require at least {}, found {:?}",
@@ -183,7 +183,7 @@ mod tests {
183183
#[test]
184184
fn test_validate_threshold() {
185185
let n = rand::thread_rng().gen_range(2..600) as u64;
186-
let min_threshold = ((n as f64) * 0.4).ceil() as u64;
186+
let min_threshold = ((n as f64) * 0.6).ceil() as u64;
187187
for k in 0..min_threshold {
188188
assert!(ThresholdParameters::validate_threshold(n, Threshold::new(k)).is_err());
189189
}
@@ -196,7 +196,7 @@ mod tests {
196196
#[test]
197197
fn test_threshold_parameters_constructor() {
198198
let n: usize = rand::thread_rng().gen_range(2..600);
199-
let min_threshold = ((n as f64) * 0.4).ceil() as usize;
199+
let min_threshold = ((n as f64) * 0.6).ceil() as usize;
200200

201201
let participants = gen_participants(n);
202202
for k in 1..min_threshold {

crates/node/src/key_events.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ pub async fn keygen_computation_inner(
6868
SignatureScheme::V2Secp256k1 => {
6969
let keyshare =
7070
RobustEcdsaSignatureProvider::run_key_generation_client(threshold, channel).await?;
71-
println!("did we compute?");
7271
let public_key = keyshare.public_key.into_contract_interface_type();
7372
(KeyshareData::V2Secp256k1(keyshare), public_key)
7473
}

crates/node/src/providers/robust_ecdsa.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ impl SignatureProvider for RobustEcdsaSignatureProvider {
129129
threshold: usize,
130130
channel: NetworkTaskChannel,
131131
) -> anyhow::Result<Self::KeygenOutput> {
132-
EcdsaSignatureProvider::run_key_generation_client_internal(threshold, channel).await
132+
let number_of_participants = channel.participants().len();
133+
let robust_ecdsa_threshold = translate_threshold(threshold, number_of_participants);
134+
EcdsaSignatureProvider::run_key_generation_client_internal(robust_ecdsa_threshold, channel)
135+
.await
133136
}
134137

135138
async fn run_key_resharing_client(
@@ -139,11 +142,23 @@ impl SignatureProvider for RobustEcdsaSignatureProvider {
139142
old_participants: &ParticipantsConfig,
140143
channel: NetworkTaskChannel,
141144
) -> anyhow::Result<Self::KeygenOutput> {
145+
let number_of_participants = channel.participants().len();
146+
let new_robust_ecdsa_threshold = translate_threshold(new_threshold, number_of_participants);
147+
148+
// This is a bad hack, but cannot think of a better way to solve it, as the struct
149+
// comes directly from generic implementations, so probably this is the best place
150+
// to do so anyway
151+
let mut old_participants_patched = old_participants.clone();
152+
old_participants_patched.threshold = translate_threshold(
153+
old_participants.threshold as usize,
154+
old_participants.participants.len(),
155+
) as u64;
156+
142157
EcdsaSignatureProvider::run_key_resharing_client_internal(
143-
new_threshold,
158+
new_robust_ecdsa_threshold,
144159
my_share,
145160
public_key,
146-
old_participants,
161+
&old_participants_patched,
147162
channel,
148163
)
149164
.await
@@ -205,3 +220,19 @@ impl SignatureProvider for RobustEcdsaSignatureProvider {
205220
Ok(())
206221
}
207222
}
223+
224+
pub(super) fn get_number_of_signers(threshold: usize, _number_of_participants: usize) -> usize {
225+
// TODO: this is the case for the other schemes, might not be our best choice for this one
226+
threshold
227+
}
228+
229+
/// This function translates the current threshold from the contract
230+
/// to the threshold expected by the robust-ecdsa scheme, which
231+
/// is semantically different.
232+
/// The function should be no longer needed when these issues are solved:
233+
/// https://github.com/near/threshold-signatures/issues/255
234+
/// https://github.com/near/mpc/issues/1649
235+
pub(super) fn translate_threshold(threshold: usize, number_of_participants: usize) -> usize {
236+
let number_of_signers = get_number_of_signers(threshold, number_of_participants);
237+
(number_of_signers - 1) / 2
238+
}

crates/node/src/providers/robust_ecdsa/presign.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use crate::network::{MeshNetworkClient, NetworkTaskChannel};
77
use crate::primitives::{ParticipantId, UniqueId};
88
use crate::protocol::run_protocol;
99
use crate::providers::robust_ecdsa::{
10-
KeygenOutput, RobustEcdsaSignatureProvider, RobustEcdsaTaskId,
10+
get_number_of_signers, translate_threshold, KeygenOutput, RobustEcdsaSignatureProvider,
11+
RobustEcdsaTaskId,
1112
};
1213
use crate::providers::HasParticipants;
1314
use crate::tracking::AutoAbortTaskCollection;
@@ -92,9 +93,9 @@ impl RobustEcdsaSignatureProvider {
9293
.collect();
9394

9495
let threshold = mpc_config.participants.threshold as usize;
95-
96-
// TODO: we need to guarantee this is less than the total number of participants in the contract
97-
anyhow::ensure!(running_participants.len() >= 2 * threshold + 1);
96+
let num_signers = get_number_of_signers(threshold, running_participants.len());
97+
let robust_ecdsa_threshold = translate_threshold(threshold, running_participants.len());
98+
anyhow::ensure!(robust_ecdsa_threshold * 2 + 1 <= num_signers);
9899

99100
loop {
100101
progress_tracker.update_progress();
@@ -114,13 +115,13 @@ impl RobustEcdsaSignatureProvider {
114115
{
115116
let id = presignature_store.generate_and_reserve_id();
116117
let participants = match client.select_random_active_participants_including_me(
117-
2 * threshold + 1,
118+
num_signers,
118119
&running_participants,
119120
) {
120121
Ok(participants) => participants,
121122
Err(e) => {
122123
tracing::warn!(
123-
"Can't choose active participants for a triple: {}. Sleeping.",
124+
"Can't choose active participants for a robust-ecdsa presignature: {}. Sleeping.",
124125
e
125126
);
126127
// that should not happen often, so sleeping here is okay
@@ -139,7 +140,7 @@ impl RobustEcdsaSignatureProvider {
139140
let _in_flight = in_flight;
140141
let _semaphore_guard = parallelism_limiter.acquire().await?;
141142
let presignature = PresignComputation {
142-
threshold,
143+
threshold: robust_ecdsa_threshold,
143144
keygen_out,
144145
}
145146
.perform_leader_centric_computation(
@@ -177,8 +178,12 @@ impl RobustEcdsaSignatureProvider {
177178
) -> anyhow::Result<()> {
178179
let domain_data = self.domain_data(domain_id)?;
179180

181+
let number_of_participants = self.mpc_config.participants.participants.len();
182+
let threshold = self.mpc_config.participants.threshold as usize;
183+
let robust_ecdsa_threshold = translate_threshold(threshold, number_of_participants);
184+
180185
FollowerPresignComputation {
181-
threshold: self.mpc_config.participants.threshold as usize,
186+
threshold: robust_ecdsa_threshold,
182187
keygen_out: domain_data.keyshare,
183188
out_presignature_store: domain_data.presignature_store,
184189
out_presignature_id: id,
@@ -224,6 +229,11 @@ impl MpcLeaderCentricComputation<PresignOutput> for PresignComputation {
224229
.map(Participant::from)
225230
.collect::<Vec<_>>();
226231
let me = channel.my_participant_id();
232+
println!(
233+
"computing presign {} {}",
234+
self.threshold,
235+
cs_participants.len()
236+
);
227237
let protocol = presign(
228238
&cs_participants,
229239
me.into(),

crates/node/src/tests/basic_cluster.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use near_time::Clock;
1414
#[tokio::test]
1515
async fn test_basic_cluster() {
1616
init_integration_logger();
17-
const NUM_PARTICIPANTS: usize = 5;
18-
const THRESHOLD: usize = 2;
17+
const NUM_PARTICIPANTS: usize = 6;
18+
const THRESHOLD: usize = 5;
1919
const TXN_DELAY_BLOCKS: u64 = 1;
2020
let temp_dir = tempfile::tempdir().unwrap();
2121
let mut setup: IntegrationTestSetup = IntegrationTestSetup::new(
@@ -50,15 +50,17 @@ async fn test_basic_cluster() {
5050
scheme: SignatureScheme::V2Secp256k1,
5151
};
5252

53+
let domains = vec![
54+
signature_domain_ecdsa.clone(),
55+
signature_domain_eddsa.clone(),
56+
ckd_domain.clone(),
57+
signature_domain_robust_ecdsa.clone(),
58+
];
59+
5360
{
5461
let mut contract = setup.indexer.contract_mut().await;
5562
contract.initialize(setup.participants.clone());
56-
contract.add_domains(vec![
57-
signature_domain_ecdsa.clone(),
58-
signature_domain_eddsa.clone(),
59-
ckd_domain.clone(),
60-
signature_domain_robust_ecdsa.clone(),
61-
]);
63+
contract.add_domains(domains.clone());
6264
}
6365

6466
let _runs = setup
@@ -71,7 +73,7 @@ async fn test_basic_cluster() {
7173
.indexer
7274
.wait_for_contract_state(
7375
|state| matches!(state, ContractState::Running(_)),
74-
DEFAULT_MAX_PROTOCOL_WAIT_TIME * 3,
76+
DEFAULT_MAX_PROTOCOL_WAIT_TIME * domains.len() as u32,
7577
)
7678
.await
7779
.expect("timeout waiting for keygen to complete");

crates/node/src/tests/multidomain.rs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use near_time::Clock;
1414
#[tokio::test]
1515
async fn test_basic_multidomain() {
1616
init_integration_logger();
17-
const NUM_PARTICIPANTS: usize = 7;
18-
const THRESHOLD: usize = 3;
17+
const NUM_PARTICIPANTS: usize = 6;
18+
const THRESHOLD: usize = 5;
1919
const TXN_DELAY_BLOCKS: u64 = 1;
2020
let temp_dir = tempfile::tempdir().unwrap();
2121
let mut setup = IntegrationTestSetup::new(
@@ -65,7 +65,7 @@ async fn test_basic_multidomain() {
6565
.indexer
6666
.wait_for_contract_state(
6767
|state| matches!(state, ContractState::Running(_)),
68-
DEFAULT_MAX_PROTOCOL_WAIT_TIME * 3,
68+
DEFAULT_MAX_PROTOCOL_WAIT_TIME * domains.len() as u32,
6969
)
7070
.await
7171
.expect("must not exceed timeout");
@@ -97,29 +97,29 @@ async fn test_basic_multidomain() {
9797
}
9898
}
9999
}
100+
let new_domains = vec![
101+
DomainConfig {
102+
id: DomainId(4),
103+
scheme: SignatureScheme::Ed25519,
104+
},
105+
DomainConfig {
106+
id: DomainId(5),
107+
scheme: SignatureScheme::Secp256k1,
108+
},
109+
DomainConfig {
110+
id: DomainId(6),
111+
scheme: SignatureScheme::Bls12381,
112+
},
113+
DomainConfig {
114+
id: DomainId(7),
115+
scheme: SignatureScheme::V2Secp256k1,
116+
},
117+
];
100118

101119
{
102-
let new_domains = vec![
103-
DomainConfig {
104-
id: DomainId(4),
105-
scheme: SignatureScheme::Ed25519,
106-
},
107-
DomainConfig {
108-
id: DomainId(5),
109-
scheme: SignatureScheme::Secp256k1,
110-
},
111-
DomainConfig {
112-
id: DomainId(6),
113-
scheme: SignatureScheme::Bls12381,
114-
},
115-
DomainConfig {
116-
id: DomainId(7),
117-
scheme: SignatureScheme::V2Secp256k1,
118-
},
119-
];
120120
let mut contract = setup.indexer.contract_mut().await;
121121
contract.add_domains(new_domains.clone());
122-
domains.extend(new_domains);
122+
domains.extend(new_domains.clone());
123123
}
124124
setup
125125
.indexer
@@ -134,7 +134,7 @@ async fn test_basic_multidomain() {
134134
.indexer
135135
.wait_for_contract_state(
136136
|state| matches!(state, ContractState::Running(_)),
137-
DEFAULT_MAX_PROTOCOL_WAIT_TIME * 3,
137+
DEFAULT_MAX_PROTOCOL_WAIT_TIME * new_domains.len() as u32,
138138
)
139139
.await
140140
.expect("must not exceed timeout");
@@ -183,7 +183,7 @@ async fn test_basic_multidomain() {
183183
}
184184
}
185185
},
186-
DEFAULT_MAX_PROTOCOL_WAIT_TIME * 4,
186+
DEFAULT_MAX_PROTOCOL_WAIT_TIME * domains.len() as u32,
187187
)
188188
.await
189189
.expect("must not exceed timeout");

crates/node/src/tests/resharing.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ use super::DEFAULT_BLOCK_TIME;
1717
#[tokio::test]
1818
async fn test_key_resharing_simple() {
1919
init_integration_logger();
20-
const NUM_PARTICIPANTS: usize = 5;
21-
const THRESHOLD: usize = 2;
20+
const NUM_PARTICIPANTS: usize = 6;
21+
const THRESHOLD: usize = 5;
2222
const TXN_DELAY_BLOCKS: u64 = 1;
2323
let temp_dir = tempfile::tempdir().unwrap();
2424
let mut setup = IntegrationTestSetup::new(
@@ -76,6 +76,15 @@ async fn test_key_resharing_simple() {
7676
.map(|config| AutoAbortTask::from(tokio::spawn(config.run())))
7777
.collect::<Vec<_>>();
7878

79+
setup
80+
.indexer
81+
.wait_for_contract_state(
82+
|state| matches!(state, ContractState::Running(_)),
83+
DEFAULT_MAX_PROTOCOL_WAIT_TIME * domains.len() as u32,
84+
)
85+
.await
86+
.expect("must not exceed timeout");
87+
7988
// Sanity check.
8089
assert!(request_signature_and_await_response(
8190
&mut setup.indexer,
@@ -102,7 +111,7 @@ async fn test_key_resharing_simple() {
102111
}
103112
_ => false,
104113
},
105-
DEFAULT_MAX_PROTOCOL_WAIT_TIME,
114+
DEFAULT_MAX_PROTOCOL_WAIT_TIME * domains.len() as u32,
106115
)
107116
.await
108117
.expect("Timeout waiting for resharing to complete");

0 commit comments

Comments
 (0)