diff --git a/key_protocol/src/key_management/key_tree/keys_public.rs b/key_protocol/src/key_management/key_tree/keys_public.rs index 8bcd0ee0..7680ca68 100644 --- a/key_protocol/src/key_management/key_tree/keys_public.rs +++ b/key_protocol/src/key_management/key_tree/keys_public.rs @@ -1,4 +1,4 @@ -use k256::elliptic_curve::{PrimeField as _}; +use k256::elliptic_curve::PrimeField as _; use serde::{Deserialize, Serialize}; use crate::key_management::key_tree::traits::KeyTreeNode; @@ -25,9 +25,11 @@ impl ChildKeysPublic { .expect("hash_value is 64 bytes, must be safe to get first 32"), ) .expect("Expect a valid Private Key"); - let csk = nssa::PrivateKey::tweak(cssk.value()).unwrap(); + let csk = nssa::PrivateKey::tweak(cssk.value()).expect("`key_protocol::key_management::keys_public::root()`: Invalid private key produced from `tweak`"); - let ccc = *hash_value.last_chunk::<32>().unwrap(); + let ccc = *hash_value + .last_chunk::<32>() + .expect("hash_value is 64 bytes, must be safe to get last 32"); let cpk = nssa::PublicKey::new_from_private_key(&csk); Self { @@ -43,22 +45,18 @@ impl ChildKeysPublic { pub fn nth_child(&self, cci: u32) -> Self { let hash_value = self.compute_hash_value(cci); - let cssk = nssa::PrivateKey::try_new( - (k256::Scalar::from_repr( - (*hash_value - .first_chunk::<32>() - .expect("hash_value is 64 bytes, must be safe to get first 32")) - .into(), - ) - .expect("Expect a valid k256 scalar")) - .add( - &k256::Scalar::from_repr((*self.cssk.value()).into()) - .expect("Expect a valid k256 scalar"), - ) - .to_bytes() + let lhs = k256::Scalar::from_repr( + (*hash_value + .first_chunk::<32>() + .expect("hash_value is 64 bytes, must be safe to get first 32")) .into(), ) - .expect("Expect a valid private key"); + .expect("Expect a valid k256 scalar"); + let rhs = k256::Scalar::from_repr((*self.cssk.value()).into()) + .expect("Expect a valid k256 scalar"); + + let cssk = nssa::PrivateKey::try_new(lhs.add(&rhs).to_bytes().into()) + .expect("Expect a valid private key"); let csk = nssa::PrivateKey::tweak(cssk.value()).expect("Expect a valid Private Key"); @@ -84,7 +82,9 @@ impl ChildKeysPublic { fn compute_hash_value(&self, cci: u32) -> [u8; 64] { let mut hash_input = vec![]; - // Only support harden keys to maintain PQ resilience. + // Simplified key logic by only supporting harden keys. + // Non-harden keys would require access to untweaked public keys associated to `cssk`s. + // Thus, not PQ secure. hash_input.extend_from_slice(&[0_u8]); hash_input.extend_from_slice(self.cssk.value()); diff --git a/nssa/src/signature/private_key.rs b/nssa/src/signature/private_key.rs index 3a73dab2..c4cc0771 100644 --- a/nssa/src/signature/private_key.rs +++ b/nssa/src/signature/private_key.rs @@ -63,12 +63,16 @@ impl PrivateKey { &self.0 } + /// `tweak` produces the "tweaked secret key" (`sk`) given a public account's `ssk`. + /// We use "tweaked keys" to shield the public accounts' `ssk` against quantum threats. + /// The "tweaked keys" are used for Schnorr Signatures (BIP-340). + /// The usage of these keys will be greatly reduced once LEE is upgraded to use a PQ signatures. pub fn tweak(value: &[u8; 32]) -> Result { if !Self::is_valid_key(*value) { return Err(NssaError::InvalidPrivateKey); } - let sk = k256::SecretKey::from_slice(value).expect("Expect a valid secret key"); + let sk = k256::SecretKey::from_slice(value).map_err(|_e| NssaError::InvalidPrivateKey)?; let hashed: [u8; 32] = Impl::hash_bytes(sk.public_key().to_encoded_point(true).as_bytes()) .as_bytes() @@ -77,11 +81,11 @@ impl PrivateKey { let sk = sk.to_nonzero_scalar(); - Self::try_new( - sk.add(&k256::Scalar::from_repr((hashed).into()).expect("Expect a valid k256 scalar")) - .to_bytes() - .into(), - ) + let scalar = k256::Scalar::from_repr(hashed.into()) + .into_option() + .ok_or(NssaError::InvalidPrivateKey)?; + + Self::try_new(sk.add(&scalar).to_bytes().into()) } } @@ -98,4 +102,33 @@ mod tests { fn produce_key() { let _key = PrivateKey::new_os_random(); } + + #[test] + fn tweak_rejects_zero_key() { + assert!(matches!( + PrivateKey::tweak(&[0_u8; 32]), + Err(NssaError::InvalidPrivateKey) + )); + } + + // tweak: 0xFF…FF exceeds the secp256k1 curve order + #[test] + fn tweak_rejects_out_of_range_key() { + assert!(matches!( + PrivateKey::tweak(&[0xFF; 32]), + Err(NssaError::InvalidPrivateKey) + )); + } + + #[test] + fn tweak_deterministic() { + let tweaked = PrivateKey::tweak(&[1_u8; 32]).unwrap(); + assert_eq!( + tweaked.value(), + &[ + 242, 210, 33, 19, 65, 108, 136, 176, 179, 128, 110, 210, 107, 193, 168, 112, 206, + 171, 86, 238, 131, 10, 39, 36, 44, 39, 246, 20, 46, 193, 204, 66 + ] + ); + } }