From 3797eca0bfd87ac5960488507aedc2c48f05e954 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Fri, 30 Jan 2026 15:57:18 -0800 Subject: [PATCH] Remove InstallationKeyPair --- crypto/src/keys.rs | 5 +++ .../examples/double_ratchet_basic.rs | 7 ++-- double-ratchets/examples/out_of_order_demo.rs | 14 +++---- .../examples/serialization_demo.rs | 6 +-- double-ratchets/examples/storage_demo.rs | 10 ++--- double-ratchets/src/ffi/key.rs | 9 ++-- double-ratchets/src/keypair.rs | 39 ----------------- double-ratchets/src/lib.rs | 2 - double-ratchets/src/state.rs | 42 +++++++++---------- double-ratchets/src/storage/session.rs | 23 +++++----- double-ratchets/src/storage/sqlite.rs | 10 ++--- double-ratchets/src/storage/types.rs | 3 +- double-ratchets/src/types.rs | 5 +++ 13 files changed, 70 insertions(+), 105 deletions(-) delete mode 100644 double-ratchets/src/keypair.rs diff --git a/crypto/src/keys.rs b/crypto/src/keys.rs index dec77d3..26e83e8 100644 --- a/crypto/src/keys.rs +++ b/crypto/src/keys.rs @@ -53,6 +53,11 @@ impl PrivateKey32 { pub fn random() -> PrivateKey32 { Self::random_from_rng(&mut OsRng) } + + // Convenience function to generate a PublicKey32 + pub fn public_key(&self) -> PublicKey32 { + PublicKey32::from(self) + } } impl From<[u8; 32]> for PrivateKey32 { diff --git a/double-ratchets/examples/double_ratchet_basic.rs b/double-ratchets/examples/double_ratchet_basic.rs index 297cc10..0533af6 100644 --- a/double-ratchets/examples/double_ratchet_basic.rs +++ b/double-ratchets/examples/double_ratchet_basic.rs @@ -1,13 +1,14 @@ -use double_ratchets::{InstallationKeyPair, RatchetState, hkdf::PrivateV1Domain}; +use crypto::PrivateKey32; +use double_ratchets::{RatchetState, hkdf::PrivateV1Domain}; fn main() { // === Initial shared secret (X3DH / prekey result in real systems) === let shared_secret = [42u8; 32]; - let bob_dh = InstallationKeyPair::generate(); + let bob_dh = PrivateKey32::random(); let mut alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_dh.public().clone()); + RatchetState::init_sender(shared_secret, bob_dh.public_key()); let mut bob: RatchetState = RatchetState::init_receiver(shared_secret, bob_dh); let (ciphertext, header) = alice.encrypt_message(b"Hello Bob!"); diff --git a/double-ratchets/examples/out_of_order_demo.rs b/double-ratchets/examples/out_of_order_demo.rs index a2dbb4d..1d0f35e 100644 --- a/double-ratchets/examples/out_of_order_demo.rs +++ b/double-ratchets/examples/out_of_order_demo.rs @@ -1,11 +1,11 @@ //! Demonstrates out-of-order message handling with skipped keys persistence. //! //! Run with: cargo run --example out_of_order_demo --features storage - +#[allow(unused_imports)] +use crypto::PrivateKey32; #[cfg(feature = "storage")] use double_ratchets::{ - InstallationKeyPair, RatchetState, SqliteStorage, StorageConfig, hkdf::DefaultDomain, - state::Header, + RatchetState, SqliteStorage, StorageConfig, hkdf::DefaultDomain, state::Header, }; fn main() { @@ -22,10 +22,10 @@ fn run_demo() { // Setup let shared_secret = [0x42u8; 32]; - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = PrivateKey32::random(); let alice_state: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); let bob_state: RatchetState = RatchetState::init_receiver(shared_secret, bob_keypair); @@ -81,9 +81,9 @@ fn run_demo() { .expect("Failed to create storage"); // Re-setup - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = PrivateKey32::random(); let alice_state: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); let bob_state: RatchetState = RatchetState::init_receiver(shared_secret, bob_keypair); diff --git a/double-ratchets/examples/serialization_demo.rs b/double-ratchets/examples/serialization_demo.rs index 76a5878..455b116 100644 --- a/double-ratchets/examples/serialization_demo.rs +++ b/double-ratchets/examples/serialization_demo.rs @@ -1,13 +1,13 @@ -use double_ratchets::{InstallationKeyPair, RatchetState, hkdf::PrivateV1Domain}; +use double_ratchets::{RatchetState, hkdf::PrivateV1Domain, types::DhPrivateKey}; fn main() { // === Initial shared secret (X3DH / prekey result in real systems) === let shared_secret = [42u8; 32]; - let bob_dh = InstallationKeyPair::generate(); + let bob_dh = DhPrivateKey::random(); let mut alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_dh.public().clone()); + RatchetState::init_sender(shared_secret, bob_dh.public_key().clone()); let mut bob: RatchetState = RatchetState::init_receiver(shared_secret, bob_dh); let (ciphertext, header) = alice.encrypt_message(b"Hello Bob!"); diff --git a/double-ratchets/examples/storage_demo.rs b/double-ratchets/examples/storage_demo.rs index ce05bd4..de8ed5c 100644 --- a/double-ratchets/examples/storage_demo.rs +++ b/double-ratchets/examples/storage_demo.rs @@ -3,10 +3,10 @@ //! Run with: cargo run --example storage_demo --features storage //! For SQLCipher: cargo run --example storage_demo --features sqlcipher +#[allow(unused_imports)] +use crypto::PrivateKey32; #[cfg(feature = "storage")] -use double_ratchets::{ - InstallationKeyPair, RatchetSession, SqliteStorage, StorageConfig, hkdf::PrivateV1Domain, -}; +use double_ratchets::{RatchetSession, SqliteStorage, StorageConfig, hkdf::PrivateV1Domain}; fn main() { println!("=== Double Ratchet Storage Demo ===\n"); @@ -140,7 +140,7 @@ fn ensure_tmp_directory() { fn run_conversation(alice_storage: &mut SqliteStorage, bob_storage: &mut SqliteStorage) { // === Setup: Simulate X3DH key exchange === let shared_secret = [0x42u8; 32]; // In reality, this comes from X3DH - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = PrivateKey32::random(); let conv_id = "conv1"; @@ -148,7 +148,7 @@ fn run_conversation(alice_storage: &mut SqliteStorage, bob_storage: &mut SqliteS alice_storage, conv_id, shared_secret, - bob_keypair.public().clone(), + bob_keypair.public_key(), ) .unwrap(); diff --git a/double-ratchets/src/ffi/key.rs b/double-ratchets/src/ffi/key.rs index ee8a496..a355e8e 100644 --- a/double-ratchets/src/ffi/key.rs +++ b/double-ratchets/src/ffi/key.rs @@ -1,19 +1,18 @@ +use crypto::PrivateKey32; use safer_ffi::prelude::*; -use crate::InstallationKeyPair; - #[derive_ReprC] #[repr(opaque)] -pub struct FFIInstallationKeyPair(pub(crate) InstallationKeyPair); +pub struct FFIInstallationKeyPair(pub(crate) PrivateKey32); #[ffi_export] fn installation_key_pair_generate() -> repr_c::Box { - Box::new(FFIInstallationKeyPair(InstallationKeyPair::generate())).into() + Box::new(FFIInstallationKeyPair(PrivateKey32::random())).into() } #[ffi_export] fn installation_key_pair_public(keypair: &FFIInstallationKeyPair) -> [u8; 32] { - keypair.0.public().clone().to_bytes() + keypair.0.public_key().to_bytes() } #[ffi_export] diff --git a/double-ratchets/src/keypair.rs b/double-ratchets/src/keypair.rs deleted file mode 100644 index 4f3a6ac..0000000 --- a/double-ratchets/src/keypair.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crypto::{PrivateKey32, PublicKey32}; -use rand_core::OsRng; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -use crate::types::SharedSecret; - -#[derive(Clone, Zeroize, ZeroizeOnDrop)] -pub struct InstallationKeyPair { - secret: PrivateKey32, - public: PublicKey32, -} - -impl InstallationKeyPair { - pub fn generate() -> Self { - let secret = PrivateKey32::random_from_rng(OsRng); - let public = PublicKey32::from(&secret); - Self { secret, public } - } - - pub fn dh(&self, their_public: &PublicKey32) -> SharedSecret { - self.secret.diffie_hellman(their_public).to_bytes() - } - - pub fn public(&self) -> &PublicKey32 { - &self.public - } - - /// Export the secret key as raw bytes for serialization/storage. - pub fn secret_bytes(&self) -> &[u8; 32] { - self.secret.as_bytes() - } - - /// Import the secret key from raw bytes. - pub fn from_secret_bytes(bytes: [u8; 32]) -> Self { - let secret = PrivateKey32::from(bytes); - let public = PublicKey32::from(&secret); - Self { secret, public } - } -} diff --git a/double-ratchets/src/lib.rs b/double-ratchets/src/lib.rs index f2cd789..d7c609c 100644 --- a/double-ratchets/src/lib.rs +++ b/double-ratchets/src/lib.rs @@ -2,14 +2,12 @@ pub mod aead; pub mod errors; pub mod ffi; pub mod hkdf; -pub mod keypair; pub mod reader; pub mod state; #[cfg(feature = "storage")] pub mod storage; pub mod types; -pub use keypair::InstallationKeyPair; pub use state::{Header, RatchetState}; #[cfg(feature = "storage")] pub use storage::{RatchetSession, SessionError, SqliteStorage, StorageConfig, StorageError}; diff --git a/double-ratchets/src/state.rs b/double-ratchets/src/state.rs index ea1b2a3..ed9f15b 100644 --- a/double-ratchets/src/state.rs +++ b/double-ratchets/src/state.rs @@ -8,9 +8,8 @@ use crate::{ aead::{decrypt, encrypt}, errors::RatchetError, hkdf::{DefaultDomain, HkdfInfo, kdf_chain, kdf_root}, - keypair::InstallationKeyPair, reader::Reader, - types::{ChainKey, MessageKey, Nonce, RootKey, SharedSecret}, + types::{ChainKey, DhPrivateKey, MessageKey, Nonce, RootKey, SharedSecret}, }; /// Current binary format version. @@ -28,7 +27,7 @@ pub struct RatchetState { pub sending_chain: Option, pub receiving_chain: Option, - pub dh_self: InstallationKeyPair, + pub dh_self: DhPrivateKey, pub dh_remote: Option, pub msg_send: u32, @@ -104,8 +103,7 @@ impl RatchetState { write_option(&mut buf, self.sending_chain); write_option(&mut buf, self.receiving_chain); - let dh_secret = self.dh_self.secret_bytes(); - buf.extend_from_slice(dh_secret); + buf.extend_from_slice(self.dh_self.as_bytes()); write_option(&mut buf, dh_remote); @@ -141,7 +139,7 @@ impl RatchetState { let receiving_chain = reader.read_option()?; let mut dh_self_bytes: [u8; 32] = reader.read_array()?; - let dh_self = InstallationKeyPair::from_secret_bytes(dh_self_bytes); + let dh_self = DhPrivateKey::from(dh_self_bytes); dh_self_bytes.zeroize(); let dh_remote = reader.read_option()?.map(PublicKey32::from); @@ -234,11 +232,11 @@ impl RatchetState { /// /// A new `RatchetState` ready to send the first message. pub fn init_sender(shared_secret: SharedSecret, remote_pub: PublicKey32) -> Self { - let dh_self = InstallationKeyPair::generate(); + let dh_self = DhPrivateKey::random(); // Initial DH - let dh_out = dh_self.dh(&remote_pub); - let (root_key, sending_chain) = kdf_root::(&shared_secret, &dh_out); + let dh_out = dh_self.diffie_hellman(&remote_pub); + let (root_key, sending_chain) = kdf_root::(&shared_secret, dh_out.as_bytes()); Self { root_key, @@ -271,7 +269,7 @@ impl RatchetState { /// # Returns /// /// A new `RatchetState` ready to receive the first message. - pub fn init_receiver(shared_secret: SharedSecret, dh_self: InstallationKeyPair) -> Self { + pub fn init_receiver(shared_secret: SharedSecret, dh_self: DhPrivateKey) -> Self { Self { root_key: shared_secret, @@ -297,8 +295,8 @@ impl RatchetState { /// /// * `remote_pub` - The new DH public key from the sender. pub fn dh_ratchet_receive(&mut self, remote_pub: PublicKey32) { - let dh_out = self.dh_self.dh(&remote_pub); - let (new_root, recv_chain) = kdf_root::(&self.root_key, &dh_out); + let dh_out = self.dh_self.diffie_hellman(&remote_pub); + let (new_root, recv_chain) = kdf_root::(&self.root_key, dh_out.as_bytes()); self.root_key = new_root; self.receiving_chain = Some(recv_chain); @@ -312,9 +310,9 @@ impl RatchetState { pub fn dh_ratchet_send(&mut self) { let remote = self.dh_remote.expect("no remote DH key"); - self.dh_self = InstallationKeyPair::generate(); - let dh_out = self.dh_self.dh(&remote); - let (new_root, send_chain) = kdf_root::(&self.root_key, &dh_out); + self.dh_self = DhPrivateKey::random(); + let dh_out = self.dh_self.diffie_hellman(&remote); + let (new_root, send_chain) = kdf_root::(&self.root_key, dh_out.as_bytes()); self.root_key = new_root; self.sending_chain = Some(send_chain); @@ -345,7 +343,7 @@ impl RatchetState { *chain = next_chain; let header = Header { - dh_pub: self.dh_self.public().clone(), + dh_pub: PublicKey32::from(&self.dh_self), msg_num: self.msg_send, prev_chain_len: self.prev_chain_len, }; @@ -478,10 +476,10 @@ mod tests { let shared_secret = [0x42; 32]; // Bob generates his long-term keypair - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = DhPrivateKey::random(); // Alice initializes as sender, knowing Bob's public key - let alice = RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + let alice = RatchetState::init_sender(shared_secret, bob_keypair.public_key()); // Bob initializes as receiver with his private key let bob = RatchetState::init_receiver(shared_secret, bob_keypair); @@ -554,7 +552,7 @@ mod tests { bob.decrypt_message(&ct, header).unwrap(); // Bob performs DH ratchet by trying to send - let old_bob_pub = bob.dh_self.public().clone(); + let old_bob_pub = bob.dh_self.public_key(); let (bob_ct, bob_header) = { let mut b = bob.clone(); b.encrypt_message(b"reply") @@ -562,7 +560,7 @@ mod tests { assert_ne!(bob_header.dh_pub, old_bob_pub); // Alice receives Bob's message with new DH pub → ratchets - let old_alice_pub = alice.dh_self.public().clone(); + let old_alice_pub = alice.dh_self.public_key(); let old_root = alice.root_key; // Even if decrypt fails (wrong key), ratchet should happen @@ -688,8 +686,8 @@ mod tests { restored.dh_remote.map(|pk| pk.to_bytes()) ); assert_eq!( - alice.dh_self.public().to_bytes(), - restored.dh_self.public().to_bytes() + alice.dh_self.public_key().to_bytes(), + restored.dh_self.public_key().to_bytes() ); } diff --git a/double-ratchets/src/storage/session.rs b/double-ratchets/src/storage/session.rs index 387a41c..36f16cb 100644 --- a/double-ratchets/src/storage/session.rs +++ b/double-ratchets/src/storage/session.rs @@ -1,11 +1,10 @@ use crypto::PublicKey32; use crate::{ - InstallationKeyPair, errors::RatchetError, hkdf::HkdfInfo, state::{Header, RatchetState}, - types::SharedSecret, + types::{DhPrivateKey, SharedSecret}, }; use super::{SqliteStorage, StorageError}; @@ -93,7 +92,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> { storage: &'a mut SqliteStorage, conversation_id: impl Into, shared_secret: SharedSecret, - dh_self: InstallationKeyPair, + dh_self: DhPrivateKey, ) -> Result { let conversation_id = conversation_id.into(); if storage.exists(&conversation_id)? { @@ -180,7 +179,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> { #[cfg(test)] mod tests { use super::*; - use crate::{hkdf::DefaultDomain, keypair::InstallationKeyPair, storage::StorageConfig}; + use crate::{hkdf::DefaultDomain, storage::StorageConfig, types::DhPrivateKey}; fn create_test_storage() -> SqliteStorage { SqliteStorage::new(StorageConfig::InMemory).unwrap() @@ -191,9 +190,9 @@ mod tests { let mut storage = create_test_storage(); let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = DhPrivateKey::random(); let alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); // Create session { @@ -214,9 +213,9 @@ mod tests { let mut storage = create_test_storage(); let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = DhPrivateKey::random(); let alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); // Create and encrypt { @@ -238,9 +237,9 @@ mod tests { let mut storage = create_test_storage(); let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); + let bob_keypair = DhPrivateKey::random(); let alice: RatchetState = - RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + RatchetState::init_sender(shared_secret, bob_keypair.public_key()); let bob: RatchetState = RatchetState::init_receiver(shared_secret, bob_keypair); @@ -278,8 +277,8 @@ mod tests { let mut storage = create_test_storage(); let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); - let bob_pub = bob_keypair.public().clone(); + let bob_keypair = DhPrivateKey::random(); + let bob_pub = bob_keypair.public_key(); // First call creates { diff --git a/double-ratchets/src/storage/sqlite.rs b/double-ratchets/src/storage/sqlite.rs index 2c061f8..f2e9ba1 100644 --- a/double-ratchets/src/storage/sqlite.rs +++ b/double-ratchets/src/storage/sqlite.rs @@ -280,7 +280,7 @@ fn blob_to_array(blob: Vec) -> [u8; N] { #[cfg(test)] mod tests { use super::*; - use crate::{hkdf::DefaultDomain, keypair::InstallationKeyPair}; + use crate::hkdf::DefaultDomain; fn create_test_storage() -> SqliteStorage { SqliteStorage::new(StorageConfig::InMemory).unwrap() @@ -288,8 +288,8 @@ mod tests { fn create_test_state() -> (RatchetState, RatchetState) { let shared_secret = [0x42; 32]; - let bob_keypair = InstallationKeyPair::generate(); - let alice = RatchetState::init_sender(shared_secret, bob_keypair.public().clone()); + let bob_keypair = DhPrivateKey::random(); + let alice = RatchetState::init_sender(shared_secret, bob_keypair.public_key()); let bob = RatchetState::init_receiver(shared_secret, bob_keypair); (alice, bob) } @@ -309,8 +309,8 @@ mod tests { assert_eq!(alice.msg_recv, loaded.msg_recv); assert_eq!(alice.prev_chain_len, loaded.prev_chain_len); assert_eq!( - alice.dh_self.public().to_bytes(), - loaded.dh_self.public().to_bytes() + alice.dh_self.public_key().as_bytes(), + loaded.dh_self.public_key().as_bytes() ); } diff --git a/double-ratchets/src/storage/types.rs b/double-ratchets/src/storage/types.rs index 8d3f9de..9a60416 100644 --- a/double-ratchets/src/storage/types.rs +++ b/double-ratchets/src/storage/types.rs @@ -53,11 +53,10 @@ impl From<&RatchetState> for RatchetStateRecord { impl RatchetStateRecord { pub fn into_ratchet_state(self, skipped_keys: Vec) -> RatchetState { - use crate::keypair::InstallationKeyPair; use std::collections::HashMap; use std::marker::PhantomData; - let dh_self = InstallationKeyPair::from_secret_bytes(self.dh_self_secret); + let dh_self = DhPrivateKey::from(self.dh_self_secret); let dh_remote = self.dh_remote.map(PublicKey32::from); let skipped: HashMap<(PublicKey32, u32), MessageKey> = skipped_keys diff --git a/double-ratchets/src/types.rs b/double-ratchets/src/types.rs index bbad3a8..db0edb1 100644 --- a/double-ratchets/src/types.rs +++ b/double-ratchets/src/types.rs @@ -1,3 +1,8 @@ +use crypto::PrivateKey32; + +/// Type alias for diffie-hellman private keys. +pub type DhPrivateKey = PrivateKey32; + /// Type alias for root keys (32 bytes). pub type RootKey = [u8; 32]; /// Type alias for chain keys (sending/receiving, 32 bytes).