-
Notifications
You must be signed in to change notification settings - Fork 31
feat: adding reconstruction threshold in node #3640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
02dd798
44e9e76
7d86856
b33f455
02c5313
a378c56
3da1652
aeed6e7
7257a16
a20a7a8
1afd958
db47b85
0cf1b53
27e40bf
7878ac8
1f28966
7bca4d8
263bc8d
f45ebf5
c8fec49
6e1ce47
5056109
5f5af82
fbf22be
b310e84
ca8b199
da2d190
7458432
4f52bdd
29613b8
3b329e2
f168468
cd557e1
e769a53
df26736
876bcfe
fc57a33
484d40e
ec871da
ee76a11
6a4fa9e
0ef09e5
bc928d4
e3d6c89
2c690bd
86bbbd2
158c838
6b08b91
fa67289
5c9a3fe
be0b517
db5692e
41184ee
cb5725f
02e0d13
eed4c8f
8423f2e
79f9666
05bdcfa
fb858bd
50cf59e
49b39a7
aa1b927
1597647
5055b0e
5f71e41
4874fb3
6c535c5
446f162
1395243
37cf136
392549d
3d94416
71392e0
a0c3637
7c6164b
d80827e
fad6726
5792061
0b8b201
4b2f853
f89e30d
766fef6
d759fc8
7733fd0
9cc8741
954a69a
18e1ed1
a2cc899
aac0973
ec959e4
955e669
e89f261
19f1ca5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| use crate::common; | ||
| use crate::common::{ | ||
| CKD_PV_VERIFICATION_PORT_SEED, CKD_VERIFICATION_PORT_SEED, must_get_bls_public_key, | ||
| must_get_domain, must_setup_cluster, | ||
| }; | ||
|
|
||
| use anyhow::Context; | ||
| use blstrs::{G1Projective, G2Projective, Scalar}; | ||
|
|
@@ -7,8 +10,7 @@ use group::ff::Field as _; | |
| use near_account_id::AccountId; | ||
| use near_mpc_contract_interface::types::kdf::derive_app_id; | ||
| use near_mpc_contract_interface::types::{ | ||
| Bls12381G1PublicKey, Bls12381G2PublicKey, CKDAppPublicKey, CKDAppPublicKeyPV, Curve, | ||
| DomainPurpose, | ||
| Bls12381G1PublicKey, Bls12381G2PublicKey, CKDAppPublicKey, CKDAppPublicKeyPV, Protocol, | ||
| }; | ||
| use rand::SeedableRng; | ||
| use threshold_signatures::confidential_key_derivation::{ | ||
|
|
@@ -42,20 +44,11 @@ fn verify_ckd( | |
| #[expect(non_snake_case)] | ||
| async fn ckd_response__passes_cryptographic_verification() { | ||
| // given | ||
| let (cluster, running) = | ||
| common::must_setup_cluster(common::CKD_VERIFICATION_PORT_SEED, |_| {}).await; | ||
|
|
||
| let bls_domain = running | ||
| .domains | ||
| .domains | ||
| .iter() | ||
| .find(|d| { | ||
| Curve::from(d.protocol) == Curve::Bls12381 && matches!(d.purpose, DomainPurpose::CKD) | ||
| }) | ||
| .expect("no Bls12381 CKD domain found") | ||
| .clone(); | ||
|
|
||
| let mpc_pk = common::must_get_bls_public_key(&running, bls_domain.id); | ||
| let (cluster, running) = must_setup_cluster(CKD_VERIFICATION_PORT_SEED, |_| {}).await; | ||
|
|
||
| let bls_domain = must_get_domain(&running, Protocol::ConfidentialKeyDerivation); | ||
|
|
||
| let mpc_pk = must_get_bls_public_key(&running, bls_domain.id); | ||
| let user = cluster.default_user_account().clone(); | ||
|
|
||
| let mut rng = rand::rngs::StdRng::seed_from_u64(1); | ||
|
|
@@ -95,20 +88,11 @@ async fn ckd_response__passes_cryptographic_verification() { | |
| #[expect(non_snake_case)] | ||
| async fn ckd_pv_response__passes_cryptographic_verification() { | ||
| // given | ||
| let (cluster, running) = | ||
| common::must_setup_cluster(common::CKD_PV_VERIFICATION_PORT_SEED, |_| {}).await; | ||
|
|
||
| let bls_domain = running | ||
| .domains | ||
| .domains | ||
| .iter() | ||
| .find(|d| { | ||
| Curve::from(d.protocol) == Curve::Bls12381 && matches!(d.purpose, DomainPurpose::CKD) | ||
| }) | ||
| .expect("no Bls12381 CKD domain found") | ||
| .clone(); | ||
|
|
||
| let mpc_pk = common::must_get_bls_public_key(&running, bls_domain.id); | ||
| let (cluster, running) = must_setup_cluster(CKD_PV_VERIFICATION_PORT_SEED, |_| {}).await; | ||
|
|
||
| let bls_domain = must_get_domain(&running, Protocol::ConfidentialKeyDerivation); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here for example |
||
|
|
||
| let mpc_pk = must_get_bls_public_key(&running, bls_domain.id); | ||
| let user = cluster.default_user_account().clone(); | ||
|
|
||
| let mut rng = rand::rngs::StdRng::seed_from_u64(2); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| use crate::common::{ | ||
| DISTINCT_RECONSTRUCTION_THRESHOLDS_PORT_SEED, damgard_etal_domain, generate_ckd_app_public_key, | ||
| must_get_domain, must_setup_cluster, sign_all_schemes, | ||
| }; | ||
|
|
||
| use near_mpc_contract_interface::types::Protocol; | ||
| use rand::SeedableRng; | ||
|
|
||
| /// Each domain signs using its own reconstruction threshold rather than the | ||
| /// governance threshold. The governance threshold is 4 with a total of 6 nodes. | ||
| /// The signing procedure succeeds despite Damgard et al. requiring at least 7 | ||
| /// participants under a governance-threshold signing model. | ||
| /// Therefore, signing is performed using the reconstruction threshold, | ||
| /// not the governance threshold. | ||
|
SimonRastikian marked this conversation as resolved.
|
||
| #[tokio::test] | ||
| #[expect(non_snake_case)] | ||
| async fn distinct_reconstruction_thresholds__should_sign_for_every_scheme() { | ||
| // Given | ||
| let (cluster, contract_state) = | ||
| must_setup_cluster(DISTINCT_RECONSTRUCTION_THRESHOLDS_PORT_SEED, |c| { | ||
| c.num_nodes = 6; | ||
| c.initial_participant_indices = (0..6).collect(); | ||
| c.threshold = 4; | ||
| c.triples_to_buffer = 2; | ||
| c.presignatures_to_buffer = 2; | ||
|
SimonRastikian marked this conversation as resolved.
|
||
| c.domains | ||
| .push(damgard_etal_domain(c.domains.len() as u64, 3)); | ||
| }) | ||
| .await; | ||
|
|
||
| let ckd_domain = must_get_domain(&contract_state, Protocol::ConfidentialKeyDerivation); | ||
|
|
||
| // When / Then | ||
| let mut rng = rand::rngs::StdRng::seed_from_u64(0); | ||
| sign_all_schemes(&cluster, &contract_state, &mut rng).await; | ||
|
|
||
| let outcome = cluster | ||
| .send_ckd_request( | ||
| ckd_domain.id, | ||
| generate_ckd_app_public_key(&mut rng), | ||
| cluster.default_user_account(), | ||
| ) | ||
| .await | ||
| .expect("ckd request failed"); | ||
| assert!( | ||
| outcome.is_success(), | ||
| "ckd request failed: {:?}", | ||
| outcome.failure_message() | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ use near_mpc_contract_interface::types::{ | |
| }; | ||
| use serde_json::json; | ||
|
|
||
| /// 9 parallel calls (3 robust ECDSA + 2 ECDSA + 2 EdDSA + 2 CKD) via the test parallel | ||
| /// 9 parallel calls (3 DamgardEtAl + 2 ECDSA + 2 EdDSA + 2 CKD) via the test parallel | ||
| /// contract, against a 6-node / threshold-5 cluster that carries all four signing-scheme | ||
| /// domains. Verifies all calls succeed and both the signature and CKD queues drain. | ||
| #[tokio::test] | ||
|
|
@@ -25,12 +25,7 @@ async fn mpc_cluster_should_successfully_process_parallel_requests() { | |
| c.initial_participant_indices = (0..6).collect(); | ||
| c.threshold = 5; | ||
| c.domains = vec![ | ||
| DomainConfig { | ||
| id: DomainId(0), | ||
| protocol: Protocol::DamgardEtAl, | ||
| reconstruction_threshold: ReconstructionThreshold::new(3), | ||
| purpose: DomainPurpose::Sign, | ||
| }, | ||
| common::damgard_etal_domain(0, 3), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quite often there is a similar change that reduces code redundancy. this is due to introducing damgard_etal_domain |
||
| DomainConfig { | ||
| id: DomainId(1), | ||
| protocol: Protocol::CaitSith, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,37 +1,33 @@ | ||
| use crate::common; | ||
|
|
||
| use mpc_primitives::domain::{Curve, DomainId}; | ||
| use near_mpc_contract_interface::types::{ | ||
| DomainConfig, DomainPurpose, Protocol, ProtocolContractState, ReconstructionThreshold, | ||
| use crate::common::{ | ||
| REQUEST_DURING_RESHARING_PORT_SEED, damgard_etal_domain, generate_ckd_app_public_key, | ||
| must_get_domain, must_setup_cluster, sign_all_schemes, | ||
| }; | ||
|
|
||
| use near_mpc_contract_interface::types::{Protocol, ProtocolContractState}; | ||
| use rand::SeedableRng; | ||
|
|
||
| /// Tests that signature and CKD requests are processed using the previous | ||
| /// running state's threshold while resharing is in progress. | ||
| /// | ||
| /// Setup: 6 nodes, 5 initial participants (threshold 5). Domains cover | ||
| /// classic ECDSA (CaitSith), robust ECDSA (DamgardEtAl), EdDSA (Frost) and | ||
| /// CKD (ConfidentialKeyDerivation). Threshold is 5 because robust ECDSA | ||
| /// requires ≥ 5 signers (see `robust_ecdsa::translate_threshold`). Begin | ||
| /// resharing to all 6 with threshold 6, then kill node 5 so resharing can't | ||
| /// complete. Requests should still succeed using the old threshold of 5 | ||
| /// across all signing schemes. | ||
| /// classic ECDSA (CaitSith), DamgardEtAl, EdDSA (Frost) and | ||
| /// CKD (ConfidentialKeyDerivation). The DamgardEtAl domain uses a | ||
| /// reconstruction threshold of `t = 3`, which requires `2t - 1 = 5` signers, | ||
| /// so we need at least 5 participants. Begin resharing to all 6 with threshold | ||
| /// 6, then kill node 5 so resharing can't complete. Requests should still | ||
| /// succeed using the previous running state across all signing schemes. | ||
| #[tokio::test] | ||
| async fn test_request_during_resharing() { | ||
| // given | ||
| let (mut cluster, contract_state) = | ||
| common::must_setup_cluster(common::REQUEST_DURING_RESHARING_PORT_SEED, |c| { | ||
| must_setup_cluster(REQUEST_DURING_RESHARING_PORT_SEED, |c| { | ||
| c.num_nodes = 6; | ||
| c.initial_participant_indices = (0..5).collect(); | ||
| c.threshold = 5; | ||
| c.triples_to_buffer = 2; | ||
| c.presignatures_to_buffer = 2; | ||
| c.domains.push(DomainConfig { | ||
| id: DomainId(c.domains.len() as u64), | ||
| protocol: Protocol::DamgardEtAl, | ||
| reconstruction_threshold: ReconstructionThreshold::new(3), | ||
| purpose: DomainPurpose::Sign, | ||
| }); | ||
| c.domains | ||
| .push(damgard_etal_domain(c.domains.len() as u64, 3)); | ||
|
Comment on lines
-29
to
+30
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is another example |
||
| }) | ||
| .await; | ||
|
|
||
|
|
@@ -46,66 +42,18 @@ async fn test_request_during_resharing() { | |
| cluster.kill_nodes(&[5]).expect("failed to kill node 5"); | ||
|
|
||
| // then | ||
| let ecdsa_domain = contract_state | ||
| .domains | ||
| .domains | ||
| .iter() | ||
| .find(|d| { | ||
| Curve::from(d.protocol) == Curve::Secp256k1 | ||
| && d.protocol == Protocol::CaitSith | ||
| && d.purpose == DomainPurpose::Sign | ||
| }) | ||
| .expect("no CaitSith Sign domain"); | ||
| let robust_ecdsa_domain = contract_state | ||
| .domains | ||
| .domains | ||
| .iter() | ||
| .find(|d| d.protocol == Protocol::DamgardEtAl && d.purpose == DomainPurpose::Sign) | ||
| .expect("no DamgardEtAl Sign domain"); | ||
| let eddsa_domain = contract_state | ||
| .domains | ||
| .domains | ||
| .iter() | ||
| .find(|d| { | ||
| Curve::from(d.protocol) == Curve::Edwards25519 && d.purpose == DomainPurpose::Sign | ||
| }) | ||
| .expect("no Edwards25519 Sign domain"); | ||
| let ckd_domain = contract_state | ||
| .domains | ||
| .domains | ||
| .iter() | ||
| .find(|d| d.purpose == DomainPurpose::CKD) | ||
| .expect("no CKD domain"); | ||
| let ckd_domain = must_get_domain(&contract_state, Protocol::ConfidentialKeyDerivation); | ||
|
|
||
| let mut rng = rand::rngs::StdRng::seed_from_u64(0); | ||
| for i in 0..3 { | ||
| for (label, domain_id, is_eddsa) in [ | ||
| ("ECDSA", ecdsa_domain.id, false), | ||
| ("robust ECDSA", robust_ecdsa_domain.id, false), | ||
| ("EdDSA", eddsa_domain.id, true), | ||
| ] { | ||
| let payload = if is_eddsa { | ||
| common::generate_eddsa_payload(&mut rng) | ||
| } else { | ||
| common::generate_ecdsa_payload(&mut rng) | ||
| }; | ||
| tracing::info!(i, label, "sending sign request during resharing"); | ||
| let outcome = cluster | ||
| .send_sign_request(domain_id, payload, cluster.default_user_account()) | ||
| .await | ||
| .expect("sign request failed"); | ||
| assert!( | ||
| outcome.is_success(), | ||
| "{label} sign request {i} failed: {:?}", | ||
| outcome.failure_message() | ||
| ); | ||
| } | ||
| tracing::info!(i, "sending sign requests during resharing"); | ||
| sign_all_schemes(&cluster, &contract_state, &mut rng).await; | ||
|
|
||
| tracing::info!(i, "sending CKD request during resharing"); | ||
| let outcome = cluster | ||
| .send_ckd_request( | ||
| ckd_domain.id, | ||
| common::generate_ckd_app_public_key(&mut rng), | ||
| generate_ckd_app_public_key(&mut rng), | ||
| cluster.default_user_account(), | ||
| ) | ||
| .await | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is nice refactor because I introduced must_get_domain. We see a couple of these changes in e2e-tests/tests as there is a lot of code redundancy that was now compressed with a single function call