Skip to content
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

test(crypto): CRP-2599 add smoke test for VetKdProtocol impl #3649

Merged
merged 31 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1e62718
feat(crypto): CRP-2648 split EncryptedKey::combine
fspreiss Nov 29, 2024
ab79fd6
Fix and improve tests
randombit Dec 13, 2024
46d205a
Fix export of types
randombit Dec 20, 2024
7f8758d
Fix benchmark and clippys
randombit Dec 20, 2024
41736a9
Fix exports for CSP
randombit Dec 20, 2024
c5b9a87
fmt
randombit Dec 20, 2024
0571494
Appease bazel
randombit Dec 20, 2024
4ae6de2
Merge branch 'master' into jack/crp-2648
randombit Jan 6, 2025
51698d1
Alas
randombit Jan 6, 2025
45a6066
bench
randombit Jan 6, 2025
43140b0
Address review comments
randombit Jan 14, 2025
7788833
Fix benchmarks
randombit Jan 14, 2025
1e900a3
fmt
randombit Jan 14, 2025
358ff3e
Remove debug println fix occasional test fail
randombit Jan 14, 2025
c5cb6bb
Fix CSP tests
randombit Jan 15, 2025
f6099f2
Merge branch 'master' into jack/crp-2648
randombit Jan 15, 2025
0032b10
fixup
randombit Jan 15, 2025
d2f9153
fmt
randombit Jan 15, 2025
54fe8df
Merge branch 'master' into jack/crp-2648
randombit Jan 15, 2025
b92d509
Fix csp tests
randombit Jan 15, 2025
6462e14
Merge branch 'master' into jack/crp-2648
randombit Jan 17, 2025
4f2f9f5
feat(crypto): CRP-2599 implement VetKdProtocol trait for CryptoComponent
fspreiss Jan 22, 2025
bbf4730
Merge remote-tracking branch 'origin' into franzstefan/CRP-2599-vetkd…
fspreiss Jan 27, 2025
de4505a
Use registry version from store
fspreiss Jan 28, 2025
5df202b
test(crypto): CRP-2599 add smoke test for VetKdProtocol impl
fspreiss Jan 28, 2025
c2bd7a6
Clippy
fspreiss Jan 28, 2025
f1bfc15
Merge remote-tracking branch 'origin' into franzstefan/CRP-2599-vetkd…
fspreiss Jan 29, 2025
901e20c
Merge remote-tracking branch 'origin' into franzstefan/CRP-2599-vetkd…
fspreiss Jan 29, 2025
7aaf67e
Merge branch 'franzstefan/CRP-2599-vetkdprotocol-impl' into franzstef…
fspreiss Jan 29, 2025
91d73d0
Merge remote-tracking branch 'origin' into franzstefan/CRP-2599-vetkd…
fspreiss Jan 31, 2025
e82aa0f
Revert unintended change
fspreiss Jan 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rs/crypto/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ MACRO_DEPENDENCIES = [

DEV_DEPENDENCIES = [
# Keep sorted.
"//packages/ic-vetkd-utils",
"//rs/certification/test-utils",
"//rs/crypto/ecdsa_secp256r1",
"//rs/crypto/for_verification_only",
Expand Down
1 change: 1 addition & 0 deletions rs/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ ic-protobuf = { path = "../protobuf" }
ic-registry-client-helpers = { path = "../registry/helpers" }
ic-registry-keys = { path = "../registry/keys" }
ic-types = { path = "../types/types" }
ic-vetkd-utils = { path = "../../packages/ic-vetkd-utils" }
parking_lot = { workspace = true }
rustls = { workspace = true }
serde = { workspace = true }
Expand Down
19 changes: 18 additions & 1 deletion rs/crypto/test_utils/ni-dkg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,11 @@ impl NiDkgTestEnvironment {
let temp_crypto_builder = TempCryptoComponent::builder()
.with_registry(Arc::clone(&self.registry) as Arc<_>)
.with_node_id(node_id)
.with_keys(NodeKeysToGenerate::only_dkg_dealing_encryption_key())
.with_keys(NodeKeysToGenerate {
generate_node_signing_keys: true,
generate_dkg_dealing_encryption_keys: true,
..NodeKeysToGenerate::none()
})
.with_rng(ChaCha20Rng::from_seed(rng.gen()));
let temp_crypto_builder = if use_remote_vault {
temp_crypto_builder.with_remote_vault()
Expand All @@ -891,6 +895,11 @@ impl NiDkgTestEnvironment {
.expect("Failed to retrieve node public keys")
.dkg_dealing_encryption_public_key
.expect("missing dkg_dealing_encryption_pk");
let node_signing_pubkey = temp_crypto
.current_node_public_keys()
.expect("Failed to retrieve node public keys")
.node_signing_public_key
.expect("missing dkg_dealing_encryption_pk");
self.crypto_components.insert(node_id, temp_crypto);

// Insert DKG dealing encryption public key into registry
Expand All @@ -901,6 +910,14 @@ impl NiDkgTestEnvironment {
Some(dkg_dealing_encryption_pubkey),
)
.expect("failed to add DKG dealing encryption key to registry");
// Insert node signing public key into registry
self.registry_data
.add(
&make_crypto_node_key(node_id, KeyPurpose::NodeSigning),
ni_dkg_config.registry_version(),
Some(node_signing_pubkey),
)
.expect("failed to add node signing public key to registry");
}

/// Cleans up nodes whose IDs are no longer in use
Expand Down
218 changes: 218 additions & 0 deletions rs/crypto/tests/vetkd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
use ic_crypto_temp_crypto::CryptoComponentRng;
use ic_crypto_temp_crypto::TempCryptoComponentGeneric;
use ic_crypto_test_utils::crypto_for;
use ic_crypto_test_utils_ni_dkg::{
run_ni_dkg_and_create_single_transcript, NiDkgTestEnvironment, RandomNiDkgConfig,
};
use ic_crypto_test_utils_reproducible_rng::reproducible_rng;
use ic_interfaces::crypto::VetKdProtocol;
use ic_interfaces::crypto::{LoadTranscriptResult, NiDkgAlgorithm};
use ic_types::crypto::canister_threshold_sig::MasterPublicKey;
use ic_types::crypto::threshold_sig::ni_dkg::config::NiDkgConfig;
use ic_types::crypto::threshold_sig::ni_dkg::{NiDkgId, NiDkgTranscript};
use ic_types::crypto::threshold_sig::ThresholdSigPublicKey;
use ic_types::crypto::vetkd::VetKdArgs;
use ic_types::crypto::vetkd::VetKdEncryptedKey;
use ic_types::crypto::vetkd::VetKdEncryptedKeyShare;
use ic_types::crypto::AlgorithmId;
use ic_types::crypto::ExtendedDerivationPath;
use ic_types::{NodeId, NumberOfNodes};
use ic_types_test_utils::ids::canister_test_id;
use rand::prelude::*;
use rand_chacha::ChaCha20Rng;
use std::collections::{BTreeMap, BTreeSet};
use std::convert::TryFrom;

#[test]
fn should_consistently_derive_the_same_vetkey_given_sufficient_shares() {
let rng = &mut reproducible_rng();
let subnet_size = rng.gen_range(1..7);
let (config, dkg_id, crypto_components) = setup_with_random_ni_dkg_config(subnet_size, rng);

let transcript = run_ni_dkg_and_load_transcript_for_receivers(&config, &crypto_components);

let derivation_path = ExtendedDerivationPath {
caller: canister_test_id(234).get(),
derivation_path: vec![b"some".to_vec(), b"derivation".to_vec(), b"path".to_vec()],
};
let derived_public_key = ic_crypto_utils_canister_threshold_sig::derive_vetkd_public_key(
&MasterPublicKey {
algorithm_id: AlgorithmId::ThresBls12_381,
public_key: ThresholdSigPublicKey::try_from(&transcript)
.expect("invalid transcript")
.into_bytes()
.to_vec(),
},
&derivation_path,
)
.expect("failed to compute derived public key");
let transport_secret_key =
ic_vetkd_utils::TransportSecretKey::from_seed(rng.gen::<[u8; 32]>().to_vec())
.expect("failed to create transport secret key");
let vetkd_args = VetKdArgs {
ni_dkg_id: dkg_id,
derivation_path,
derivation_id: b"some-derivation-id".to_vec(),
encryption_public_key: transport_secret_key.public_key(),
};

let mut expected_decrypted_key: Option<Vec<u8>> = None;
for _ in 1..=3 {
let encrypted_key = create_key_shares_and_verify_and_combine(
KeyShareCreatorsAndCombiner {
creators: n_random_nodes_in(
config.receivers().get(),
config.threshold().get(),
rng,
),
combiner: random_node_in(config.receivers().get(), rng),
},
&vetkd_args,
&crypto_components,
);

let random_verifier = random_node_in(config.receivers().get(), rng);
assert_eq!(
crypto_for(random_verifier, &crypto_components)
.verify_encrypted_key(&encrypted_key, &vetkd_args),
Ok(())
);

let decrypted_key = transport_secret_key
.decrypt(
&encrypted_key.encrypted_key,
&derived_public_key,
&vetkd_args.derivation_id,
)
.expect("failed to decrypt vetKey");

if let Some(expected_decrypted_key) = &expected_decrypted_key {
assert_eq!(&decrypted_key, expected_decrypted_key);
} else {
expected_decrypted_key = Some(decrypted_key);
}
}
}

fn setup_with_random_ni_dkg_config<R: Rng + CryptoRng>(
subnet_size: usize,
rng: &mut R,
) -> (
NiDkgConfig,
NiDkgId,
BTreeMap<NodeId, TempCryptoComponentGeneric<ChaCha20Rng>>,
) {
let config = RandomNiDkgConfig::builder()
.subnet_size(subnet_size)
.build(rng)
.into_config();
let dkg_id = config.dkg_id().clone();
let crypto_components =
NiDkgTestEnvironment::new_for_config_with_remote_vault(&config, rng).crypto_components;
(config, dkg_id, crypto_components)
}

fn create_key_shares_and_verify_and_combine<C: CryptoComponentRng>(
creators_and_combiner: KeyShareCreatorsAndCombiner,
vetkd_args: &VetKdArgs,
crypto_components: &BTreeMap<NodeId, TempCryptoComponentGeneric<C>>,
) -> VetKdEncryptedKey {
let key_shares = create_and_verify_key_shares_for_each(
&creators_and_combiner.creators,
vetkd_args,
crypto_components,
);
crypto_for(creators_and_combiner.combiner, crypto_components)
.combine_encrypted_key_shares(&key_shares, vetkd_args)
.expect("failed to combine signature shares")
}

fn create_and_verify_key_shares_for_each<C: CryptoComponentRng>(
key_share_creators: &[NodeId],
vetkd_args: &VetKdArgs,
crypto_components: &BTreeMap<NodeId, TempCryptoComponentGeneric<C>>,
) -> BTreeMap<NodeId, VetKdEncryptedKeyShare> {
key_share_creators
.iter()
.map(|creator| {
let crypto = crypto_for(*creator, crypto_components);
let key_share = crypto
.create_encrypted_key_share(vetkd_args.clone())
.unwrap_or_else(|e| {
panic!(
"vetKD encrypted key share creation by node {:?} failed: {}",
creator, e
)
});
assert_eq!(
crypto.verify_encrypted_key_share(*creator, &key_share, vetkd_args),
Ok(())
);
(*creator, key_share)
})
.collect()
}

#[derive(Clone, Debug)]
struct KeyShareCreatorsAndCombiner {
creators: Vec<NodeId>,
combiner: NodeId,
}

/////////////////////////////////////////////////////////////////////////////////
// The following helper functions where copied from threshold_sigs_with_ni_dkg.rs
/////////////////////////////////////////////////////////////////////////////////

fn run_ni_dkg_and_load_transcript_for_receivers<C: CryptoComponentRng>(
config: &NiDkgConfig,
crypto_components: &BTreeMap<NodeId, TempCryptoComponentGeneric<C>>,
) -> NiDkgTranscript {
let transcript = run_ni_dkg_and_create_single_transcript(config, crypto_components);
load_transcript_for_receivers_expecting_status(
config,
&transcript,
crypto_components,
Some(LoadTranscriptResult::SigningKeyAvailable),
);
transcript
}

fn load_transcript_for_receivers_expecting_status<C: CryptoComponentRng>(
config: &NiDkgConfig,
transcript: &NiDkgTranscript,
crypto_components: &BTreeMap<NodeId, TempCryptoComponentGeneric<C>>,
expected_status: Option<LoadTranscriptResult>,
) {
for node_id in config.receivers().get() {
let result = crypto_for(*node_id, crypto_components).load_transcript(transcript);

if result.is_err() {
panic!(
"failed to load transcript {} for node {}: {}",
transcript,
*node_id,
result.unwrap_err()
);
}

if let Some(expected_status) = expected_status {
let result = result.unwrap();
assert_eq!(result, expected_status);
}
}
}

fn random_node_in<R: Rng + CryptoRng>(nodes: &BTreeSet<NodeId>, rng: &mut R) -> NodeId {
*nodes.iter().choose(rng).expect("nodes empty")
}

fn n_random_nodes_in<R: Rng + CryptoRng>(
nodes: &BTreeSet<NodeId>,
n: NumberOfNodes,
rng: &mut R,
) -> Vec<NodeId> {
let n_usize = usize::try_from(n.get()).expect("conversion to usize failed");
let chosen = nodes.iter().copied().choose_multiple(rng, n_usize);
assert_eq!(chosen.len(), n_usize);
chosen
}