From 5adee47a9772bb31a463e937965678c697fcbe3d Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:40:45 -0800 Subject: [PATCH] replace StaticSecret with PrivateKey32 --- Cargo.lock | 1 + conversations/Cargo.toml | 1 + conversations/src/crypto.rs | 3 +-- conversations/src/identity.rs | 11 +++++----- conversations/src/inbox/handshake.rs | 16 +++++++-------- conversations/src/inbox/inbox.rs | 10 +++++----- crypto/src/keys.rs | 28 ++++++++++++++++++++++++++ crypto/src/lib.rs | 2 +- crypto/src/x3dh.rs | 30 ++++++++++++++-------------- 9 files changed, 65 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9850d5..14ee89a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -473,6 +473,7 @@ dependencies = [ "crypto", "hex", "prost", + "rand", "rand_core", "safer-ffi", "thiserror", diff --git a/conversations/Cargo.toml b/conversations/Cargo.toml index 0fd90ed..46f4b1f 100644 --- a/conversations/Cargo.toml +++ b/conversations/Cargo.toml @@ -13,6 +13,7 @@ crypto = { path = "../crypto" } hex = "0.4.3" prost = "0.14.1" rand_core = { version = "0.6" } +rand = "0.8.5" safer-ffi = "0.1.13" thiserror = "2.0.17" x25519-dalek = { version = "2.0.1", features = ["static_secrets", "reusable_secrets", "getrandom"] } diff --git a/conversations/src/crypto.rs b/conversations/src/crypto.rs index d02976a..7a85d72 100644 --- a/conversations/src/crypto.rs +++ b/conversations/src/crypto.rs @@ -2,8 +2,7 @@ pub use blake2::Digest; use blake2::{Blake2b, digest}; use prost::bytes::Bytes; -pub use crypto::PublicKey32; -pub use x25519_dalek::StaticSecret; +pub use crypto::{PrivateKey32, PublicKey32}; pub trait CopyBytes { fn copy_to_bytes(&self) -> Bytes; diff --git a/conversations/src/identity.rs b/conversations/src/identity.rs index 9cc5f70..8ee5e1e 100644 --- a/conversations/src/identity.rs +++ b/conversations/src/identity.rs @@ -1,11 +1,10 @@ use blake2::{Blake2b512, Digest}; use std::fmt; -use crate::crypto::{PublicKey32, StaticSecret}; -use x25519_dalek::PublicKey; +use crate::crypto::{PrivateKey32, PublicKey32}; pub struct Identity { - secret: StaticSecret, + secret: PrivateKey32, } impl fmt::Debug for Identity { @@ -20,7 +19,7 @@ impl fmt::Debug for Identity { impl Identity { pub fn new() -> Self { Self { - secret: StaticSecret::random(), + secret: PrivateKey32::random(), } } @@ -29,10 +28,10 @@ impl Identity { } pub fn public_key(&self) -> PublicKey32 { - PublicKey::from(&self.secret).into() + PublicKey32::from(&self.secret) } - pub fn secret(&self) -> &StaticSecret { + pub fn secret(&self) -> &PrivateKey32 { &self.secret } } diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index b94d7e8..8fe1bfe 100644 --- a/conversations/src/inbox/handshake.rs +++ b/conversations/src/inbox/handshake.rs @@ -5,7 +5,7 @@ use blake2::{ use crypto::{DomainSeparator, PrekeyBundle, SecretKey32, X3Handshake}; use rand_core::{CryptoRng, RngCore}; -use crate::crypto::{PublicKey32, StaticSecret}; +use crate::crypto::{PrivateKey32, PublicKey32}; type Blake2bMac256 = Blake2bMac; @@ -21,7 +21,7 @@ pub struct InboxHandshake {} impl InboxHandshake { /// Performs pub fn perform_as_initiator( - identity_keypair: &StaticSecret, + identity_keypair: &PrivateKey32, recipient_bundle: &PrekeyBundle, rng: &mut R, ) -> (SecretKey32, PublicKey32) { @@ -42,9 +42,9 @@ impl InboxHandshake { /// * `initiator_identity` - Initiator's identity public key /// * `initiator_ephemeral` - Initiator's ephemeral public key pub fn perform_as_responder( - identity_keypair: &StaticSecret, - signed_prekey: &StaticSecret, - onetime_prekey: Option<&StaticSecret>, + identity_keypair: &PrivateKey32, + signed_prekey: &PrivateKey32, + onetime_prekey: Option<&PrivateKey32>, initiator_identity: &PublicKey32, initiator_ephemeral: &PublicKey32, ) -> SecretKey32 { @@ -85,12 +85,12 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = StaticSecret::random_from_rng(&mut rng); + let alice_identity = PrivateKey32::random_from_rng(&mut rng); let alice_identity_pub = PublicKey32::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = StaticSecret::random_from_rng(&mut rng); - let bob_signed_prekey = StaticSecret::random_from_rng(&mut rng); + let bob_identity = PrivateKey32::random_from_rng(&mut rng); + let bob_signed_prekey = PrivateKey32::random_from_rng(&mut rng); let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey); // Create Bob's prekey bundle diff --git a/conversations/src/inbox/inbox.rs b/conversations/src/inbox/inbox.rs index 2a025ac..b4c4552 100644 --- a/conversations/src/inbox/inbox.rs +++ b/conversations/src/inbox/inbox.rs @@ -9,7 +9,7 @@ use crypto::{PrekeyBundle, SecretKey32}; use crate::context::Introduction; use crate::conversation::{ChatError, ConversationId, Convo, ConvoFactory, Id, PrivateV1Convo}; -use crate::crypto::{Blake2b128, CopyBytes, Digest, PublicKey32, StaticSecret}; +use crate::crypto::{Blake2b128, CopyBytes, Digest, PrivateKey32, PublicKey32}; use crate::identity::Identity; use crate::inbox::handshake::InboxHandshake; use crate::proto; @@ -24,7 +24,7 @@ fn delivery_address_for_installation(_: PublicKey32) -> String { pub struct Inbox { ident: Rc, local_convo_id: String, - ephemeral_keys: HashMap, + ephemeral_keys: HashMap, } impl<'a> std::fmt::Debug for Inbox { @@ -46,7 +46,7 @@ impl Inbox { Self { ident, local_convo_id, - ephemeral_keys: HashMap::::new(), + ephemeral_keys: HashMap::::new(), } } @@ -56,7 +56,7 @@ impl Inbox { } pub fn create_bundle(&mut self) -> PrekeyBundle { - let ephemeral = StaticSecret::random(); + let ephemeral = PrivateKey32::random_from_rng(&mut OsRng); let signed_prekey = PublicKey32::from(&ephemeral); self.ephemeral_keys @@ -193,7 +193,7 @@ impl Inbox { Ok(frame) } - fn lookup_ephemeral_key(&self, key: &str) -> Result<&StaticSecret, ChatError> { + fn lookup_ephemeral_key(&self, key: &str) -> Result<&PrivateKey32, ChatError> { self.ephemeral_keys .get(key) .ok_or_else(|| return ChatError::UnknownEphemeralKey()) diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index aebb0b0..748fa2a 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -1,4 +1,5 @@ use generic_array::{GenericArray, typenum::U32}; +use rand_core::{CryptoRng, OsRng, RngCore}; use std::{fmt::Debug, ops::Deref}; use x25519_dalek; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -18,6 +19,12 @@ impl From<&x25519_dalek::StaticSecret> for PublicKey32 { } } +impl From<&PrivateKey32> for PublicKey32 { + fn from(value: &PrivateKey32) -> Self { + Self(x25519_dalek::PublicKey::from(&value.0)) + } +} + impl From<[u8; 32]> for PublicKey32 { fn from(value: [u8; 32]) -> Self { Self(x25519_dalek::PublicKey::from(value)) @@ -37,6 +44,27 @@ impl AsRef<[u8]> for PublicKey32 { } } +#[derive(Clone, Zeroize, ZeroizeOnDrop)] +pub struct PrivateKey32(x25519_dalek::StaticSecret); + +impl PrivateKey32 { + pub fn random_from_rng(mut csprng: T) -> Self { + Self(x25519_dalek::StaticSecret::random_from_rng(csprng)) + } + + //TODO: Remove. Force internal callers provide Rng to make deterministic testing possible + pub fn random() -> PrivateKey32 { + Self::random_from_rng(&mut OsRng) + } +} + +impl Deref for PrivateKey32 { + type Target = x25519_dalek::StaticSecret; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)] pub struct SecretKey32([u8; 32]); diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 8dbdc72..f1f74e9 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -1,5 +1,5 @@ mod keys; mod x3dh; -pub use keys::{PublicKey32, SecretKey32}; +pub use keys::{PrivateKey32, PublicKey32, SecretKey32}; pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake}; diff --git a/crypto/src/x3dh.rs b/crypto/src/x3dh.rs index b91505a..be8e8a7 100644 --- a/crypto/src/x3dh.rs +++ b/crypto/src/x3dh.rs @@ -3,9 +3,9 @@ use std::marker::PhantomData; use hkdf::Hkdf; use rand_core::{CryptoRng, RngCore}; use sha2::Sha256; -use x25519_dalek::{SharedSecret, StaticSecret}; +use x25519_dalek::SharedSecret; -use crate::keys::{PublicKey32, SecretKey32}; +use crate::keys::{PrivateKey32, PublicKey32, SecretKey32}; /// A prekey bundle containing the public keys needed to initiate an X3DH key exchange. #[derive(Clone, Debug)] @@ -66,12 +66,12 @@ impl X3Handshake { /// # Returns /// A tuple of (shared secret bytes, ephemeral public key) pub fn initator( - identity_keypair: &StaticSecret, + identity_keypair: &PrivateKey32, recipient_bundle: &PrekeyBundle, rng: &mut R, ) -> (SecretKey32, PublicKey32) { - // Generate ephemeral key for this handshake (using StaticSecret for multiple DH operations) - let ephemeral_secret = StaticSecret::random_from_rng(rng); + // Generate ephemeral key for this handshake + let ephemeral_secret = PrivateKey32::random_from_rng(rng); let ephemeral_public = PublicKey32::from(&ephemeral_secret); // Perform the 4 Diffie-Hellman operations @@ -101,9 +101,9 @@ impl X3Handshake { /// # Returns /// The derived shared secret bytes pub fn responder( - identity_keypair: &StaticSecret, - signed_prekey: &StaticSecret, - onetime_prekey: Option<&StaticSecret>, + identity_keypair: &PrivateKey32, + signed_prekey: &PrivateKey32, + onetime_prekey: Option<&PrivateKey32>, initiator_identity: &PublicKey32, initiator_ephemeral: &PublicKey32, ) -> SecretKey32 { @@ -134,17 +134,17 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = StaticSecret::random_from_rng(&mut rng); + let alice_identity = PrivateKey32::random_from_rng(&mut rng); let alice_identity_pub = PublicKey32::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = StaticSecret::random_from_rng(&mut rng); + let bob_identity = PrivateKey32::random_from_rng(&mut rng); let bob_identity_pub = PublicKey32::from(&bob_identity); - let bob_signed_prekey = StaticSecret::random_from_rng(&mut rng); + let bob_signed_prekey = PrivateKey32::random_from_rng(&mut rng); let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey); - let bob_onetime_prekey = StaticSecret::random_from_rng(&mut rng); + let bob_onetime_prekey = PrivateKey32::random_from_rng(&mut rng); let bob_onetime_prekey_pub = PublicKey32::from(&bob_onetime_prekey); // Create Bob's prekey bundle (with one-time prekey) @@ -177,14 +177,14 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = StaticSecret::random_from_rng(&mut rng); + let alice_identity = PrivateKey32::random_from_rng(&mut rng); let alice_identity_pub = PublicKey32::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = StaticSecret::random_from_rng(&mut rng); + let bob_identity = PrivateKey32::random_from_rng(&mut rng); let bob_identity_pub = PublicKey32::from(&bob_identity); - let bob_signed_prekey = StaticSecret::random_from_rng(&mut rng); + let bob_signed_prekey = PrivateKey32::random_from_rng(&mut rng); let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey); // Create Bob's prekey bundle (without one-time prekey)