Skip to content

Commit

Permalink
test(crypto): CRP-2599 add smoke test for VetKdProtocol impl (#3649)
Browse files Browse the repository at this point in the history
Adds smoke tests for the VetKdProtocol trait implementation added in
#3565

---------

Co-authored-by: Jack Lloyd <[email protected]>
  • Loading branch information
fspreiss and randombit authored Jan 31, 2025
1 parent cde7071 commit 61639ea
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 1 deletion.
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
}

0 comments on commit 61639ea

Please sign in to comment.