Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[uniffi] Add ratchet_tree to join_group #122

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions mls-rs-uniffi/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,26 @@ pub type UniFFIConfig = client_builder::WithIdentityProvider<
#[derive(Debug, Clone, uniffi::Record)]
pub struct ClientConfig {
pub group_state_storage: Arc<dyn GroupStateStorage>,
/// Use the ratchet tree extension. If this is false, then you
/// must supply `ratchet_tree` out of band to clients.
pub use_ratchet_tree_extension: bool,
}

impl Default for ClientConfig {
fn default() -> Self {
Self {
group_state_storage: Arc::new(GroupStateStorageAdapter::new(
InMemoryGroupStateStorage::new(),
)),
use_ratchet_tree_extension: true,
}
}
}

// TODO(mgeisler): turn into an associated function when UniFFI
// supports them: https://github.com/mozilla/uniffi-rs/issues/1074.
/// Create a client config with an in-memory group state storage.
#[uniffi::export]
pub fn client_config_default() -> ClientConfig {
ClientConfig {
group_state_storage: Arc::new(GroupStateStorageAdapter::new(
InMemoryGroupStateStorage::new(),
)),
}
ClientConfig::default()
}
85 changes: 78 additions & 7 deletions mls-rs-uniffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use tokio::sync::Mutex;
use mls_rs::error::{IntoAnyError, MlsError};
use mls_rs::group;
use mls_rs::identity::basic;
use mls_rs::mls_rules;
use mls_rs::{CipherSuiteProvider, CryptoProvider};
use mls_rs_core::identity;
use mls_rs_core::identity::{BasicCredential, IdentityProvider};
Expand Down Expand Up @@ -312,12 +313,16 @@ impl Client {
let basic_credential = BasicCredential::new(id);
let signing_identity =
identity::SigningIdentity::new(basic_credential.into_credential(), public_key.into());

let mls_rules = mls_rules::DefaultMlsRules::new().with_commit_options(
mls_rules::CommitOptions::default()
.with_ratchet_tree_extension(client_config.use_ratchet_tree_extension),
);
let client = mls_rs::Client::builder()
.crypto_provider(crypto_provider)
.identity_provider(basic::BasicIdentityProvider::new())
.signing_identity(signing_identity, secret_key.into(), cipher_suite.into())
.group_state_storage(client_config.group_state_storage.into())
.mls_rules(mls_rules)
.build();

Client { inner: client }
Expand Down Expand Up @@ -360,9 +365,20 @@ impl Client {

/// Join an existing group.
///
/// You must supply `ratchet_tree` if the client that created
/// `welcome_message` did not set `use_ratchet_tree_extension`.
///
/// See [`mls_rs::Client::join_group`] for details.
pub async fn join_group(&self, welcome_message: &Message) -> Result<JoinInfo, Error> {
let (group, new_member_info) = self.inner.join_group(None, &welcome_message.inner).await?;
pub async fn join_group(
&self,
ratchet_tree: Option<RatchetTree>,
welcome_message: &Message,
) -> Result<JoinInfo, Error> {
let ratchet_tree = ratchet_tree.map(TryInto::try_into).transpose()?;
let (group, new_member_info) = self
.inner
.join_group(ratchet_tree, &welcome_message.inner)
.await?;

let group = Arc::new(Group {
inner: Arc::new(Mutex::new(group)),
Expand All @@ -388,20 +404,28 @@ impl Client {
}
}

#[derive(Clone, Debug, uniffi::Record)]
#[derive(Clone, Debug, PartialEq, uniffi::Record)]
pub struct RatchetTree {
pub bytes: Vec<u8>,
}

impl TryFrom<mls_rs::group::ExportedTree<'static>> for RatchetTree {
impl TryFrom<mls_rs::group::ExportedTree<'_>> for RatchetTree {
type Error = Error;

fn try_from(exported_tree: mls_rs::group::ExportedTree<'static>) -> Result<Self, Error> {
fn try_from(exported_tree: mls_rs::group::ExportedTree<'_>) -> Result<Self, Error> {
let bytes = exported_tree.to_bytes()?;
Ok(Self { bytes })
}
}

impl TryFrom<RatchetTree> for group::ExportedTree<'static> {
type Error = Error;

fn try_from(ratchet_tree: RatchetTree) -> Result<Self, Error> {
group::ExportedTree::from_bytes(&ratchet_tree.bytes).map_err(Into::into)
}
}

#[derive(Clone, Debug, uniffi::Record)]
pub struct CommitOutput {
/// Commit message to send to other group members.
Expand Down Expand Up @@ -516,6 +540,15 @@ impl Group {
group.write_to_storage().await.map_err(Into::into)
}

/// Export the current epoch's ratchet tree in serialized format.
///
/// This function is used to provide the current group tree to new
/// members when `use_ratchet_tree_extension` is set to false in
/// `ClientConfig`.
pub fn export_tree(&self) -> Result<RatchetTree, Error> {
self.inner().export_tree().try_into()
}

/// Perform a commit of received proposals (or an empty commit).
///
/// TODO: ensure `path_required` is always set in
Expand Down Expand Up @@ -756,12 +789,14 @@ mod tests {

let alice_config = ClientConfig {
group_state_storage: Arc::new(CustomGroupStateStorage::new()),
..Default::default()
};
let alice_keypair = generate_signature_keypair(CipherSuite::Curve25519Aes128)?;
let alice = Client::new(b"alice".to_vec(), alice_keypair, alice_config);

let bob_config = ClientConfig {
group_state_storage: Arc::new(CustomGroupStateStorage::new()),
..Default::default()
};
let bob_keypair = generate_signature_keypair(CipherSuite::Curve25519Aes128)?;
let bob = Client::new(b"bob".to_vec(), bob_keypair, bob_config);
Expand All @@ -771,7 +806,7 @@ mod tests {
let commit = alice_group.add_members(vec![Arc::new(bob_key_package)])?;
alice_group.process_incoming_message(commit.commit_message)?;

let bob_group = bob.join_group(&commit.welcome_messages[0])?.group;
let bob_group = bob.join_group(None, &commit.welcome_messages[0])?.group;
let message = alice_group.encrypt_application_message(b"hello, bob")?;
let received_message = bob_group.process_incoming_message(Arc::new(message))?;

Expand All @@ -784,4 +819,40 @@ mod tests {

Ok(())
}

#[test]
#[cfg(not(mls_build_async))]
fn test_ratchet_tree_not_included() -> Result<(), Error> {
let alice_config = ClientConfig {
use_ratchet_tree_extension: true,
..ClientConfig::default()
};

let alice_keypair = generate_signature_keypair(CipherSuite::Curve25519Aes128)?;
let alice = Client::new(b"alice".to_vec(), alice_keypair, alice_config);
let group = alice.create_group(None)?;

assert_eq!(group.commit()?.ratchet_tree, None);
Ok(())
}

#[test]
#[cfg(not(mls_build_async))]
fn test_ratchet_tree_included() -> Result<(), Error> {
let alice_config = ClientConfig {
use_ratchet_tree_extension: false,
..ClientConfig::default()
};

let alice_keypair = generate_signature_keypair(CipherSuite::Curve25519Aes128)?;
let alice = Client::new(b"alice".to_vec(), alice_keypair, alice_config);
let group = alice.create_group(None)?;

let ratchet_tree: group::ExportedTree =
group.commit()?.ratchet_tree.unwrap().try_into().unwrap();
group.inner().apply_pending_commit()?;

assert_eq!(ratchet_tree, group.inner().export_tree());
Ok(())
}
}
13 changes: 13 additions & 0 deletions mls-rs-uniffi/tests/ratchet_tree_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from mls_rs_uniffi import CipherSuite, generate_signature_keypair, Client, \
client_config_default

client_config = client_config_default()
client_config.use_ratchet_tree_extension = False

key = generate_signature_keypair(CipherSuite.CURVE25519_AES128)
alice = Client(b'alice', key, client_config)

group = alice.create_group(None)
commit = group.commit()

assert commit.ratchet_tree is not None
1 change: 1 addition & 0 deletions mls-rs-uniffi/tests/scenarios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ generate_python_tests!(client_config_default_sync, None);
// supported in the next UniFFI release
// TODO(mgeisler): add back simple_scenario_async
generate_python_tests!(simple_scenario_sync, None);
generate_python_tests!(ratchet_tree_sync, None);
4 changes: 2 additions & 2 deletions mls-rs-uniffi/tests/simple_scenario_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def max_epoch_id(self, group_id: bytes):
return last.id

group_state_storage = PythonGroupStateStorage()
client_config = ClientConfig(group_state_storage)
client_config = ClientConfig(group_state_storage, use_ratchet_tree_extension=True)

key = generate_signature_keypair(CipherSuite.CURVE25519_AES128)
alice = Client(b'alice', key, client_config)
Expand All @@ -73,7 +73,7 @@ def max_epoch_id(self, group_id: bytes):

commit = alice.add_members([message])
alice.process_incoming_message(commit.commit_message)
bob = bob.join_group(commit.welcome_messages[0]).group
bob = bob.join_group(None, commit.welcome_messages[0]).group

msg = alice.encrypt_application_message(b'hello, bob')
output = bob.process_incoming_message(msg)
Expand Down
Loading