Skip to content

Commit

Permalink
Add & Remove identity bindings (#889)
Browse files Browse the repository at this point in the history
  • Loading branch information
tuddman authored Jul 16, 2024
1 parent 8fe95b8 commit d65eb42
Show file tree
Hide file tree
Showing 2 changed files with 267 additions and 42 deletions.
14 changes: 5 additions & 9 deletions bindings_ffi/src/inbox_owner.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use xmtp_cryptography::signature::{RecoverableSignature, SignatureError};

// TODO proper error handling
#[derive(Debug, thiserror::Error)]
pub enum SigningError {
Expand Down Expand Up @@ -32,17 +34,11 @@ impl xmtp_mls::InboxOwner for RustInboxOwner {
self.ffi_inbox_owner.get_address().to_lowercase()
}

fn sign(
&self,
text: &str,
) -> Result<
xmtp_cryptography::signature::RecoverableSignature,
xmtp_cryptography::signature::SignatureError,
> {
fn sign(&self, text: &str) -> Result<RecoverableSignature, SignatureError> {
let bytes = self
.ffi_inbox_owner
.sign(text.to_string())
.map_err(|_flat_err| xmtp_cryptography::signature::SignatureError::Unknown)?;
Ok(xmtp_cryptography::signature::RecoverableSignature::Eip191Signature(bytes))
.map_err(|_flat_err| SignatureError::Unknown)?;
Ok(RecoverableSignature::Eip191Signature(bytes))
}
}
295 changes: 262 additions & 33 deletions bindings_ffi/src/mls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ pub fn generate_inbox_id(account_address: String, nonce: u64) -> String {

#[derive(uniffi::Object)]
pub struct FfiSignatureRequest {
// Using `tokio::sync::Mutex`bc rust MutexGuard cannot be sent between threads.
// Using `tokio::sync::Mutex` bc rust MutexGuard cannot be sent between threads.
inner: Arc<tokio::sync::Mutex<SignatureRequest>>,
}

Expand Down Expand Up @@ -326,6 +326,56 @@ impl FfiXmtpClient {
self.inner_client.send_history_request().await?;
Ok(())
}

/// Adds an identity - really a wallet address - to the existing client
pub async fn add_wallet(
&self,
existing_wallet_address: &str,
new_wallet_address: &str,
) -> Result<Arc<FfiSignatureRequest>, GenericError> {
let inbox_id = self.inner_client.inbox_id();
let signature_request = self.inner_client.associate_wallet(
inbox_id,
existing_wallet_address.into(),
new_wallet_address.into(),
)?;

let request = Arc::new(FfiSignatureRequest {
inner: Arc::new(tokio::sync::Mutex::new(signature_request)),
});

Ok(request)
}

pub async fn apply_signature_request(
&self,
signature_request: Arc<FfiSignatureRequest>,
) -> Result<(), GenericError> {
let signature_request = signature_request.inner.lock().await;
self.inner_client
.apply_signature_request(signature_request.clone())
.await?;

Ok(())
}

/// Revokes or removes an identity - really a wallet address - from the existing client
pub async fn revoke_wallet(
&self,
wallet_address: &str,
) -> Result<Arc<FfiSignatureRequest>, GenericError> {
let inbox_id = self.inner_client.inbox_id();
let signature_request = self
.inner_client
.revoke_wallet(inbox_id, wallet_address.into())
.await?;

let request = Arc::new(FfiSignatureRequest {
inner: Arc::new(tokio::sync::Mutex::new(signature_request)),
});

Ok(request)
}
}

#[derive(uniffi::Record, Default)]
Expand Down Expand Up @@ -1599,50 +1649,183 @@ mod tests {
assert!(result_errored, "did not error on wrong encryption key")
}

use super::FfiSignatureRequest;
async fn sign_with_wallet(
wallet: &xmtp_cryptography::utils::LocalWallet,
signature_request: &FfiSignatureRequest,
) {
let signature_text = signature_request.inner.lock().await.signature_text();
let wallet_signature: Vec<u8> = wallet.sign(&signature_text.clone()).unwrap().into();

signature_request
.inner
.lock()
.await
.add_signature(Box::new(
xmtp_id::associations::RecoverableEcdsaSignature::new(
signature_text,
wallet_signature,
),
))
.await
.unwrap();
}

use xmtp_cryptography::utils::generate_local_wallet;

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_create_group_with_members() {
let amal = new_test_client().await;
let bola = new_test_client().await;
async fn test_can_add_wallet_to_inbox() {
// Setup the initial first client
let ffi_inbox_owner = LocalWalletInboxOwner::new();
let nonce = 1;
let inbox_id = generate_inbox_id(&ffi_inbox_owner.get_address(), &nonce);

let group = amal
.conversations()
.create_group(
vec![bola.account_address.clone()],
FfiCreateGroupOptions::default(),
)
let path = tmp_path();
let key = static_enc_key().to_vec();
let client = create_client(
Box::new(MockLogger {}),
xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(),
false,
Some(path.clone()),
Some(key),
&inbox_id,
ffi_inbox_owner.get_address(),
nonce,
None,
None,
)
.await
.unwrap();

register_client(&ffi_inbox_owner, &client).await;

let signature_request = client.signature_request().unwrap().clone();

sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await;

let conn = client.inner_client.store().conn().unwrap();
let state = client
.inner_client
.get_latest_association_state(&conn, &inbox_id)
.await
.expect("could not get state");

assert_eq!(state.members().len(), 2);

// Now, add the second wallet to the client

let wallet_to_add = generate_local_wallet();
let new_account_address = wallet_to_add.get_address();
println!("second address: {}", new_account_address);

let signature_request = client
.add_wallet(&ffi_inbox_owner.get_address(), &new_account_address)
.await
.expect("could not add wallet");

sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await;
sign_with_wallet(&wallet_to_add, &signature_request).await;

client
.apply_signature_request(signature_request)
.await
.unwrap();

let members = group.list_members().unwrap();
assert_eq!(members.len(), 2);
let updated_state = client
.inner_client
.get_latest_association_state(&conn, &inbox_id)
.await
.expect("could not get state");

assert_eq!(updated_state.members().len(), 3);
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_create_group_with_metadata() {
let amal = new_test_client().await;
let bola = new_test_client().await;
async fn test_can_revoke_wallet() {
// Setup the initial first client
let ffi_inbox_owner = LocalWalletInboxOwner::new();
let nonce = 1;
let inbox_id = generate_inbox_id(&ffi_inbox_owner.get_address(), &nonce);

let group = amal
.conversations()
.create_group(
vec![bola.account_address.clone()],
FfiCreateGroupOptions {
permissions: Some(FfiGroupPermissionsOptions::AdminOnly),
group_name: Some("Group Name".to_string()),
group_image_url_square: Some("url".to_string()),
group_description: Some("group description".to_string()),
group_pinned_frame_url: Some("pinned frame".to_string()),
},
)
let path = tmp_path();
let key = static_enc_key().to_vec();
let client = create_client(
Box::new(MockLogger {}),
xmtp_api_grpc::LOCALHOST_ADDRESS.to_string(),
false,
Some(path.clone()),
Some(key),
&inbox_id,
ffi_inbox_owner.get_address(),
nonce,
None,
None,
)
.await
.unwrap();

register_client(&ffi_inbox_owner, &client).await;

let signature_request = client.signature_request().unwrap().clone();

sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await;

let conn = client.inner_client.store().conn().unwrap();
let state = client
.inner_client
.get_latest_association_state(&conn, &inbox_id)
.await
.expect("could not get state");

assert_eq!(state.members().len(), 2);

// Now, add the second wallet to the client

let wallet_to_add = generate_local_wallet();
let new_account_address = wallet_to_add.get_address();
println!("second address: {}", new_account_address);

let signature_request = client
.add_wallet(&ffi_inbox_owner.get_address(), &new_account_address)
.await
.expect("could not add wallet");

sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await;
sign_with_wallet(&wallet_to_add, &signature_request).await;

client
.apply_signature_request(signature_request.clone())
.await
.unwrap();

let members = group.list_members().unwrap();
assert_eq!(members.len(), 2);
assert_eq!(group.group_name().unwrap(), "Group Name");
assert_eq!(group.group_image_url_square().unwrap(), "url");
assert_eq!(group.group_description().unwrap(), "group description");
assert_eq!(group.group_pinned_frame_url().unwrap(), "pinned frame");
let updated_state = client
.inner_client
.get_latest_association_state(&conn, &inbox_id)
.await
.expect("could not get state");

assert_eq!(updated_state.members().len(), 3);

// Now, revoke the second wallet
let signature_request = client
.revoke_wallet(&new_account_address)
.await
.expect("could not revoke wallet");

sign_with_wallet(&ffi_inbox_owner.wallet, &signature_request).await;

client
.apply_signature_request(signature_request)
.await
.unwrap();

let revoked_state = client
.inner_client
.get_latest_association_state(&conn, &inbox_id)
.await
.expect("could not get state");

assert_eq!(revoked_state.members().len(), 2);
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
Expand Down Expand Up @@ -1737,6 +1920,52 @@ mod tests {
);
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_create_group_with_members() {
let amal = new_test_client().await;
let bola = new_test_client().await;

let group = amal
.conversations()
.create_group(
vec![bola.account_address.clone()],
FfiCreateGroupOptions::default(),
)
.await
.unwrap();

let members = group.list_members().unwrap();
assert_eq!(members.len(), 2);
}

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_create_group_with_metadata() {
let amal = new_test_client().await;
let bola = new_test_client().await;

let group = amal
.conversations()
.create_group(
vec![bola.account_address.clone()],
FfiCreateGroupOptions {
permissions: Some(FfiGroupPermissionsOptions::AdminOnly),
group_name: Some("Group Name".to_string()),
group_image_url_square: Some("url".to_string()),
group_description: Some("group description".to_string()),
group_pinned_frame_url: Some("pinned frame".to_string()),
},
)
.await
.unwrap();

let members = group.list_members().unwrap();
assert_eq!(members.len(), 2);
assert_eq!(group.group_name().unwrap(), "Group Name");
assert_eq!(group.group_image_url_square().unwrap(), "url");
assert_eq!(group.group_description().unwrap(), "group description");
assert_eq!(group.group_pinned_frame_url().unwrap(), "pinned frame");
}

// Looks like this test might be a separate issue
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
#[ignore]
Expand Down

0 comments on commit d65eb42

Please sign in to comment.