|
| 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