diff --git a/Cargo.toml b/Cargo.toml index bcc6250e1ee..51c26a16831 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,6 +125,7 @@ wasmer = { version = "2.2.1" } wasmer-compiler-singlepass = { version = "2.2.1" } wasmparser = { version = "0.107.0", default-features = false } extend = { version = "1.2.0" } +zeroize = { version = "1.3.0" } # Both the release and test profiles use `panic = "unwind"` to allow certain parts of the Radix # Engine to be able to catch panics. As an example, the native-vm has a `catch_unwind` to catch diff --git a/examples/everything/Cargo.lock b/examples/everything/Cargo.lock index 3c2e1d3f77d..cbd9d68982c 100644 --- a/examples/everything/Cargo.lock +++ b/examples/everything/Cargo.lock @@ -817,6 +817,7 @@ dependencies = [ "serde", "sha3", "strum", + "zeroize", ] [[package]] diff --git a/radix-clis/Cargo.lock b/radix-clis/Cargo.lock index edf9932d980..571046f1672 100644 --- a/radix-clis/Cargo.lock +++ b/radix-clis/Cargo.lock @@ -1145,6 +1145,7 @@ dependencies = [ "secp256k1", "sha3", "strum", + "zeroize", ] [[package]] diff --git a/radix-common/Cargo.toml b/radix-common/Cargo.toml index 65a5979f94b..e2de46b403a 100644 --- a/radix-common/Cargo.toml +++ b/radix-common/Cargo.toml @@ -31,6 +31,7 @@ ed25519-dalek = { workspace = true, features = ["u64_backend"] } secp256k1 = { workspace = true, features = ["recovery"], optional = true } blst = { workspace = true, optional = false } sha3 = { workspace = true, optional = false } +zeroize = { workspace = true, optional = false } [dev-dependencies] serde_json = { workspace = true } diff --git a/radix-common/src/crypto/ed25519/private_key.rs b/radix-common/src/crypto/ed25519/private_key.rs index 5ac75758719..74f4a16edf1 100644 --- a/radix-common/src/crypto/ed25519/private_key.rs +++ b/radix-common/src/crypto/ed25519/private_key.rs @@ -1,7 +1,10 @@ use super::Ed25519Signature; use crate::internal_prelude::*; use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signer}; +use zeroize::Zeroize; +#[derive(Zeroize)] +#[zeroize(drop)] pub struct Ed25519PrivateKey(SecretKey); impl Ed25519PrivateKey { diff --git a/radix-common/src/crypto/secp256k1/private_key.rs b/radix-common/src/crypto/secp256k1/private_key.rs index e854d31c4ec..9072037faf2 100644 --- a/radix-common/src/crypto/secp256k1/private_key.rs +++ b/radix-common/src/crypto/secp256k1/private_key.rs @@ -1,35 +1,48 @@ +use super::Secp256k1Signature; use crate::internal_prelude::*; use ::secp256k1::{All, Message, PublicKey, Secp256k1, SecretKey}; - -use super::Secp256k1Signature; +use zeroize::{DefaultIsZeroes, Zeroize}; lazy_static::lazy_static! { pub(crate) static ref SECP256K1_CTX: Secp256k1 = secp256k1::Secp256k1::new(); } -pub struct Secp256k1PrivateKey(SecretKey); +#[derive(Copy, Clone)] +pub struct SecretKeyWrapper(SecretKey); +impl Default for SecretKeyWrapper { + fn default() -> Self { + let mut data = [0u8; secp256k1::constants::SECRET_KEY_SIZE]; + data[secp256k1::constants::SECRET_KEY_SIZE - 1] = 1; + Self(SecretKey::from_slice(&data).unwrap()) + } +} +impl DefaultIsZeroes for SecretKeyWrapper {} + +#[derive(Zeroize)] +#[zeroize(drop)] +pub struct Secp256k1PrivateKey(SecretKeyWrapper); impl Secp256k1PrivateKey { - pub const LENGTH: usize = 32; + pub const LENGTH: usize = secp256k1::constants::SECRET_KEY_SIZE; pub fn public_key(&self) -> Secp256k1PublicKey { - Secp256k1PublicKey(PublicKey::from_secret_key(&SECP256K1_CTX, &self.0).serialize()) + Secp256k1PublicKey(PublicKey::from_secret_key(&SECP256K1_CTX, &self.0 .0).serialize()) } pub fn sign(&self, msg_hash: &impl IsHash) -> Secp256k1Signature { let m = Message::from_digest_slice(msg_hash.as_ref()).expect("Hash is always a valid message"); - let signature = SECP256K1_CTX.sign_ecdsa_recoverable(&m, &self.0); + let signature = SECP256K1_CTX.sign_ecdsa_recoverable(&m, &self.0 .0); let (recovery_id, signature_data) = signature.serialize_compact(); - let mut buf = [0u8; 65]; + let mut buf = [0u8; Secp256k1Signature::LENGTH]; buf[0] = recovery_id.to_i32() as u8; buf[1..].copy_from_slice(&signature_data); Secp256k1Signature(buf) } pub fn to_bytes(&self) -> Vec { - self.0.secret_bytes().to_vec() + self.0 .0.secret_bytes().to_vec() } pub fn to_hex(&self) -> String { @@ -46,7 +59,9 @@ impl Secp256k1PrivateKey { if slice.len() != Secp256k1PrivateKey::LENGTH { return Err(()); } - Ok(Self(SecretKey::from_slice(slice).map_err(|_| ())?)) + Ok(Self(SecretKeyWrapper( + SecretKey::from_slice(slice).map_err(|_| ())?, + ))) } pub fn from_u64(n: u64) -> Result { @@ -54,7 +69,9 @@ impl Secp256k1PrivateKey { (&mut bytes[Secp256k1PrivateKey::LENGTH - 8..Secp256k1PrivateKey::LENGTH]) .copy_from_slice(&n.to_be_bytes()); - Ok(Self(SecretKey::from_slice(&bytes).map_err(|_| ())?)) + Ok(Self(SecretKeyWrapper( + SecretKey::from_slice(&bytes).map_err(|_| ())?, + ))) } } @@ -77,4 +94,28 @@ mod tests { assert_eq!(sk.sign(&test_message_hash), sig); assert!(verify_secp256k1(&test_message_hash, &pk, &sig)); } + + #[test] + fn default_value() { + let key: SecretKeyWrapper = SecretKeyWrapper::default(); + assert_eq!( + key.0.secret_bytes(), + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1 + ] + ); + } + + #[test] + fn verify_zeroize() { + let bytes = "4fd3fb62d6b7a4749f75d56d06b0aea1ec2c2a6986d2bfa975d7891585590fea"; + let mut key = Secp256k1PrivateKey::from_bytes(&hex::decode(bytes).unwrap()).unwrap(); + key.zeroize(); + + assert_eq!( + key.0 .0.secret_bytes(), + SecretKeyWrapper::default().0.secret_bytes() + ); + } }