From b6e19cd9ea25788e34012615579938434bb031c0 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:04:24 -0800 Subject: [PATCH 1/6] rename SecretKey to SecretKey32 --- conversations/src/conversation/privatev1.rs | 4 ++-- conversations/src/inbox/handshake.rs | 8 ++++---- conversations/src/inbox/inbox.rs | 4 ++-- crypto/src/keys.rs | 14 +++++++------- crypto/src/x3dh.rs | 10 +++++----- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/conversations/src/conversation/privatev1.rs b/conversations/src/conversation/privatev1.rs index c40c18e..a9a0aed 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/conversation/privatev1.rs @@ -2,7 +2,7 @@ use chat_proto::logoschat::{ convos::private_v1::{PrivateV1Frame, private_v1_frame::FrameType}, encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption}, }; -use crypto::SecretKey; +use crypto::SecretKey32; use prost::{Message, bytes::Bytes}; use crate::{ @@ -15,7 +15,7 @@ use crate::{ pub struct PrivateV1Convo {} impl PrivateV1Convo { - pub fn new(_seed_key: SecretKey) -> Self { + pub fn new(_seed_key: SecretKey32) -> Self { Self {} } diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index bbcb088..ff8c195 100644 --- a/conversations/src/inbox/handshake.rs +++ b/conversations/src/inbox/handshake.rs @@ -2,7 +2,7 @@ use blake2::{ Blake2bMac, digest::{FixedOutput, consts::U32}, }; -use crypto::{DomainSeparator, PrekeyBundle, SecretKey, X3Handshake}; +use crypto::{DomainSeparator, PrekeyBundle, SecretKey32, X3Handshake}; use rand_core::{CryptoRng, RngCore}; use crate::crypto::{PublicKey, StaticSecret}; @@ -24,7 +24,7 @@ impl InboxHandshake { identity_keypair: &StaticSecret, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SecretKey, PublicKey) { + ) -> (SecretKey32, PublicKey) { // Perform X3DH handshake to get shared secret let (shared_secret, ephemeral_public) = InboxKeyExchange::initator(identity_keypair, recipient_bundle, rng); @@ -47,7 +47,7 @@ impl InboxHandshake { onetime_prekey: Option<&StaticSecret>, initiator_identity: &PublicKey, initiator_ephemeral: &PublicKey, - ) -> SecretKey { + ) -> SecretKey32 { // Perform X3DH to get shared secret let shared_secret = InboxKeyExchange::responder( identity_keypair, @@ -61,7 +61,7 @@ impl InboxHandshake { } /// Derive keys from X3DH shared secret - fn derive_keys_from_shared_secret(shared_secret: SecretKey) -> SecretKey { + fn derive_keys_from_shared_secret(shared_secret: SecretKey32) -> SecretKey32 { let seed_key: [u8; 32] = Blake2bMac256::new_with_salt_and_personal( shared_secret.as_bytes(), &[], // No salt - input already has high entropy diff --git a/conversations/src/inbox/inbox.rs b/conversations/src/inbox/inbox.rs index f117a3c..9d7e75f 100644 --- a/conversations/src/inbox/inbox.rs +++ b/conversations/src/inbox/inbox.rs @@ -5,7 +5,7 @@ use rand_core::OsRng; use std::collections::HashMap; use std::rc::Rc; -use crypto::{PrekeyBundle, SecretKey}; +use crypto::{PrekeyBundle, SecretKey32}; use crate::context::Introduction; use crate::conversation::{ChatError, ConversationId, Convo, ConvoFactory, Id, PrivateV1Convo}; @@ -140,7 +140,7 @@ impl Inbox { fn perform_handshake( &self, payload: proto::EncryptedPayload, - ) -> Result<(SecretKey, proto::InboxV1Frame), ChatError> { + ) -> Result<(SecretKey32, proto::InboxV1Frame), ChatError> { let handshake = Self::extract_payload(payload)?; let header = handshake .header diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index 1b78ea7..9cc00eb 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -4,27 +4,27 @@ pub use generic_array::{GenericArray, typenum::U32}; use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)] -pub struct SecretKey([u8; 32]); +pub struct SecretKey32([u8; 32]); -impl SecretKey { +impl SecretKey32 { pub fn as_bytes(&self) -> &[u8] { self.0.as_slice() } } -impl From<[u8; 32]> for SecretKey { +impl From<[u8; 32]> for SecretKey32 { fn from(value: [u8; 32]) -> Self { - SecretKey(value) + SecretKey32(value) } } -impl From> for SecretKey { +impl From> for SecretKey32 { fn from(value: GenericArray) -> Self { - SecretKey(value.into()) + SecretKey32(value.into()) } } -impl Debug for SecretKey { +impl Debug for SecretKey32 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("SecretKey").field(&"<32 bytes>").finish() } diff --git a/crypto/src/x3dh.rs b/crypto/src/x3dh.rs index 7b869d8..a3b911c 100644 --- a/crypto/src/x3dh.rs +++ b/crypto/src/x3dh.rs @@ -5,7 +5,7 @@ use rand_core::{CryptoRng, RngCore}; use sha2::Sha256; use x25519_dalek::{PublicKey, SharedSecret, StaticSecret}; -use crate::keys::SecretKey; +use crate::keys::SecretKey32; /// A prekey bundle containing the public keys needed to initiate an X3DH key exchange. #[derive(Clone, Debug)] @@ -35,7 +35,7 @@ impl X3Handshake { dh2: &SharedSecret, dh3: &SharedSecret, dh4: Option<&SharedSecret>, - ) -> SecretKey { + ) -> SecretKey32 { // Concatenate all DH outputs let mut km = Vec::new(); km.extend_from_slice(dh1.as_bytes()); @@ -52,7 +52,7 @@ impl X3Handshake { hk.expand(Self::domain_separator(), &mut output) .expect("32 bytes is valid HKDF output length"); - // Move into SecretKey so it gets zeroized on drop. + // Move into SecretKey32 so it gets zeroized on drop. output.into() } @@ -69,7 +69,7 @@ impl X3Handshake { identity_keypair: &StaticSecret, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SecretKey, PublicKey) { + ) -> (SecretKey32, PublicKey) { // Generate ephemeral key for this handshake (using StaticSecret for multiple DH operations) let ephemeral_secret = StaticSecret::random_from_rng(rng); let ephemeral_public = PublicKey::from(&ephemeral_secret); @@ -106,7 +106,7 @@ impl X3Handshake { onetime_prekey: Option<&StaticSecret>, initiator_identity: &PublicKey, initiator_ephemeral: &PublicKey, - ) -> SecretKey { + ) -> SecretKey32 { let dh1 = signed_prekey.diffie_hellman(initiator_identity); let dh2 = identity_keypair.diffie_hellman(initiator_ephemeral); let dh3 = signed_prekey.diffie_hellman(initiator_ephemeral); From 649625311fff252df29d54be9b4c49338ca98a0a Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:19:08 -0800 Subject: [PATCH 2/6] Remove uneeded export --- crypto/src/keys.rs | 3 +-- crypto/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index 9cc00eb..95477d0 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -1,6 +1,5 @@ +use generic_array::{GenericArray, typenum::U32}; use std::fmt::Debug; - -pub use generic_array::{GenericArray, typenum::U32}; use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)] diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index c543994..3e0f53c 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -1,5 +1,5 @@ mod keys; mod x3dh; -pub use keys::{GenericArray, SecretKey}; +pub use keys::SecretKey32; pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake}; From c1abc82f283eab0e7a4272a4b6d89a8780ae489d Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:53:31 -0800 Subject: [PATCH 3/6] replace PublicKey with PublicKey32 --- conversations/src/crypto.rs | 6 ++-- conversations/src/identity.rs | 7 +++-- conversations/src/inbox/handshake.rs | 16 +++++------ conversations/src/inbox/inbox.rs | 10 +++---- conversations/src/inbox/introduction.rs | 10 +++---- crypto/src/keys.rs | 37 ++++++++++++++++++++++++- crypto/src/lib.rs | 2 +- crypto/src/x3dh.rs | 32 ++++++++++----------- 8 files changed, 79 insertions(+), 41 deletions(-) diff --git a/conversations/src/crypto.rs b/conversations/src/crypto.rs index ecf0d11..d02976a 100644 --- a/conversations/src/crypto.rs +++ b/conversations/src/crypto.rs @@ -1,13 +1,15 @@ pub use blake2::Digest; use blake2::{Blake2b, digest}; use prost::bytes::Bytes; -pub use x25519_dalek::{PublicKey, StaticSecret}; + +pub use crypto::PublicKey32; +pub use x25519_dalek::StaticSecret; pub trait CopyBytes { fn copy_to_bytes(&self) -> Bytes; } -impl CopyBytes for PublicKey { +impl CopyBytes for PublicKey32 { fn copy_to_bytes(&self) -> Bytes { Bytes::copy_from_slice(self.as_bytes()) } diff --git a/conversations/src/identity.rs b/conversations/src/identity.rs index c5646a7..9cc5f70 100644 --- a/conversations/src/identity.rs +++ b/conversations/src/identity.rs @@ -1,7 +1,8 @@ use blake2::{Blake2b512, Digest}; use std::fmt; -use crate::crypto::{PublicKey, StaticSecret}; +use crate::crypto::{PublicKey32, StaticSecret}; +use x25519_dalek::PublicKey; pub struct Identity { secret: StaticSecret, @@ -27,8 +28,8 @@ impl Identity { hex::encode(Blake2b512::digest(self.public_key())) } - pub fn public_key(&self) -> PublicKey { - PublicKey::from(&self.secret) + pub fn public_key(&self) -> PublicKey32 { + PublicKey::from(&self.secret).into() } pub fn secret(&self) -> &StaticSecret { diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index ff8c195..b94d7e8 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::{PublicKey, StaticSecret}; +use crate::crypto::{PublicKey32, StaticSecret}; type Blake2bMac256 = Blake2bMac; @@ -24,13 +24,13 @@ impl InboxHandshake { identity_keypair: &StaticSecret, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SecretKey32, PublicKey) { + ) -> (SecretKey32, PublicKey32) { // Perform X3DH handshake to get shared secret let (shared_secret, ephemeral_public) = InboxKeyExchange::initator(identity_keypair, recipient_bundle, rng); let seed_key = Self::derive_keys_from_shared_secret(shared_secret); - (seed_key, ephemeral_public) + (seed_key, ephemeral_public.into()) } /// Perform the Inbox Handshake after receiving a keyBundle @@ -45,8 +45,8 @@ impl InboxHandshake { identity_keypair: &StaticSecret, signed_prekey: &StaticSecret, onetime_prekey: Option<&StaticSecret>, - initiator_identity: &PublicKey, - initiator_ephemeral: &PublicKey, + initiator_identity: &PublicKey32, + initiator_ephemeral: &PublicKey32, ) -> SecretKey32 { // Perform X3DH to get shared secret let shared_secret = InboxKeyExchange::responder( @@ -86,16 +86,16 @@ mod tests { // Alice (initiator) generates her identity key let alice_identity = StaticSecret::random_from_rng(&mut rng); - let alice_identity_pub = PublicKey::from(&alice_identity); + 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_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); + let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey); // Create Bob's prekey bundle let bob_bundle = PrekeyBundle { - identity_key: PublicKey::from(&bob_identity), + identity_key: PublicKey32::from(&bob_identity), signed_prekey: bob_signed_prekey_pub, signature: [0u8; 64], onetime_prekey: None, diff --git a/conversations/src/inbox/inbox.rs b/conversations/src/inbox/inbox.rs index 9d7e75f..2a025ac 100644 --- a/conversations/src/inbox/inbox.rs +++ b/conversations/src/inbox/inbox.rs @@ -9,14 +9,14 @@ use crypto::{PrekeyBundle, SecretKey32}; use crate::context::Introduction; use crate::conversation::{ChatError, ConversationId, Convo, ConvoFactory, Id, PrivateV1Convo}; -use crate::crypto::{Blake2b128, CopyBytes, Digest, PublicKey, StaticSecret}; +use crate::crypto::{Blake2b128, CopyBytes, Digest, PublicKey32, StaticSecret}; use crate::identity::Identity; use crate::inbox::handshake::InboxHandshake; use crate::proto; use crate::types::{AddressedEncryptedPayload, ContentData}; /// Compute the deterministic Delivery_address for an installation -fn delivery_address_for_installation(_: PublicKey) -> String { +fn delivery_address_for_installation(_: PublicKey32) -> String { // TODO: Implement Delivery Address "delivery_address".into() } @@ -58,7 +58,7 @@ impl Inbox { pub fn create_bundle(&mut self) -> PrekeyBundle { let ephemeral = StaticSecret::random(); - let signed_prekey = PublicKey::from(&ephemeral); + let signed_prekey = PublicKey32::from(&ephemeral); self.ephemeral_keys .insert(hex::encode(signed_prekey.as_bytes()), ephemeral); @@ -149,12 +149,12 @@ impl Inbox { let ephemeral_key = self.lookup_ephemeral_key(&pubkey_hex)?; - let initator_static = PublicKey::from( + let initator_static = PublicKey32::from( <[u8; 32]>::try_from(header.initiator_static.as_ref()) .map_err(|_| ChatError::BadBundleValue("wrong size - initator static".into()))?, ); - let initator_ephemeral = PublicKey::from( + let initator_ephemeral = PublicKey32::from( <[u8; 32]>::try_from(header.initiator_ephemeral.as_ref()) .map_err(|_| ChatError::BadBundleValue("wrong size - initator ephemeral".into()))?, ); diff --git a/conversations/src/inbox/introduction.rs b/conversations/src/inbox/introduction.rs index 0955e11..8d5ea63 100644 --- a/conversations/src/inbox/introduction.rs +++ b/conversations/src/inbox/introduction.rs @@ -1,12 +1,12 @@ use crypto::PrekeyBundle; -use x25519_dalek::PublicKey; +use crate::crypto::PublicKey32; use crate::errors::ChatError; /// Supplies remote participants with the required keys to use Inbox protocol pub struct Introduction { - pub installation_key: PublicKey, - pub ephemeral_key: PublicKey, + pub installation_key: PublicKey32, + pub ephemeral_key: PublicKey32, } impl From for Introduction { @@ -48,13 +48,13 @@ impl TryFrom<&[u8]> for Introduction { .map_err(|_| ChatError::BadParsing("installation_key"))? .try_into() .map_err(|_| ChatError::InvalidKeyLength)?; - let installation_key = PublicKey::from(installation_bytes); + let installation_key = PublicKey32::from(installation_bytes); let ephemeral_bytes: [u8; 32] = hex::decode(parts[1]) .map_err(|_| ChatError::BadParsing("ephemeral_key"))? .try_into() .map_err(|_| ChatError::InvalidKeyLength)?; - let ephemeral_key = PublicKey::from(ephemeral_bytes); + let ephemeral_key = PublicKey32::from(ephemeral_bytes); Ok(Introduction { installation_key, diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index 95477d0..aebb0b0 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -1,7 +1,42 @@ use generic_array::{GenericArray, typenum::U32}; -use std::fmt::Debug; +use std::{fmt::Debug, ops::Deref}; +use x25519_dalek; use zeroize::{Zeroize, ZeroizeOnDrop}; +#[derive(Debug, Copy, Clone)] +pub struct PublicKey32(x25519_dalek::PublicKey); + +impl From for PublicKey32 { + fn from(value: x25519_dalek::PublicKey) -> Self { + Self(value) + } +} + +impl From<&x25519_dalek::StaticSecret> for PublicKey32 { + fn from(value: &x25519_dalek::StaticSecret) -> Self { + Self(x25519_dalek::PublicKey::from(value)) + } +} + +impl From<[u8; 32]> for PublicKey32 { + fn from(value: [u8; 32]) -> Self { + Self(x25519_dalek::PublicKey::from(value)) + } +} + +impl Deref for PublicKey32 { + type Target = x25519_dalek::PublicKey; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for PublicKey32 { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + #[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)] pub struct SecretKey32([u8; 32]); diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 3e0f53c..8dbdc72 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -1,5 +1,5 @@ mod keys; mod x3dh; -pub use keys::SecretKey32; +pub use keys::{PublicKey32, SecretKey32}; pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake}; diff --git a/crypto/src/x3dh.rs b/crypto/src/x3dh.rs index a3b911c..b91505a 100644 --- a/crypto/src/x3dh.rs +++ b/crypto/src/x3dh.rs @@ -3,17 +3,17 @@ use std::marker::PhantomData; use hkdf::Hkdf; use rand_core::{CryptoRng, RngCore}; use sha2::Sha256; -use x25519_dalek::{PublicKey, SharedSecret, StaticSecret}; +use x25519_dalek::{SharedSecret, StaticSecret}; -use crate::keys::SecretKey32; +use crate::keys::{PublicKey32, SecretKey32}; /// A prekey bundle containing the public keys needed to initiate an X3DH key exchange. #[derive(Clone, Debug)] pub struct PrekeyBundle { - pub identity_key: PublicKey, - pub signed_prekey: PublicKey, + pub identity_key: PublicKey32, + pub signed_prekey: PublicKey32, pub signature: [u8; 64], - pub onetime_prekey: Option, + pub onetime_prekey: Option, } pub trait DomainSeparator { @@ -69,10 +69,10 @@ impl X3Handshake { identity_keypair: &StaticSecret, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SecretKey32, PublicKey) { + ) -> (SecretKey32, PublicKey32) { // Generate ephemeral key for this handshake (using StaticSecret for multiple DH operations) let ephemeral_secret = StaticSecret::random_from_rng(rng); - let ephemeral_public = PublicKey::from(&ephemeral_secret); + let ephemeral_public = PublicKey32::from(&ephemeral_secret); // Perform the 4 Diffie-Hellman operations let dh1 = identity_keypair.diffie_hellman(&recipient_bundle.signed_prekey); @@ -104,8 +104,8 @@ impl X3Handshake { identity_keypair: &StaticSecret, signed_prekey: &StaticSecret, onetime_prekey: Option<&StaticSecret>, - initiator_identity: &PublicKey, - initiator_ephemeral: &PublicKey, + initiator_identity: &PublicKey32, + initiator_ephemeral: &PublicKey32, ) -> SecretKey32 { let dh1 = signed_prekey.diffie_hellman(initiator_identity); let dh2 = identity_keypair.diffie_hellman(initiator_ephemeral); @@ -135,17 +135,17 @@ mod tests { // Alice (initiator) generates her identity key let alice_identity = StaticSecret::random_from_rng(&mut rng); - let alice_identity_pub = PublicKey::from(&alice_identity); + 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_pub = PublicKey::from(&bob_identity); + let bob_identity_pub = PublicKey32::from(&bob_identity); let bob_signed_prekey = StaticSecret::random_from_rng(&mut rng); - let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); + let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey); let bob_onetime_prekey = StaticSecret::random_from_rng(&mut rng); - let bob_onetime_prekey_pub = PublicKey::from(&bob_onetime_prekey); + let bob_onetime_prekey_pub = PublicKey32::from(&bob_onetime_prekey); // Create Bob's prekey bundle (with one-time prekey) let bob_bundle = PrekeyBundle { @@ -178,14 +178,14 @@ mod tests { // Alice (initiator) generates her identity key let alice_identity = StaticSecret::random_from_rng(&mut rng); - let alice_identity_pub = PublicKey::from(&alice_identity); + 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_pub = PublicKey::from(&bob_identity); + let bob_identity_pub = PublicKey32::from(&bob_identity); let bob_signed_prekey = StaticSecret::random_from_rng(&mut rng); - let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); + let bob_signed_prekey_pub = PublicKey32::from(&bob_signed_prekey); // Create Bob's prekey bundle (without one-time prekey) let bob_bundle = PrekeyBundle { 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 4/6] 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) From 143cb380528642448e27cfbb8cd64fc26f90bbf6 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:28:29 -0800 Subject: [PATCH 5/6] Migrate to Crypto::PublicKey32 and PrivateKey32 --- Cargo.lock | 1 + conversations/src/crypto.rs | 1 + crypto/src/keys.rs | 31 +++++++++++++----------- double-ratchets/Cargo.toml | 2 +- double-ratchets/src/ffi/doubleratchet.rs | 4 +-- double-ratchets/src/keypair.rs | 18 +++++++------- double-ratchets/src/state.rs | 18 +++++++------- double-ratchets/src/storage/session.rs | 4 +-- double-ratchets/src/storage/types.rs | 13 +++++++--- 9 files changed, 51 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14ee89a..69ff283 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,6 +218,7 @@ version = "0.0.1" dependencies = [ "blake2", "chacha20poly1305", + "crypto", "hkdf", "rand", "rand_core", diff --git a/conversations/src/crypto.rs b/conversations/src/crypto.rs index 7a85d72..5807c31 100644 --- a/conversations/src/crypto.rs +++ b/conversations/src/crypto.rs @@ -4,6 +4,7 @@ use prost::bytes::Bytes; pub use crypto::{PrivateKey32, PublicKey32}; +// TODO: (P4) Make handing of Keys in Prost easier pub trait CopyBytes { fn copy_to_bytes(&self) -> Bytes; } diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index 748fa2a..dec77d3 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -1,24 +1,15 @@ use generic_array::{GenericArray, typenum::U32}; use rand_core::{CryptoRng, OsRng, RngCore}; -use std::{fmt::Debug, ops::Deref}; +use std::{ + fmt::Debug, + ops::{Deref, DerefMut}, +}; use x25519_dalek; use zeroize::{Zeroize, ZeroizeOnDrop}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct PublicKey32(x25519_dalek::PublicKey); -impl From for PublicKey32 { - fn from(value: x25519_dalek::PublicKey) -> Self { - Self(value) - } -} - -impl From<&x25519_dalek::StaticSecret> for PublicKey32 { - fn from(value: &x25519_dalek::StaticSecret) -> Self { - Self(x25519_dalek::PublicKey::from(value)) - } -} - impl From<&PrivateKey32> for PublicKey32 { fn from(value: &PrivateKey32) -> Self { Self(x25519_dalek::PublicKey::from(&value.0)) @@ -38,6 +29,12 @@ impl Deref for PublicKey32 { } } +impl DerefMut for PublicKey32 { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl AsRef<[u8]> for PublicKey32 { fn as_ref(&self) -> &[u8] { self.0.as_ref() @@ -58,6 +55,12 @@ impl PrivateKey32 { } } +impl From<[u8; 32]> for PrivateKey32 { + fn from(value: [u8; 32]) -> Self { + Self(x25519_dalek::StaticSecret::from(value)) + } +} + impl Deref for PrivateKey32 { type Target = x25519_dalek::StaticSecret; fn deref(&self) -> &Self::Target { diff --git a/double-ratchets/Cargo.toml b/double-ratchets/Cargo.toml index de78550..1e5b0be 100644 --- a/double-ratchets/Cargo.toml +++ b/double-ratchets/Cargo.toml @@ -11,8 +11,8 @@ name = "generate-headers" required-features = ["headers"] [dependencies] -x25519-dalek = { version="2.0.1", features=["static_secrets"] } chacha20poly1305 = "0.10.1" +crypto = { path = "../crypto" } rand_core = "0.6.4" rand = "0.8.5" hkdf = "0.12.4" diff --git a/double-ratchets/src/ffi/doubleratchet.rs b/double-ratchets/src/ffi/doubleratchet.rs index e09cebd..0a7957b 100644 --- a/double-ratchets/src/ffi/doubleratchet.rs +++ b/double-ratchets/src/ffi/doubleratchet.rs @@ -1,5 +1,5 @@ +use crypto::PublicKey32; use safer_ffi::prelude::*; -use x25519_dalek::PublicKey; use crate::{ Header, RatchetState, @@ -22,7 +22,7 @@ fn double_ratchet_init_sender( shared_secret: [u8; 32], remote_pub: [u8; 32], ) -> repr_c::Box { - let state = RatchetState::init_sender(shared_secret, PublicKey::from(remote_pub)); + let state = RatchetState::init_sender(shared_secret, PublicKey32::from(remote_pub)); Box::new(FFIRatchetState(state)).into() } diff --git a/double-ratchets/src/keypair.rs b/double-ratchets/src/keypair.rs index 7943646..4f3a6ac 100644 --- a/double-ratchets/src/keypair.rs +++ b/double-ratchets/src/keypair.rs @@ -1,27 +1,27 @@ +use crypto::{PrivateKey32, PublicKey32}; use rand_core::OsRng; -use x25519_dalek::{PublicKey, StaticSecret}; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::types::SharedSecret; #[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct InstallationKeyPair { - secret: StaticSecret, - public: PublicKey, + secret: PrivateKey32, + public: PublicKey32, } impl InstallationKeyPair { pub fn generate() -> Self { - let secret = StaticSecret::random_from_rng(OsRng); - let public = PublicKey::from(&secret); + let secret = PrivateKey32::random_from_rng(OsRng); + let public = PublicKey32::from(&secret); Self { secret, public } } - pub fn dh(&self, their_public: &PublicKey) -> SharedSecret { + pub fn dh(&self, their_public: &PublicKey32) -> SharedSecret { self.secret.diffie_hellman(their_public).to_bytes() } - pub fn public(&self) -> &PublicKey { + pub fn public(&self) -> &PublicKey32 { &self.public } @@ -32,8 +32,8 @@ impl InstallationKeyPair { /// Import the secret key from raw bytes. pub fn from_secret_bytes(bytes: [u8; 32]) -> Self { - let secret = StaticSecret::from(bytes); - let public = PublicKey::from(&secret); + let secret = PrivateKey32::from(bytes); + let public = PublicKey32::from(&secret); Self { secret, public } } } diff --git a/double-ratchets/src/state.rs b/double-ratchets/src/state.rs index 48ad359..ea1b2a3 100644 --- a/double-ratchets/src/state.rs +++ b/double-ratchets/src/state.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, marker::PhantomData}; +use crypto::PublicKey32; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError}; -use x25519_dalek::PublicKey; use zeroize::{Zeroize, Zeroizing}; use crate::{ @@ -29,13 +29,13 @@ pub struct RatchetState { pub receiving_chain: Option, pub dh_self: InstallationKeyPair, - pub dh_remote: Option, + pub dh_remote: Option, pub msg_send: u32, pub msg_recv: u32, pub prev_chain_len: u32, - pub skipped_keys: HashMap<(PublicKey, u32), MessageKey>, + pub skipped_keys: HashMap<(PublicKey32, u32), MessageKey>, pub(crate) _domain: PhantomData, } @@ -144,7 +144,7 @@ impl RatchetState { let dh_self = InstallationKeyPair::from_secret_bytes(dh_self_bytes); dh_self_bytes.zeroize(); - let dh_remote = reader.read_option()?.map(PublicKey::from); + let dh_remote = reader.read_option()?.map(PublicKey32::from); let msg_send = reader.read_u32()?; let msg_recv = reader.read_u32()?; @@ -153,7 +153,7 @@ impl RatchetState { let skipped_count = reader.read_u32()? as usize; let mut skipped_keys = HashMap::with_capacity(skipped_count); for _ in 0..skipped_count { - let pk = PublicKey::from(reader.read_array::<32>()?); + let pk = PublicKey32::from(reader.read_array::<32>()?); let msg_num = reader.read_u32()?; let mk: MessageKey = reader.read_array()?; skipped_keys.insert((pk, msg_num), mk); @@ -198,7 +198,7 @@ impl<'de, D: HkdfInfo> Deserialize<'de> for RatchetState { /// Public header attached to every encrypted message (unencrypted but authenticated). #[derive(Clone, Debug)] pub struct Header { - pub dh_pub: PublicKey, + pub dh_pub: PublicKey32, pub msg_num: u32, pub prev_chain_len: u32, } @@ -233,7 +233,7 @@ impl RatchetState { /// # Returns /// /// A new `RatchetState` ready to send the first message. - pub fn init_sender(shared_secret: SharedSecret, remote_pub: PublicKey) -> Self { + pub fn init_sender(shared_secret: SharedSecret, remote_pub: PublicKey32) -> Self { let dh_self = InstallationKeyPair::generate(); // Initial DH @@ -296,7 +296,7 @@ impl RatchetState { /// # Arguments /// /// * `remote_pub` - The new DH public key from the sender. - pub fn dh_ratchet_receive(&mut self, remote_pub: PublicKey) { + pub fn dh_ratchet_receive(&mut self, remote_pub: PublicKey32) { let dh_out = self.dh_self.dh(&remote_pub); let (new_root, recv_chain) = kdf_root::(&self.root_key, &dh_out); @@ -605,7 +605,7 @@ mod tests { // Tamper with header (change DH pub byte) let mut tampered_pub_bytes = header.dh_pub.to_bytes(); tampered_pub_bytes[0] ^= 0xff; - header.dh_pub = PublicKey::from(tampered_pub_bytes); + header.dh_pub = PublicKey32::from(tampered_pub_bytes); let result = bob.decrypt_message(&ct, header); assert!(result.is_err()); diff --git a/double-ratchets/src/storage/session.rs b/double-ratchets/src/storage/session.rs index 399af8d..387a41c 100644 --- a/double-ratchets/src/storage/session.rs +++ b/double-ratchets/src/storage/session.rs @@ -1,4 +1,4 @@ -use x25519_dalek::PublicKey; +use crypto::PublicKey32; use crate::{ InstallationKeyPair, @@ -82,7 +82,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> { storage: &'a mut SqliteStorage, conversation_id: impl Into, shared_secret: SharedSecret, - remote_pub: PublicKey, + remote_pub: PublicKey32, ) -> Result { let state = RatchetState::::init_sender(shared_secret, remote_pub); Self::create(storage, conversation_id, state) diff --git a/double-ratchets/src/storage/types.rs b/double-ratchets/src/storage/types.rs index 6a1cd80..8d3f9de 100644 --- a/double-ratchets/src/storage/types.rs +++ b/double-ratchets/src/storage/types.rs @@ -3,8 +3,8 @@ use crate::{ state::{RatchetState, SkippedKey}, types::MessageKey, }; +use crypto::PublicKey32; use thiserror::Error; -use x25519_dalek::PublicKey; #[derive(Debug, Error)] pub enum StorageError { @@ -58,11 +58,16 @@ impl RatchetStateRecord { use std::marker::PhantomData; let dh_self = InstallationKeyPair::from_secret_bytes(self.dh_self_secret); - let dh_remote = self.dh_remote.map(PublicKey::from); + let dh_remote = self.dh_remote.map(PublicKey32::from); - let skipped: HashMap<(PublicKey, u32), MessageKey> = skipped_keys + let skipped: HashMap<(PublicKey32, u32), MessageKey> = skipped_keys .into_iter() - .map(|sk| ((PublicKey::from(sk.public_key), sk.msg_num), sk.message_key)) + .map(|sk| { + ( + (PublicKey32::from(sk.public_key), sk.msg_num), + sk.message_key, + ) + }) .collect(); RatchetState { From 3797eca0bfd87ac5960488507aedc2c48f05e954 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:57:18 -0800 Subject: [PATCH 6/6] Remove InstallationKeyPair --- crypto/src/keys.rs | 5 +++ .../examples/double_ratchet_basic.rs | 7 ++-- double-ratchets/examples/out_of_order_demo.rs | 14 +++---- .../examples/serialization_demo.rs | 6 +-- double-ratchets/examples/storage_demo.rs | 10 ++--- double-ratchets/src/ffi/key.rs | 9 ++-- double-ratchets/src/keypair.rs | 39 ----------------- double-ratchets/src/lib.rs | 2 - double-ratchets/src/state.rs | 42 +++++++++---------- double-ratchets/src/storage/session.rs | 23 +++++----- double-ratchets/src/storage/sqlite.rs | 10 ++--- double-ratchets/src/storage/types.rs | 3 +- double-ratchets/src/types.rs | 5 +++ 13 files changed, 70 insertions(+), 105 deletions(-) delete mode 100644 double-ratchets/src/keypair.rs diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index dec77d3..26e83e8 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -53,6 +53,11 @@ impl PrivateKey32 { pub fn random() -> PrivateKey32 { Self::random_from_rng(&mut OsRng) } + + // Convenience function to generate a PublicKey32 + pub fn public_key(&self) -> PublicKey32 { + PublicKey32::from(self) + } } impl From<[u8; 32]> for PrivateKey32 { diff --git a/double-ratchets/examples/double_ratchet_basic.rs b/double-ratchets/examples/double_ratchet_basic.rs index 297cc10..0533af6 100644 --- a/double-ratchets/examples/double_ratchet_basic.rs +++ b/double-ratchets/examples/double_ratchet_basic.rs @@ -1,13 +1,14 @@ -use double_ratchets::{InstallationKeyPair, RatchetState, hkdf::PrivateV1Domain}; +use crypto::PrivateKey32; +use double_ratchets::{RatchetState, hkdf::PrivateV1Domain}; fn main() { // === Initial shared secret (X3DH / prekey result in real systems) === let shared_secret = [42u8; 32]; - let bob_dh = InstallationKeyPair::generate(); + let bob_dh = PrivateKey32::random(); let mut alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_dh.public().clone()); + RatchetState::init_sender(shared_secret, bob_dh.public_key()); let mut bob: RatchetState = RatchetState::init_receiver(shared_secret, bob_dh); let (ciphertext, header) = alice.encrypt_message(b"Hello Bob!"); diff --git a/double-ratchets/examples/out_of_order_demo.rs b/double-ratchets/examples/out_of_order_demo.rs index a2dbb4d..1d0f35e 100644 --- a/double-ratchets/examples/out_of_order_demo.rs +++ b/double-ratchets/examples/out_of_order_demo.rs @@ -1,11 +1,11 @@ //! Demonstrates out-of-order message handling with skipped keys persistence. //! //! Run with: cargo run --example out_of_order_demo --features storage - +#[allow(unused_imports)] +use crypto::PrivateKey32; #[cfg(feature = "storage")] use double_ratchets::{ - InstallationKeyPair, RatchetState, SqliteStorage, StorageConfig, hkdf::DefaultDomain, - state::Header, + RatchetState, SqliteStorage, StorageConfig, hkdf::DefaultDomain, state::Header, }; fn main() { @@ -22,10 +22,10 @@ fn run_demo() { // Setup let shared_secret = [0x42u8; 32]; - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = PrivateKey32::random(); let alice_state: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); let bob_state: RatchetState = RatchetState::init_receiver(shared_secret, bob_keypair); @@ -81,9 +81,9 @@ fn run_demo() { .expect("Failed to create storage"); // Re-setup - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = PrivateKey32::random(); let alice_state: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); let bob_state: RatchetState = RatchetState::init_receiver(shared_secret, bob_keypair); diff --git a/double-ratchets/examples/serialization_demo.rs b/double-ratchets/examples/serialization_demo.rs index 76a5878..455b116 100644 --- a/double-ratchets/examples/serialization_demo.rs +++ b/double-ratchets/examples/serialization_demo.rs @@ -1,13 +1,13 @@ -use double_ratchets::{InstallationKeyPair, RatchetState, hkdf::PrivateV1Domain}; +use double_ratchets::{RatchetState, hkdf::PrivateV1Domain, types::DhPrivateKey}; fn main() { // === Initial shared secret (X3DH / prekey result in real systems) === let shared_secret = [42u8; 32]; - let bob_dh = InstallationKeyPair::generate(); + let bob_dh = DhPrivateKey::random(); let mut alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_dh.public().clone()); + RatchetState::init_sender(shared_secret, bob_dh.public_key().clone()); let mut bob: RatchetState = RatchetState::init_receiver(shared_secret, bob_dh); let (ciphertext, header) = alice.encrypt_message(b"Hello Bob!"); diff --git a/double-ratchets/examples/storage_demo.rs b/double-ratchets/examples/storage_demo.rs index ce05bd4..de8ed5c 100644 --- a/double-ratchets/examples/storage_demo.rs +++ b/double-ratchets/examples/storage_demo.rs @@ -3,10 +3,10 @@ //! Run with: cargo run --example storage_demo --features storage //! For SQLCipher: cargo run --example storage_demo --features sqlcipher +#[allow(unused_imports)] +use crypto::PrivateKey32; #[cfg(feature = "storage")] -use double_ratchets::{ - InstallationKeyPair, RatchetSession, SqliteStorage, StorageConfig, hkdf::PrivateV1Domain, -}; +use double_ratchets::{RatchetSession, SqliteStorage, StorageConfig, hkdf::PrivateV1Domain}; fn main() { println!("=== Double Ratchet Storage Demo ===\n"); @@ -140,7 +140,7 @@ fn ensure_tmp_directory() { fn run_conversation(alice_storage: &mut SqliteStorage, bob_storage: &mut SqliteStorage) { // === Setup: Simulate X3DH key exchange === let shared_secret = [0x42u8; 32]; // In reality, this comes from X3DH - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = PrivateKey32::random(); let conv_id = "conv1"; @@ -148,7 +148,7 @@ fn run_conversation(alice_storage: &mut SqliteStorage, bob_storage: &mut SqliteS alice_storage, conv_id, shared_secret, - bob_keypair.public().clone(), + bob_keypair.public_key(), ) .unwrap(); diff --git a/double-ratchets/src/ffi/key.rs b/double-ratchets/src/ffi/key.rs index ee8a496..a355e8e 100644 --- a/double-ratchets/src/ffi/key.rs +++ b/double-ratchets/src/ffi/key.rs @@ -1,19 +1,18 @@ +use crypto::PrivateKey32; use safer_ffi::prelude::*; -use crate::InstallationKeyPair; - #[derive_ReprC] #[repr(opaque)] -pub struct FFIInstallationKeyPair(pub(crate) InstallationKeyPair); +pub struct FFIInstallationKeyPair(pub(crate) PrivateKey32); #[ffi_export] fn installation_key_pair_generate() -> repr_c::Box { - Box::new(FFIInstallationKeyPair(InstallationKeyPair::generate())).into() + Box::new(FFIInstallationKeyPair(PrivateKey32::random())).into() } #[ffi_export] fn installation_key_pair_public(keypair: &FFIInstallationKeyPair) -> [u8; 32] { - keypair.0.public().clone().to_bytes() + keypair.0.public_key().to_bytes() } #[ffi_export] diff --git a/double-ratchets/src/keypair.rs b/double-ratchets/src/keypair.rs deleted file mode 100644 index 4f3a6ac..0000000 --- a/double-ratchets/src/keypair.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crypto::{PrivateKey32, PublicKey32}; -use rand_core::OsRng; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -use crate::types::SharedSecret; - -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct InstallationKeyPair { - secret: PrivateKey32, - public: PublicKey32, -} - -impl InstallationKeyPair { - pub fn generate() -> Self { - let secret = PrivateKey32::random_from_rng(OsRng); - let public = PublicKey32::from(&secret); - Self { secret, public } - } - - pub fn dh(&self, their_public: &PublicKey32) -> SharedSecret { - self.secret.diffie_hellman(their_public).to_bytes() - } - - pub fn public(&self) -> &PublicKey32 { - &self.public - } - - /// Export the secret key as raw bytes for serialization/storage. - pub fn secret_bytes(&self) -> &[u8; 32] { - self.secret.as_bytes() - } - - /// Import the secret key from raw bytes. - pub fn from_secret_bytes(bytes: [u8; 32]) -> Self { - let secret = PrivateKey32::from(bytes); - let public = PublicKey32::from(&secret); - Self { secret, public } - } -} diff --git a/double-ratchets/src/lib.rs b/double-ratchets/src/lib.rs index f2cd789..d7c609c 100644 --- a/double-ratchets/src/lib.rs +++ b/double-ratchets/src/lib.rs @@ -2,14 +2,12 @@ pub mod aead; pub mod errors; pub mod ffi; pub mod hkdf; -pub mod keypair; pub mod reader; pub mod state; #[cfg(feature = "storage")] pub mod storage; pub mod types; -pub use keypair::InstallationKeyPair; pub use state::{Header, RatchetState}; #[cfg(feature = "storage")] pub use storage::{RatchetSession, SessionError, SqliteStorage, StorageConfig, StorageError}; diff --git a/double-ratchets/src/state.rs b/double-ratchets/src/state.rs index ea1b2a3..ed9f15b 100644 --- a/double-ratchets/src/state.rs +++ b/double-ratchets/src/state.rs @@ -8,9 +8,8 @@ use crate::{ aead::{decrypt, encrypt}, errors::RatchetError, hkdf::{DefaultDomain, HkdfInfo, kdf_chain, kdf_root}, - keypair::InstallationKeyPair, reader::Reader, - types::{ChainKey, MessageKey, Nonce, RootKey, SharedSecret}, + types::{ChainKey, DhPrivateKey, MessageKey, Nonce, RootKey, SharedSecret}, }; /// Current binary format version. @@ -28,7 +27,7 @@ pub struct RatchetState { pub sending_chain: Option, pub receiving_chain: Option, - pub dh_self: InstallationKeyPair, + pub dh_self: DhPrivateKey, pub dh_remote: Option, pub msg_send: u32, @@ -104,8 +103,7 @@ impl RatchetState { write_option(&mut buf, self.sending_chain); write_option(&mut buf, self.receiving_chain); - let dh_secret = self.dh_self.secret_bytes(); - buf.extend_from_slice(dh_secret); + buf.extend_from_slice(self.dh_self.as_bytes()); write_option(&mut buf, dh_remote); @@ -141,7 +139,7 @@ impl RatchetState { let receiving_chain = reader.read_option()?; let mut dh_self_bytes: [u8; 32] = reader.read_array()?; - let dh_self = InstallationKeyPair::from_secret_bytes(dh_self_bytes); + let dh_self = DhPrivateKey::from(dh_self_bytes); dh_self_bytes.zeroize(); let dh_remote = reader.read_option()?.map(PublicKey32::from); @@ -234,11 +232,11 @@ impl RatchetState { /// /// A new `RatchetState` ready to send the first message. pub fn init_sender(shared_secret: SharedSecret, remote_pub: PublicKey32) -> Self { - let dh_self = InstallationKeyPair::generate(); + let dh_self = DhPrivateKey::random(); // Initial DH - let dh_out = dh_self.dh(&remote_pub); - let (root_key, sending_chain) = kdf_root::(&shared_secret, &dh_out); + let dh_out = dh_self.diffie_hellman(&remote_pub); + let (root_key, sending_chain) = kdf_root::(&shared_secret, dh_out.as_bytes()); Self { root_key, @@ -271,7 +269,7 @@ impl RatchetState { /// # Returns /// /// A new `RatchetState` ready to receive the first message. - pub fn init_receiver(shared_secret: SharedSecret, dh_self: InstallationKeyPair) -> Self { + pub fn init_receiver(shared_secret: SharedSecret, dh_self: DhPrivateKey) -> Self { Self { root_key: shared_secret, @@ -297,8 +295,8 @@ impl RatchetState { /// /// * `remote_pub` - The new DH public key from the sender. pub fn dh_ratchet_receive(&mut self, remote_pub: PublicKey32) { - let dh_out = self.dh_self.dh(&remote_pub); - let (new_root, recv_chain) = kdf_root::(&self.root_key, &dh_out); + let dh_out = self.dh_self.diffie_hellman(&remote_pub); + let (new_root, recv_chain) = kdf_root::(&self.root_key, dh_out.as_bytes()); self.root_key = new_root; self.receiving_chain = Some(recv_chain); @@ -312,9 +310,9 @@ impl RatchetState { pub fn dh_ratchet_send(&mut self) { let remote = self.dh_remote.expect("no remote DH key"); - self.dh_self = InstallationKeyPair::generate(); - let dh_out = self.dh_self.dh(&remote); - let (new_root, send_chain) = kdf_root::(&self.root_key, &dh_out); + self.dh_self = DhPrivateKey::random(); + let dh_out = self.dh_self.diffie_hellman(&remote); + let (new_root, send_chain) = kdf_root::(&self.root_key, dh_out.as_bytes()); self.root_key = new_root; self.sending_chain = Some(send_chain); @@ -345,7 +343,7 @@ impl RatchetState { *chain = next_chain; let header = Header { - dh_pub: self.dh_self.public().clone(), + dh_pub: PublicKey32::from(&self.dh_self), msg_num: self.msg_send, prev_chain_len: self.prev_chain_len, }; @@ -478,10 +476,10 @@ mod tests { let shared_secret = [0x42; 32]; // Bob generates his long-term keypair - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = DhPrivateKey::random(); // Alice initializes as sender, knowing Bob's public key - let alice = RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + let alice = RatchetState::init_sender(shared_secret, bob_keypair.public_key()); // Bob initializes as receiver with his private key let bob = RatchetState::init_receiver(shared_secret, bob_keypair); @@ -554,7 +552,7 @@ mod tests { bob.decrypt_message(&ct, header).unwrap(); // Bob performs DH ratchet by trying to send - let old_bob_pub = bob.dh_self.public().clone(); + let old_bob_pub = bob.dh_self.public_key(); let (bob_ct, bob_header) = { let mut b = bob.clone(); b.encrypt_message(b"reply") @@ -562,7 +560,7 @@ mod tests { assert_ne!(bob_header.dh_pub, old_bob_pub); // Alice receives Bob's message with new DH pub → ratchets - let old_alice_pub = alice.dh_self.public().clone(); + let old_alice_pub = alice.dh_self.public_key(); let old_root = alice.root_key; // Even if decrypt fails (wrong key), ratchet should happen @@ -688,8 +686,8 @@ mod tests { restored.dh_remote.map(|pk| pk.to_bytes()) ); assert_eq!( - alice.dh_self.public().to_bytes(), - restored.dh_self.public().to_bytes() + alice.dh_self.public_key().to_bytes(), + restored.dh_self.public_key().to_bytes() ); } diff --git a/double-ratchets/src/storage/session.rs b/double-ratchets/src/storage/session.rs index 387a41c..36f16cb 100644 --- a/double-ratchets/src/storage/session.rs +++ b/double-ratchets/src/storage/session.rs @@ -1,11 +1,10 @@ use crypto::PublicKey32; use crate::{ - InstallationKeyPair, errors::RatchetError, hkdf::HkdfInfo, state::{Header, RatchetState}, - types::SharedSecret, + types::{DhPrivateKey, SharedSecret}, }; use super::{SqliteStorage, StorageError}; @@ -93,7 +92,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> { storage: &'a mut SqliteStorage, conversation_id: impl Into, shared_secret: SharedSecret, - dh_self: InstallationKeyPair, + dh_self: DhPrivateKey, ) -> Result { let conversation_id = conversation_id.into(); if storage.exists(&conversation_id)? { @@ -180,7 +179,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> { #[cfg(test)] mod tests { use super::*; - use crate::{hkdf::DefaultDomain, keypair::InstallationKeyPair, storage::StorageConfig}; + use crate::{hkdf::DefaultDomain, storage::StorageConfig, types::DhPrivateKey}; fn create_test_storage() -> SqliteStorage { SqliteStorage::new(StorageConfig::InMemory).unwrap() @@ -191,9 +190,9 @@ mod tests { let mut storage = create_test_storage(); let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = DhPrivateKey::random(); let alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); // Create session { @@ -214,9 +213,9 @@ mod tests { let mut storage = create_test_storage(); let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = DhPrivateKey::random(); let alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); // Create and encrypt { @@ -238,9 +237,9 @@ mod tests { let mut storage = create_test_storage(); let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = DhPrivateKey::random(); let alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); let bob: RatchetState = RatchetState::init_receiver(shared_secret, bob_keypair); @@ -278,8 +277,8 @@ mod tests { let mut storage = create_test_storage(); let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); - let bob_pub = bob_keypair.public().clone(); + let bob_keypair = DhPrivateKey::random(); + let bob_pub = bob_keypair.public_key(); // First call creates { diff --git a/double-ratchets/src/storage/sqlite.rs b/double-ratchets/src/storage/sqlite.rs index 2c061f8..f2e9ba1 100644 --- a/double-ratchets/src/storage/sqlite.rs +++ b/double-ratchets/src/storage/sqlite.rs @@ -280,7 +280,7 @@ fn blob_to_array(blob: Vec) -> [u8; N] { #[cfg(test)] mod tests { use super::*; - use crate::{hkdf::DefaultDomain, keypair::InstallationKeyPair}; + use crate::hkdf::DefaultDomain; fn create_test_storage() -> SqliteStorage { SqliteStorage::new(StorageConfig::InMemory).unwrap() @@ -288,8 +288,8 @@ mod tests { fn create_test_state() -> (RatchetState, RatchetState) { let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); - let alice = RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + let bob_keypair = DhPrivateKey::random(); + let alice = RatchetState::init_sender(shared_secret, bob_keypair.public_key()); let bob = RatchetState::init_receiver(shared_secret, bob_keypair); (alice, bob) } @@ -309,8 +309,8 @@ mod tests { assert_eq!(alice.msg_recv, loaded.msg_recv); assert_eq!(alice.prev_chain_len, loaded.prev_chain_len); assert_eq!( - alice.dh_self.public().to_bytes(), - loaded.dh_self.public().to_bytes() + alice.dh_self.public_key().as_bytes(), + loaded.dh_self.public_key().as_bytes() ); } diff --git a/double-ratchets/src/storage/types.rs b/double-ratchets/src/storage/types.rs index 8d3f9de..9a60416 100644 --- a/double-ratchets/src/storage/types.rs +++ b/double-ratchets/src/storage/types.rs @@ -53,11 +53,10 @@ impl From<&RatchetState> for RatchetStateRecord { impl RatchetStateRecord { pub fn into_ratchet_state(self, skipped_keys: Vec) -> RatchetState { - use crate::keypair::InstallationKeyPair; use std::collections::HashMap; use std::marker::PhantomData; - let dh_self = InstallationKeyPair::from_secret_bytes(self.dh_self_secret); + let dh_self = DhPrivateKey::from(self.dh_self_secret); let dh_remote = self.dh_remote.map(PublicKey32::from); let skipped: HashMap<(PublicKey32, u32), MessageKey> = skipped_keys diff --git a/double-ratchets/src/types.rs b/double-ratchets/src/types.rs index bbad3a8..db0edb1 100644 --- a/double-ratchets/src/types.rs +++ b/double-ratchets/src/types.rs @@ -1,3 +1,8 @@ +use crypto::PrivateKey32; + +/// Type alias for diffie-hellman private keys. +pub type DhPrivateKey = PrivateKey32; + /// Type alias for root keys (32 bytes). pub type RootKey = [u8; 32]; /// Type alias for chain keys (sending/receiving, 32 bytes).