Add Introductions (#22)

This commit is contained in:
Jazz Turner-Baggs 2026-01-26 23:53:44 +07:00 committed by GitHub
parent fe23c39321
commit d40e72be9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 92 additions and 11 deletions

View File

@ -6,9 +6,11 @@ use crate::{
conversation::{ConversationId, ConversationIdOwned, ConversationStore},
identity::Identity,
inbox::Inbox,
proto,
types::{ContentData, PayloadData},
};
pub use crate::inbox::Introduction;
// This is the main entry point to the conversations api.
// Ctx manages lifetimes of objects to process and generate payloads.
pub struct Context {
@ -30,17 +32,16 @@ impl Context {
pub fn create_private_convo(
&mut self,
remote_bundle: &PrekeyBundle,
remote_bundle: &Introduction,
content: String,
) -> ConversationIdOwned {
let (convo, _payloads) = self
) -> (ConversationIdOwned, Vec<proto::EncryptedPayload>) {
let (convo, payloads) = self
.inbox
.invite_to_private_convo(remote_bundle, content)
.unwrap_or_else(|_| todo!("Log/Surface Error"));
self.store.insert_convo(convo)
// TODO: Change return type to handle outbout packets.
let convo_id = self.store.insert_convo(convo);
(convo_id, payloads)
}
pub fn send_content(&mut self, _convo_id: ConversationId, _content: &[u8]) -> Vec<PayloadData> {

View File

@ -12,4 +12,8 @@ pub enum ChatError {
BadBundleValue(String),
#[error("handshake initiated with a unknown ephemeral key")]
UnknownEphemeralKey(),
#[error("expected a different key length")]
InvalidKeyLength,
#[error("bytes provided to {0} failed")]
BadParsing(&'static str),
}

View File

@ -1,4 +1,6 @@
mod handshake;
mod inbox;
mod introduction;
pub use inbox::Inbox;
pub use introduction::Introduction;

View File

@ -8,6 +8,7 @@ use std::rc::Rc;
use crypto::{PrekeyBundle, SecretKey};
use crate::context::Introduction;
use crate::conversation::{ChatError, ConversationId, Convo, ConvoFactory, Id, PrivateV1Convo};
use crate::crypto::{Blake2b128, CopyBytes, Digest, PublicKey, StaticSecret};
use crate::identity::Identity;
@ -66,13 +67,21 @@ impl Inbox {
pub fn invite_to_private_convo(
&self,
remote_bundle: &PrekeyBundle,
remote_bundle: &Introduction,
initial_message: String,
) -> Result<(PrivateV1Convo, Vec<proto::EncryptedPayload>), ChatError> {
let mut rng = OsRng;
// TODO: Include signature in introduction bundle. Manaully fill for now
let pkb = PrekeyBundle {
identity_key: remote_bundle.installation_key,
signed_prekey: remote_bundle.ephemeral_key,
signature: [0u8; 64],
onetime_prekey: None,
};
let (seed_key, ephemeral_pub) =
InboxHandshake::perform_as_initiator(&self.ident.secret(), remote_bundle, &mut rng);
InboxHandshake::perform_as_initiator(&self.ident.secret(), &pkb, &mut rng);
let mut convo = PrivateV1Convo::new(seed_key);
@ -89,8 +98,8 @@ impl Inbox {
let header = proto::InboxHeaderV1 {
initiator_static: self.ident.public_key().copy_to_bytes(),
initiator_ephemeral: ephemeral_pub.copy_to_bytes(),
responder_static: remote_bundle.identity_key.copy_to_bytes(),
responder_ephemeral: remote_bundle.signed_prekey.copy_to_bytes(),
responder_static: remote_bundle.installation_key.copy_to_bytes(),
responder_ephemeral: remote_bundle.ephemeral_key.copy_to_bytes(),
};
let handshake = proto::InboxHandshakeV1 {
@ -226,7 +235,7 @@ mod tests {
let bundle = raya_inbox.create_bundle();
let (_, payloads) = saro_inbox
.invite_to_private_convo(&bundle, "hello".into())
.invite_to_private_convo(&bundle.into(), "hello".into())
.unwrap();
let encrypted_payload = payloads

View File

@ -0,0 +1,65 @@
use crypto::PrekeyBundle;
use x25519_dalek::PublicKey;
use crate::errors::ChatError;
/// Supplies remote participants with the required keys to use Inbox protocol
pub struct Introduction {
pub installation_key: PublicKey,
pub ephemeral_key: PublicKey,
}
impl From<PrekeyBundle> for Introduction {
fn from(value: PrekeyBundle) -> Self {
Introduction {
installation_key: value.identity_key,
ephemeral_key: value.signed_prekey,
}
}
}
impl Into<Vec<u8>> for Introduction {
fn into(self) -> Vec<u8> {
// TODO: avoid copies, via writing directly to slice
let link = format!(
"Bundle:{}:{}",
hex::encode(self.installation_key.as_bytes()),
hex::encode(self.ephemeral_key.as_bytes()),
);
link.into_bytes()
}
}
impl TryFrom<Vec<u8>> for Introduction {
type Error = ChatError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
let str_value =
String::from_utf8(value).map_err(|_| ChatError::BadParsing("Introduction"))?;
let parts: Vec<&str> = str_value.splitn(3, ':').collect();
if parts[0] != "Bundle" {
return Err(ChatError::BadBundleValue(
"not recognized as an introduction bundle".into(),
));
}
let installation_bytes: [u8; 32] = hex::decode(parts[1])
.map_err(|_| ChatError::BadParsing("installation_key"))?
.try_into()
.map_err(|_| ChatError::InvalidKeyLength)?;
let installation_key = PublicKey::from(installation_bytes);
let ephemeral_bytes: [u8; 32] = hex::decode(parts[1])
.map_err(|_| ChatError::BadParsing("ephemeral_key"))?
.try_into()
.map_err(|_| ChatError::InvalidKeyLength)?;
let ephemeral_key = PublicKey::from(ephemeral_bytes);
Ok(Introduction {
installation_key,
ephemeral_key,
})
}
}