-
Notifications
You must be signed in to change notification settings - Fork 41
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
Changes from 94 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
3c3912f
9041366
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
133ba43
6b4107e
80de0a0
2481a55
feaa80a
46342fa
1905cd5
857ac6f
2066927
d22f3c1
aeedac8
1ec15b5
6b119e8
98b5c73
6eae2be
86aa75e
b2bc11a
cb69750
aec2f59
19954d8
996d9a3
06a9a47
a83a1f2
87c2d07
5c53876
d416017
0156843
316d15b
bc8bc72
75b82c4
6e5dfce
c3269ea
17d649e
96535d6
9ca92e1
e350511
5d3fedc
9231d99
4ab6098
3e3af3b
da56e05
97a24ae
c09f549
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 |
|---|---|---|
|
|
@@ -7,8 +7,9 @@ use blstrs::{G1Projective, Scalar}; | |
| use e2e_tests::{CLUSTER_WAIT_TIMEOUT, MpcCluster, MpcClusterConfig, metrics}; | ||
| use group::Group; | ||
| use near_mpc_contract_interface::types::{ | ||
| Bls12381G2PublicKey, CKDAppPublicKey, Curve, DomainId, DomainPurpose, ProtocolContractState, | ||
| PublicKey, PublicKeyExtended, RunningContractState, | ||
| Bls12381G2PublicKey, CKDAppPublicKey, Curve, DomainConfig, DomainId, DomainPurpose, Protocol, | ||
| ProtocolContractState, PublicKey, PublicKeyExtended, ReconstructionThreshold, | ||
| RunningContractState, | ||
| }; | ||
| use near_mpc_crypto_types::Bls12381G1PublicKey; | ||
| use serde_json::json; | ||
|
|
@@ -36,6 +37,7 @@ pub const CONTRACT_UPGRADE_COMPATIBILITY_TESTNET_PORT_SEED: u16 = 19; | |
| pub const TIMEOUT_METRIC_PORT_SEED: u16 = 20; | ||
| pub const MIGRATION_BACK_PORT_SEED: u16 = 21; | ||
| pub const SIGTERM_HANDLER_PORT_SEED: u16 = 22; | ||
| pub const DISTINCT_RECONSTRUCTION_THRESHOLDS_PORT_SEED: u16 = 23; | ||
|
|
||
| /// Start a cluster, wait for Running state and presignatures to buffer. | ||
| /// | ||
|
|
@@ -376,6 +378,28 @@ pub fn must_get_bls_public_key( | |
| } | ||
| } | ||
|
|
||
| /// Builds a `DamgardEtAl` signing domain with reconstruction threshold `t`, which needs `2t - 1` signers. | ||
| pub fn damgard_etal_domain(id: u64, t: u64) -> DomainConfig { | ||
| DomainConfig { | ||
| id: DomainId(id), | ||
| protocol: Protocol::DamgardEtAl, | ||
| reconstruction_threshold: ReconstructionThreshold::new(t), | ||
| purpose: DomainPurpose::Sign, | ||
| } | ||
| } | ||
|
|
||
| /// Returns the domain running `protocol_type`, panicking if absent. Each | ||
| /// protocol appears at most once per registry, so it identifies a unique domain. | ||
|
SimonRastikian marked this conversation as resolved.
Outdated
|
||
| pub fn must_get_domain(running: &RunningContractState, protocol_type: Protocol) -> DomainConfig { | ||
| running | ||
| .domains | ||
| .domains | ||
| .iter() | ||
| .find(|d| d.protocol == protocol_type) | ||
| .unwrap_or_else(|| panic!("no domain with protocol {protocol_type:?}")) | ||
| .clone() | ||
| } | ||
|
|
||
| /// Send a sign request and assert the network produced a successful response. | ||
| /// | ||
| /// Panics if the request can't be submitted to the contract — the test cannot | ||
|
|
@@ -406,6 +430,34 @@ pub async fn send_sign_request( | |
| Ok(()) | ||
| } | ||
|
|
||
| /// Sign with every scheme in `running`, asserting each request succeeds. | ||
| pub async fn sign_all_schemes( | ||
| cluster: &MpcCluster, | ||
| running: &RunningContractState, | ||
| rng: &mut impl rand::Rng, | ||
| ) { | ||
|
Contributor
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. nit: the doc comment here is useless, as it just paraphrases the function name. non-nit: I think instead of signing for all schemes, we may want to sign for all domains (that do signatures) in the contract? Such that, in case we have two cait-sith domains, they both get a request.
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.
Contributor
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. @SimonRastikian I don't think this comment was addressed.
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.
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. Sorry for the miss, there was a lot of comments |
||
| for (label, protocol) in [ | ||
| ("ECDSA", Protocol::CaitSith), | ||
| ("Damgard et al", Protocol::DamgardEtAl), | ||
| ("EdDSA", Protocol::Frost), | ||
| ] { | ||
| let domain = must_get_domain(running, protocol); | ||
| let payload = match Curve::from(protocol) { | ||
| Curve::Edwards25519 => generate_eddsa_payload(rng), | ||
| _ => generate_ecdsa_payload(rng), | ||
| }; | ||
| 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 failed: {:?}", | ||
| outcome.failure_message() | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /// Send a CKD request and assert the network produced a successful response. | ||
| /// | ||
| /// Panics if the request can't be submitted to the contract — the test cannot | ||
|
|
||
| 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. | ||
|
Contributor
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. although this is correct, it is a very indirect way to check that the per domain thresholds are being used. Here we just need to check that the signatures are produced correctly, integration tests in the node should be able to figure that the correct threshold is used. Therefore this part of the comment can be removed.
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. I would still vote to keep it as it is indeed part of the logic of the test. But I guess your comment anw was not a blocker
Contributor
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.
A more direct approach for testing this would be to shut down some of the nodes. E.g. you have governance and cat-sith threshold of 6, but set damgard-et-al reconstruction threshold to 3 (e.g. signing threshold of 5), then you bring down all but 5 nodes, now, we expect cait-sith signatures to fail, but damgard to be successful.
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. @kevindeforth I killed nodes in the test 0156843
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. Does this sound good to you? |
||
| #[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; | ||
|
Comment on lines
+26
to
+27
Contributor
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. shouldn't triples be at least 4 in this case?
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. I think it's fine here as is. You need 2 triples per presignature and then you have a background triple generation process that continuously refills the buffer to 2 triples.
Contributor
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. we usually put enough triples in the buffer for all the signatures in the test to try to avoid flakiness as much as possible. |
||
| 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); | ||
|
SimonRastikian marked this conversation as resolved.
Outdated
|
||
| 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() | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.