Update PublicKey -> X25519PublicKey

This commit is contained in:
Jazz Turner-Baggs 2026-02-12 16:02:49 -08:00
parent 28d74b7404
commit 5121ba4eed
No known key found for this signature in database
17 changed files with 100 additions and 85 deletions

1
Cargo.lock generated
View File

@ -225,6 +225,7 @@ version = "0.0.1"
dependencies = [ dependencies = [
"blake2", "blake2",
"chacha20poly1305", "chacha20poly1305",
"crypto",
"hkdf", "hkdf",
"rand", "rand",
"rand_core", "rand_core",

View File

@ -12,3 +12,6 @@ members = [
[workspace.dependencies] [workspace.dependencies]
blake2 = "0.10" blake2 = "0.10"
storage = { path = "storage" } storage = { path = "storage" }
# Internal crates
crypto = { path = "crypto" }

View File

@ -7,10 +7,10 @@ use chat_proto::logoschat::{
encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption}, encryption::{Doubleratchet, EncryptedPayload, encrypted_payload::Encryption},
}; };
use crypto::SymmetricKey32; use crypto::SymmetricKey32;
use crypto::X25519PublicKey;
use double_ratchets::{Header, InstallationKeyPair, RatchetState}; use double_ratchets::{Header, InstallationKeyPair, RatchetState};
use prost::{Message, bytes::Bytes}; use prost::{Message, bytes::Bytes};
use std::fmt::Debug; use std::fmt::Debug;
use x25519_dalek::PublicKey;
use crate::{ use crate::{
conversation::{ChatError, ConversationId, Convo, Id}, conversation::{ChatError, ConversationId, Convo, Id},
@ -60,7 +60,7 @@ pub struct PrivateV1Convo {
} }
impl 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 base_convo_id = BaseConvoId::new(&seed_key);
let local_convo_id = base_convo_id.id_for_participant(Role::Initiator); let local_convo_id = base_convo_id.id_for_participant(Role::Initiator);
let remote_convo_id = base_convo_id.id_for_participant(Role::Responder); 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())); 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 let byte_arr: [u8; 32] = dr_header
.dh .dh
.to_vec() .to_vec()
.try_into() .try_into()
.map_err(|_| EncryptionError::Decryption("invalid public key length".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 // Build the Header that DR impl expects
let header = Header { let header = Header {
@ -230,7 +230,7 @@ mod tests {
let saro = StaticSecret::random(); let saro = StaticSecret::random();
let raya = 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 seed_key = saro.diffie_hellman(&pub_raya);
let send_content_bytes = vec![0, 2, 4, 6, 8]; let send_content_bytes = vec![0, 2, 4, 6, 8];

View File

@ -1,11 +1,13 @@
pub use crypto::X25519PublicKey;
pub use x25519_dalek::StaticSecret;
use prost::bytes::Bytes; use prost::bytes::Bytes;
pub use x25519_dalek::{PublicKey, StaticSecret};
pub trait CopyBytes { pub trait CopyBytes {
fn copy_to_bytes(&self) -> Bytes; fn copy_to_bytes(&self) -> Bytes;
} }
impl CopyBytes for PublicKey { impl CopyBytes for X25519PublicKey {
fn copy_to_bytes(&self) -> Bytes { fn copy_to_bytes(&self) -> Bytes {
Bytes::copy_from_slice(self.as_bytes()) Bytes::copy_from_slice(self.as_bytes())
} }

View File

@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
use crate::crypto::{PublicKey, StaticSecret}; use crate::crypto::{StaticSecret, X25519PublicKey};
pub struct Identity { pub struct Identity {
secret: StaticSecret, secret: StaticSecret,
@ -22,8 +22,8 @@ impl Identity {
} }
} }
pub fn public_key(&self) -> PublicKey { pub fn public_key(&self) -> X25519PublicKey {
PublicKey::from(&self.secret) X25519PublicKey::from(&self.secret)
} }
pub fn secret(&self) -> &StaticSecret { pub fn secret(&self) -> &StaticSecret {

View File

@ -10,14 +10,14 @@ use crypto::{PrekeyBundle, SymmetricKey32};
use crate::context::Introduction; use crate::context::Introduction;
use crate::conversation::{ChatError, ConversationId, Convo, Id, PrivateV1Convo}; 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::identity::Identity;
use crate::inbox::handshake::InboxHandshake; use crate::inbox::handshake::InboxHandshake;
use crate::proto; use crate::proto;
use crate::types::{AddressedEncryptedPayload, ContentData}; use crate::types::{AddressedEncryptedPayload, ContentData};
/// Compute the deterministic Delivery_address for an installation /// 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 // TODO: Implement Delivery Address
"delivery_address".into() "delivery_address".into()
} }
@ -54,7 +54,7 @@ impl Inbox {
pub fn create_intro_bundle(&mut self) -> Introduction { pub fn create_intro_bundle(&mut self) -> Introduction {
let ephemeral = StaticSecret::random(); let ephemeral = StaticSecret::random();
let ephemeral_key: PublicKey = (&ephemeral).into(); let ephemeral_key: X25519PublicKey = (&ephemeral).into();
self.ephemeral_keys self.ephemeral_keys
.insert(hex::encode(ephemeral_key.as_bytes()), ephemeral); .insert(hex::encode(ephemeral_key.as_bytes()), ephemeral);
@ -173,13 +173,13 @@ impl Inbox {
header: proto::InboxHeaderV1, header: proto::InboxHeaderV1,
bytes: Bytes, bytes: Bytes,
) -> Result<(SymmetricKey32, proto::InboxV1Frame), ChatError> { ) -> Result<(SymmetricKey32, proto::InboxV1Frame), ChatError> {
// Get PublicKeys from protobuf // Get X25519PublicKeys from protobuf
let initator_static = PublicKey::from( let initator_static = X25519PublicKey::from(
<[u8; 32]>::try_from(header.initiator_static.as_ref()) <[u8; 32]>::try_from(header.initiator_static.as_ref())
.map_err(|_| ChatError::BadBundleValue("wrong size - initator static".into()))?, .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()) <[u8; 32]>::try_from(header.initiator_ephemeral.as_ref())
.map_err(|_| ChatError::BadBundleValue("wrong size - initator ephemeral".into()))?, .map_err(|_| ChatError::BadBundleValue("wrong size - initator ephemeral".into()))?,
); );
@ -221,7 +221,7 @@ impl Inbox {
.ok_or(ChatError::UnknownEphemeralKey()) .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 // TODO: Implement ID according to spec
hex::encode(Blake2b512::digest(pubkey)) hex::encode(Blake2b512::digest(pubkey))
} }

View File

@ -5,7 +5,7 @@ use blake2::{
use crypto::{DomainSeparator, PrekeyBundle, SymmetricKey32, X3Handshake}; use crypto::{DomainSeparator, PrekeyBundle, SymmetricKey32, X3Handshake};
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use crate::crypto::{PublicKey, StaticSecret}; use crate::crypto::{StaticSecret, X25519PublicKey};
type Blake2bMac256 = Blake2bMac<U32>; type Blake2bMac256 = Blake2bMac<U32>;
@ -24,7 +24,7 @@ impl InboxHandshake {
identity_keypair: &StaticSecret, identity_keypair: &StaticSecret,
recipient_bundle: &PrekeyBundle, recipient_bundle: &PrekeyBundle,
rng: &mut R, rng: &mut R,
) -> (SymmetricKey32, PublicKey) { ) -> (SymmetricKey32, X25519PublicKey) {
// Perform X3DH handshake to get shared secret // Perform X3DH handshake to get shared secret
let (shared_secret, ephemeral_public) = let (shared_secret, ephemeral_public) =
InboxKeyExchange::initator(identity_keypair, recipient_bundle, rng); InboxKeyExchange::initator(identity_keypair, recipient_bundle, rng);
@ -45,8 +45,8 @@ impl InboxHandshake {
identity_keypair: &StaticSecret, identity_keypair: &StaticSecret,
signed_prekey: &StaticSecret, signed_prekey: &StaticSecret,
onetime_prekey: Option<&StaticSecret>, onetime_prekey: Option<&StaticSecret>,
initiator_identity: &PublicKey, initiator_identity: &X25519PublicKey,
initiator_ephemeral: &PublicKey, initiator_ephemeral: &X25519PublicKey,
) -> SymmetricKey32 { ) -> SymmetricKey32 {
// Perform X3DH to get shared secret // Perform X3DH to get shared secret
let shared_secret = InboxKeyExchange::responder( let shared_secret = InboxKeyExchange::responder(
@ -86,16 +86,16 @@ mod tests {
// Alice (initiator) generates her identity key // Alice (initiator) generates her identity key
let alice_identity = StaticSecret::random_from_rng(rng); 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 // Bob (responder) generates his keys
let bob_identity = StaticSecret::random_from_rng(rng); let bob_identity = StaticSecret::random_from_rng(rng);
let bob_signed_prekey = 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 // Create Bob's prekey bundle
let bob_bundle = PrekeyBundle { let bob_bundle = PrekeyBundle {
identity_key: PublicKey::from(&bob_identity), identity_key: X25519PublicKey::from(&bob_identity),
signed_prekey: bob_signed_prekey_pub, signed_prekey: bob_signed_prekey_pub,
signature: crypto::Ed25519Signature([0u8; 64]), signature: crypto::Ed25519Signature([0u8; 64]),
onetime_prekey: None, onetime_prekey: None,

View File

@ -1,15 +1,15 @@
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
use chat_proto::logoschat::intro::IntroBundle; use chat_proto::logoschat::intro::IntroBundle;
use crypto::Ed25519Signature; use crypto::{Ed25519Signature, X25519PublicKey};
use prost::Message; use prost::Message;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use x25519_dalek::{PublicKey, StaticSecret}; use x25519_dalek::StaticSecret;
use crate::errors::ChatError; use crate::errors::ChatError;
const BUNDLE_PREFIX: &str = "logos_chatintro_1_"; const BUNDLE_PREFIX: &str = "logos_chatintro_1_";
fn intro_binding_message(ephemeral: &PublicKey) -> Vec<u8> { fn intro_binding_message(ephemeral: &X25519PublicKey) -> Vec<u8> {
let mut message = Vec::with_capacity(BUNDLE_PREFIX.len() + 32); let mut message = Vec::with_capacity(BUNDLE_PREFIX.len() + 32);
message.extend_from_slice(BUNDLE_PREFIX.as_bytes()); message.extend_from_slice(BUNDLE_PREFIX.as_bytes());
message.extend_from_slice(ephemeral.as_bytes()); message.extend_from_slice(ephemeral.as_bytes());
@ -18,7 +18,7 @@ fn intro_binding_message(ephemeral: &PublicKey) -> Vec<u8> {
pub(crate) fn sign_intro_binding<R: RngCore + CryptoRng>( pub(crate) fn sign_intro_binding<R: RngCore + CryptoRng>(
secret: &StaticSecret, secret: &StaticSecret,
ephemeral: &PublicKey, ephemeral: &X25519PublicKey,
rng: R, rng: R,
) -> Ed25519Signature { ) -> Ed25519Signature {
let message = intro_binding_message(ephemeral); let message = intro_binding_message(ephemeral);
@ -26,8 +26,8 @@ pub(crate) fn sign_intro_binding<R: RngCore + CryptoRng>(
} }
pub(crate) fn verify_intro_binding( pub(crate) fn verify_intro_binding(
pubkey: &PublicKey, pubkey: &X25519PublicKey,
ephemeral: &PublicKey, ephemeral: &X25519PublicKey,
signature: &Ed25519Signature, signature: &Ed25519Signature,
) -> Result<(), crypto::SignatureError> { ) -> Result<(), crypto::SignatureError> {
let message = intro_binding_message(ephemeral); 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 /// Supplies remote participants with the required keys to use Inbox protocol
pub struct Introduction { pub struct Introduction {
installation_key: PublicKey, installation_key: X25519PublicKey,
ephemeral_key: PublicKey, ephemeral_key: X25519PublicKey,
signature: Ed25519Signature, signature: Ed25519Signature,
} }
@ -45,7 +45,7 @@ impl Introduction {
/// Create a new `Introduction` by signing the ephemeral key with the installation secret. /// Create a new `Introduction` by signing the ephemeral key with the installation secret.
pub(crate) fn new<R: RngCore + CryptoRng>( pub(crate) fn new<R: RngCore + CryptoRng>(
installation_secret: &StaticSecret, installation_secret: &StaticSecret,
ephemeral_key: PublicKey, ephemeral_key: X25519PublicKey,
rng: R, rng: R,
) -> Self { ) -> Self {
let installation_key = installation_secret.into(); 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 &self.installation_key
} }
pub fn ephemeral_key(&self) -> &PublicKey { pub fn ephemeral_key(&self) -> &X25519PublicKey {
&self.ephemeral_key &self.ephemeral_key
} }
@ -126,8 +126,8 @@ impl TryFrom<&[u8]> for Introduction {
.try_into() .try_into()
.map_err(|_| ChatError::BadBundleValue("invalid signature length".into()))?; .map_err(|_| ChatError::BadBundleValue("invalid signature length".into()))?;
let installation_key = PublicKey::from(installation_bytes); let installation_key = X25519PublicKey::from(installation_bytes);
let ephemeral_key = PublicKey::from(ephemeral_bytes); let ephemeral_key = X25519PublicKey::from(ephemeral_bytes);
let signature = Ed25519Signature(signature_bytes); let signature = Ed25519Signature(signature_bytes);
verify_intro_binding(&installation_key, &ephemeral_key, &signature) verify_intro_binding(&installation_key, &ephemeral_key, &signature)
@ -150,7 +150,7 @@ mod tests {
let install_secret = StaticSecret::random_from_rng(OsRng); let install_secret = StaticSecret::random_from_rng(OsRng);
let ephemeral_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) Introduction::new(&install_secret, ephemeral_pub, OsRng)
} }

View File

@ -2,6 +2,6 @@ mod keys;
mod x3dh; mod x3dh;
mod xeddsa_sign; mod xeddsa_sign;
pub use keys::{GenericArray, SymmetricKey32}; pub use keys::{GenericArray, SymmetricKey32, X25519PublicKey};
pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake}; pub use x3dh::{DomainSeparator, PrekeyBundle, X3Handshake};
pub use xeddsa_sign::{Ed25519Signature, SignatureError, xeddsa_sign, xeddsa_verify}; pub use xeddsa_sign::{Ed25519Signature, SignatureError, xeddsa_sign, xeddsa_verify};

View File

@ -3,18 +3,18 @@ use std::marker::PhantomData;
use hkdf::Hkdf; use hkdf::Hkdf;
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use sha2::Sha256; 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; use crate::xeddsa_sign::Ed25519Signature;
/// A prekey bundle containing the public keys needed to initiate an X3DH key exchange. /// A prekey bundle containing the public keys needed to initiate an X3DH key exchange.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PrekeyBundle { pub struct PrekeyBundle {
pub identity_key: PublicKey, pub identity_key: X25519PublicKey,
pub signed_prekey: PublicKey, pub signed_prekey: X25519PublicKey,
pub signature: Ed25519Signature, pub signature: Ed25519Signature,
pub onetime_prekey: Option<PublicKey>, pub onetime_prekey: Option<X25519PublicKey>,
} }
pub trait DomainSeparator { pub trait DomainSeparator {
@ -70,10 +70,10 @@ impl<D: DomainSeparator> X3Handshake<D> {
identity_keypair: &StaticSecret, identity_keypair: &StaticSecret,
recipient_bundle: &PrekeyBundle, recipient_bundle: &PrekeyBundle,
rng: &mut R, rng: &mut R,
) -> (SymmetricKey32, PublicKey) { ) -> (SymmetricKey32, X25519PublicKey) {
// Generate ephemeral key for this handshake (using StaticSecret for multiple DH operations) // Generate ephemeral key for this handshake (using StaticSecret for multiple DH operations)
let ephemeral_secret = StaticSecret::random_from_rng(rng); 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 // Perform the 4 Diffie-Hellman operations
let dh1 = identity_keypair.diffie_hellman(&recipient_bundle.signed_prekey); let dh1 = identity_keypair.diffie_hellman(&recipient_bundle.signed_prekey);
@ -105,8 +105,8 @@ impl<D: DomainSeparator> X3Handshake<D> {
identity_keypair: &StaticSecret, identity_keypair: &StaticSecret,
signed_prekey: &StaticSecret, signed_prekey: &StaticSecret,
onetime_prekey: Option<&StaticSecret>, onetime_prekey: Option<&StaticSecret>,
initiator_identity: &PublicKey, initiator_identity: &X25519PublicKey,
initiator_ephemeral: &PublicKey, initiator_ephemeral: &X25519PublicKey,
) -> SymmetricKey32 { ) -> SymmetricKey32 {
let dh1 = signed_prekey.diffie_hellman(initiator_identity); let dh1 = signed_prekey.diffie_hellman(initiator_identity);
let dh2 = identity_keypair.diffie_hellman(initiator_ephemeral); let dh2 = identity_keypair.diffie_hellman(initiator_ephemeral);
@ -136,17 +136,17 @@ mod tests {
// Alice (initiator) generates her identity key // Alice (initiator) generates her identity key
let alice_identity = StaticSecret::random_from_rng(rng); 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 // Bob (responder) generates his keys
let bob_identity = StaticSecret::random_from_rng(rng); 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 = 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 = 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) // Create Bob's prekey bundle (with one-time prekey)
let bob_bundle = PrekeyBundle { let bob_bundle = PrekeyBundle {
@ -179,14 +179,14 @@ mod tests {
// Alice (initiator) generates her identity key // Alice (initiator) generates her identity key
let alice_identity = StaticSecret::random_from_rng(rng); 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 // Bob (responder) generates his keys
let bob_identity = StaticSecret::random_from_rng(rng); 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 = 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) // Create Bob's prekey bundle (without one-time prekey)
let bob_bundle = PrekeyBundle { let bob_bundle = PrekeyBundle {

View File

@ -4,9 +4,11 @@
//! that allow signing arbitrary messages with X25519 keys. //! that allow signing arbitrary messages with X25519 keys.
use rand_core::{CryptoRng, RngCore}; use rand_core::{CryptoRng, RngCore};
use x25519_dalek::{PublicKey, StaticSecret}; use x25519_dalek::StaticSecret;
use xeddsa::{Sign, Verify, xed25519}; use xeddsa::{Sign, Verify, xed25519};
use crate::keys::X25519PublicKey;
/// A 64-byte XEdDSA signature over an Ed25519-compatible curve. /// A 64-byte XEdDSA signature over an Ed25519-compatible curve.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Ed25519Signature(pub [u8; 64]); pub struct Ed25519Signature(pub [u8; 64]);
@ -62,7 +64,7 @@ pub fn xeddsa_sign<R: RngCore + CryptoRng>(
/// # Returns /// # Returns
/// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise /// `Ok(())` if the signature is valid, `Err(SignatureError)` otherwise
pub fn xeddsa_verify( pub fn xeddsa_verify(
pubkey: &PublicKey, pubkey: &X25519PublicKey,
message: &[u8], message: &[u8],
signature: &Ed25519Signature, signature: &Ed25519Signature,
) -> Result<(), SignatureError> { ) -> Result<(), SignatureError> {
@ -80,7 +82,7 @@ mod tests {
#[test] #[test]
fn test_sign_and_verify_roundtrip() { fn test_sign_and_verify_roundtrip() {
let secret = StaticSecret::random_from_rng(OsRng); let secret = StaticSecret::random_from_rng(OsRng);
let public = PublicKey::from(&secret); let public = X25519PublicKey::from(&secret);
let message = b"test message"; let message = b"test message";
let signature = xeddsa_sign(&secret, message, OsRng); let signature = xeddsa_sign(&secret, message, OsRng);
@ -96,7 +98,7 @@ mod tests {
let signature = xeddsa_sign(&secret, message, OsRng); let signature = xeddsa_sign(&secret, message, OsRng);
let wrong_secret = StaticSecret::random_from_rng(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!( assert_eq!(
xeddsa_verify(&wrong_public, message, &signature), xeddsa_verify(&wrong_public, message, &signature),
@ -107,7 +109,7 @@ mod tests {
#[test] #[test]
fn test_wrong_message_fails() { fn test_wrong_message_fails() {
let secret = StaticSecret::random_from_rng(OsRng); let secret = StaticSecret::random_from_rng(OsRng);
let public = PublicKey::from(&secret); let public = X25519PublicKey::from(&secret);
let message = b"test message"; let message = b"test message";
let signature = xeddsa_sign(&secret, message, OsRng); let signature = xeddsa_sign(&secret, message, OsRng);
@ -121,7 +123,7 @@ mod tests {
#[test] #[test]
fn test_corrupted_signature_fails() { fn test_corrupted_signature_fails() {
let secret = StaticSecret::random_from_rng(OsRng); let secret = StaticSecret::random_from_rng(OsRng);
let public = PublicKey::from(&secret); let public = X25519PublicKey::from(&secret);
let message = b"test message"; let message = b"test message";
let mut signature = xeddsa_sign(&secret, message, OsRng); let mut signature = xeddsa_sign(&secret, message, OsRng);

View File

@ -13,6 +13,7 @@ required-features = ["headers"]
[dependencies] [dependencies]
x25519-dalek = { version="2.0.1", features=["static_secrets"] } x25519-dalek = { version="2.0.1", features=["static_secrets"] }
chacha20poly1305 = "0.10.1" chacha20poly1305 = "0.10.1"
crypto.workspace = true
rand_core = "0.6.4" rand_core = "0.6.4"
rand = "0.8.5" rand = "0.8.5"
hkdf = "0.12.4" hkdf = "0.12.4"

View File

@ -1,5 +1,5 @@
use crypto::X25519PublicKey;
use safer_ffi::prelude::*; use safer_ffi::prelude::*;
use x25519_dalek::PublicKey;
use crate::{ use crate::{
Header, RatchetState, Header, RatchetState,
@ -22,7 +22,7 @@ fn double_ratchet_init_sender(
shared_secret: [u8; 32], shared_secret: [u8; 32],
remote_pub: [u8; 32], remote_pub: [u8; 32],
) -> repr_c::Box<FFIRatchetState> { ) -> repr_c::Box<FFIRatchetState> {
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() Box::new(FFIRatchetState(state)).into()
} }

View File

@ -1,5 +1,6 @@
use crypto::X25519PublicKey;
use rand_core::OsRng; use rand_core::OsRng;
use x25519_dalek::{PublicKey, StaticSecret}; use x25519_dalek::StaticSecret;
use zeroize::{Zeroize, ZeroizeOnDrop}; use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::types::SharedSecret; use crate::types::SharedSecret;
@ -7,21 +8,21 @@ use crate::types::SharedSecret;
#[derive(Clone, Zeroize, ZeroizeOnDrop)] #[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct InstallationKeyPair { pub struct InstallationKeyPair {
secret: StaticSecret, secret: StaticSecret,
public: PublicKey, public: X25519PublicKey,
} }
impl InstallationKeyPair { impl InstallationKeyPair {
pub fn generate() -> Self { pub fn generate() -> Self {
let secret = StaticSecret::random_from_rng(OsRng); let secret = StaticSecret::random_from_rng(OsRng);
let public = PublicKey::from(&secret); let public = X25519PublicKey::from(&secret);
Self { secret, public } 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() self.secret.diffie_hellman(their_public).to_bytes()
} }
pub fn public(&self) -> &PublicKey { pub fn public(&self) -> &X25519PublicKey {
&self.public &self.public
} }
@ -33,14 +34,14 @@ impl InstallationKeyPair {
/// Import the secret key from raw bytes. /// Import the secret key from raw bytes.
pub fn from_secret_bytes(bytes: [u8; 32]) -> Self { pub fn from_secret_bytes(bytes: [u8; 32]) -> Self {
let secret = StaticSecret::from(bytes); let secret = StaticSecret::from(bytes);
let public = PublicKey::from(&secret); let public = X25519PublicKey::from(&secret);
Self { secret, public } Self { secret, public }
} }
} }
impl From<StaticSecret> for InstallationKeyPair { impl From<StaticSecret> for InstallationKeyPair {
fn from(value: StaticSecret) -> Self { fn from(value: StaticSecret) -> Self {
let public = PublicKey::from(&value); let public = X25519PublicKey::from(&value);
Self { Self {
secret: value, secret: value,
public, public,

View File

@ -1,7 +1,7 @@
use std::{collections::HashMap, marker::PhantomData}; use std::{collections::HashMap, marker::PhantomData};
use crypto::X25519PublicKey;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError}; use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError};
use x25519_dalek::PublicKey;
use zeroize::{Zeroize, Zeroizing}; use zeroize::{Zeroize, Zeroizing};
use crate::{ use crate::{
@ -29,13 +29,13 @@ pub struct RatchetState<D: HkdfInfo = DefaultDomain> {
pub receiving_chain: Option<ChainKey>, pub receiving_chain: Option<ChainKey>,
pub dh_self: InstallationKeyPair, pub dh_self: InstallationKeyPair,
pub dh_remote: Option<PublicKey>, pub dh_remote: Option<X25519PublicKey>,
pub msg_send: u32, pub msg_send: u32,
pub msg_recv: u32, pub msg_recv: u32,
pub prev_chain_len: u32, pub prev_chain_len: u32,
pub skipped_keys: HashMap<(PublicKey, u32), MessageKey>, pub skipped_keys: HashMap<(X25519PublicKey, u32), MessageKey>,
pub(crate) _domain: PhantomData<D>, pub(crate) _domain: PhantomData<D>,
} }
@ -144,7 +144,7 @@ impl<D: HkdfInfo> RatchetState<D> {
let dh_self = InstallationKeyPair::from_secret_bytes(dh_self_bytes); let dh_self = InstallationKeyPair::from_secret_bytes(dh_self_bytes);
dh_self_bytes.zeroize(); 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_send = reader.read_u32()?;
let msg_recv = reader.read_u32()?; let msg_recv = reader.read_u32()?;
@ -153,7 +153,7 @@ impl<D: HkdfInfo> RatchetState<D> {
let skipped_count = reader.read_u32()? as usize; let skipped_count = reader.read_u32()? as usize;
let mut skipped_keys = HashMap::with_capacity(skipped_count); let mut skipped_keys = HashMap::with_capacity(skipped_count);
for _ in 0..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 msg_num = reader.read_u32()?;
let mk: MessageKey = reader.read_array()?; let mk: MessageKey = reader.read_array()?;
skipped_keys.insert((pk, msg_num), mk); skipped_keys.insert((pk, msg_num), mk);
@ -198,7 +198,7 @@ impl<'de, D: HkdfInfo> Deserialize<'de> for RatchetState<D> {
/// Public header attached to every encrypted message (unencrypted but authenticated). /// Public header attached to every encrypted message (unencrypted but authenticated).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Header { pub struct Header {
pub dh_pub: PublicKey, pub dh_pub: X25519PublicKey,
pub msg_num: u32, pub msg_num: u32,
pub prev_chain_len: u32, pub prev_chain_len: u32,
} }
@ -233,7 +233,7 @@ impl<D: HkdfInfo> RatchetState<D> {
/// # Returns /// # Returns
/// ///
/// A new `RatchetState` ready to send the first message. /// 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(); let dh_self = InstallationKeyPair::generate();
// Initial DH // Initial DH
@ -296,7 +296,7 @@ impl<D: HkdfInfo> RatchetState<D> {
/// # Arguments /// # Arguments
/// ///
/// * `remote_pub` - The new DH public key from the sender. /// * `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 dh_out = self.dh_self.dh(&remote_pub);
let (new_root, recv_chain) = kdf_root::<D>(&self.root_key, &dh_out); let (new_root, recv_chain) = kdf_root::<D>(&self.root_key, &dh_out);
@ -605,7 +605,7 @@ mod tests {
// Tamper with header (change DH pub byte) // Tamper with header (change DH pub byte)
let mut tampered_pub_bytes = header.dh_pub.to_bytes(); let mut tampered_pub_bytes = header.dh_pub.to_bytes();
tampered_pub_bytes[0] ^= 0xff; 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); let result = bob.decrypt_message(&ct, header);
assert!(result.is_err()); assert!(result.is_err());

View File

@ -1,6 +1,6 @@
//! Session wrapper for automatic state persistence. //! Session wrapper for automatic state persistence.
use x25519_dalek::PublicKey; use crypto::X25519PublicKey;
use crate::{ use crate::{
InstallationKeyPair, SessionError, InstallationKeyPair, SessionError,
@ -54,7 +54,7 @@ impl<'a, D: HkdfInfo + Clone> RatchetSession<'a, D> {
storage: &'a mut RatchetStorage, storage: &'a mut RatchetStorage,
conversation_id: &str, conversation_id: &str,
shared_secret: SharedSecret, shared_secret: SharedSecret,
remote_pub: PublicKey, remote_pub: X25519PublicKey,
) -> Result<Self, SessionError> { ) -> Result<Self, SessionError> {
if storage.exists(conversation_id)? { if storage.exists(conversation_id)? {
return Err(SessionError::ConvAlreadyExists(conversation_id.to_string())); return Err(SessionError::ConvAlreadyExists(conversation_id.to_string()));

View File

@ -1,11 +1,11 @@
//! Storage types for ratchet state. //! Storage types for ratchet state.
use crypto::X25519PublicKey;
use crate::{ use crate::{
hkdf::HkdfInfo, hkdf::HkdfInfo,
state::{RatchetState, SkippedKey}, state::{RatchetState, SkippedKey},
types::MessageKey, types::MessageKey,
}; };
use x25519_dalek::PublicKey;
/// Raw state data for storage (without generic parameter). /// Raw state data for storage (without generic parameter).
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -42,11 +42,16 @@ impl RatchetStateRecord {
use std::marker::PhantomData; use std::marker::PhantomData;
let dh_self = InstallationKeyPair::from_secret_bytes(self.dh_self_secret); 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() .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(); .collect();
RatchetState { RatchetState {