From 14535369fd8492fa30e42c5ed8224088d8452783 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:06:04 -0800 Subject: [PATCH 1/9] Add generic SymmetricKey container --- conversations/src/conversation/privatev1.rs | 13 +++--- crypto/src/keys.rs | 44 +++++++++++++++------ 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/conversations/src/conversation/privatev1.rs b/conversations/src/conversation/privatev1.rs index e48c27c..1182ed9 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/conversation/privatev1.rs @@ -39,7 +39,7 @@ struct BaseConvoId([u8; 18]); impl BaseConvoId { fn new(key: &SecretKey) -> Self { - let base = Blake2bMac::::new_with_salt_and_personal(key.as_slice(), b"", b"L-PV1-CID") + let base = Blake2bMac::::new_with_salt_and_personal(key.as_bytes(), b"", b"L-PV1-CID") .expect("fixed inputs should never fail"); Self(base.finalize_fixed().into()) } @@ -87,7 +87,7 @@ impl PrivateV1Convo { let remote_convo_id = base_convo_id.id_for_participant(Role::Initiator); // TODO: Danger - Fix double-ratchets types to Accept SecretKey - let dr_state = RatchetState::init_receiver(seed_key.as_bytes().to_owned(), dh_self); + let dr_state = RatchetState::init_receiver(seed_key.DANGER_to_bytes(), dh_self); Self { local_convo_id, @@ -234,14 +234,11 @@ mod tests { let seed_key = saro.diffie_hellman(&pub_raya); let send_content_bytes = vec![0, 2, 4, 6, 8]; - let mut sr_convo = - PrivateV1Convo::new_initiator(SecretKey::from(seed_key.to_bytes()), pub_raya); + let mut sr_convo = PrivateV1Convo::new_initiator(SymmetricKey32::from(&seed_key), pub_raya); let installation_key_pair = InstallationKeyPair::from(raya); - let mut rs_convo = PrivateV1Convo::new_responder( - SecretKey::from(seed_key.to_bytes()), - installation_key_pair, - ); + let mut rs_convo = + PrivateV1Convo::new_responder(SymmetricKey32::from(&seed_key), installation_key_pair); let send_frame = PrivateV1Frame { conversation_id: "_".into(), diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index 4eeccb1..6751f37 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -1,35 +1,53 @@ use std::fmt::Debug; pub use generic_array::{GenericArray, typenum::U32}; +use x25519_dalek::SharedSecret; use zeroize::{Zeroize, ZeroizeOnDrop}; +/// A Generic secret key container for symmetric keys. +/// SymmetricKey retains ownership of bytes to ensure they are Zeroized on drop. #[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)] -pub struct SecretKey([u8; 32]); +pub struct SymmetricKey([u8; N]); -impl SecretKey { - pub fn as_slice(&self) -> &[u8] { +impl SymmetricKey { + pub fn as_bytes(&self) -> &[u8] { self.0.as_slice() } - pub fn as_bytes(&self) -> &[u8; 32] { - &self.0 + /// Returns internal [u8; N]. + /// This function by passes zeroize_on_drop, and will be deprecated once all consumers have been migrated + #[allow(nonstandard_style)] + pub fn DANGER_to_bytes(self) -> [u8; N] { + // TODO: (P3) Remove once DR ported to use safe keys. + self.0 } } -impl From<[u8; 32]> for SecretKey { - fn from(value: [u8; 32]) -> Self { - SecretKey(value) +impl From<[u8; N]> for SymmetricKey { + fn from(value: [u8; N]) -> Self { + SymmetricKey(value) } } -impl From> for SecretKey { +impl Debug for SymmetricKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "SymmetricKey(...{N} Bytes Redacted...)") + } +} + +// TODO: (P5) look into typenum::generic_const_mappings to avoid having to implment From +pub type SymmetricKey32 = SymmetricKey<32>; + +impl From> for SymmetricKey32 { fn from(value: GenericArray) -> Self { - SecretKey(value.into()) + SymmetricKey(value.into()) } } -impl Debug for SecretKey { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("SecretKey").field(&"<32 bytes>").finish() +impl From<&SharedSecret> for SymmetricKey32 { + // This relies on the feature 'zeroize' being set for x25519-dalek. + // If not the SharedSecret will need to manually zeroized + fn from(value: &SharedSecret) -> Self { + value.to_bytes().into() } } From 7abd7f1f801fb378103cbbd5135975b3bb712cb6 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:07:40 -0800 Subject: [PATCH 2/9] Rename SecretKey to SymmetricKey32 --- conversations/src/conversation/privatev1.rs | 12 ++++++------ conversations/src/inbox/handler.rs | 4 ++-- conversations/src/inbox/handshake.rs | 8 ++++---- crypto/src/lib.rs | 2 +- crypto/src/x3dh.rs | 10 +++++----- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/conversations/src/conversation/privatev1.rs b/conversations/src/conversation/privatev1.rs index 1182ed9..b67661f 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/conversation/privatev1.rs @@ -6,7 +6,7 @@ use chat_proto::logoschat::{ convos::private_v1::{PrivateV1Frame, private_v1_frame::FrameType}, encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption}, }; -use crypto::SecretKey; +use crypto::SymmetricKey32; use double_ratchets::{Header, InstallationKeyPair, RatchetState}; use prost::{Message, bytes::Bytes}; use std::fmt::Debug; @@ -38,7 +38,7 @@ impl Role { struct BaseConvoId([u8; 18]); impl BaseConvoId { - fn new(key: &SecretKey) -> Self { + fn new(key: &SymmetricKey32) -> Self { let base = Blake2bMac::::new_with_salt_and_personal(key.as_bytes(), b"", b"L-PV1-CID") .expect("fixed inputs should never fail"); Self(base.finalize_fixed().into()) @@ -60,12 +60,12 @@ pub struct PrivateV1Convo { } impl PrivateV1Convo { - pub fn new_initiator(seed_key: SecretKey, remote: PublicKey) -> Self { + pub fn new_initiator(seed_key: SymmetricKey32, remote: PublicKey) -> Self { let base_convo_id = BaseConvoId::new(&seed_key); let local_convo_id = base_convo_id.id_for_participant(Role::Initiator); let remote_convo_id = base_convo_id.id_for_participant(Role::Responder); - // TODO: Danger - Fix double-ratchets types to Accept SecretKey + // TODO: Danger - Fix double-ratchets types to Accept SymmetricKey32 // perhaps update the DH to work with cryptocrate. // init_sender doesn't take ownership of the key so a reference can be used. let shared_secret: [u8; 32] = seed_key.as_bytes().to_vec().try_into().unwrap(); @@ -79,14 +79,14 @@ impl PrivateV1Convo { } pub fn new_responder( - seed_key: SecretKey, + seed_key: SymmetricKey32, dh_self: InstallationKeyPair, // TODO: (P3) Rename; This accepts a Ephemeral key in most cases ) -> Self { let base_convo_id = BaseConvoId::new(&seed_key); let local_convo_id = base_convo_id.id_for_participant(Role::Responder); let remote_convo_id = base_convo_id.id_for_participant(Role::Initiator); - // TODO: Danger - Fix double-ratchets types to Accept SecretKey + // TODO: Danger - Fix double-ratchets types to Accept SymmetricKey32 let dr_state = RatchetState::init_receiver(seed_key.DANGER_to_bytes(), dh_self); Self { diff --git a/conversations/src/inbox/handler.rs b/conversations/src/inbox/handler.rs index a2fc472..c438c23 100644 --- a/conversations/src/inbox/handler.rs +++ b/conversations/src/inbox/handler.rs @@ -6,7 +6,7 @@ use rand_core::OsRng; use std::collections::HashMap; use std::rc::Rc; -use crypto::{PrekeyBundle, SecretKey}; +use crypto::{PrekeyBundle, SymmetricKey32}; use crate::context::Introduction; use crate::conversation::{ChatError, ConversationId, Convo, Id, PrivateV1Convo}; @@ -172,7 +172,7 @@ impl Inbox { ephemeral_key: &StaticSecret, header: proto::InboxHeaderV1, bytes: Bytes, - ) -> Result<(SecretKey, proto::InboxV1Frame), ChatError> { + ) -> Result<(SymmetricKey32, proto::InboxV1Frame), ChatError> { // Get PublicKeys from protobuf let initator_static = PublicKey::from( <[u8; 32]>::try_from(header.initiator_static.as_ref()) diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index eda7d00..5d8d0f3 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, SymmetricKey32, 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) { + ) -> (SymmetricKey32, 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 { + ) -> SymmetricKey32 { // 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: SymmetricKey32) -> SymmetricKey32 { let seed_key: [u8; 32] = Blake2bMac256::new_with_salt_and_personal( shared_secret.as_slice(), &[], // No salt - input already has high entropy diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 3b8776b..d44231f 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -2,6 +2,6 @@ mod keys; mod x3dh; mod xeddsa_sign; -pub use keys::{GenericArray, SecretKey}; +pub use keys::{GenericArray, SymmetricKey32}; pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake}; pub use xeddsa_sign::{Ed25519Signature, SignatureError, xeddsa_sign, xeddsa_verify}; diff --git a/crypto/src/x3dh.rs b/crypto/src/x3dh.rs index b71c498..d942fbd 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::SymmetricKey32; use crate::xeddsa_sign::Ed25519Signature; /// A prekey bundle containing the public keys needed to initiate an X3DH key exchange. @@ -36,7 +36,7 @@ impl X3Handshake { dh2: &SharedSecret, dh3: &SharedSecret, dh4: Option<&SharedSecret>, - ) -> SecretKey { + ) -> SymmetricKey32 { // Concatenate all DH outputs let mut km = Vec::new(); km.extend_from_slice(dh1.as_bytes()); @@ -53,7 +53,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 SymmetricKey32 so it gets zeroized on drop. output.into() } @@ -70,7 +70,7 @@ impl X3Handshake { identity_keypair: &StaticSecret, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SecretKey, PublicKey) { + ) -> (SymmetricKey32, 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); @@ -107,7 +107,7 @@ impl X3Handshake { onetime_prekey: Option<&StaticSecret>, initiator_identity: &PublicKey, initiator_ephemeral: &PublicKey, - ) -> SecretKey { + ) -> SymmetricKey32 { 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 da8fcf8bd6b01053e6ef12588bba87a81827e0a8 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:08:07 -0800 Subject: [PATCH 3/9] Update SymmetricKey usage --- conversations/src/conversation/privatev1.rs | 2 +- conversations/src/inbox/handshake.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conversations/src/conversation/privatev1.rs b/conversations/src/conversation/privatev1.rs index b67661f..2711436 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/conversation/privatev1.rs @@ -68,7 +68,7 @@ impl PrivateV1Convo { // TODO: Danger - Fix double-ratchets types to Accept SymmetricKey32 // perhaps update the DH to work with cryptocrate. // init_sender doesn't take ownership of the key so a reference can be used. - let shared_secret: [u8; 32] = seed_key.as_bytes().to_vec().try_into().unwrap(); + let shared_secret: [u8; 32] = seed_key.DANGER_to_bytes(); let dr_state = RatchetState::init_sender(shared_secret, remote); Self { diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index 5d8d0f3..8da581a 100644 --- a/conversations/src/inbox/handshake.rs +++ b/conversations/src/inbox/handshake.rs @@ -63,7 +63,7 @@ impl InboxHandshake { /// Derive keys from X3DH shared secret fn derive_keys_from_shared_secret(shared_secret: SymmetricKey32) -> SymmetricKey32 { let seed_key: [u8; 32] = Blake2bMac256::new_with_salt_and_personal( - shared_secret.as_slice(), + shared_secret.as_bytes(), &[], // No salt - input already has high entropy b"InboxV1-Seed", ) From 28d74b740463eb2aa03c01f4eb9f4cc58bbb3bbf Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:02:26 -0800 Subject: [PATCH 4/9] Add X25519PublicKey --- crypto/src/keys.rs | 48 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index 6751f37..d95637d 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -1,9 +1,51 @@ -use std::fmt::Debug; - pub use generic_array::{GenericArray, typenum::U32}; -use x25519_dalek::SharedSecret; + +use rand_core::{CryptoRng, OsRng, RngCore}; +use std::{fmt::Debug, ops::Deref}; +use x25519_dalek::{PublicKey, SharedSecret, StaticSecret}; +use xeddsa::xed25519; use zeroize::{Zeroize, ZeroizeOnDrop}; +#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, Zeroize)] // TODO: (!) Zeroize only required by InstallationKeyPair +pub struct X25519PublicKey(x25519_dalek::PublicKey); + +impl From for X25519PublicKey { + fn from(value: x25519_dalek::PublicKey) -> Self { + Self(value) + } +} + +impl From<&StaticSecret> for X25519PublicKey { + fn from(value: &StaticSecret) -> Self { + Self(x25519_dalek::PublicKey::from(value)) + } +} + +impl From<[u8; 32]> for X25519PublicKey { + fn from(value: [u8; 32]) -> Self { + Self(x25519_dalek::PublicKey::from(value)) + } +} + +impl Deref for X25519PublicKey { + type Target = PublicKey; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef<[u8]> for X25519PublicKey { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl From<&X25519PublicKey> for xed25519::PublicKey { + fn from(value: &X25519PublicKey) -> Self { + Self::from(&value.0) + } +} + /// A Generic secret key container for symmetric keys. /// SymmetricKey retains ownership of bytes to ensure they are Zeroized on drop. #[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)] From 5121ba4eed29e38e7617bf95fc4da399a04d2882 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:02:49 -0800 Subject: [PATCH 5/9] Update PublicKey -> X25519PublicKey --- Cargo.lock | 1 + Cargo.toml | 3 ++ conversations/src/conversation/privatev1.rs | 10 +++---- conversations/src/crypto.rs | 6 ++-- conversations/src/identity.rs | 6 ++-- conversations/src/inbox/handler.rs | 14 ++++----- conversations/src/inbox/handshake.rs | 14 ++++----- conversations/src/inbox/introduction.rs | 28 +++++++++--------- crypto/src/lib.rs | 2 +- crypto/src/x3dh.rs | 32 ++++++++++----------- crypto/src/xeddsa_sign.rs | 14 +++++---- double-ratchets/Cargo.toml | 1 + double-ratchets/src/ffi/doubleratchet.rs | 4 +-- double-ratchets/src/keypair.rs | 15 +++++----- double-ratchets/src/state.rs | 18 ++++++------ double-ratchets/src/storage/session.rs | 4 +-- double-ratchets/src/storage/types.rs | 13 ++++++--- 17 files changed, 100 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23e3f38..cf2f06e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,7 @@ version = "0.0.1" dependencies = [ "blake2", "chacha20poly1305", + "crypto", "hkdf", "rand", "rand_core", diff --git a/Cargo.toml b/Cargo.toml index 0381c7d..ccc6be4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,6 @@ members = [ [workspace.dependencies] blake2 = "0.10" storage = { path = "storage" } + +# Internal crates +crypto = { path = "crypto" } diff --git a/conversations/src/conversation/privatev1.rs b/conversations/src/conversation/privatev1.rs index 2711436..3b36381 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/conversation/privatev1.rs @@ -7,10 +7,10 @@ use chat_proto::logoschat::{ encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption}, }; use crypto::SymmetricKey32; +use crypto::X25519PublicKey; use double_ratchets::{Header, InstallationKeyPair, RatchetState}; use prost::{Message, bytes::Bytes}; use std::fmt::Debug; -use x25519_dalek::PublicKey; use crate::{ conversation::{ChatError, ConversationId, Convo, Id}, @@ -60,7 +60,7 @@ pub struct PrivateV1Convo { } impl PrivateV1Convo { - pub fn new_initiator(seed_key: SymmetricKey32, remote: PublicKey) -> Self { + pub fn new_initiator(seed_key: SymmetricKey32, remote: X25519PublicKey) -> Self { let base_convo_id = BaseConvoId::new(&seed_key); let local_convo_id = base_convo_id.id_for_participant(Role::Initiator); let remote_convo_id = base_convo_id.id_for_participant(Role::Responder); @@ -125,13 +125,13 @@ impl PrivateV1Convo { return Err(EncryptionError::Decryption("missing payload".into())); }; - // Turn the bytes into a PublicKey + // Turn the bytes into a X25519PublicKey let byte_arr: [u8; 32] = dr_header .dh .to_vec() .try_into() .map_err(|_| EncryptionError::Decryption("invalid public key length".into()))?; - let dh_pub = PublicKey::from(byte_arr); + let dh_pub = X25519PublicKey::from(byte_arr); // Build the Header that DR impl expects let header = Header { @@ -230,7 +230,7 @@ mod tests { let saro = StaticSecret::random(); let raya = StaticSecret::random(); - let pub_raya = PublicKey::from(&raya); + let pub_raya = X25519PublicKey::from(&raya); let seed_key = saro.diffie_hellman(&pub_raya); let send_content_bytes = vec![0, 2, 4, 6, 8]; diff --git a/conversations/src/crypto.rs b/conversations/src/crypto.rs index ff9531f..5104a9b 100644 --- a/conversations/src/crypto.rs +++ b/conversations/src/crypto.rs @@ -1,11 +1,13 @@ +pub use crypto::X25519PublicKey; +pub use x25519_dalek::StaticSecret; + use prost::bytes::Bytes; -pub use x25519_dalek::{PublicKey, StaticSecret}; pub trait CopyBytes { fn copy_to_bytes(&self) -> Bytes; } -impl CopyBytes for PublicKey { +impl CopyBytes for X25519PublicKey { 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 44dd79e..dea0532 100644 --- a/conversations/src/identity.rs +++ b/conversations/src/identity.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::crypto::{PublicKey, StaticSecret}; +use crate::crypto::{StaticSecret, X25519PublicKey}; pub struct Identity { secret: StaticSecret, @@ -22,8 +22,8 @@ impl Identity { } } - pub fn public_key(&self) -> PublicKey { - PublicKey::from(&self.secret) + pub fn public_key(&self) -> X25519PublicKey { + X25519PublicKey::from(&self.secret) } pub fn secret(&self) -> &StaticSecret { diff --git a/conversations/src/inbox/handler.rs b/conversations/src/inbox/handler.rs index c438c23..6fa1faf 100644 --- a/conversations/src/inbox/handler.rs +++ b/conversations/src/inbox/handler.rs @@ -10,14 +10,14 @@ use crypto::{PrekeyBundle, SymmetricKey32}; use crate::context::Introduction; use crate::conversation::{ChatError, ConversationId, Convo, Id, PrivateV1Convo}; -use crate::crypto::{CopyBytes, PublicKey, StaticSecret}; +use crate::crypto::{CopyBytes, StaticSecret, X25519PublicKey}; 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(_: X25519PublicKey) -> String { // TODO: Implement Delivery Address "delivery_address".into() } @@ -54,7 +54,7 @@ impl Inbox { pub fn create_intro_bundle(&mut self) -> Introduction { let ephemeral = StaticSecret::random(); - let ephemeral_key: PublicKey = (&ephemeral).into(); + let ephemeral_key: X25519PublicKey = (&ephemeral).into(); self.ephemeral_keys .insert(hex::encode(ephemeral_key.as_bytes()), ephemeral); @@ -173,13 +173,13 @@ impl Inbox { header: proto::InboxHeaderV1, bytes: Bytes, ) -> Result<(SymmetricKey32, proto::InboxV1Frame), ChatError> { - // Get PublicKeys from protobuf - let initator_static = PublicKey::from( + // Get X25519PublicKeys from protobuf + let initator_static = X25519PublicKey::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 = X25519PublicKey::from( <[u8; 32]>::try_from(header.initiator_ephemeral.as_ref()) .map_err(|_| ChatError::BadBundleValue("wrong size - initator ephemeral".into()))?, ); @@ -221,7 +221,7 @@ impl Inbox { .ok_or(ChatError::UnknownEphemeralKey()) } - pub fn inbox_identifier_for_key(pubkey: PublicKey) -> String { + pub fn inbox_identifier_for_key(pubkey: X25519PublicKey) -> String { // TODO: Implement ID according to spec hex::encode(Blake2b512::digest(pubkey)) } diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index 8da581a..157b5c5 100644 --- a/conversations/src/inbox/handshake.rs +++ b/conversations/src/inbox/handshake.rs @@ -5,7 +5,7 @@ use blake2::{ use crypto::{DomainSeparator, PrekeyBundle, SymmetricKey32, X3Handshake}; use rand_core::{CryptoRng, RngCore}; -use crate::crypto::{PublicKey, StaticSecret}; +use crate::crypto::{StaticSecret, X25519PublicKey}; type Blake2bMac256 = Blake2bMac; @@ -24,7 +24,7 @@ impl InboxHandshake { identity_keypair: &StaticSecret, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SymmetricKey32, PublicKey) { + ) -> (SymmetricKey32, X25519PublicKey) { // Perform X3DH handshake to get shared secret let (shared_secret, ephemeral_public) = InboxKeyExchange::initator(identity_keypair, recipient_bundle, rng); @@ -45,8 +45,8 @@ impl InboxHandshake { identity_keypair: &StaticSecret, signed_prekey: &StaticSecret, onetime_prekey: Option<&StaticSecret>, - initiator_identity: &PublicKey, - initiator_ephemeral: &PublicKey, + initiator_identity: &X25519PublicKey, + initiator_ephemeral: &X25519PublicKey, ) -> SymmetricKey32 { // 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(rng); - let alice_identity_pub = PublicKey::from(&alice_identity); + let alice_identity_pub = X25519PublicKey::from(&alice_identity); // Bob (responder) generates his keys let bob_identity = StaticSecret::random_from_rng(rng); let bob_signed_prekey = StaticSecret::random_from_rng(rng); - let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); + let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); // Create Bob's prekey bundle let bob_bundle = PrekeyBundle { - identity_key: PublicKey::from(&bob_identity), + identity_key: X25519PublicKey::from(&bob_identity), signed_prekey: bob_signed_prekey_pub, signature: crypto::Ed25519Signature([0u8; 64]), onetime_prekey: None, diff --git a/conversations/src/inbox/introduction.rs b/conversations/src/inbox/introduction.rs index 90b200c..0080c43 100644 --- a/conversations/src/inbox/introduction.rs +++ b/conversations/src/inbox/introduction.rs @@ -1,15 +1,15 @@ use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; use chat_proto::logoschat::intro::IntroBundle; -use crypto::Ed25519Signature; +use crypto::{Ed25519Signature, X25519PublicKey}; use prost::Message; use rand_core::{CryptoRng, RngCore}; -use x25519_dalek::{PublicKey, StaticSecret}; +use x25519_dalek::StaticSecret; use crate::errors::ChatError; const BUNDLE_PREFIX: &str = "logos_chatintro_1_"; -fn intro_binding_message(ephemeral: &PublicKey) -> Vec { +fn intro_binding_message(ephemeral: &X25519PublicKey) -> Vec { let mut message = Vec::with_capacity(BUNDLE_PREFIX.len() + 32); message.extend_from_slice(BUNDLE_PREFIX.as_bytes()); message.extend_from_slice(ephemeral.as_bytes()); @@ -18,7 +18,7 @@ fn intro_binding_message(ephemeral: &PublicKey) -> Vec { pub(crate) fn sign_intro_binding( secret: &StaticSecret, - ephemeral: &PublicKey, + ephemeral: &X25519PublicKey, rng: R, ) -> Ed25519Signature { let message = intro_binding_message(ephemeral); @@ -26,8 +26,8 @@ pub(crate) fn sign_intro_binding( } pub(crate) fn verify_intro_binding( - pubkey: &PublicKey, - ephemeral: &PublicKey, + pubkey: &X25519PublicKey, + ephemeral: &X25519PublicKey, signature: &Ed25519Signature, ) -> Result<(), crypto::SignatureError> { let message = intro_binding_message(ephemeral); @@ -36,8 +36,8 @@ pub(crate) fn verify_intro_binding( /// Supplies remote participants with the required keys to use Inbox protocol pub struct Introduction { - installation_key: PublicKey, - ephemeral_key: PublicKey, + installation_key: X25519PublicKey, + ephemeral_key: X25519PublicKey, signature: Ed25519Signature, } @@ -45,7 +45,7 @@ impl Introduction { /// Create a new `Introduction` by signing the ephemeral key with the installation secret. pub(crate) fn new( installation_secret: &StaticSecret, - ephemeral_key: PublicKey, + ephemeral_key: X25519PublicKey, rng: R, ) -> Self { let installation_key = installation_secret.into(); @@ -57,11 +57,11 @@ impl Introduction { } } - pub fn installation_key(&self) -> &PublicKey { + pub fn installation_key(&self) -> &X25519PublicKey { &self.installation_key } - pub fn ephemeral_key(&self) -> &PublicKey { + pub fn ephemeral_key(&self) -> &X25519PublicKey { &self.ephemeral_key } @@ -126,8 +126,8 @@ impl TryFrom<&[u8]> for Introduction { .try_into() .map_err(|_| ChatError::BadBundleValue("invalid signature length".into()))?; - let installation_key = PublicKey::from(installation_bytes); - let ephemeral_key = PublicKey::from(ephemeral_bytes); + let installation_key = X25519PublicKey::from(installation_bytes); + let ephemeral_key = X25519PublicKey::from(ephemeral_bytes); let signature = Ed25519Signature(signature_bytes); verify_intro_binding(&installation_key, &ephemeral_key, &signature) @@ -150,7 +150,7 @@ mod tests { let install_secret = StaticSecret::random_from_rng(OsRng); let ephemeral_secret = StaticSecret::random_from_rng(OsRng); - let ephemeral_pub: PublicKey = (&ephemeral_secret).into(); + let ephemeral_pub: X25519PublicKey = (&ephemeral_secret).into(); Introduction::new(&install_secret, ephemeral_pub, OsRng) } diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index d44231f..5c305a9 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -2,6 +2,6 @@ mod keys; mod x3dh; mod xeddsa_sign; -pub use keys::{GenericArray, SymmetricKey32}; +pub use keys::{GenericArray, SymmetricKey32, X25519PublicKey}; pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake}; pub use xeddsa_sign::{Ed25519Signature, SignatureError, xeddsa_sign, xeddsa_verify}; diff --git a/crypto/src/x3dh.rs b/crypto/src/x3dh.rs index d942fbd..1de0919 100644 --- a/crypto/src/x3dh.rs +++ b/crypto/src/x3dh.rs @@ -3,18 +3,18 @@ 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::SymmetricKey32; +use crate::keys::{SymmetricKey32, X25519PublicKey}; use crate::xeddsa_sign::Ed25519Signature; /// 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: X25519PublicKey, + pub signed_prekey: X25519PublicKey, pub signature: Ed25519Signature, - pub onetime_prekey: Option, + pub onetime_prekey: Option, } pub trait DomainSeparator { @@ -70,10 +70,10 @@ impl X3Handshake { identity_keypair: &StaticSecret, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SymmetricKey32, PublicKey) { + ) -> (SymmetricKey32, X25519PublicKey) { // 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 = X25519PublicKey::from(&ephemeral_secret); // Perform the 4 Diffie-Hellman operations let dh1 = identity_keypair.diffie_hellman(&recipient_bundle.signed_prekey); @@ -105,8 +105,8 @@ impl X3Handshake { identity_keypair: &StaticSecret, signed_prekey: &StaticSecret, onetime_prekey: Option<&StaticSecret>, - initiator_identity: &PublicKey, - initiator_ephemeral: &PublicKey, + initiator_identity: &X25519PublicKey, + initiator_ephemeral: &X25519PublicKey, ) -> SymmetricKey32 { let dh1 = signed_prekey.diffie_hellman(initiator_identity); let dh2 = identity_keypair.diffie_hellman(initiator_ephemeral); @@ -136,17 +136,17 @@ mod tests { // Alice (initiator) generates her identity key let alice_identity = StaticSecret::random_from_rng(rng); - let alice_identity_pub = PublicKey::from(&alice_identity); + let alice_identity_pub = X25519PublicKey::from(&alice_identity); // Bob (responder) generates his keys let bob_identity = StaticSecret::random_from_rng(rng); - let bob_identity_pub = PublicKey::from(&bob_identity); + let bob_identity_pub = X25519PublicKey::from(&bob_identity); let bob_signed_prekey = StaticSecret::random_from_rng(rng); - let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); + let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); let bob_onetime_prekey = StaticSecret::random_from_rng(rng); - let bob_onetime_prekey_pub = PublicKey::from(&bob_onetime_prekey); + let bob_onetime_prekey_pub = X25519PublicKey::from(&bob_onetime_prekey); // Create Bob's prekey bundle (with one-time prekey) let bob_bundle = PrekeyBundle { @@ -179,14 +179,14 @@ mod tests { // Alice (initiator) generates her identity key let alice_identity = StaticSecret::random_from_rng(rng); - let alice_identity_pub = PublicKey::from(&alice_identity); + let alice_identity_pub = X25519PublicKey::from(&alice_identity); // Bob (responder) generates his keys let bob_identity = StaticSecret::random_from_rng(rng); - let bob_identity_pub = PublicKey::from(&bob_identity); + let bob_identity_pub = X25519PublicKey::from(&bob_identity); let bob_signed_prekey = StaticSecret::random_from_rng(rng); - let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); + let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); // Create Bob's prekey bundle (without one-time prekey) let bob_bundle = PrekeyBundle { diff --git a/crypto/src/xeddsa_sign.rs b/crypto/src/xeddsa_sign.rs index 8426a99..e598a3a 100644 --- a/crypto/src/xeddsa_sign.rs +++ b/crypto/src/xeddsa_sign.rs @@ -4,9 +4,11 @@ //! that allow signing arbitrary messages with X25519 keys. use rand_core::{CryptoRng, RngCore}; -use x25519_dalek::{PublicKey, StaticSecret}; +use x25519_dalek::StaticSecret; use xeddsa::{Sign, Verify, xed25519}; +use crate::keys::X25519PublicKey; + /// A 64-byte XEdDSA signature over an Ed25519-compatible curve. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Ed25519Signature(pub [u8; 64]); @@ -62,7 +64,7 @@ pub fn xeddsa_sign( /// # Returns /// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise pub fn xeddsa_verify( - pubkey: &PublicKey, + pubkey: &X25519PublicKey, message: &[u8], signature: &Ed25519Signature, ) -> Result<(), SignatureError> { @@ -80,7 +82,7 @@ mod tests { #[test] fn test_sign_and_verify_roundtrip() { let secret = StaticSecret::random_from_rng(OsRng); - let public = PublicKey::from(&secret); + let public = X25519PublicKey::from(&secret); let message = b"test message"; let signature = xeddsa_sign(&secret, message, OsRng); @@ -96,7 +98,7 @@ mod tests { let signature = xeddsa_sign(&secret, message, OsRng); let wrong_secret = StaticSecret::random_from_rng(OsRng); - let wrong_public = PublicKey::from(&wrong_secret); + let wrong_public = X25519PublicKey::from(&wrong_secret); assert_eq!( xeddsa_verify(&wrong_public, message, &signature), @@ -107,7 +109,7 @@ mod tests { #[test] fn test_wrong_message_fails() { let secret = StaticSecret::random_from_rng(OsRng); - let public = PublicKey::from(&secret); + let public = X25519PublicKey::from(&secret); let message = b"test message"; let signature = xeddsa_sign(&secret, message, OsRng); @@ -121,7 +123,7 @@ mod tests { #[test] fn test_corrupted_signature_fails() { let secret = StaticSecret::random_from_rng(OsRng); - let public = PublicKey::from(&secret); + let public = X25519PublicKey::from(&secret); let message = b"test message"; let mut signature = xeddsa_sign(&secret, message, OsRng); diff --git a/double-ratchets/Cargo.toml b/double-ratchets/Cargo.toml index 6d1038c..fc3f27d 100644 --- a/double-ratchets/Cargo.toml +++ b/double-ratchets/Cargo.toml @@ -13,6 +13,7 @@ required-features = ["headers"] [dependencies] x25519-dalek = { version="2.0.1", features=["static_secrets"] } chacha20poly1305 = "0.10.1" +crypto.workspace = true 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..e3953f3 100644 --- a/double-ratchets/src/ffi/doubleratchet.rs +++ b/double-ratchets/src/ffi/doubleratchet.rs @@ -1,5 +1,5 @@ +use crypto::X25519PublicKey; 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, X25519PublicKey::from(remote_pub)); Box::new(FFIRatchetState(state)).into() } diff --git a/double-ratchets/src/keypair.rs b/double-ratchets/src/keypair.rs index 88852c2..a7caa9c 100644 --- a/double-ratchets/src/keypair.rs +++ b/double-ratchets/src/keypair.rs @@ -1,5 +1,6 @@ +use crypto::X25519PublicKey; use rand_core::OsRng; -use x25519_dalek::{PublicKey, StaticSecret}; +use x25519_dalek::StaticSecret; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::types::SharedSecret; @@ -7,21 +8,21 @@ use crate::types::SharedSecret; #[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct InstallationKeyPair { secret: StaticSecret, - public: PublicKey, + public: X25519PublicKey, } impl InstallationKeyPair { pub fn generate() -> Self { let secret = StaticSecret::random_from_rng(OsRng); - let public = PublicKey::from(&secret); + let public = X25519PublicKey::from(&secret); Self { secret, public } } - pub fn dh(&self, their_public: &PublicKey) -> SharedSecret { + pub fn dh(&self, their_public: &X25519PublicKey) -> SharedSecret { self.secret.diffie_hellman(their_public).to_bytes() } - pub fn public(&self) -> &PublicKey { + pub fn public(&self) -> &X25519PublicKey { &self.public } @@ -33,14 +34,14 @@ 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 public = X25519PublicKey::from(&secret); Self { secret, public } } } impl From for InstallationKeyPair { fn from(value: StaticSecret) -> Self { - let public = PublicKey::from(&value); + let public = X25519PublicKey::from(&value); Self { secret: value, public, diff --git a/double-ratchets/src/state.rs b/double-ratchets/src/state.rs index 6e27b07..87b0665 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::X25519PublicKey; 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<(X25519PublicKey, 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(X25519PublicKey::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 = X25519PublicKey::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: X25519PublicKey, 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: X25519PublicKey) -> 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: X25519PublicKey) { 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 = X25519PublicKey::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 ea3cdfc..4ec8607 100644 --- a/double-ratchets/src/storage/session.rs +++ b/double-ratchets/src/storage/session.rs @@ -1,6 +1,6 @@ //! Session wrapper for automatic state persistence. -use x25519_dalek::PublicKey; +use crypto::X25519PublicKey; use crate::{ InstallationKeyPair, SessionError, @@ -54,7 +54,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> { storage: &'a mut RatchetStorage, conversation_id: &str, shared_secret: SharedSecret, - remote_pub: PublicKey, + remote_pub: X25519PublicKey, ) -> Result { if storage.exists(conversation_id)? { return Err(SessionError::ConvAlreadyExists(conversation_id.to_string())); diff --git a/double-ratchets/src/storage/types.rs b/double-ratchets/src/storage/types.rs index 485e67a..93f5a3a 100644 --- a/double-ratchets/src/storage/types.rs +++ b/double-ratchets/src/storage/types.rs @@ -1,11 +1,11 @@ //! Storage types for ratchet state. +use crypto::X25519PublicKey; use crate::{ hkdf::HkdfInfo, state::{RatchetState, SkippedKey}, types::MessageKey, }; -use x25519_dalek::PublicKey; /// Raw state data for storage (without generic parameter). #[derive(Debug, Clone)] @@ -42,11 +42,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(X25519PublicKey::from); - let skipped: HashMap<(PublicKey, u32), MessageKey> = skipped_keys + let skipped: HashMap<(X25519PublicKey, u32), MessageKey> = skipped_keys .into_iter() - .map(|sk| ((PublicKey::from(sk.public_key), sk.msg_num), sk.message_key)) + .map(|sk| { + ( + (X25519PublicKey::from(sk.public_key), sk.msg_num), + sk.message_key, + ) + }) .collect(); RatchetState { From 85ef7631d2bb6f8f9358264e4d11f930411b4e68 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:03:05 -0800 Subject: [PATCH 6/9] Add X25519PrivateKey --- crypto/src/keys.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index d95637d..01a6783 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -46,6 +46,27 @@ impl From<&X25519PublicKey> for xed25519::PublicKey { } } +#[derive(Clone, Zeroize, ZeroizeOnDrop)] +pub struct X25519PrivateKey(x25519_dalek::StaticSecret); + +impl X25519PrivateKey { + pub fn random_from_rng(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() -> X25519PrivateKey { + Self::random_from_rng(&mut OsRng) + } +} + +impl Deref for X25519PrivateKey { + type Target = x25519_dalek::StaticSecret; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// A Generic secret key container for symmetric keys. /// SymmetricKey retains ownership of bytes to ensure they are Zeroized on drop. #[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)] From 99264d2f66b2d551f6a31443175008993befab1b Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:25:09 -0800 Subject: [PATCH 7/9] Update PrivateKey -> X25519PrivateKey --- conversations/src/conversation/privatev1.rs | 6 ++--- conversations/src/crypto.rs | 3 +-- conversations/src/identity.rs | 8 +++--- conversations/src/inbox/handler.rs | 12 ++++----- conversations/src/inbox/handshake.rs | 16 +++++------ conversations/src/inbox/introduction.rs | 11 ++++---- crypto/src/keys.rs | 26 +++++++++++++----- crypto/src/lib.rs | 2 +- crypto/src/x3dh.rs | 30 ++++++++++----------- crypto/src/xeddsa_sign.rs | 15 +++++------ double-ratchets/src/keypair.rs | 13 +++++---- 11 files changed, 75 insertions(+), 67 deletions(-) diff --git a/conversations/src/conversation/privatev1.rs b/conversations/src/conversation/privatev1.rs index 3b36381..59a5b94 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/conversation/privatev1.rs @@ -221,14 +221,14 @@ impl Debug for PrivateV1Convo { #[cfg(test)] mod tests { - use x25519_dalek::StaticSecret; + use crypto::X25519PrivateKey; use super::*; #[test] fn test_encrypt_roundtrip() { - let saro = StaticSecret::random(); - let raya = StaticSecret::random(); + let saro = X25519PrivateKey::random(); + let raya = X25519PrivateKey::random(); let pub_raya = X25519PublicKey::from(&raya); diff --git a/conversations/src/crypto.rs b/conversations/src/crypto.rs index 5104a9b..f58309f 100644 --- a/conversations/src/crypto.rs +++ b/conversations/src/crypto.rs @@ -1,5 +1,4 @@ -pub use crypto::X25519PublicKey; -pub use x25519_dalek::StaticSecret; +pub use crypto::{X25519PrivateKey, X25519PublicKey}; use prost::bytes::Bytes; diff --git a/conversations/src/identity.rs b/conversations/src/identity.rs index dea0532..861897f 100644 --- a/conversations/src/identity.rs +++ b/conversations/src/identity.rs @@ -1,9 +1,9 @@ use std::fmt; -use crate::crypto::{StaticSecret, X25519PublicKey}; +use crate::crypto::{X25519PrivateKey, X25519PublicKey}; pub struct Identity { - secret: StaticSecret, + secret: X25519PrivateKey, } impl fmt::Debug for Identity { @@ -18,7 +18,7 @@ impl fmt::Debug for Identity { impl Identity { pub fn new() -> Self { Self { - secret: StaticSecret::random(), + secret: X25519PrivateKey::random(), } } @@ -26,7 +26,7 @@ impl Identity { X25519PublicKey::from(&self.secret) } - pub fn secret(&self) -> &StaticSecret { + pub fn secret(&self) -> &X25519PrivateKey { &self.secret } } diff --git a/conversations/src/inbox/handler.rs b/conversations/src/inbox/handler.rs index 6fa1faf..332598a 100644 --- a/conversations/src/inbox/handler.rs +++ b/conversations/src/inbox/handler.rs @@ -10,7 +10,7 @@ use crypto::{PrekeyBundle, SymmetricKey32}; use crate::context::Introduction; use crate::conversation::{ChatError, ConversationId, Convo, Id, PrivateV1Convo}; -use crate::crypto::{CopyBytes, StaticSecret, X25519PublicKey}; +use crate::crypto::{CopyBytes, X25519PrivateKey, X25519PublicKey}; use crate::identity::Identity; use crate::inbox::handshake::InboxHandshake; use crate::proto; @@ -25,7 +25,7 @@ fn delivery_address_for_installation(_: X25519PublicKey) -> String { pub struct Inbox { ident: Rc, local_convo_id: String, - ephemeral_keys: HashMap, + ephemeral_keys: HashMap, } impl std::fmt::Debug for Inbox { @@ -47,12 +47,12 @@ impl Inbox { Self { ident, local_convo_id, - ephemeral_keys: HashMap::::new(), + ephemeral_keys: HashMap::::new(), } } pub fn create_intro_bundle(&mut self) -> Introduction { - let ephemeral = StaticSecret::random(); + let ephemeral = X25519PrivateKey::random(); let ephemeral_key: X25519PublicKey = (&ephemeral).into(); self.ephemeral_keys @@ -169,7 +169,7 @@ impl Inbox { fn perform_handshake( &self, - ephemeral_key: &StaticSecret, + ephemeral_key: &X25519PrivateKey, header: proto::InboxHeaderV1, bytes: Bytes, ) -> Result<(SymmetricKey32, proto::InboxV1Frame), ChatError> { @@ -215,7 +215,7 @@ impl Inbox { Ok(frame) } - fn lookup_ephemeral_key(&self, key: &str) -> Result<&StaticSecret, ChatError> { + fn lookup_ephemeral_key(&self, key: &str) -> Result<&X25519PrivateKey, ChatError> { self.ephemeral_keys .get(key) .ok_or(ChatError::UnknownEphemeralKey()) diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index 157b5c5..5e8ffe1 100644 --- a/conversations/src/inbox/handshake.rs +++ b/conversations/src/inbox/handshake.rs @@ -5,7 +5,7 @@ use blake2::{ use crypto::{DomainSeparator, PrekeyBundle, SymmetricKey32, X3Handshake}; use rand_core::{CryptoRng, RngCore}; -use crate::crypto::{StaticSecret, X25519PublicKey}; +use crate::crypto::{X25519PrivateKey, X25519PublicKey}; type Blake2bMac256 = Blake2bMac; @@ -21,7 +21,7 @@ pub struct InboxHandshake {} impl InboxHandshake { /// Performs pub fn perform_as_initiator( - identity_keypair: &StaticSecret, + identity_keypair: &X25519PrivateKey, recipient_bundle: &PrekeyBundle, rng: &mut R, ) -> (SymmetricKey32, X25519PublicKey) { @@ -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: &X25519PrivateKey, + signed_prekey: &X25519PrivateKey, + onetime_prekey: Option<&X25519PrivateKey>, initiator_identity: &X25519PublicKey, initiator_ephemeral: &X25519PublicKey, ) -> SymmetricKey32 { @@ -85,12 +85,12 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = StaticSecret::random_from_rng(rng); + let alice_identity = X25519PrivateKey::random_from_rng(rng); let alice_identity_pub = X25519PublicKey::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = StaticSecret::random_from_rng(rng); - let bob_signed_prekey = StaticSecret::random_from_rng(rng); + let bob_identity = X25519PrivateKey::random_from_rng(rng); + let bob_signed_prekey = X25519PrivateKey::random_from_rng(rng); let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); // Create Bob's prekey bundle diff --git a/conversations/src/inbox/introduction.rs b/conversations/src/inbox/introduction.rs index 0080c43..5c4397e 100644 --- a/conversations/src/inbox/introduction.rs +++ b/conversations/src/inbox/introduction.rs @@ -1,9 +1,8 @@ use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; use chat_proto::logoschat::intro::IntroBundle; -use crypto::{Ed25519Signature, X25519PublicKey}; +use crypto::{Ed25519Signature, X25519PrivateKey, X25519PublicKey}; use prost::Message; use rand_core::{CryptoRng, RngCore}; -use x25519_dalek::StaticSecret; use crate::errors::ChatError; @@ -17,7 +16,7 @@ fn intro_binding_message(ephemeral: &X25519PublicKey) -> Vec { } pub(crate) fn sign_intro_binding( - secret: &StaticSecret, + secret: &X25519PrivateKey, ephemeral: &X25519PublicKey, rng: R, ) -> Ed25519Signature { @@ -44,7 +43,7 @@ pub struct Introduction { impl Introduction { /// Create a new `Introduction` by signing the ephemeral key with the installation secret. pub(crate) fn new( - installation_secret: &StaticSecret, + installation_secret: &X25519PrivateKey, ephemeral_key: X25519PublicKey, rng: R, ) -> Self { @@ -147,9 +146,9 @@ mod tests { use rand_core::OsRng; fn create_test_introduction() -> Introduction { - let install_secret = StaticSecret::random_from_rng(OsRng); + let install_secret = X25519PrivateKey::random_from_rng(OsRng); - let ephemeral_secret = StaticSecret::random_from_rng(OsRng); + let ephemeral_secret = X25519PrivateKey::random_from_rng(OsRng); let ephemeral_pub: X25519PublicKey = (&ephemeral_secret).into(); Introduction::new(&install_secret, ephemeral_pub, OsRng) diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index 01a6783..ad8bba7 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -2,7 +2,7 @@ pub use generic_array::{GenericArray, typenum::U32}; use rand_core::{CryptoRng, OsRng, RngCore}; use std::{fmt::Debug, ops::Deref}; -use x25519_dalek::{PublicKey, SharedSecret, StaticSecret}; +use x25519_dalek; use xeddsa::xed25519; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -15,9 +15,9 @@ impl From for X25519PublicKey { } } -impl From<&StaticSecret> for X25519PublicKey { - fn from(value: &StaticSecret) -> Self { - Self(x25519_dalek::PublicKey::from(value)) +impl From<&X25519PrivateKey> for X25519PublicKey { + fn from(value: &X25519PrivateKey) -> Self { + Self(x25519_dalek::PublicKey::from(&value.0)) } } @@ -28,7 +28,7 @@ impl From<[u8; 32]> for X25519PublicKey { } impl Deref for X25519PublicKey { - type Target = PublicKey; + type Target = x25519_dalek::PublicKey; fn deref(&self) -> &Self::Target { &self.0 } @@ -60,6 +60,12 @@ impl X25519PrivateKey { } } +impl From<[u8; 32]> for X25519PrivateKey { + fn from(value: [u8; 32]) -> Self { + Self(x25519_dalek::StaticSecret::from(value)) + } +} + impl Deref for X25519PrivateKey { type Target = x25519_dalek::StaticSecret; fn deref(&self) -> &Self::Target { @@ -67,6 +73,12 @@ impl Deref for X25519PrivateKey { } } +impl From<&X25519PrivateKey> for xed25519::PrivateKey { + fn from(value: &X25519PrivateKey) -> Self { + Self::from(&value.0) + } +} + /// A Generic secret key container for symmetric keys. /// SymmetricKey retains ownership of bytes to ensure they are Zeroized on drop. #[derive(Clone, Zeroize, ZeroizeOnDrop, PartialEq)] @@ -107,10 +119,10 @@ impl From> for SymmetricKey32 { } } -impl From<&SharedSecret> for SymmetricKey32 { +impl From<&x25519_dalek::SharedSecret> for SymmetricKey32 { // This relies on the feature 'zeroize' being set for x25519-dalek. // If not the SharedSecret will need to manually zeroized - fn from(value: &SharedSecret) -> Self { + fn from(value: &x25519_dalek::SharedSecret) -> Self { value.to_bytes().into() } } diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 5c305a9..a746996 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -2,6 +2,6 @@ mod keys; mod x3dh; mod xeddsa_sign; -pub use keys::{GenericArray, SymmetricKey32, X25519PublicKey}; +pub use keys::{GenericArray, SymmetricKey32, X25519PrivateKey, X25519PublicKey}; pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake}; pub use xeddsa_sign::{Ed25519Signature, SignatureError, xeddsa_sign, xeddsa_verify}; diff --git a/crypto/src/x3dh.rs b/crypto/src/x3dh.rs index 1de0919..e7e5099 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::{SymmetricKey32, X25519PublicKey}; +use crate::keys::{SymmetricKey32, X25519PrivateKey, X25519PublicKey}; use crate::xeddsa_sign::Ed25519Signature; /// A prekey bundle containing the public keys needed to initiate an X3DH key exchange. @@ -67,12 +67,12 @@ impl X3Handshake { /// # Returns /// A tuple of (shared secret bytes, ephemeral public key) pub fn initator( - identity_keypair: &StaticSecret, + identity_keypair: &X25519PrivateKey, recipient_bundle: &PrekeyBundle, rng: &mut R, ) -> (SymmetricKey32, X25519PublicKey) { - // 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 (using X25519PrivateKey for multiple DH operations) + let ephemeral_secret = X25519PrivateKey::random_from_rng(rng); let ephemeral_public = X25519PublicKey::from(&ephemeral_secret); // Perform the 4 Diffie-Hellman operations @@ -102,9 +102,9 @@ impl X3Handshake { /// # Returns /// The derived shared secret bytes pub fn responder( - identity_keypair: &StaticSecret, - signed_prekey: &StaticSecret, - onetime_prekey: Option<&StaticSecret>, + identity_keypair: &X25519PrivateKey, + signed_prekey: &X25519PrivateKey, + onetime_prekey: Option<&X25519PrivateKey>, initiator_identity: &X25519PublicKey, initiator_ephemeral: &X25519PublicKey, ) -> SymmetricKey32 { @@ -135,17 +135,17 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = StaticSecret::random_from_rng(rng); + let alice_identity = X25519PrivateKey::random_from_rng(rng); let alice_identity_pub = X25519PublicKey::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = StaticSecret::random_from_rng(rng); + let bob_identity = X25519PrivateKey::random_from_rng(rng); let bob_identity_pub = X25519PublicKey::from(&bob_identity); - let bob_signed_prekey = StaticSecret::random_from_rng(rng); + let bob_signed_prekey = X25519PrivateKey::random_from_rng(rng); let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); - let bob_onetime_prekey = StaticSecret::random_from_rng(rng); + let bob_onetime_prekey = X25519PrivateKey::random_from_rng(rng); let bob_onetime_prekey_pub = X25519PublicKey::from(&bob_onetime_prekey); // Create Bob's prekey bundle (with one-time prekey) @@ -178,14 +178,14 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = StaticSecret::random_from_rng(rng); + let alice_identity = X25519PrivateKey::random_from_rng(rng); let alice_identity_pub = X25519PublicKey::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = StaticSecret::random_from_rng(rng); + let bob_identity = X25519PrivateKey::random_from_rng(rng); let bob_identity_pub = X25519PublicKey::from(&bob_identity); - let bob_signed_prekey = StaticSecret::random_from_rng(rng); + let bob_signed_prekey = X25519PrivateKey::random_from_rng(rng); let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); // Create Bob's prekey bundle (without one-time prekey) diff --git a/crypto/src/xeddsa_sign.rs b/crypto/src/xeddsa_sign.rs index e598a3a..cc3bb26 100644 --- a/crypto/src/xeddsa_sign.rs +++ b/crypto/src/xeddsa_sign.rs @@ -4,10 +4,9 @@ //! that allow signing arbitrary messages with X25519 keys. use rand_core::{CryptoRng, RngCore}; -use x25519_dalek::StaticSecret; use xeddsa::{Sign, Verify, xed25519}; -use crate::keys::X25519PublicKey; +use crate::keys::{X25519PrivateKey, X25519PublicKey}; /// A 64-byte XEdDSA signature over an Ed25519-compatible curve. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -46,7 +45,7 @@ pub struct SignatureError; /// # Returns /// An `Ed25519Signature` pub fn xeddsa_sign( - secret: &StaticSecret, + secret: &X25519PrivateKey, message: &[u8], mut rng: R, ) -> Ed25519Signature { @@ -81,7 +80,7 @@ mod tests { #[test] fn test_sign_and_verify_roundtrip() { - let secret = StaticSecret::random_from_rng(OsRng); + let secret = X25519PrivateKey::random_from_rng(OsRng); let public = X25519PublicKey::from(&secret); let message = b"test message"; @@ -92,12 +91,12 @@ mod tests { #[test] fn test_wrong_key_fails() { - let secret = StaticSecret::random_from_rng(OsRng); + let secret = X25519PrivateKey::random_from_rng(OsRng); let message = b"test message"; let signature = xeddsa_sign(&secret, message, OsRng); - let wrong_secret = StaticSecret::random_from_rng(OsRng); + let wrong_secret = X25519PrivateKey::random_from_rng(OsRng); let wrong_public = X25519PublicKey::from(&wrong_secret); assert_eq!( @@ -108,7 +107,7 @@ mod tests { #[test] fn test_wrong_message_fails() { - let secret = StaticSecret::random_from_rng(OsRng); + let secret = X25519PrivateKey::random_from_rng(OsRng); let public = X25519PublicKey::from(&secret); let message = b"test message"; @@ -122,7 +121,7 @@ mod tests { #[test] fn test_corrupted_signature_fails() { - let secret = StaticSecret::random_from_rng(OsRng); + let secret = X25519PrivateKey::random_from_rng(OsRng); let public = X25519PublicKey::from(&secret); let message = b"test message"; diff --git a/double-ratchets/src/keypair.rs b/double-ratchets/src/keypair.rs index a7caa9c..724fbca 100644 --- a/double-ratchets/src/keypair.rs +++ b/double-ratchets/src/keypair.rs @@ -1,19 +1,18 @@ -use crypto::X25519PublicKey; +use crypto::{X25519PrivateKey, X25519PublicKey}; use rand_core::OsRng; -use x25519_dalek::StaticSecret; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::types::SharedSecret; #[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct InstallationKeyPair { - secret: StaticSecret, + secret: X25519PrivateKey, public: X25519PublicKey, } impl InstallationKeyPair { pub fn generate() -> Self { - let secret = StaticSecret::random_from_rng(OsRng); + let secret = X25519PrivateKey::random_from_rng(OsRng); let public = X25519PublicKey::from(&secret); Self { secret, public } } @@ -33,14 +32,14 @@ impl InstallationKeyPair { /// Import the secret key from raw bytes. pub fn from_secret_bytes(bytes: [u8; 32]) -> Self { - let secret = StaticSecret::from(bytes); + let secret = X25519PrivateKey::from(bytes); let public = X25519PublicKey::from(&secret); Self { secret, public } } } -impl From for InstallationKeyPair { - fn from(value: StaticSecret) -> Self { +impl From for InstallationKeyPair { + fn from(value: X25519PrivateKey) -> Self { let public = X25519PublicKey::from(&value); Self { secret: value, From c9d84a482c4d56614b10627c73ace57d1eff9817 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:37:31 -0800 Subject: [PATCH 8/9] Rename to PublicKey and PrivateKey --- conversations/src/conversation/privatev1.rs | 16 +++--- conversations/src/crypto.rs | 4 +- conversations/src/identity.rs | 12 ++--- conversations/src/inbox/handler.rs | 24 ++++----- conversations/src/inbox/handshake.rs | 28 +++++------ conversations/src/inbox/introduction.rs | 34 ++++++------- crypto/src/keys.rs | 37 +++++++------- crypto/src/lib.rs | 2 +- crypto/src/x3dh.rs | 56 ++++++++++----------- crypto/src/xeddsa_sign.rs | 24 ++++----- double-ratchets/src/ffi/doubleratchet.rs | 4 +- double-ratchets/src/keypair.rs | 24 ++++----- double-ratchets/src/state.rs | 18 +++---- double-ratchets/src/storage/session.rs | 4 +- double-ratchets/src/storage/types.rs | 8 +-- 15 files changed, 147 insertions(+), 148 deletions(-) diff --git a/conversations/src/conversation/privatev1.rs b/conversations/src/conversation/privatev1.rs index 59a5b94..40ff484 100644 --- a/conversations/src/conversation/privatev1.rs +++ b/conversations/src/conversation/privatev1.rs @@ -7,7 +7,7 @@ use chat_proto::logoschat::{ encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption}, }; use crypto::SymmetricKey32; -use crypto::X25519PublicKey; +use crypto::PublicKey; use double_ratchets::{Header, InstallationKeyPair, RatchetState}; use prost::{Message, bytes::Bytes}; use std::fmt::Debug; @@ -60,7 +60,7 @@ pub struct PrivateV1Convo { } impl PrivateV1Convo { - pub fn new_initiator(seed_key: SymmetricKey32, remote: X25519PublicKey) -> Self { + pub fn new_initiator(seed_key: SymmetricKey32, remote: PublicKey) -> Self { let base_convo_id = BaseConvoId::new(&seed_key); let local_convo_id = base_convo_id.id_for_participant(Role::Initiator); let remote_convo_id = base_convo_id.id_for_participant(Role::Responder); @@ -125,13 +125,13 @@ impl PrivateV1Convo { return Err(EncryptionError::Decryption("missing payload".into())); }; - // Turn the bytes into a X25519PublicKey + // Turn the bytes into a PublicKey let byte_arr: [u8; 32] = dr_header .dh .to_vec() .try_into() .map_err(|_| EncryptionError::Decryption("invalid public key length".into()))?; - let dh_pub = X25519PublicKey::from(byte_arr); + let dh_pub = PublicKey::from(byte_arr); // Build the Header that DR impl expects let header = Header { @@ -221,16 +221,16 @@ impl Debug for PrivateV1Convo { #[cfg(test)] mod tests { - use crypto::X25519PrivateKey; + use crypto::PrivateKey; use super::*; #[test] fn test_encrypt_roundtrip() { - let saro = X25519PrivateKey::random(); - let raya = X25519PrivateKey::random(); + let saro = PrivateKey::random(); + let raya = PrivateKey::random(); - let pub_raya = X25519PublicKey::from(&raya); + let pub_raya = PublicKey::from(&raya); let seed_key = saro.diffie_hellman(&pub_raya); let send_content_bytes = vec![0, 2, 4, 6, 8]; diff --git a/conversations/src/crypto.rs b/conversations/src/crypto.rs index f58309f..764341b 100644 --- a/conversations/src/crypto.rs +++ b/conversations/src/crypto.rs @@ -1,4 +1,4 @@ -pub use crypto::{X25519PrivateKey, X25519PublicKey}; +pub use crypto::{PrivateKey, PublicKey}; use prost::bytes::Bytes; @@ -6,7 +6,7 @@ pub trait CopyBytes { fn copy_to_bytes(&self) -> Bytes; } -impl CopyBytes for X25519PublicKey { +impl CopyBytes for PublicKey { 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 861897f..de3be1e 100644 --- a/conversations/src/identity.rs +++ b/conversations/src/identity.rs @@ -1,9 +1,9 @@ use std::fmt; -use crate::crypto::{X25519PrivateKey, X25519PublicKey}; +use crate::crypto::{PrivateKey, PublicKey}; pub struct Identity { - secret: X25519PrivateKey, + secret: PrivateKey, } impl fmt::Debug for Identity { @@ -18,15 +18,15 @@ impl fmt::Debug for Identity { impl Identity { pub fn new() -> Self { Self { - secret: X25519PrivateKey::random(), + secret: PrivateKey::random(), } } - pub fn public_key(&self) -> X25519PublicKey { - X25519PublicKey::from(&self.secret) + pub fn public_key(&self) -> PublicKey { + PublicKey::from(&self.secret) } - pub fn secret(&self) -> &X25519PrivateKey { + pub fn secret(&self) -> &PrivateKey { &self.secret } } diff --git a/conversations/src/inbox/handler.rs b/conversations/src/inbox/handler.rs index 332598a..504f812 100644 --- a/conversations/src/inbox/handler.rs +++ b/conversations/src/inbox/handler.rs @@ -10,14 +10,14 @@ use crypto::{PrekeyBundle, SymmetricKey32}; use crate::context::Introduction; use crate::conversation::{ChatError, ConversationId, Convo, Id, PrivateV1Convo}; -use crate::crypto::{CopyBytes, X25519PrivateKey, X25519PublicKey}; +use crate::crypto::{CopyBytes, PrivateKey, PublicKey}; 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(_: X25519PublicKey) -> String { +fn delivery_address_for_installation(_: PublicKey) -> String { // TODO: Implement Delivery Address "delivery_address".into() } @@ -25,7 +25,7 @@ fn delivery_address_for_installation(_: X25519PublicKey) -> String { pub struct Inbox { ident: Rc, local_convo_id: String, - ephemeral_keys: HashMap, + ephemeral_keys: HashMap, } impl std::fmt::Debug for Inbox { @@ -47,14 +47,14 @@ impl Inbox { Self { ident, local_convo_id, - ephemeral_keys: HashMap::::new(), + ephemeral_keys: HashMap::::new(), } } pub fn create_intro_bundle(&mut self) -> Introduction { - let ephemeral = X25519PrivateKey::random(); + let ephemeral = PrivateKey::random(); - let ephemeral_key: X25519PublicKey = (&ephemeral).into(); + let ephemeral_key: PublicKey = (&ephemeral).into(); self.ephemeral_keys .insert(hex::encode(ephemeral_key.as_bytes()), ephemeral); @@ -169,17 +169,17 @@ impl Inbox { fn perform_handshake( &self, - ephemeral_key: &X25519PrivateKey, + ephemeral_key: &PrivateKey, header: proto::InboxHeaderV1, bytes: Bytes, ) -> Result<(SymmetricKey32, proto::InboxV1Frame), ChatError> { - // Get X25519PublicKeys from protobuf - let initator_static = X25519PublicKey::from( + // Get PublicKeys from protobuf + let initator_static = PublicKey::from( <[u8; 32]>::try_from(header.initiator_static.as_ref()) .map_err(|_| ChatError::BadBundleValue("wrong size - initator static".into()))?, ); - let initator_ephemeral = X25519PublicKey::from( + let initator_ephemeral = PublicKey::from( <[u8; 32]>::try_from(header.initiator_ephemeral.as_ref()) .map_err(|_| ChatError::BadBundleValue("wrong size - initator ephemeral".into()))?, ); @@ -215,13 +215,13 @@ impl Inbox { Ok(frame) } - fn lookup_ephemeral_key(&self, key: &str) -> Result<&X25519PrivateKey, ChatError> { + fn lookup_ephemeral_key(&self, key: &str) -> Result<&PrivateKey, ChatError> { self.ephemeral_keys .get(key) .ok_or(ChatError::UnknownEphemeralKey()) } - pub fn inbox_identifier_for_key(pubkey: X25519PublicKey) -> String { + pub fn inbox_identifier_for_key(pubkey: PublicKey) -> String { // TODO: Implement ID according to spec hex::encode(Blake2b512::digest(pubkey)) } diff --git a/conversations/src/inbox/handshake.rs b/conversations/src/inbox/handshake.rs index 5e8ffe1..8a93a5a 100644 --- a/conversations/src/inbox/handshake.rs +++ b/conversations/src/inbox/handshake.rs @@ -5,7 +5,7 @@ use blake2::{ use crypto::{DomainSeparator, PrekeyBundle, SymmetricKey32, X3Handshake}; use rand_core::{CryptoRng, RngCore}; -use crate::crypto::{X25519PrivateKey, X25519PublicKey}; +use crate::crypto::{PrivateKey, PublicKey}; type Blake2bMac256 = Blake2bMac; @@ -21,10 +21,10 @@ pub struct InboxHandshake {} impl InboxHandshake { /// Performs pub fn perform_as_initiator( - identity_keypair: &X25519PrivateKey, + identity_keypair: &PrivateKey, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SymmetricKey32, X25519PublicKey) { + ) -> (SymmetricKey32, PublicKey) { // Perform X3DH handshake to get shared secret let (shared_secret, ephemeral_public) = InboxKeyExchange::initator(identity_keypair, recipient_bundle, rng); @@ -42,11 +42,11 @@ impl InboxHandshake { /// * `initiator_identity` - Initiator's identity public key /// * `initiator_ephemeral` - Initiator's ephemeral public key pub fn perform_as_responder( - identity_keypair: &X25519PrivateKey, - signed_prekey: &X25519PrivateKey, - onetime_prekey: Option<&X25519PrivateKey>, - initiator_identity: &X25519PublicKey, - initiator_ephemeral: &X25519PublicKey, + identity_keypair: &PrivateKey, + signed_prekey: &PrivateKey, + onetime_prekey: Option<&PrivateKey>, + initiator_identity: &PublicKey, + initiator_ephemeral: &PublicKey, ) -> SymmetricKey32 { // Perform X3DH to get shared secret let shared_secret = InboxKeyExchange::responder( @@ -85,17 +85,17 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = X25519PrivateKey::random_from_rng(rng); - let alice_identity_pub = X25519PublicKey::from(&alice_identity); + let alice_identity = PrivateKey::random_from_rng(rng); + let alice_identity_pub = PublicKey::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = X25519PrivateKey::random_from_rng(rng); - let bob_signed_prekey = X25519PrivateKey::random_from_rng(rng); - let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); + let bob_identity = PrivateKey::random_from_rng(rng); + let bob_signed_prekey = PrivateKey::random_from_rng(rng); + let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); // Create Bob's prekey bundle let bob_bundle = PrekeyBundle { - identity_key: X25519PublicKey::from(&bob_identity), + identity_key: PublicKey::from(&bob_identity), signed_prekey: bob_signed_prekey_pub, signature: crypto::Ed25519Signature([0u8; 64]), onetime_prekey: None, diff --git a/conversations/src/inbox/introduction.rs b/conversations/src/inbox/introduction.rs index 5c4397e..9f6f5c0 100644 --- a/conversations/src/inbox/introduction.rs +++ b/conversations/src/inbox/introduction.rs @@ -1,6 +1,6 @@ use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; use chat_proto::logoschat::intro::IntroBundle; -use crypto::{Ed25519Signature, X25519PrivateKey, X25519PublicKey}; +use crypto::{Ed25519Signature, PrivateKey, PublicKey}; use prost::Message; use rand_core::{CryptoRng, RngCore}; @@ -8,7 +8,7 @@ use crate::errors::ChatError; const BUNDLE_PREFIX: &str = "logos_chatintro_1_"; -fn intro_binding_message(ephemeral: &X25519PublicKey) -> Vec { +fn intro_binding_message(ephemeral: &PublicKey) -> Vec { let mut message = Vec::with_capacity(BUNDLE_PREFIX.len() + 32); message.extend_from_slice(BUNDLE_PREFIX.as_bytes()); message.extend_from_slice(ephemeral.as_bytes()); @@ -16,8 +16,8 @@ fn intro_binding_message(ephemeral: &X25519PublicKey) -> Vec { } pub(crate) fn sign_intro_binding( - secret: &X25519PrivateKey, - ephemeral: &X25519PublicKey, + secret: &PrivateKey, + ephemeral: &PublicKey, rng: R, ) -> Ed25519Signature { let message = intro_binding_message(ephemeral); @@ -25,8 +25,8 @@ pub(crate) fn sign_intro_binding( } pub(crate) fn verify_intro_binding( - pubkey: &X25519PublicKey, - ephemeral: &X25519PublicKey, + pubkey: &PublicKey, + ephemeral: &PublicKey, signature: &Ed25519Signature, ) -> Result<(), crypto::SignatureError> { let message = intro_binding_message(ephemeral); @@ -35,16 +35,16 @@ pub(crate) fn verify_intro_binding( /// Supplies remote participants with the required keys to use Inbox protocol pub struct Introduction { - installation_key: X25519PublicKey, - ephemeral_key: X25519PublicKey, + installation_key: PublicKey, + ephemeral_key: PublicKey, signature: Ed25519Signature, } impl Introduction { /// Create a new `Introduction` by signing the ephemeral key with the installation secret. pub(crate) fn new( - installation_secret: &X25519PrivateKey, - ephemeral_key: X25519PublicKey, + installation_secret: &PrivateKey, + ephemeral_key: PublicKey, rng: R, ) -> Self { let installation_key = installation_secret.into(); @@ -56,11 +56,11 @@ impl Introduction { } } - pub fn installation_key(&self) -> &X25519PublicKey { + pub fn installation_key(&self) -> &PublicKey { &self.installation_key } - pub fn ephemeral_key(&self) -> &X25519PublicKey { + pub fn ephemeral_key(&self) -> &PublicKey { &self.ephemeral_key } @@ -125,8 +125,8 @@ impl TryFrom<&[u8]> for Introduction { .try_into() .map_err(|_| ChatError::BadBundleValue("invalid signature length".into()))?; - let installation_key = X25519PublicKey::from(installation_bytes); - let ephemeral_key = X25519PublicKey::from(ephemeral_bytes); + let installation_key = PublicKey::from(installation_bytes); + let ephemeral_key = PublicKey::from(ephemeral_bytes); let signature = Ed25519Signature(signature_bytes); verify_intro_binding(&installation_key, &ephemeral_key, &signature) @@ -146,10 +146,10 @@ mod tests { use rand_core::OsRng; fn create_test_introduction() -> Introduction { - let install_secret = X25519PrivateKey::random_from_rng(OsRng); + let install_secret = PrivateKey::random_from_rng(OsRng); - let ephemeral_secret = X25519PrivateKey::random_from_rng(OsRng); - let ephemeral_pub: X25519PublicKey = (&ephemeral_secret).into(); + let ephemeral_secret = PrivateKey::random_from_rng(OsRng); + let ephemeral_pub: PublicKey = (&ephemeral_secret).into(); Introduction::new(&install_secret, ephemeral_pub, OsRng) } diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index ad8bba7..4986785 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -2,79 +2,78 @@ pub use generic_array::{GenericArray, typenum::U32}; use rand_core::{CryptoRng, OsRng, RngCore}; use std::{fmt::Debug, ops::Deref}; -use x25519_dalek; -use xeddsa::xed25519; +use xeddsa::xed25519::{PrivateKey as EdPrivateKey, PublicKey as EdPublicKey}; use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, Zeroize)] // TODO: (!) Zeroize only required by InstallationKeyPair -pub struct X25519PublicKey(x25519_dalek::PublicKey); +pub struct PublicKey(x25519_dalek::PublicKey); -impl From for X25519PublicKey { +impl From for PublicKey { fn from(value: x25519_dalek::PublicKey) -> Self { Self(value) } } -impl From<&X25519PrivateKey> for X25519PublicKey { - fn from(value: &X25519PrivateKey) -> Self { +impl From<&PrivateKey> for PublicKey { + fn from(value: &PrivateKey) -> Self { Self(x25519_dalek::PublicKey::from(&value.0)) } } -impl From<[u8; 32]> for X25519PublicKey { +impl From<[u8; 32]> for PublicKey { fn from(value: [u8; 32]) -> Self { Self(x25519_dalek::PublicKey::from(value)) } } -impl Deref for X25519PublicKey { +impl Deref for PublicKey { type Target = x25519_dalek::PublicKey; fn deref(&self) -> &Self::Target { &self.0 } } -impl AsRef<[u8]> for X25519PublicKey { +impl AsRef<[u8]> for PublicKey { fn as_ref(&self) -> &[u8] { self.0.as_ref() } } -impl From<&X25519PublicKey> for xed25519::PublicKey { - fn from(value: &X25519PublicKey) -> Self { +impl From<&PublicKey> for EdPublicKey { + fn from(value: &PublicKey) -> Self { Self::from(&value.0) } } #[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct X25519PrivateKey(x25519_dalek::StaticSecret); +pub struct PrivateKey(x25519_dalek::StaticSecret); -impl X25519PrivateKey { +impl PrivateKey { pub fn random_from_rng(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() -> X25519PrivateKey { - Self::random_from_rng(&mut OsRng) + pub fn random() -> PrivateKey { + Self::random_from_rng(OsRng) } } -impl From<[u8; 32]> for X25519PrivateKey { +impl From<[u8; 32]> for PrivateKey { fn from(value: [u8; 32]) -> Self { Self(x25519_dalek::StaticSecret::from(value)) } } -impl Deref for X25519PrivateKey { +impl Deref for PrivateKey { type Target = x25519_dalek::StaticSecret; fn deref(&self) -> &Self::Target { &self.0 } } -impl From<&X25519PrivateKey> for xed25519::PrivateKey { - fn from(value: &X25519PrivateKey) -> Self { +impl From<&PrivateKey> for EdPrivateKey { + fn from(value: &PrivateKey) -> Self { Self::from(&value.0) } } diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index a746996..aa2974a 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -2,6 +2,6 @@ mod keys; mod x3dh; mod xeddsa_sign; -pub use keys::{GenericArray, SymmetricKey32, X25519PrivateKey, X25519PublicKey}; +pub use keys::{GenericArray, SymmetricKey32, PrivateKey, PublicKey}; pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake}; pub use xeddsa_sign::{Ed25519Signature, SignatureError, xeddsa_sign, xeddsa_verify}; diff --git a/crypto/src/x3dh.rs b/crypto/src/x3dh.rs index e7e5099..c58a41a 100644 --- a/crypto/src/x3dh.rs +++ b/crypto/src/x3dh.rs @@ -5,16 +5,16 @@ use rand_core::{CryptoRng, RngCore}; use sha2::Sha256; use x25519_dalek::SharedSecret; -use crate::keys::{SymmetricKey32, X25519PrivateKey, X25519PublicKey}; +use crate::keys::{SymmetricKey32, PrivateKey, PublicKey}; use crate::xeddsa_sign::Ed25519Signature; /// A prekey bundle containing the public keys needed to initiate an X3DH key exchange. #[derive(Clone, Debug)] pub struct PrekeyBundle { - pub identity_key: X25519PublicKey, - pub signed_prekey: X25519PublicKey, + pub identity_key: PublicKey, + pub signed_prekey: PublicKey, pub signature: Ed25519Signature, - pub onetime_prekey: Option, + pub onetime_prekey: Option, } pub trait DomainSeparator { @@ -67,13 +67,13 @@ impl X3Handshake { /// # Returns /// A tuple of (shared secret bytes, ephemeral public key) pub fn initator( - identity_keypair: &X25519PrivateKey, + identity_keypair: &PrivateKey, recipient_bundle: &PrekeyBundle, rng: &mut R, - ) -> (SymmetricKey32, X25519PublicKey) { - // Generate ephemeral key for this handshake (using X25519PrivateKey for multiple DH operations) - let ephemeral_secret = X25519PrivateKey::random_from_rng(rng); - let ephemeral_public = X25519PublicKey::from(&ephemeral_secret); + ) -> (SymmetricKey32, PublicKey) { + // Generate ephemeral key for this handshake (using PrivateKey for multiple DH operations) + let ephemeral_secret = PrivateKey::random_from_rng(rng); + let ephemeral_public = PublicKey::from(&ephemeral_secret); // Perform the 4 Diffie-Hellman operations let dh1 = identity_keypair.diffie_hellman(&recipient_bundle.signed_prekey); @@ -102,11 +102,11 @@ impl X3Handshake { /// # Returns /// The derived shared secret bytes pub fn responder( - identity_keypair: &X25519PrivateKey, - signed_prekey: &X25519PrivateKey, - onetime_prekey: Option<&X25519PrivateKey>, - initiator_identity: &X25519PublicKey, - initiator_ephemeral: &X25519PublicKey, + identity_keypair: &PrivateKey, + signed_prekey: &PrivateKey, + onetime_prekey: Option<&PrivateKey>, + initiator_identity: &PublicKey, + initiator_ephemeral: &PublicKey, ) -> SymmetricKey32 { let dh1 = signed_prekey.diffie_hellman(initiator_identity); let dh2 = identity_keypair.diffie_hellman(initiator_ephemeral); @@ -135,18 +135,18 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = X25519PrivateKey::random_from_rng(rng); - let alice_identity_pub = X25519PublicKey::from(&alice_identity); + let alice_identity = PrivateKey::random_from_rng(rng); + let alice_identity_pub = PublicKey::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = X25519PrivateKey::random_from_rng(rng); - let bob_identity_pub = X25519PublicKey::from(&bob_identity); + let bob_identity = PrivateKey::random_from_rng(rng); + let bob_identity_pub = PublicKey::from(&bob_identity); - let bob_signed_prekey = X25519PrivateKey::random_from_rng(rng); - let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); + let bob_signed_prekey = PrivateKey::random_from_rng(rng); + let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); - let bob_onetime_prekey = X25519PrivateKey::random_from_rng(rng); - let bob_onetime_prekey_pub = X25519PublicKey::from(&bob_onetime_prekey); + let bob_onetime_prekey = PrivateKey::random_from_rng(rng); + let bob_onetime_prekey_pub = PublicKey::from(&bob_onetime_prekey); // Create Bob's prekey bundle (with one-time prekey) let bob_bundle = PrekeyBundle { @@ -178,15 +178,15 @@ mod tests { let mut rng = OsRng; // Alice (initiator) generates her identity key - let alice_identity = X25519PrivateKey::random_from_rng(rng); - let alice_identity_pub = X25519PublicKey::from(&alice_identity); + let alice_identity = PrivateKey::random_from_rng(rng); + let alice_identity_pub = PublicKey::from(&alice_identity); // Bob (responder) generates his keys - let bob_identity = X25519PrivateKey::random_from_rng(rng); - let bob_identity_pub = X25519PublicKey::from(&bob_identity); + let bob_identity = PrivateKey::random_from_rng(rng); + let bob_identity_pub = PublicKey::from(&bob_identity); - let bob_signed_prekey = X25519PrivateKey::random_from_rng(rng); - let bob_signed_prekey_pub = X25519PublicKey::from(&bob_signed_prekey); + let bob_signed_prekey = PrivateKey::random_from_rng(rng); + let bob_signed_prekey_pub = PublicKey::from(&bob_signed_prekey); // Create Bob's prekey bundle (without one-time prekey) let bob_bundle = PrekeyBundle { diff --git a/crypto/src/xeddsa_sign.rs b/crypto/src/xeddsa_sign.rs index cc3bb26..026b560 100644 --- a/crypto/src/xeddsa_sign.rs +++ b/crypto/src/xeddsa_sign.rs @@ -6,7 +6,7 @@ use rand_core::{CryptoRng, RngCore}; use xeddsa::{Sign, Verify, xed25519}; -use crate::keys::{X25519PrivateKey, X25519PublicKey}; +use crate::keys::{PrivateKey, PublicKey}; /// A 64-byte XEdDSA signature over an Ed25519-compatible curve. #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -45,7 +45,7 @@ pub struct SignatureError; /// # Returns /// An `Ed25519Signature` pub fn xeddsa_sign( - secret: &X25519PrivateKey, + secret: &PrivateKey, message: &[u8], mut rng: R, ) -> Ed25519Signature { @@ -63,7 +63,7 @@ pub fn xeddsa_sign( /// # Returns /// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise pub fn xeddsa_verify( - pubkey: &X25519PublicKey, + pubkey: &PublicKey, message: &[u8], signature: &Ed25519Signature, ) -> Result<(), SignatureError> { @@ -80,8 +80,8 @@ mod tests { #[test] fn test_sign_and_verify_roundtrip() { - let secret = X25519PrivateKey::random_from_rng(OsRng); - let public = X25519PublicKey::from(&secret); + let secret = PrivateKey::random_from_rng(OsRng); + let public = PublicKey::from(&secret); let message = b"test message"; let signature = xeddsa_sign(&secret, message, OsRng); @@ -91,13 +91,13 @@ mod tests { #[test] fn test_wrong_key_fails() { - let secret = X25519PrivateKey::random_from_rng(OsRng); + let secret = PrivateKey::random_from_rng(OsRng); let message = b"test message"; let signature = xeddsa_sign(&secret, message, OsRng); - let wrong_secret = X25519PrivateKey::random_from_rng(OsRng); - let wrong_public = X25519PublicKey::from(&wrong_secret); + let wrong_secret = PrivateKey::random_from_rng(OsRng); + let wrong_public = PublicKey::from(&wrong_secret); assert_eq!( xeddsa_verify(&wrong_public, message, &signature), @@ -107,8 +107,8 @@ mod tests { #[test] fn test_wrong_message_fails() { - let secret = X25519PrivateKey::random_from_rng(OsRng); - let public = X25519PublicKey::from(&secret); + let secret = PrivateKey::random_from_rng(OsRng); + let public = PublicKey::from(&secret); let message = b"test message"; let signature = xeddsa_sign(&secret, message, OsRng); @@ -121,8 +121,8 @@ mod tests { #[test] fn test_corrupted_signature_fails() { - let secret = X25519PrivateKey::random_from_rng(OsRng); - let public = X25519PublicKey::from(&secret); + let secret = PrivateKey::random_from_rng(OsRng); + let public = PublicKey::from(&secret); let message = b"test message"; let mut signature = xeddsa_sign(&secret, message, OsRng); diff --git a/double-ratchets/src/ffi/doubleratchet.rs b/double-ratchets/src/ffi/doubleratchet.rs index e3953f3..96f816d 100644 --- a/double-ratchets/src/ffi/doubleratchet.rs +++ b/double-ratchets/src/ffi/doubleratchet.rs @@ -1,4 +1,4 @@ -use crypto::X25519PublicKey; +use crypto::PublicKey; use safer_ffi::prelude::*; use crate::{ @@ -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, X25519PublicKey::from(remote_pub)); + let state = RatchetState::init_sender(shared_secret, PublicKey::from(remote_pub)); Box::new(FFIRatchetState(state)).into() } diff --git a/double-ratchets/src/keypair.rs b/double-ratchets/src/keypair.rs index 724fbca..d154989 100644 --- a/double-ratchets/src/keypair.rs +++ b/double-ratchets/src/keypair.rs @@ -1,4 +1,4 @@ -use crypto::{X25519PrivateKey, X25519PublicKey}; +use crypto::{PrivateKey, PublicKey}; use rand_core::OsRng; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -6,22 +6,22 @@ use crate::types::SharedSecret; #[derive(Clone, Zeroize, ZeroizeOnDrop)] pub struct InstallationKeyPair { - secret: X25519PrivateKey, - public: X25519PublicKey, + secret: PrivateKey, + public: PublicKey, } impl InstallationKeyPair { pub fn generate() -> Self { - let secret = X25519PrivateKey::random_from_rng(OsRng); - let public = X25519PublicKey::from(&secret); + let secret = PrivateKey::random_from_rng(OsRng); + let public = PublicKey::from(&secret); Self { secret, public } } - pub fn dh(&self, their_public: &X25519PublicKey) -> SharedSecret { + pub fn dh(&self, their_public: &PublicKey) -> SharedSecret { self.secret.diffie_hellman(their_public).to_bytes() } - pub fn public(&self) -> &X25519PublicKey { + pub fn public(&self) -> &PublicKey { &self.public } @@ -32,15 +32,15 @@ impl InstallationKeyPair { /// Import the secret key from raw bytes. pub fn from_secret_bytes(bytes: [u8; 32]) -> Self { - let secret = X25519PrivateKey::from(bytes); - let public = X25519PublicKey::from(&secret); + let secret = PrivateKey::from(bytes); + let public = PublicKey::from(&secret); Self { secret, public } } } -impl From for InstallationKeyPair { - fn from(value: X25519PrivateKey) -> Self { - let public = X25519PublicKey::from(&value); +impl From for InstallationKeyPair { + fn from(value: PrivateKey) -> Self { + let public = PublicKey::from(&value); Self { secret: value, public, diff --git a/double-ratchets/src/state.rs b/double-ratchets/src/state.rs index 87b0665..1ec4936 100644 --- a/double-ratchets/src/state.rs +++ b/double-ratchets/src/state.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, marker::PhantomData}; -use crypto::X25519PublicKey; +use crypto::PublicKey; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError}; use zeroize::{Zeroize, Zeroizing}; @@ -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<(X25519PublicKey, u32), MessageKey>, + pub skipped_keys: HashMap<(PublicKey, 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(X25519PublicKey::from); + let dh_remote = reader.read_option()?.map(PublicKey::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 = X25519PublicKey::from(reader.read_array::<32>()?); + let pk = PublicKey::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: X25519PublicKey, + pub dh_pub: PublicKey, 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: X25519PublicKey) -> Self { + pub fn init_sender(shared_secret: SharedSecret, remote_pub: PublicKey) -> 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: X25519PublicKey) { + pub fn dh_ratchet_receive(&mut self, remote_pub: PublicKey) { 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 = X25519PublicKey::from(tampered_pub_bytes); + header.dh_pub = PublicKey::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 4ec8607..a7f18df 100644 --- a/double-ratchets/src/storage/session.rs +++ b/double-ratchets/src/storage/session.rs @@ -1,6 +1,6 @@ //! Session wrapper for automatic state persistence. -use crypto::X25519PublicKey; +use crypto::PublicKey; use crate::{ InstallationKeyPair, SessionError, @@ -54,7 +54,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> { storage: &'a mut RatchetStorage, conversation_id: &str, shared_secret: SharedSecret, - remote_pub: X25519PublicKey, + remote_pub: PublicKey, ) -> Result { if storage.exists(conversation_id)? { return Err(SessionError::ConvAlreadyExists(conversation_id.to_string())); diff --git a/double-ratchets/src/storage/types.rs b/double-ratchets/src/storage/types.rs index 93f5a3a..df54535 100644 --- a/double-ratchets/src/storage/types.rs +++ b/double-ratchets/src/storage/types.rs @@ -1,5 +1,5 @@ //! Storage types for ratchet state. -use crypto::X25519PublicKey; +use crypto::PublicKey; use crate::{ hkdf::HkdfInfo, @@ -42,13 +42,13 @@ impl RatchetStateRecord { use std::marker::PhantomData; let dh_self = InstallationKeyPair::from_secret_bytes(self.dh_self_secret); - let dh_remote = self.dh_remote.map(X25519PublicKey::from); + let dh_remote = self.dh_remote.map(PublicKey::from); - let skipped: HashMap<(X25519PublicKey, u32), MessageKey> = skipped_keys + let skipped: HashMap<(PublicKey, u32), MessageKey> = skipped_keys .into_iter() .map(|sk| { ( - (X25519PublicKey::from(sk.public_key), sk.msg_num), + (PublicKey::from(sk.public_key), sk.msg_num), sk.message_key, ) }) From 69541ea5fb2393a57fdb58ea847d5ee3afc4e347 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:47:35 -0800 Subject: [PATCH 9/9] Alias x25519_dalek::PublicKey to DrPublicKey --- crypto/src/keys.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index 4986785..cfcdef9 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -2,32 +2,33 @@ pub use generic_array::{GenericArray, typenum::U32}; use rand_core::{CryptoRng, OsRng, RngCore}; use std::{fmt::Debug, ops::Deref}; +use x25519_dalek::{PublicKey as DrPublicKey, SharedSecret, StaticSecret as DrPrivateKey}; use xeddsa::xed25519::{PrivateKey as EdPrivateKey, PublicKey as EdPublicKey}; use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq, Zeroize)] // TODO: (!) Zeroize only required by InstallationKeyPair -pub struct PublicKey(x25519_dalek::PublicKey); +pub struct PublicKey(DrPublicKey); -impl From for PublicKey { - fn from(value: x25519_dalek::PublicKey) -> Self { +impl From for PublicKey { + fn from(value: DrPublicKey) -> Self { Self(value) } } impl From<&PrivateKey> for PublicKey { fn from(value: &PrivateKey) -> Self { - Self(x25519_dalek::PublicKey::from(&value.0)) + Self(DrPublicKey::from(&value.0)) } } impl From<[u8; 32]> for PublicKey { fn from(value: [u8; 32]) -> Self { - Self(x25519_dalek::PublicKey::from(value)) + Self(DrPublicKey::from(value)) } } impl Deref for PublicKey { - type Target = x25519_dalek::PublicKey; + type Target = DrPublicKey; fn deref(&self) -> &Self::Target { &self.0 } @@ -46,11 +47,11 @@ impl From<&PublicKey> for EdPublicKey { } #[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct PrivateKey(x25519_dalek::StaticSecret); +pub struct PrivateKey(DrPrivateKey); impl PrivateKey { pub fn random_from_rng(csprng: T) -> Self { - Self(x25519_dalek::StaticSecret::random_from_rng(csprng)) + Self(DrPrivateKey::random_from_rng(csprng)) } //TODO: Remove. Force internal callers provide Rng to make deterministic testing possible @@ -61,12 +62,12 @@ impl PrivateKey { impl From<[u8; 32]> for PrivateKey { fn from(value: [u8; 32]) -> Self { - Self(x25519_dalek::StaticSecret::from(value)) + Self(DrPrivateKey::from(value)) } } impl Deref for PrivateKey { - type Target = x25519_dalek::StaticSecret; + type Target = DrPrivateKey; fn deref(&self) -> &Self::Target { &self.0 } @@ -118,10 +119,10 @@ impl From> for SymmetricKey32 { } } -impl From<&x25519_dalek::SharedSecret> for SymmetricKey32 { +impl From<&SharedSecret> for SymmetricKey32 { // This relies on the feature 'zeroize' being set for x25519-dalek. // If not the SharedSecret will need to manually zeroized - fn from(value: &x25519_dalek::SharedSecret) -> Self { + fn from(value: &SharedSecret) -> Self { value.to_bytes().into() } }