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

move add and remove members to use wallet addresses #363

Merged
merged 22 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 18 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
17 changes: 15 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev/gen_protos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ if ! cargo install --list | grep "protoc-gen-prost-crate" > /dev/null; then
exit 1
fi
fi
if ! buf generate https://github.com/xmtp/proto.git#branch=nmolnar/mls-verification-service,subdir=proto; then
insipx marked this conversation as resolved.
Show resolved Hide resolved
if ! buf generate https://github.com/xmtp/proto.git#branch=insipx/remove-add-wallet-addresses,subdir=proto; then
echo "Failed to generate protobuf definitions"
exit 1
fi
Expand Down
2 changes: 2 additions & 0 deletions xmtp_cryptography/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ serde = "1.0.163"
sha2 = "0.10.7"
sha3 = "0.10.6"
thiserror = "1.0.40"
curve25519-dalek = "4"
log = "0.4"

[features]
ws = ["ethers/ws"]
Expand Down
66 changes: 65 additions & 1 deletion xmtp_cryptography/src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use curve25519_dalek::{edwards::CompressedEdwardsY, traits::IsIdentity};
use ethers_core::types::{self as ethers_types, H160};
pub use k256::ecdsa::{RecoveryId, SigningKey, VerifyingKey};
use k256::Secp256k1;
Expand Down Expand Up @@ -111,14 +112,58 @@ pub fn h160addr_to_string(bytes: H160) -> String {
s
}

/// Check if an string is a valid ethereum address (valid hex and length 20).
pub fn is_valid_ethereum_address<S: AsRef<str>>(address: S) -> bool {
let address = address.as_ref();
let address = address.strip_prefix("0x").unwrap_or(address);

if address.len() != 40 {
return false;
}

address.chars().all(|c| c.is_ascii_hexdigit())
}

/// Check if an ed25519 public signature key is valid.
pub fn is_valid_ed25519_public_key<Bytes: AsRef<[u8]>>(public_key: Bytes) -> bool {
insipx marked this conversation as resolved.
Show resolved Hide resolved
let public_key = public_key.as_ref();

let compressed = match CompressedEdwardsY::from_slice(public_key) {
Ok(v) => v,
Err(_) => {
log::debug!("Invalid ed22519 public key. Does not have length of 32");
return false;
}
};

match compressed.decompress() {
Some(point) => {
if point.is_small_order() || point.is_identity() {
log::debug!(
"Invalid public key, not a point on the curve or is the identity element."
);
return false;
}
}
None => {
log::debug!("Not a valid ed25519 public key: Decompression failure");
return false;
}
}

true
}

