Remove InstallationKeyPair

This commit is contained in:
Jazz Turner-Baggs 2026-01-30 15:57:18 -08:00
parent 143cb38052
commit 3797eca0bf
No known key found for this signature in database
13 changed files with 70 additions and 105 deletions

View File

@ -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 {

View File

@ -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<PrivateV1Domain> =
RatchetState::init_sender(shared_secret, bob_dh.public().clone());
RatchetState::init_sender(shared_secret, bob_dh.public_key());
let mut bob: RatchetState<PrivateV1Domain> = RatchetState::init_receiver(shared_secret, bob_dh);
let (ciphertext, header) = alice.encrypt_message(b"Hello Bob!");

View File

@ -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<DefaultDomain> =
RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
RatchetState::init_sender(shared_secret, bob_keypair.public_key());
let bob_state: RatchetState<DefaultDomain> =
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<DefaultDomain> =
RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
RatchetState::init_sender(shared_secret, bob_keypair.public_key());
let bob_state: RatchetState<DefaultDomain> =
RatchetState::init_receiver(shared_secret, bob_keypair);

View File

@ -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<PrivateV1Domain> =
RatchetState::init_sender(shared_secret, bob_dh.public().clone());
RatchetState::init_sender(shared_secret, bob_dh.public_key().clone());
let mut bob: RatchetState<PrivateV1Domain> = RatchetState::init_receiver(shared_secret, bob_dh);
let (ciphertext, header) = alice.encrypt_message(b"Hello Bob!");

View File

@ -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();

View File

@ -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<FFIInstallationKeyPair> {
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]

View File

@ -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 }
}
}

View File

@ -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};

View File

@ -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<D: HkdfInfo = DefaultDomain> {
pub sending_chain: Option<ChainKey>,
pub receiving_chain: Option<ChainKey>,
pub dh_self: InstallationKeyPair,
pub dh_self: DhPrivateKey,
pub dh_remote: Option<PublicKey32>,
pub msg_send: u32,
@ -104,8 +103,7 @@ impl<D: HkdfInfo> RatchetState<D> {
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<D: HkdfInfo> RatchetState<D> {
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<D: HkdfInfo> RatchetState<D> {
///
/// 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::<D>(&shared_secret, &dh_out);
let dh_out = dh_self.diffie_hellman(&remote_pub);
let (root_key, sending_chain) = kdf_root::<D>(&shared_secret, dh_out.as_bytes());
Self {
root_key,
@ -271,7 +269,7 @@ impl<D: HkdfInfo> RatchetState<D> {
/// # 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<D: HkdfInfo> RatchetState<D> {
///
/// * `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::<D>(&self.root_key, &dh_out);
let dh_out = self.dh_self.diffie_hellman(&remote_pub);
let (new_root, recv_chain) = kdf_root::<D>(&self.root_key, dh_out.as_bytes());
self.root_key = new_root;
self.receiving_chain = Some(recv_chain);
@ -312,9 +310,9 @@ impl<D: HkdfInfo> RatchetState<D> {
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::<D>(&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::<D>(&self.root_key, dh_out.as_bytes());
self.root_key = new_root;
self.sending_chain = Some(send_chain);
@ -345,7 +343,7 @@ impl<D: HkdfInfo> RatchetState<D> {
*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()
);
}

View File

@ -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<String>,
shared_secret: SharedSecret,
dh_self: InstallationKeyPair,
dh_self: DhPrivateKey,
) -> Result<Self, StorageError> {
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<DefaultDomain> =
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<DefaultDomain> =
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<DefaultDomain> =
RatchetState::init_sender(shared_secret, bob_keypair.public().clone());
RatchetState::init_sender(shared_secret, bob_keypair.public_key());
let bob: RatchetState<DefaultDomain> =
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
{

View File

@ -280,7 +280,7 @@ fn blob_to_array<const N: usize>(blob: Vec<u8>) -> [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<DefaultDomain>, RatchetState<DefaultDomain>) {
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()
);
}

View File

@ -53,11 +53,10 @@ impl<D: HkdfInfo> From<&RatchetState<D>> for RatchetStateRecord {
impl RatchetStateRecord {
pub fn into_ratchet_state<D: HkdfInfo>(self, skipped_keys: Vec<SkippedKey>) -> RatchetState<D> {
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

View File

@ -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).