Skip to content

Commit 61639ea

Browse files
fspreissrandombit
andauthored
test(crypto): CRP-2599 add smoke test for VetKdProtocol impl (#3649)
Adds smoke tests for the VetKdProtocol trait implementation added in #3565 --------- Co-authored-by: Jack Lloyd <jack.lloyd@dfinity.org>
1 parent cde7071 commit 61639ea

File tree

5 files changed

+239
-1
lines changed

5 files changed

+239
-1
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rs/crypto/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ MACRO_DEPENDENCIES = [
5959

6060
DEV_DEPENDENCIES = [
6161
# Keep sorted.
62+
"//packages/ic-vetkd-utils",
6263
"//rs/certification/test-utils",
6364
"//rs/crypto/ecdsa_secp256r1",
6465
"//rs/crypto/for_verification_only",

rs/crypto/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ ic-protobuf = { path = "../protobuf" }
4141
ic-registry-client-helpers = { path = "../registry/helpers" }
4242
ic-registry-keys = { path = "../registry/keys" }
4343
ic-types = { path = "../types/types" }
44+
ic-vetkd-utils = { path = "../../packages/ic-vetkd-utils" }
4445
parking_lot = { workspace = true }
4546
rustls = { workspace = true }
4647
serde = { workspace = true }

rs/crypto/test_utils/ni-dkg/src/lib.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,11 @@ impl NiDkgTestEnvironment {
878878
let temp_crypto_builder = TempCryptoComponent::builder()
879879
.with_registry(Arc::clone(&self.registry) as Arc<_>)
880880
.with_node_id(node_id)
881-
.with_keys(NodeKeysToGenerate::only_dkg_dealing_encryption_key())
881+
.with_keys(NodeKeysToGenerate {
882+
generate_node_signing_keys: true,
883+
generate_dkg_dealing_encryption_keys: true,
884+
..NodeKeysToGenerate::none()
885+
})
882886
.with_rng(ChaCha20Rng::from_seed(rng.gen()));
883887
let temp_crypto_builder = if use_remote_vault {
884888
temp_crypto_builder.with_remote_vault()
@@ -891,6 +895,11 @@ impl NiDkgTestEnvironment {
891895
.expect("Failed to retrieve node public keys")
892896
.dkg_dealing_encryption_public_key
893897
.expect("missing dkg_dealing_encryption_pk");
898+
let node_signing_pubkey = temp_crypto
899+
.current_node_public_keys()
900+
.expect("Failed to retrieve node public keys")
901+
.node_signing_public_key
902+
.expect("missing dkg_dealing_encryption_pk");
894903
self.crypto_components.insert(node_id, temp_crypto);
895904

896905
// Insert DKG dealing encryption public key into registry
@@ -901,6 +910,14 @@ impl NiDkgTestEnvironment {
901910
Some(dkg_dealing_encryption_pubkey),
902911
)
903912
.expect("failed to add DKG dealing encryption key to registry");
913+
// Insert node signing public key into registry
914+
self.registry_data
915+
.add(
916+
&make_crypto_node_key(node_id, KeyPurpose::NodeSigning),
917+
ni_dkg_config.registry_version(),
918+
Some(node_signing_pubkey),
919+
)
920+
.expect("failed to add node signing public key to registry");
904921
}
905922

906923
/// Cleans up nodes whose IDs are no longer in use

rs/crypto/tests/vetkd.rs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
use ic_crypto_temp_crypto::CryptoComponentRng;
2+
use ic_crypto_temp_crypto::TempCryptoComponentGeneric;
3+
use ic_crypto_test_utils::crypto_for;
4+
use ic_crypto_test_utils_ni_dkg::{
5+
run_ni_dkg_and_create_single_transcript, NiDkgTestEnvironment, RandomNiDkgConfig,
6+
};
7+
use ic_crypto_test_utils_reproducible_rng::reproducible_rng;
8+
use ic_interfaces::crypto::VetKdProtocol;
9+
use ic_interfaces::crypto::{LoadTranscriptResult, NiDkgAlgorithm};
10+
use ic_types::crypto::canister_threshold_sig::MasterPublicKey;
11+
use ic_types::crypto::threshold_sig::ni_dkg::config::NiDkgConfig;
12+
use ic_types::crypto::threshold_sig::ni_dkg::{NiDkgId, NiDkgTranscript};
13+
use ic_types::crypto::threshold_sig::ThresholdSigPublicKey;
14+
use ic_types::crypto::vetkd::VetKdArgs;
15+
use ic_types::crypto::vetkd::VetKdEncryptedKey;
16+
use ic_types::crypto::vetkd::VetKdEncryptedKeyShare;
17+
use ic_types::crypto::AlgorithmId;
18+
use ic_types::crypto::ExtendedDerivationPath;
19+
use ic_types::{NodeId, NumberOfNodes};
20+
use ic_types_test_utils::ids::canister_test_id;
21+
use rand::prelude::*;
22+
use rand_chacha::ChaCha20Rng;
23+
use std::collections::{BTreeMap, BTreeSet};
24+
use std::convert::TryFrom;
25+
26+
#[test]
27+
fn should_consistently_derive_the_same_vetkey_given_sufficient_shares() {
28+
let rng = &mut reproducible_rng();
29+
let subnet_size = rng.gen_range(1..7);
30+
let (config, dkg_id, crypto_components) = setup_with_random_ni_dkg_config(subnet_size, rng);
31+
32+
let transcript = run_ni_dkg_and_load_transcript_for_receivers(&config, &crypto_components);
33+
34+
let derivation_path = ExtendedDerivationPath {
35+
caller: canister_test_id(234).get(),
36+
derivation_path: vec![b"some".to_vec(), b"derivation".to_vec(), b"path".to_vec()],
37+
};
38+
let derived_public_key = ic_crypto_utils_canister_threshold_sig::derive_vetkd_public_key(
39+
&MasterPublicKey {
40+
algorithm_id: AlgorithmId::ThresBls12_381,
41+
public_key: ThresholdSigPublicKey::try_from(&transcript)
42+
.expect("invalid transcript")
43+
.into_bytes()
44+
.to_vec(),
45+
},
46+
&derivation_path,
47+
)
48+
.expect("failed to compute derived public key");
49+
let transport_secret_key =
50+
ic_vetkd_utils::TransportSecretKey::from_seed(rng.gen::<[u8; 32]>().to_vec())
51+
.expect("failed to create transport secret key");
52+
let vetkd_args = VetKdArgs {
53+
ni_dkg_id: dkg_id,
54+
derivation_path,
55+
derivation_id: b"some-derivation-id".to_vec(),
56+
encryption_public_key: transport_secret_key.public_key(),
57+
};
58+
59+
let mut expected_decrypted_key: Option<Vec<u8>> = None;
60+
for _ in 1..=3 {
61+
let encrypted_key = create_key_shares_and_verify_and_combine(
62+
KeyShareCreatorsAndCombiner {
63+
creators: n_random_nodes_in(
64+
config.receivers().get(),
65+
config.threshold().get(),
66+
rng,
67+
),
68+
combiner: random_node_in(config.receivers().get(), rng),
69+
},
70+
&vetkd_args,
71+
&crypto_components,
72+
);
73+
74+
let random_verifier = random_node_in(config.receivers().get(), rng);
75+
assert_eq!(
76+
crypto_for(random_verifier, &crypto_components)
77+
.verify_encrypted_key(&encrypted_key, &vetkd_args),
78+
Ok(())
79+
);
80+
81+
let decrypted_key = transport_secret_key
82+
.decrypt(
83+
&encrypted_key.encrypted_key,
84+
&derived_public_key,
85+
&vetkd_args.derivation_id,
86+
)
87+
.expect("failed to decrypt vetKey");
88+
89+
if let Some(expected_decrypted_key) = &expected_decrypted_key {
90+
assert_eq!(&decrypted_key, expected_decrypted_key);
91+
} else {
92+
expected_decrypted_key = Some(decrypted_key);
93+
}
94+
}
95+
}
96+
97+
fn setup_with_random_ni_dkg_config<R: Rng + CryptoRng>(
98+
subnet_size: usize,
99+
rng: &mut R,
100+
) -> (
101+
NiDkgConfig,
102+
NiDkgId,
103+
BTreeMap<NodeId, TempCryptoComponentGeneric<ChaCha20Rng>>,
104+
) {
105+
let config = RandomNiDkgConfig::builder()
106+
.subnet_size(subnet_size)
107+
.build(rng)
108+
.into_config();
109+
let dkg_id = config.dkg_id().clone();
110+
let crypto_components =
111+
NiDkgTestEnvironment::new_for_config_with_remote_vault(&config, rng).crypto_components;
112+
(config, dkg_id, crypto_components)
113+
}
114+
115+
fn create_key_shares_and_verify_and_combine<C: CryptoComponentRng>(
116+
creators_and_combiner: KeyShareCreatorsAndCombiner,
117+
vetkd_args: &VetKdArgs,
118+
crypto_components: &BTreeMap<NodeId, TempCryptoComponentGeneric<C>>,
119+
) -> VetKdEncryptedKey {
120+
let key_shares = create_and_verify_key_shares_for_each(
121+
&creators_and_combiner.creators,
122+
vetkd_args,
123+
crypto_components,
124+
);
125+
crypto_for(creators_and_combiner.combiner, crypto_components)
126+
.combine_encrypted_key_shares(&key_shares, vetkd_args)
127+
.expect("failed to combine signature shares")
128+
}
129+
130+
fn create_and_verify_key_shares_for_each<C: CryptoComponentRng>(
131+
key_share_creators: &[NodeId],
132+
vetkd_args: &VetKdArgs,
133+
crypto_components: &BTreeMap<NodeId, TempCryptoComponentGeneric<C>>,
134+
) -> BTreeMap<NodeId, VetKdEncryptedKeyShare> {
135+
key_share_creators
136+
.iter()
137+
.map(|creator| {
138+
let crypto = crypto_for(*creator, crypto_components);
139+
let key_share = crypto
140+
.create_encrypted_key_share(vetkd_args.clone())
141+
.unwrap_or_else(|e| {
142+
panic!(
143+
"vetKD encrypted key share creation by node {:?} failed: {}",
144+
creator, e
145+
)
146+
});
147+
assert_eq!(
148+
crypto.verify_encrypted_key_share(*creator, &key_share, vetkd_args),
149+
Ok(())
150+
);
151+
(*creator, key_share)
152+
})
153+
.collect()
154+
}
155+
156+
#[derive(Clone, Debug)]
157+
struct KeyShareCreatorsAndCombiner {
158+
creators: Vec<NodeId>,
159+
combiner: NodeId,
160+
}
161+
162+
/////////////////////////////////////////////////////////////////////////////////
163+
// The following helper functions where copied from threshold_sigs_with_ni_dkg.rs
164+
/////////////////////////////////////////////////////////////////////////////////
165+
166+
fn run_ni_dkg_and_load_transcript_for_receivers<C: CryptoComponentRng>(
167+
config: &NiDkgConfig,
168+
crypto_components: &BTreeMap<NodeId, TempCryptoComponentGeneric<C>>,
169+
) -> NiDkgTranscript {
170+
let transcript = run_ni_dkg_and_create_single_transcript(config, crypto_components);
171+
load_transcript_for_receivers_expecting_status(
172+
config,
173+
&transcript,
174+
crypto_components,
175+
Some(LoadTranscriptResult::SigningKeyAvailable),
176+
);
177+
transcript
178+
}
179+
180+
fn load_transcript_for_receivers_expecting_status<C: CryptoComponentRng>(
181+
config: &NiDkgConfig,
182+
transcript: &NiDkgTranscript,
183+
crypto_components: &BTreeMap<NodeId, TempCryptoComponentGeneric<C>>,
184+
expected_status: Option<LoadTranscriptResult>,
185+
) {
186+
for node_id in config.receivers().get() {
187+
let result = crypto_for(*node_id, crypto_components).load_transcript(transcript);
188+
189+
if result.is_err() {
190+
panic!(
191+
"failed to load transcript {} for node {}: {}",
192+
transcript,
193+
*node_id,
194+
result.unwrap_err()
195+
);
196+
}
197+
198+
if let Some(expected_status) = expected_status {
199+
let result = result.unwrap();
200+
assert_eq!(result, expected_status);
201+
}
202+
}
203+
}
204+
205+
fn random_node_in<R: Rng + CryptoRng>(nodes: &BTreeSet<NodeId>, rng: &mut R) -> NodeId {
206+
*nodes.iter().choose(rng).expect("nodes empty")
207+
}
208+
209+
fn n_random_nodes_in<R: Rng + CryptoRng>(
210+
nodes: &BTreeSet<NodeId>,
211+
n: NumberOfNodes,
212+
rng: &mut R,
213+
) -> Vec<NodeId> {
214+
let n_usize = usize::try_from(n.get()).expect("conversion to usize failed");
215+
let chosen = nodes.iter().copied().choose_multiple(rng, n_usize);
216+
assert_eq!(chosen.len(), n_usize);
217+
chosen
218+
}

0 commit comments

Comments
 (0)