#[cfg(test)]
pub mod tests {
use super::is_valid_ethereum_address;

use ethers::{
core::rand::thread_rng,
signers::{LocalWallet, Signer},
};

use crate::signature::RecoverableSignature;
use crate::signature::{is_valid_ed25519_public_key, RecoverableSignature};

pub async fn generate_random_signature(msg: &str) -> (String, Vec<u8>) {
let wallet = LocalWallet::new(&mut thread_rng());
Expand Down Expand Up @@ -174,4 +219,23 @@ pub mod tests {
assert!(sig.verify_signature(addr_bad, msg).is_err());
assert!(sig.verify_signature(addr, msg_bad).is_err());
}

#[test]
fn test_eth_address() {
assert!(is_valid_ethereum_address(
"0x7e57Aed10441c8879ce08E45805EC01Ee9689c9f"
));
assert_eq!(is_valid_ethereum_address("123"), false);
}

#[test]
fn test_ed25519_public_key_validation() {
let public_key =
hex::decode("5E7F70A437963A8B3D0683F949FA0508970ACB87A28139B8BD67D5B01D3B0214")
.unwrap();
assert!(is_valid_ed25519_public_key(public_key));

let invalid_key = b"invalid";
assert!(!is_valid_ed25519_public_key(invalid_key));
}
}
2 changes: 2 additions & 0 deletions xmtp_mls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ tempfile = "3.5.0"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
xmtp_api_grpc = { path = "../xmtp_api_grpc" }
flume = "0.11"
ctor = "0.2"
tokio = { version = "1.28.1", features = ["macros", "time", "rt-multi-thread"] }
16 changes: 7 additions & 9 deletions xmtp_mls/src/api_client_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ where
loop {
let mut result = retry_async!(
self.retry_strategy,
(|| async {
(async {
self.api_client
.query(QueryRequest {
content_topics: vec![topic.to_string()],
Expand Down Expand Up @@ -91,7 +91,7 @@ where
) -> Result<Vec<u8>, ApiError> {
let res = retry_async!(
self.retry_strategy,
(|| async {
(async {
self.api_client
.register_installation(RegisterInstallationRequest {
last_resort_key_package: Some(KeyPackageUpload {
Expand All @@ -108,7 +108,7 @@ where
pub async fn upload_key_packages(&self, key_packages: Vec<Vec<u8>>) -> Result<(), ApiError> {
retry_async!(
self.retry_strategy,
(|| async {
(async {
self.api_client
.upload_key_packages(UploadKeyPackagesRequest {
key_packages: key_packages
Expand All @@ -132,7 +132,7 @@ where
) -> Result<KeyPackageMap, ApiError> {
let res = retry_async!(
self.retry_strategy,
(|| async {
(async {
self.api_client
.consume_key_packages(ConsumeKeyPackagesRequest {
installation_ids: installation_ids.clone(),
Expand Down Expand Up @@ -179,7 +179,7 @@ where

retry_async!(
self.retry_strategy,
(|| async {
(async {
self.api_client
.publish_welcomes(PublishWelcomesRequest {
welcome_messages: welcome_requests.clone(),
Expand All @@ -198,7 +198,7 @@ where
) -> Result<IdentityUpdatesMap, ApiError> {
let result = retry_async!(
self.retry_strategy,
(|| async {
(async {
self.api_client
.get_identity_updates(GetIdentityUpdatesRequest {
start_time_ns,
Expand Down Expand Up @@ -262,7 +262,7 @@ where

retry_async!(
self.retry_strategy,
(|| async {
(async {
self.api_client
.publish_to_group(PublishToGroupRequest {
messages: to_send.clone(),
Expand Down Expand Up @@ -650,8 +650,6 @@ mod tests {

#[tokio::test]
async fn it_retries_twice_then_succeeds() {
crate::tests::setup();

let mut mock_api = MockApiClient::new();
let topic = "topic";
let start_time_ns = 10;
Expand Down
16 changes: 15 additions & 1 deletion xmtp_mls/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use xmtp_proto::api_client::{Envelope, XmtpApiClient, XmtpMlsClient};

use crate::{
api_client_wrapper::{ApiClientWrapper, IdentityUpdate},
groups::{IntentError, MlsGroup},
groups::{AddressOrInstallationId, IntentError, MlsGroup},
identity::Identity,
retry::Retry,
storage::{
Expand Down Expand Up @@ -271,6 +271,20 @@ where
})
}

pub(crate) async fn get_key_packages(
&self,
address_or_id: AddressOrInstallationId,
) -> Result<Vec<VerifiedKeyPackage>, ClientError> {
match address_or_id {
AddressOrInstallationId::AccountAddresses(addrs) => {
self.get_key_packages_for_wallet_addresses(addrs).await
}
AddressOrInstallationId::InstallationIds(ids) => {
self.get_key_packages_for_installation_ids(ids).await
}
}
}

// Get a flat list of one key package per installation for all the wallet addresses provided.
// Revoked installations will be omitted from the list
#[allow(dead_code)]
Expand Down
42 changes: 6 additions & 36 deletions xmtp_mls/src/codecs/membership_change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::HashMap;

use prost::Message;
use xmtp_proto::xmtp::mls::message_contents::{
ContentTypeId, EncodedContent, GroupMembershipChange,
ContentTypeId, EncodedContent, GroupMembershipChanges,
};

use super::{CodecError, ContentCodec};
Expand All @@ -14,7 +14,7 @@ impl GroupMembershipChangeCodec {
const TYPE_ID: &'static str = "group_membership_change";
}

impl ContentCodec<GroupMembershipChange> for GroupMembershipChangeCodec {
impl ContentCodec<GroupMembershipChanges> for GroupMembershipChangeCodec {
fn content_type() -> ContentTypeId {
ContentTypeId {
authority_id: GroupMembershipChangeCodec::AUTHORITY_ID.to_string(),
Expand All @@ -24,7 +24,7 @@ impl ContentCodec<GroupMembershipChange> for GroupMembershipChangeCodec {
}
}

fn encode(data: GroupMembershipChange) -> Result<EncodedContent, CodecError> {
fn encode(data: GroupMembershipChanges) -> Result<EncodedContent, CodecError> {
let mut buf = Vec::new();
data.encode(&mut buf)
.map_err(|e| CodecError::Encode(e.to_string()))?;
Expand All @@ -38,43 +38,13 @@ impl ContentCodec<GroupMembershipChange> for GroupMembershipChangeCodec {
})
}

fn decode(content: EncodedContent) -> Result<GroupMembershipChange, CodecError> {
let decoded = GroupMembershipChange::decode(content.content.as_slice())
fn decode(content: EncodedContent) -> Result<GroupMembershipChanges, CodecError> {
let decoded = GroupMembershipChanges::decode(content.content.as_slice())
.map_err(|e| CodecError::Decode(e.to_string()))?;

Ok(decoded)
}
}

#[cfg(test)]
mod tests {
use xmtp_proto::xmtp::mls::message_contents::Member;

use crate::utils::test::{rand_string, rand_vec};

use super::*;

#[test]
fn test_encode_decode() {
let new_member = Member {
installation_ids: vec![rand_vec()],
wallet_address: rand_string(),
};
let data = GroupMembershipChange {
members_added: vec![new_member.clone()],
members_removed: vec![],
installations_added: vec![],
installations_removed: vec![],
};

let encoded = GroupMembershipChangeCodec::encode(data).unwrap();
assert_eq!(
encoded.clone().r#type.unwrap().type_id,
"group_membership_change"
);
assert!(!encoded.content.is_empty());

let decoded = GroupMembershipChangeCodec::decode(encoded).unwrap();
assert_eq!(decoded.members_added[0], new_member);
}
}
mod tests {}
Loading
Loading