diff --git a/Cargo.lock b/Cargo.lock index 7b1fd601484..2b0c25c6f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6984,6 +6984,7 @@ dependencies = [ "ic-crypto-internal-basic-sig-ecdsa-secp256r1", "ic-crypto-internal-basic-sig-ed25519", "ic-crypto-internal-basic-sig-rsa-pkcs1", + "ic-crypto-internal-bls12-381-vetkd", "ic-crypto-internal-csp", "ic-crypto-internal-csp-proptest-utils", "ic-crypto-internal-csp-test-utils", diff --git a/rs/crypto/BUILD.bazel b/rs/crypto/BUILD.bazel index 2c98b370a4c..3cdaa0b0151 100644 --- a/rs/crypto/BUILD.bazel +++ b/rs/crypto/BUILD.bazel @@ -17,6 +17,7 @@ DEPENDENCIES = [ "//rs/crypto/ed25519", "//rs/crypto/interfaces/sig_verification", "//rs/crypto/internal/crypto_lib/basic_sig/ed25519", + "//rs/crypto/internal/crypto_lib/bls12_381/vetkd", "//rs/crypto/internal/crypto_lib/seed", "//rs/crypto/internal/crypto_lib/threshold_sig/bls12_381", "//rs/crypto/internal/crypto_lib/threshold_sig/canister_threshold_sig", diff --git a/rs/crypto/Cargo.toml b/rs/crypto/Cargo.toml index 590ad0001dc..46b7c841deb 100644 --- a/rs/crypto/Cargo.toml +++ b/rs/crypto/Cargo.toml @@ -25,6 +25,7 @@ ic-crypto-internal-logmon = { path = "internal/logmon" } ic-crypto-internal-seed = { path = "internal/crypto_lib/seed" } ic-crypto-internal-threshold-sig-bls12381 = { path = "internal/crypto_lib/threshold_sig/bls12_381" } ic-crypto-internal-threshold-sig-canister-threshold-sig = { path = "internal/crypto_lib/threshold_sig/canister_threshold_sig" } +ic-crypto-internal-bls12-381-vetkd = { path = "internal/crypto_lib/bls12_381/vetkd" } ic-crypto-internal-types = { path = "internal/crypto_lib/types" } ic-crypto-standalone-sig-verifier = { path = "standalone-sig-verifier" } ic-crypto-tls-cert-validation = { path = "node_key_validation/tls_cert_validation" } diff --git a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/benches/vetkd.rs b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/benches/vetkd.rs index e9bf527021a..bfe8f0c80be 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/benches/vetkd.rs +++ b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/benches/vetkd.rs @@ -53,7 +53,7 @@ fn vetkd_bench(c: &mut Criterion) { group.bench_function("EncryptedKeyShare::deserialize", |b| { b.iter_batched( || eks.serialize(), - EncryptedKeyShare::deserialize, + |val| EncryptedKeyShare::deserialize(&val), BatchSize::SmallInput, ) }); diff --git a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs index 3e2da411648..e7c6bafd5b8 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs +++ b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/src/lib.rs @@ -297,7 +297,10 @@ impl EncryptedKey { } /// Deserialize an encrypted key - pub fn deserialize(val: [u8; Self::BYTES]) -> Result { + pub fn deserialize(val: &[u8]) -> Result { + if val.len() != Self::BYTES { + return Err(EncryptedKeyDeserializationError::InvalidEncryptedKey); + } let c2_start = G1Affine::BYTES; let c3_start = G1Affine::BYTES + G2Affine::BYTES; @@ -407,9 +410,10 @@ impl EncryptedKeyShare { } /// Deserialize an encrypted key share - pub fn deserialize( - val: [u8; Self::BYTES], - ) -> Result { + pub fn deserialize(val: &[u8]) -> Result { + if val.len() != Self::BYTES { + return Err(EncryptedKeyShareDeserializationError::InvalidEncryptedKeyShare); + } let c2_start = G1Affine::BYTES; let c3_start = G1Affine::BYTES + G2Affine::BYTES; diff --git a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/tests/tests.rs b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/tests/tests.rs index 0f36058d6c0..016f96bb3cd 100644 --- a/rs/crypto/internal/crypto_lib/bls12_381/vetkd/tests/tests.rs +++ b/rs/crypto/internal/crypto_lib/bls12_381/vetkd/tests/tests.rs @@ -253,7 +253,7 @@ impl<'a> VetkdTestProtocolExecution<'a> { // check that EKS serialization round trips: let eks_bytes = eks.serialize(); - let eks2 = EncryptedKeyShare::deserialize(eks_bytes).unwrap(); + let eks2 = EncryptedKeyShare::deserialize(&eks_bytes).unwrap(); assert_eq!(eks, eks2); node_info.push((node_idx as u32, node_pk.clone(), eks.clone())); diff --git a/rs/crypto/internal/crypto_service_provider/src/vault/api.rs b/rs/crypto/internal/crypto_service_provider/src/vault/api.rs index 93727e5bc21..9ff65d6a5d9 100644 --- a/rs/crypto/internal/crypto_service_provider/src/vault/api.rs +++ b/rs/crypto/internal/crypto_service_provider/src/vault/api.rs @@ -974,10 +974,14 @@ pub trait VetKdCspVault { /// Vault-level error for vetKD key share creation. #[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize)] pub enum VetKdEncryptedKeyShareCreationVaultError { - /// If some arguments are invalid - InvalidArgument(String), + /// If the secret key is missing in the key store of if it has the wrong type + SecretKeyMissingOrWrongType(String), /// If a transient internal error occurs, e.g., an RPC error communicating with the remote vault TransientInternalError(String), + /// If the given master public key is invalid + InvalidArgumentMasterPublicKey, + /// If the given encryption public key is invalid + InvalidArgumentEncryptionPublicKey, } /// An error returned by failing to generate a public seed from [`CspVault`]. diff --git a/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/mod.rs b/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/mod.rs index fef59022a17..fd7b4bf88fe 100644 --- a/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/mod.rs +++ b/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/mod.rs @@ -60,40 +60,35 @@ impl Result { let master_public_key = G2Affine::deserialize(&master_public_key).map_err(|_: PairingInvalidPoint| { - VetKdEncryptedKeyShareCreationVaultError::InvalidArgument(format!( - "invalid master public key: 0x{}", - hex::encode(&master_public_key) - )) + VetKdEncryptedKeyShareCreationVaultError::InvalidArgumentMasterPublicKey })?; let transport_public_key = TransportPublicKey::deserialize(&encryption_public_key) .map_err(|e| match e { TransportPublicKeyDeserializationError::InvalidPublicKey => { - VetKdEncryptedKeyShareCreationVaultError::InvalidArgument(format!( - "invalid encryption public key: 0x{}", - hex::encode(&encryption_public_key) - )) + VetKdEncryptedKeyShareCreationVaultError::InvalidArgumentEncryptionPublicKey } })?; let secret_key_from_store = self.sks_read_lock().get(&key_id).ok_or( - VetKdEncryptedKeyShareCreationVaultError::InvalidArgument(format!( - "missing key with ID {key_id:?}", + VetKdEncryptedKeyShareCreationVaultError::SecretKeyMissingOrWrongType(format!( + "missing key with ID {key_id}" )), )?; - let secret_bls_scalar = if let CspSecretKey::ThresBls12_381(secret_key_bytes) = - &secret_key_from_store - { - // We use the unchecked deserialization here because it is slighly cheaper, but mainly because - // it cannot fail, and the data is anyway trusted as it comes from the secret key store. - Ok(Scalar::deserialize_unchecked( - secret_key_bytes.inner_secret().expose_secret(), - )) - } else { - Err(VetKdEncryptedKeyShareCreationVaultError::InvalidArgument( - format!("wrong secret key type for key with ID {key_id}: expected ThresBls12_381"), - )) - }?; + let secret_bls_scalar = + if let CspSecretKey::ThresBls12_381(secret_key_bytes) = &secret_key_from_store { + // We use the unchecked deserialization here because it is slighly cheaper, but mainly because + // it cannot fail, and the data is anyway trusted as it comes from the secret key store. + Ok(Scalar::deserialize_unchecked( + secret_key_bytes.inner_secret().expose_secret(), + )) + } else { + Err( + VetKdEncryptedKeyShareCreationVaultError::SecretKeyMissingOrWrongType(format!( + "wrong secret key type for key with ID {key_id}: expected ThresBls12_381" + )), + ) + }?; // Create encrypted key share using our library let encrypted_key_share = EncryptedKeyShare::create( diff --git a/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/tests.rs b/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/tests.rs index fdc86140ec4..68108017335 100644 --- a/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/tests.rs +++ b/rs/crypto/internal/crypto_service_provider/src/vault/local_csp_vault/vetkd/tests.rs @@ -63,8 +63,8 @@ fn should_fail_to_create_key_share_with_invalid_master_public_key() { let result = test_env.create_encrypted_vetkd_key_share(); assert_matches!( - result, Err(VetKdEncryptedKeyShareCreationVaultError::InvalidArgument(error)) - if error.contains("invalid master public key") + result, + Err(VetKdEncryptedKeyShareCreationVaultError::InvalidArgumentMasterPublicKey) ); } @@ -79,8 +79,8 @@ fn should_fail_to_create_key_share_with_invalid_encryption_public_key() { let result = test_env.create_encrypted_vetkd_key_share(); assert_matches!( - result, Err(VetKdEncryptedKeyShareCreationVaultError::InvalidArgument(error)) - if error.contains("invalid encryption public key") + result, + Err(VetKdEncryptedKeyShareCreationVaultError::InvalidArgumentEncryptionPublicKey) ); } @@ -94,7 +94,7 @@ fn should_fail_to_create_key_share_if_key_is_missing_in_secret_key_store() { let result = test_env.create_encrypted_vetkd_key_share(); assert_matches!( - result, Err(VetKdEncryptedKeyShareCreationVaultError::InvalidArgument(error)) + result, Err(VetKdEncryptedKeyShareCreationVaultError::SecretKeyMissingOrWrongType(error)) if error.contains("missing key with ID") ); } @@ -111,7 +111,7 @@ fn should_fail_to_create_key_share_if_key_in_secret_key_store_has_wrong_type() { let result = test_env.create_encrypted_vetkd_key_share(); assert_matches!( - result, Err(VetKdEncryptedKeyShareCreationVaultError::InvalidArgument(error)) + result, Err(VetKdEncryptedKeyShareCreationVaultError::SecretKeyMissingOrWrongType(error)) if error.contains("wrong secret key type") ); } diff --git a/rs/crypto/src/lib.rs b/rs/crypto/src/lib.rs index a720e2ff407..0d917897660 100644 --- a/rs/crypto/src/lib.rs +++ b/rs/crypto/src/lib.rs @@ -16,6 +16,7 @@ mod common; mod keygen; mod sign; mod tls; +mod vetkd; use ic_crypto_internal_csp::vault::api::CspVault; pub use sign::{ diff --git a/rs/crypto/src/sign/mod.rs b/rs/crypto/src/sign/mod.rs index 668e462383e..de4da5f1080 100644 --- a/rs/crypto/src/sign/mod.rs +++ b/rs/crypto/src/sign/mod.rs @@ -1,7 +1,5 @@ use super::*; -use crate::sign::basic_sig::BasicSigVerifierInternal; -use crate::sign::basic_sig::BasicSignerInternal; use crate::sign::multi_sig::MultiSigVerifierInternal; use crate::sign::multi_sig::MultiSignerInternal; use crate::sign::threshold_sig::{ThresholdSigVerifierInternal, ThresholdSignerInternal}; @@ -37,7 +35,9 @@ use ic_types::{NodeId, RegistryVersion, SubnetId}; use std::collections::{BTreeMap, BTreeSet}; use std::convert::TryFrom; -pub use threshold_sig::ThresholdSigDataStoreImpl; +pub(crate) use basic_sig::{BasicSigVerifierInternal, BasicSignerInternal}; +pub(crate) use threshold_sig::lazily_calculated_public_key_from_store; +pub use threshold_sig::{ThresholdSigDataStore, ThresholdSigDataStoreImpl}; mod basic_sig; mod canister_threshold_sig; diff --git a/rs/crypto/src/sign/threshold_sig.rs b/rs/crypto/src/sign/threshold_sig.rs index 0c44c0a3f44..efce632ee54 100644 --- a/rs/crypto/src/sign/threshold_sig.rs +++ b/rs/crypto/src/sign/threshold_sig.rs @@ -192,7 +192,7 @@ impl ThresholdSigVerifierInternal { /// Given that both cases indicate that the implementations of DKG and threshold /// signatures are not aligned and also a caller could not recover from this, we /// panic. -fn lazily_calculated_public_key_from_store( +pub(crate) fn lazily_calculated_public_key_from_store( lockable_threshold_sig_data_store: &LockableThresholdSigDataStore, threshold_sig_csp_client: &C, dkg_id: &NiDkgId, diff --git a/rs/crypto/src/vetkd/mod.rs b/rs/crypto/src/vetkd/mod.rs new file mode 100644 index 00000000000..90ef8070790 --- /dev/null +++ b/rs/crypto/src/vetkd/mod.rs @@ -0,0 +1,581 @@ +use super::get_log_id; +use crate::sign::lazily_calculated_public_key_from_store; +use crate::sign::BasicSigVerifierInternal; +use crate::sign::BasicSignerInternal; +use crate::sign::ThresholdSigDataStore; +use crate::{CryptoComponentImpl, LockableThresholdSigDataStore}; +use ic_crypto_internal_bls12_381_vetkd::{ + DerivationPath, EncryptedKeyCombinationError, EncryptedKeyShare, + EncryptedKeyShareDeserializationError, G2Affine, NodeIndex, PairingInvalidPoint, + TransportPublicKey, TransportPublicKeyDeserializationError, +}; +use ic_crypto_internal_csp::api::CspSigner; +use ic_crypto_internal_csp::api::ThresholdSignatureCspClient; +use ic_crypto_internal_csp::key_id::KeyIdInstantiationError; +use ic_crypto_internal_csp::vault::api::VetKdEncryptedKeyShareCreationVaultError; +use ic_crypto_internal_csp::{key_id::KeyId, vault::api::CspVault, CryptoServiceProvider}; +use ic_crypto_internal_logmon::metrics::{MetricsDomain, MetricsResult, MetricsScope}; +use ic_crypto_internal_types::sign::threshold_sig::public_coefficients::PublicCoefficients; +use ic_crypto_internal_types::sign::threshold_sig::public_key::bls12_381; +use ic_crypto_internal_types::sign::threshold_sig::public_key::CspThresholdSigPublicKey; +use ic_interfaces::crypto::VetKdProtocol; +use ic_interfaces_registry::RegistryClient; +use ic_logger::{debug, info, new_logger, ReplicaLogger}; +use ic_types::crypto::threshold_sig::errors::threshold_sig_data_not_found_error::ThresholdSigDataNotFoundError; +use ic_types::crypto::threshold_sig::ni_dkg::NiDkgId; +use ic_types::crypto::vetkd::{ + VetKdArgs, VetKdEncryptedKey, VetKdEncryptedKeyShare, VetKdKeyShareCombinationError, + VetKdKeyShareCreationError, VetKdKeyShareVerificationError, VetKdKeyVerificationError, +}; +use ic_types::crypto::{BasicSig, BasicSigOf}; +use ic_types::NodeId; +use std::collections::BTreeMap; +use std::fmt; + +impl VetKdProtocol for CryptoComponentImpl { + // TODO(CRP-2639): Adapt VetKdKeyShareCreationError so that clippy exception is no longer needed + #[allow(clippy::result_large_err)] + fn create_encrypted_key_share( + &self, + args: VetKdArgs, + ) -> Result { + let log_id = get_log_id(&self.logger); + let logger = new_logger!(&self.logger; + crypto.log_id => log_id, + crypto.trait_name => "VetKdProtocol", + crypto.method_name => "create_encrypted_key_share", + ); + debug!(logger; + crypto.description => "start", + crypto.vetkd_args => format!("{}", args), + ); + let start_time = self.metrics.now(); + let result = create_encrypted_key_share_internal( + &self.lockable_threshold_sig_data_store, + self.registry_client.as_ref(), + self.vault.as_ref(), + &self.csp, + args, + self.node_id, + ); + self.metrics.observe_duration_seconds( + MetricsDomain::VetKd, + MetricsScope::Full, + "create_encrypted_key_share", + MetricsResult::from(&result), + start_time, + ); + debug!(logger; + crypto.description => "end", + crypto.is_ok => result.is_ok(), + crypto.error => log_err(result.as_ref().err()), + crypto.vetkd_key_share => log_ok_content(&result), + ); + result + } + + fn verify_encrypted_key_share( + &self, + signer: NodeId, + key_share: &VetKdEncryptedKeyShare, + args: &VetKdArgs, + ) -> Result<(), VetKdKeyShareVerificationError> { + let log_id = get_log_id(&self.logger); + let logger = new_logger!(&self.logger; + crypto.log_id => log_id, + crypto.trait_name => "VetKdProtocol", + crypto.method_name => "verify_encrypted_key_share", + ); + debug!(logger; + crypto.description => "start", + crypto.signer => format!("{}", signer), + crypto.vetkd_key_share => format!("{}", key_share), + ); + let start_time = self.metrics.now(); + let result = verify_encrypted_key_share_internal( + &self.lockable_threshold_sig_data_store, + self.registry_client.as_ref(), + &self.csp, + key_share, + signer, + args, + ); + self.metrics.observe_duration_seconds( + MetricsDomain::VetKd, + MetricsScope::Full, + "verify_encrypted_key_share", + MetricsResult::from(&result), + start_time, + ); + debug!(logger; + crypto.description => "end", + crypto.is_ok => result.is_ok(), + crypto.error => log_err(result.as_ref().err()), + ); + result + } + + fn combine_encrypted_key_shares( + &self, + shares: &BTreeMap, + args: &VetKdArgs, + ) -> Result { + let log_id = get_log_id(&self.logger); + let logger = new_logger!(&self.logger; + crypto.log_id => log_id, + crypto.trait_name => "VetKdProtocol", + crypto.method_name => "combine_encrypted_key_shares", + ); + debug!(logger; + crypto.description => "start", + crypto.vetkd_args => format!("{}", args), + crypto.vetkd_key_shares => format!("{:?}", shares), + ); + let start_time = self.metrics.now(); + let result = combine_encrypted_key_shares_internal( + &self.lockable_threshold_sig_data_store, + &self.csp, + &self.logger, + shares, + args, + ); + self.metrics.observe_duration_seconds( + MetricsDomain::VetKd, + MetricsScope::Full, + "combine_encrypted_key_shares", + MetricsResult::from(&result), + start_time, + ); + debug!(logger; + crypto.description => "end", + crypto.is_ok => result.is_ok(), + crypto.error => log_err(result.as_ref().err()), + crypto.vetkd_key => log_ok_content(&result), + ); + result + } + + fn verify_encrypted_key( + &self, + key: &VetKdEncryptedKey, + args: &VetKdArgs, + ) -> Result<(), VetKdKeyVerificationError> { + let log_id = get_log_id(&self.logger); + let logger = new_logger!(&self.logger; + crypto.log_id => log_id, + crypto.trait_name => "VetKdProtocol", + crypto.method_name => "verify_encrypted_key", + ); + debug!(logger; + crypto.description => "start", + crypto.vetkd_args => format!("{}", args), + crypto.vetkd_key => format!("{}", key), + ); + let start_time = self.metrics.now(); + let result = + verify_encrypted_key_internal(&self.lockable_threshold_sig_data_store, key, args); + self.metrics.observe_duration_seconds( + MetricsDomain::VetKd, + MetricsScope::Full, + "verify_encrypted_key", + MetricsResult::from(&result), + start_time, + ); + debug!(logger; + crypto.description => "end", + crypto.is_ok => result.is_ok(), + crypto.error => log_err(result.as_ref().err()), + ); + result + } +} + +// TODO(CRP-2639): Adapt VetKdKeyShareCreationError so that clippy exception is no longer needed +#[allow(clippy::result_large_err)] +fn create_encrypted_key_share_internal( + lockable_threshold_sig_data_store: &LockableThresholdSigDataStore, + registry: &dyn RegistryClient, + vault: &dyn CspVault, + csp_signer: &S, + args: VetKdArgs, + self_node_id: NodeId, +) -> Result { + let (pub_coeffs_from_store, registry_version_from_store) = lockable_threshold_sig_data_store + .read() + .transcript_data(&args.ni_dkg_id) + .map(|transcript_data| { + let pub_coeffs = transcript_data.public_coefficients().clone(); + let registry_version = transcript_data.registry_version(); + (pub_coeffs, registry_version) + }) + .ok_or_else(|| { + VetKdKeyShareCreationError::ThresholdSigDataNotFound( + ThresholdSigDataNotFoundError::ThresholdSigDataNotFound { + dkg_id: args.ni_dkg_id.clone(), + }, + ) + })?; + let key_id = KeyId::try_from(&pub_coeffs_from_store).map_err(|e| match e { + KeyIdInstantiationError::InvalidArguments(msg) => { + VetKdKeyShareCreationError::KeyIdInstantiationError(msg) + } + })?; + let master_public_key = match &pub_coeffs_from_store { + PublicCoefficients::Bls12_381(pub_coeffs) => pub_coeffs + .coefficients + .iter() + .copied() + .next() + .ok_or_else(|| { + VetKdKeyShareCreationError::InternalError(format!( + "public coefficients for NI-DKG ID {} are empty", + &args.ni_dkg_id + )) + })?, + }; + + let encrypted_key_share = vault + .create_encrypted_vetkd_key_share( + key_id, + master_public_key.as_bytes().to_vec(), + args.encryption_public_key, + args.derivation_path, + args.derivation_id, + ) + .map_err(vetkd_key_share_creation_error_from_vault_error)?; + + let signature = BasicSignerInternal::sign_basic( + csp_signer, + registry, + &encrypted_key_share, + self_node_id, + // TODO(CRP-2666): Cleanup: Remove registry_version from BasicSigner::sign_basic API + registry_version_from_store, + ) + .map_err(VetKdKeyShareCreationError::KeyShareSigningError)?; + + Ok(VetKdEncryptedKeyShare { + encrypted_key_share, + node_signature: signature.get().0, + }) +} + +fn vetkd_key_share_creation_error_from_vault_error( + error: VetKdEncryptedKeyShareCreationVaultError, +) -> VetKdKeyShareCreationError { + match error { + VetKdEncryptedKeyShareCreationVaultError::SecretKeyMissingOrWrongType(error) => { + VetKdKeyShareCreationError::InternalError(format!( + "secret key missing or wrong type: {error}" + )) + } + VetKdEncryptedKeyShareCreationVaultError::InvalidArgumentMasterPublicKey => { + VetKdKeyShareCreationError::InternalError("invalid master public key".to_string()) + } + VetKdEncryptedKeyShareCreationVaultError::InvalidArgumentEncryptionPublicKey => { + VetKdKeyShareCreationError::InvalidArgumentEncryptionPublicKey + } + VetKdEncryptedKeyShareCreationVaultError::TransientInternalError(error) => { + VetKdKeyShareCreationError::TransientInternalError(error) + } + } +} + +fn verify_encrypted_key_share_internal( + lockable_threshold_sig_data_store: &LockableThresholdSigDataStore, + registry: &dyn RegistryClient, + csp_signer: &S, + key_share: &VetKdEncryptedKeyShare, + signer: NodeId, + args: &VetKdArgs, +) -> Result<(), VetKdKeyShareVerificationError> { + let registry_version_from_store = lockable_threshold_sig_data_store + .read() + .transcript_data(&args.ni_dkg_id) + .map(|transcript_data| transcript_data.registry_version()) + .ok_or_else(|| { + VetKdKeyShareVerificationError::ThresholdSigDataNotFound( + ThresholdSigDataNotFoundError::ThresholdSigDataNotFound { + dkg_id: args.ni_dkg_id.clone(), + }, + ) + })?; + + let signature = BasicSigOf::new(BasicSig(key_share.node_signature.clone())); + BasicSigVerifierInternal::verify_basic_sig( + csp_signer, + registry, + &signature, + &key_share.encrypted_key_share, + signer, + registry_version_from_store, + ) + .map_err(VetKdKeyShareVerificationError::VerificationError) +} + +fn combine_encrypted_key_shares_internal( + lockable_threshold_sig_data_store: &LockableThresholdSigDataStore, + threshold_sig_csp_client: &C, + logger: &ReplicaLogger, + shares: &BTreeMap, + args: &VetKdArgs, +) -> Result { + ensure_sufficient_shares_to_fail_fast( + shares, + lockable_threshold_sig_data_store, + &args.ni_dkg_id, + )?; + + let transcript_data_from_store = lockable_threshold_sig_data_store + .read() + .transcript_data(&args.ni_dkg_id) + .cloned() + .ok_or_else(|| { + VetKdKeyShareCombinationError::ThresholdSigDataNotFound( + ThresholdSigDataNotFoundError::ThresholdSigDataNotFound { + dkg_id: args.ni_dkg_id.clone(), + }, + ) + })?; + let pub_coeffs_from_store = match transcript_data_from_store.public_coefficients() { + PublicCoefficients::Bls12_381(pub_coeffs) => &pub_coeffs.coefficients, + }; + let reconstruction_threshold = pub_coeffs_from_store.len(); + let master_public_key = master_pubkey_from_coeffs(pub_coeffs_from_store, &args.ni_dkg_id) + .map_err(|error| match error { + MasterPubkeyFromCoeffsError::InternalError(msg) => { + VetKdKeyShareCombinationError::InternalError(msg) + } + MasterPubkeyFromCoeffsError::InvalidArgumentMasterPublicKey => { + VetKdKeyShareCombinationError::InvalidArgumentMasterPublicKey + } + })?; + let transport_public_key = TransportPublicKey::deserialize(&args.encryption_public_key) + .map_err(|e| match e { + TransportPublicKeyDeserializationError::InvalidPublicKey => { + VetKdKeyShareCombinationError::InvalidArgumentEncryptionPublicKey + } + })?; + let clib_shares: Vec<(NodeId, NodeIndex, EncryptedKeyShare)> = shares + .iter() + .map(|(&node_id, share)| { + let node_index = transcript_data_from_store.index(node_id).ok_or( + VetKdKeyShareCombinationError::InternalError(format!( + "missing index for node with ID {node_id} in threshold \ + sig data store for NI-DKG ID {}", + args.ni_dkg_id + )), + )?; + let clib_share = EncryptedKeyShare::deserialize(&share.encrypted_key_share.0).map_err( + |e| match e { + EncryptedKeyShareDeserializationError::InvalidEncryptedKeyShare => { + VetKdKeyShareCombinationError::InvalidArgumentEncryptedKeyShare + } + }, + )?; + Ok((node_id, *node_index, clib_share)) + }) + .collect::>()?; + let clib_shares_for_combine_all: Vec<(NodeIndex, EncryptedKeyShare)> = clib_shares + .iter() + .map(|(_node_id, node_index, clib_share)| (*node_index, clib_share.clone())) + .collect(); + let derivation_path = DerivationPath::new( + args.derivation_path.caller.as_slice(), + &args.derivation_path.derivation_path, + ); + + match ic_crypto_internal_bls12_381_vetkd::EncryptedKey::combine_all( + &clib_shares_for_combine_all[..], + reconstruction_threshold, + &master_public_key, + &transport_public_key, + &derivation_path, + &args.derivation_id, + ) { + Ok(encrypted_key) => Ok(encrypted_key), + Err(EncryptedKeyCombinationError::InsufficientShares) => { + Err(VetKdKeyShareCombinationError::UnsatisfiedReconstructionThreshold { + threshold: reconstruction_threshold, + share_count: clib_shares_for_combine_all.len() + }) + } + Err(EncryptedKeyCombinationError::InvalidShares) => { + info!(logger, "EncryptedKey::combine_all failed with InvalidShares, \ + falling back to EncryptedKey::combine_valid_shares" + ); + + let clib_shares_for_combine_valid: Vec<(NodeIndex, G2Affine, EncryptedKeyShare)> = clib_shares + .iter() + .map(|(node_id, node_index, clib_share)| { + let node_public_key = lazily_calculated_public_key_from_store( + lockable_threshold_sig_data_store, + threshold_sig_csp_client, + &args.ni_dkg_id, + *node_id, + ) + .map_err(|e| { + VetKdKeyShareCombinationError::IndividualPublicKeyComputationError(e) + })?; + let node_public_key_g2affine = match node_public_key { + CspThresholdSigPublicKey::ThresBls12_381(public_key_bytes) => { + G2Affine::deserialize(&public_key_bytes.0) + .map_err(|_: PairingInvalidPoint| VetKdKeyShareCombinationError::InternalError( + format!("individual public key of node with ID {node_id} in threshold sig data store") + )) + } + }?; + Ok((*node_index, node_public_key_g2affine, clib_share.clone())) + }) + .collect::>()?; + + ic_crypto_internal_bls12_381_vetkd::EncryptedKey::combine_valid_shares( + &clib_shares_for_combine_valid[..], + reconstruction_threshold, + &master_public_key, + &transport_public_key, + &derivation_path, + &args.derivation_id, + ) + .map_err(|e| { + VetKdKeyShareCombinationError::CombinationError(format!( + "failed to combine the valid encrypted vetKD key shares: {e:?}" + )) + }) + }, + Err(other_error) => { + Err(VetKdKeyShareCombinationError::CombinationError(format!( + "failed to combine the valid encrypted vetKD key shares: {other_error:?}" + ))) + } + } + .map(|encrypted_key| VetKdEncryptedKey { + encrypted_key: encrypted_key.serialize().to_vec(), + }) +} + +fn verify_encrypted_key_internal( + lockable_threshold_sig_data_store: &LockableThresholdSigDataStore, + key: &VetKdEncryptedKey, + args: &VetKdArgs, +) -> Result<(), VetKdKeyVerificationError> { + let encrypted_key = + ic_crypto_internal_bls12_381_vetkd::EncryptedKey::deserialize(&key.encrypted_key) + .map_err(|e| match e { + ic_crypto_internal_bls12_381_vetkd::EncryptedKeyDeserializationError::InvalidEncryptedKey => VetKdKeyVerificationError::InvalidArgumentEncryptedKey, + })?; + + let master_public_key = { + let pub_coeffs_from_store = lockable_threshold_sig_data_store + .read() + .transcript_data(&args.ni_dkg_id) + .map(|data| data.public_coefficients().clone()) + .ok_or_else(|| { + VetKdKeyVerificationError::ThresholdSigDataNotFound( + ThresholdSigDataNotFoundError::ThresholdSigDataNotFound { + dkg_id: args.ni_dkg_id.clone(), + }, + ) + })?; + match pub_coeffs_from_store { + PublicCoefficients::Bls12_381(bls_coeffs_trusted) => { + master_pubkey_from_coeffs(&bls_coeffs_trusted.coefficients, &args.ni_dkg_id) + .map_err(|error| match error { + MasterPubkeyFromCoeffsError::InternalError(msg) => { + VetKdKeyVerificationError::InternalError(msg) + } + MasterPubkeyFromCoeffsError::InvalidArgumentMasterPublicKey => { + VetKdKeyVerificationError::InvalidArgumentMasterPublicKey + } + })? + } + } + }; + + let transport_public_key = TransportPublicKey::deserialize(&args.encryption_public_key) + .map_err(|e| match e { + TransportPublicKeyDeserializationError::InvalidPublicKey => { + VetKdKeyVerificationError::InvalidArgumentEncryptionPublicKey + } + })?; + + match encrypted_key.is_valid( + &master_public_key, + &DerivationPath::new( + args.derivation_path.caller.as_slice(), + &args.derivation_path.derivation_path, + ), + &args.derivation_id, + &transport_public_key, + ) { + true => Ok(()), + false => Err(VetKdKeyVerificationError::VerificationError), + } +} + +fn ensure_sufficient_shares_to_fail_fast( + shares: &BTreeMap, + lockable_threshold_sig_data_store: &LockableThresholdSigDataStore, + ni_dkg_id: &NiDkgId, +) -> Result<(), VetKdKeyShareCombinationError> { + let reconstruction_threshold = lockable_threshold_sig_data_store + .read() + .transcript_data(ni_dkg_id) + .map(|data| match data.public_coefficients() { + PublicCoefficients::Bls12_381(bls_pub_coeffs) => bls_pub_coeffs.coefficients.len(), + }) + .ok_or_else(|| { + VetKdKeyShareCombinationError::ThresholdSigDataNotFound( + ThresholdSigDataNotFoundError::ThresholdSigDataNotFound { + dkg_id: ni_dkg_id.clone(), + }, + ) + })?; + let share_count = shares.len(); + if share_count < reconstruction_threshold { + Err( + VetKdKeyShareCombinationError::UnsatisfiedReconstructionThreshold { + threshold: reconstruction_threshold, + share_count, + }, + ) + } else { + Ok(()) + } +} + +fn master_pubkey_from_coeffs( + pub_coeffs: &[bls12_381::PublicKeyBytes], + ni_dkg_id: &NiDkgId, +) -> Result { + let first_coeff = pub_coeffs.iter().copied().next().ok_or_else(|| { + MasterPubkeyFromCoeffsError::InternalError(format!( + "failed to determine master public key: public coefficients + for NI-DKG ID {ni_dkg_id} are empty" + )) + })?; + let first_coeff_g2 = + G2Affine::deserialize(&first_coeff).map_err(|_: PairingInvalidPoint| { + MasterPubkeyFromCoeffsError::InvalidArgumentMasterPublicKey + })?; + Ok(first_coeff_g2) +} + +enum MasterPubkeyFromCoeffsError { + InternalError(String), + InvalidArgumentMasterPublicKey, +} + +fn log_err(error_option: Option<&T>) -> String { + if let Some(error) = error_option { + return format!("{}", error); + } + "none".to_string() +} + +pub fn log_ok_content(result: &Result) -> String { + if let Ok(content) = result { + return format!("{}", content); + } + "none".to_string() +} diff --git a/rs/interfaces/src/crypto/errors.rs b/rs/interfaces/src/crypto/errors.rs index e51872c6094..638b2d33202 100644 --- a/rs/interfaces/src/crypto/errors.rs +++ b/rs/interfaces/src/crypto/errors.rs @@ -432,10 +432,10 @@ impl ErrorReproducibility for VetKdKeyShareVerificationError { // to avoid defaults, which might be error-prone. // Upon addition of any new error this match has to be updated. - // VetKd key share verification does not depend on any local or private - // state and so is inherently replicated. match self { - Self::InvalidSignature => true, + Self::VerificationError(crypto_error) => crypto_error.is_reproducible(), + // false, as the result may change if the DKG transcript is reloaded. + Self::ThresholdSigDataNotFound(_) => false, } } } diff --git a/rs/protobuf/def/log/crypto_log_entry/v1/crypto_log_entry.proto b/rs/protobuf/def/log/crypto_log_entry/v1/crypto_log_entry.proto index e71c9f521bc..f9f2738ae0a 100644 --- a/rs/protobuf/def/log/crypto_log_entry/v1/crypto_log_entry.proto +++ b/rs/protobuf/def/log/crypto_log_entry/v1/crypto_log_entry.proto @@ -34,4 +34,8 @@ message CryptoLogEntry { google.protobuf.StringValue signature_shares = 26; google.protobuf.StringValue signature_inputs = 27; google.protobuf.UInt64Value log_id = 28; + google.protobuf.StringValue vetkd_args = 29; + google.protobuf.StringValue vetkd_key_share = 30; + google.protobuf.StringValue vetkd_key_shares = 31; + google.protobuf.StringValue vetkd_key = 32; } diff --git a/rs/protobuf/src/gen/log/log.crypto_log_entry.v1.rs b/rs/protobuf/src/gen/log/log.crypto_log_entry.v1.rs index d43066d9c89..556c14cb8db 100644 --- a/rs/protobuf/src/gen/log/log.crypto_log_entry.v1.rs +++ b/rs/protobuf/src/gen/log/log.crypto_log_entry.v1.rs @@ -85,4 +85,12 @@ pub struct CryptoLogEntry { #[prost(message, optional, tag = "28")] #[serde(skip_serializing_if = "Option::is_none")] pub log_id: ::core::option::Option, + #[prost(message, optional, tag = "29")] + pub vetkd_args: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag = "30")] + pub vetkd_key_share: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag = "31")] + pub vetkd_key_shares: ::core::option::Option<::prost::alloc::string::String>, + #[prost(message, optional, tag = "32")] + pub vetkd_key: ::core::option::Option<::prost::alloc::string::String>, } diff --git a/rs/types/types/src/crypto/hash.rs b/rs/types/types/src/crypto/hash.rs index 0241708c8fa..a2b0e5e8de3 100644 --- a/rs/types/types/src/crypto/hash.rs +++ b/rs/types/types/src/crypto/hash.rs @@ -19,7 +19,6 @@ use crate::consensus::{ use crate::crypto::canister_threshold_sig::idkg::{ IDkgDealing, IDkgDealingSupport, IDkgTranscript, SignedIDkgDealing, }; -use crate::crypto::vetkd::VetKdEncryptedKeyShareContent; use crate::crypto::{CryptoHash, CryptoHashOf, Signed}; use crate::messages::{HttpCanisterUpdate, MessageId, SignedRequestBytes}; use crate::signature::{ @@ -124,8 +123,6 @@ mod private { impl CryptoHashDomainSeal for SchnorrSigShare {} impl CryptoHashDomainSeal for VetKdKeyShare {} - impl CryptoHashDomainSeal for VetKdEncryptedKeyShareContent {} - impl CryptoHashDomainSeal for IDkgComplaintContent {} impl CryptoHashDomainSeal for Signed> {} @@ -393,12 +390,6 @@ impl CryptoHashDomain for VetKdKeyShare { } } -impl CryptoHashDomain for VetKdEncryptedKeyShareContent { - fn domain(&self) -> String { - DomainSeparator::VetKdEncryptedKeyShareContent.to_string() - } -} - impl CryptoHashDomain for IDkgComplaintContent { fn domain(&self) -> String { DomainSeparator::IDkgComplaintContent.to_string() diff --git a/rs/types/types/src/crypto/sign.rs b/rs/types/types/src/crypto/sign.rs index f0567a8296e..df8fe621833 100644 --- a/rs/types/types/src/crypto/sign.rs +++ b/rs/types/types/src/crypto/sign.rs @@ -10,6 +10,7 @@ use crate::consensus::{ NotarizationContent, RandomBeaconContent, RandomTapeContent, }; use crate::crypto::canister_threshold_sig::idkg::{IDkgDealing, SignedIDkgDealing}; +use crate::crypto::vetkd::VetKdEncryptedKeyShareContent; use crate::crypto::SignedBytesWithoutDomainSeparator; use crate::messages::{Delegation, MessageId, QueryResponseHash, WebAuthnEnvelope}; use std::convert::TryFrom; @@ -74,6 +75,7 @@ mod private { impl SignatureDomainSeal for RandomTapeContent {} impl SignatureDomainSeal for SignableMock {} impl SignatureDomainSeal for QueryResponseHash {} + impl SignatureDomainSeal for VetKdEncryptedKeyShareContent {} } impl SignatureDomain for CanisterHttpResponseMetadata { @@ -190,6 +192,12 @@ impl SignatureDomain for QueryResponseHash { } } +impl SignatureDomain for VetKdEncryptedKeyShareContent { + fn domain(&self) -> Vec { + domain_with_prepended_length(DomainSeparator::VetKdEncryptedKeyShareContent.as_str()) + } +} + // Returns a vector of bytes that contains the given domain // prepended with a single byte that holds the length of the domain. // This is the recommended format for non-empty domain separators, diff --git a/rs/types/types/src/crypto/vetkd.rs b/rs/types/types/src/crypto/vetkd.rs index 45032393436..c646be47672 100644 --- a/rs/types/types/src/crypto/vetkd.rs +++ b/rs/types/types/src/crypto/vetkd.rs @@ -1,9 +1,10 @@ use crate::crypto::impl_display_using_debug; use crate::crypto::threshold_sig::errors::threshold_sig_data_not_found_error::ThresholdSigDataNotFoundError; use crate::crypto::threshold_sig::ni_dkg::NiDkgId; +use crate::crypto::CryptoError; use crate::crypto::ExtendedDerivationPath; use crate::crypto::HexEncoding; -use crate::NodeId; +use crate::crypto::SignedBytesWithoutDomainSeparator; use serde::{Deserialize, Serialize}; use std::fmt; @@ -47,6 +48,12 @@ impl std::fmt::Debug for VetKdEncryptedKeyShareContent { } impl_display_using_debug!(VetKdEncryptedKeyShareContent); +impl SignedBytesWithoutDomainSeparator for VetKdEncryptedKeyShareContent { + fn as_signed_bytes_without_domain_separator(&self) -> Vec { + self.0.clone() + } +} + #[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] pub struct VetKdEncryptedKeyShare { pub encrypted_key_share: VetKdEncryptedKeyShareContent, @@ -83,25 +90,44 @@ impl_display_using_debug!(VetKdEncryptedKey); #[derive(Clone, Eq, PartialEq, Debug)] pub enum VetKdKeyShareCreationError { ThresholdSigDataNotFound(ThresholdSigDataNotFoundError), - SecretKeyNotFound { dkg_id: NiDkgId, key_id: String }, KeyIdInstantiationError(String), + InternalError(String), + InvalidArgumentEncryptionPublicKey, + KeyShareSigningError(CryptoError), TransientInternalError(String), } impl_display_using_debug!(VetKdKeyShareCreationError); #[derive(Clone, Eq, PartialEq, Debug)] pub enum VetKdKeyShareVerificationError { - InvalidSignature, + ThresholdSigDataNotFound(ThresholdSigDataNotFoundError), + VerificationError(CryptoError), } impl_display_using_debug!(VetKdKeyShareVerificationError); #[derive(Clone, Eq, PartialEq, Debug)] pub enum VetKdKeyShareCombinationError { - InvalidShares(Vec), - UnsatisfiedReconstructionThreshold { threshold: u32, share_count: usize }, + ThresholdSigDataNotFound(ThresholdSigDataNotFoundError), + InvalidArgumentMasterPublicKey, + InvalidArgumentEncryptionPublicKey, + InvalidArgumentEncryptedKeyShare, + IndividualPublicKeyComputationError(CryptoError), + CombinationError(String), + InternalError(String), + UnsatisfiedReconstructionThreshold { + threshold: usize, + share_count: usize, + }, } impl_display_using_debug!(VetKdKeyShareCombinationError); #[derive(Clone, Eq, PartialEq, Debug)] -pub enum VetKdKeyVerificationError {} +pub enum VetKdKeyVerificationError { + InvalidArgumentEncryptedKey, + ThresholdSigDataNotFound(ThresholdSigDataNotFoundError), + InternalError(String), + InvalidArgumentMasterPublicKey, + InvalidArgumentEncryptionPublicKey, + VerificationError, +} impl_display_using_debug!(VetKdKeyVerificationError);