Skip to content

Commit 96fcc6f

Browse files
committed
Bootstrap associations
1 parent 25126e6 commit 96fcc6f

File tree

9 files changed

+277
-6
lines changed

9 files changed

+277
-6
lines changed

Cargo.lock

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings_ffi/Cargo.lock

-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

xmtp_id/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@ chrono.workspace = true
2121
serde.workspace = true
2222
async-trait.workspace = true
2323
futures.workspace = true
24+
sha2 = "0.10.8"
25+
rand.workspace = true
26+
hex.workspace = true
2427

xmtp_id/src/associations/hashes.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use sha2::{Digest, Sha256};
2+
3+
pub fn sha256_string(input: String) -> String {
4+
let mut hasher = Sha256::new();
5+
hasher.update(input.as_bytes());
6+
let result = hasher.finalize();
7+
format!("{:x}", result)
8+
}
9+
10+
pub fn generate_xid(account_address: &String, nonce: &u64) -> String {
11+
sha256_string(format!("{}{}", account_address, nonce))
12+
}

xmtp_id/src/associations/member.rs

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#[derive(Clone, Debug, PartialEq)]
2+
pub enum MemberKind {
3+
Installation,
4+
Address,
5+
}
6+
7+
impl std::fmt::Display for MemberKind {
8+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
9+
match self {
10+
MemberKind::Installation => write!(f, "installation"),
11+
MemberKind::Address => write!(f, "address"),
12+
}
13+
}
14+
}
15+
16+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
17+
pub enum MemberIdentifier {
18+
Address(String),
19+
Installation(Vec<u8>),
20+
}
21+
22+
impl MemberIdentifier {
23+
pub fn kind(&self) -> MemberKind {
24+
match self {
25+
MemberIdentifier::Address(_) => MemberKind::Address,
26+
MemberIdentifier::Installation(_) => MemberKind::Installation,
27+
}
28+
}
29+
}
30+
31+
impl std::fmt::Display for MemberIdentifier {
32+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
33+
let as_string = match self {
34+
MemberIdentifier::Address(address) => address.to_string(),
35+
MemberIdentifier::Installation(installation) => hex::encode(installation),
36+
};
37+
38+
write!(f, "{}", as_string)
39+
}
40+
}
41+
42+
impl From<String> for MemberIdentifier {
43+
fn from(address: String) -> Self {
44+
MemberIdentifier::Address(address)
45+
}
46+
}
47+
48+
impl From<Vec<u8>> for MemberIdentifier {
49+
fn from(installation: Vec<u8>) -> Self {
50+
MemberIdentifier::Installation(installation)
51+
}
52+
}
53+
54+
#[derive(Clone, Debug, PartialEq)]
55+
pub struct Member {
56+
pub identifier: MemberIdentifier,
57+
pub added_by_entity: Option<MemberIdentifier>,
58+
}
59+
60+
impl Member {
61+
pub fn new(identifier: MemberIdentifier, added_by_entity: Option<MemberIdentifier>) -> Self {
62+
Self {
63+
identifier,
64+
added_by_entity,
65+
}
66+
}
67+
68+
pub fn kind(&self) -> MemberKind {
69+
self.identifier.kind()
70+
}
71+
}
72+
73+
impl PartialEq<MemberIdentifier> for Member {
74+
fn eq(&self, other: &MemberIdentifier) -> bool {
75+
self.identifier.eq(other)
76+
}
77+
}
78+
79+
#[cfg(test)]
80+
mod tests {
81+
use crate::associations::test_utils;
82+
83+
use super::*;
84+
85+
use test_utils::rand_string;
86+
87+
impl Default for MemberIdentifier {
88+
fn default() -> Self {
89+
MemberIdentifier::Address(rand_string())
90+
}
91+
}
92+
93+
impl Default for Member {
94+
fn default() -> Self {
95+
Self {
96+
identifier: MemberIdentifier::default(),
97+
added_by_entity: None,
98+
}
99+
}
100+
}
101+
102+
#[test]
103+
fn test_identifier_comparisons() {
104+
let address_1 = MemberIdentifier::Address("0x123".to_string());
105+
let address_2 = MemberIdentifier::Address("0x456".to_string());
106+
let address_1_copy = MemberIdentifier::Address("0x123".to_string());
107+
108+
assert!(address_1 != address_2);
109+
assert!(address_1.ne(&address_2));
110+
assert!(address_1 == address_1_copy);
111+
112+
let installation_1 = MemberIdentifier::Installation(vec![1, 2, 3]);
113+
let installation_2 = MemberIdentifier::Installation(vec![4, 5, 6]);
114+
let installation_1_copy = MemberIdentifier::Installation(vec![1, 2, 3]);
115+
116+
assert!(installation_1 != installation_2);
117+
assert!(installation_1.ne(&installation_2));
118+
assert!(installation_1 == installation_1_copy);
119+
}
120+
}

xmtp_id/src/associations/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
mod hashes;
2+
mod member;
3+
mod state;
4+
#[cfg(test)]
5+
mod test_utils;
6+
7+
pub use self::member::{Member, MemberIdentifier, MemberKind};
8+
pub use self::state::AssociationState;

xmtp_id/src/associations/state.rs

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::collections::{HashMap, HashSet};
2+
3+
use super::{hashes::generate_xid, member::Member, MemberIdentifier, MemberKind};
4+
5+
#[derive(Clone, Debug)]
6+
pub struct AssociationState {
7+
xid: String,
8+
members: HashMap<MemberIdentifier, Member>,
9+
recovery_address: String,
10+
seen_signatures: HashSet<Vec<u8>>,
11+
}
12+
13+
impl AssociationState {
14+
pub fn add(&self, member: Member) -> Self {
15+
let mut new_state = self.clone();
16+
let _ = new_state.members.insert(member.identifier.clone(), member);
17+
18+
new_state
19+
}
20+
21+
pub fn remove(&self, identifier: &MemberIdentifier) -> Self {
22+
let mut new_state = self.clone();
23+
let _ = new_state.members.remove(identifier);
24+
25+
new_state
26+
}
27+
28+
pub fn set_recovery_address(&self, recovery_address: String) -> Self {
29+
let mut new_state = self.clone();
30+
new_state.recovery_address = recovery_address;
31+
32+
new_state
33+
}
34+
35+
pub fn get(&self, identifier: &MemberIdentifier) -> Option<Member> {
36+
self.members.get(identifier).cloned()
37+
}
38+
39+
pub fn add_seen_signatures(&self, signatures: Vec<Vec<u8>>) -> Self {
40+
let mut new_state = self.clone();
41+
new_state.seen_signatures.extend(signatures);
42+
43+
new_state
44+
}
45+
46+
pub fn has_seen(&self, signature: &Vec<u8>) -> bool {
47+
self.seen_signatures.contains(signature)
48+
}
49+
50+
pub fn members(&self) -> Vec<Member> {
51+
self.members.values().cloned().collect()
52+
}
53+
54+
pub fn xid(&self) -> &String {
55+
&self.xid
56+
}
57+
58+
pub fn recovery_address(&self) -> &String {
59+
&self.recovery_address
60+
}
61+
62+
pub fn members_by_parent(&self, parent_id: &MemberIdentifier) -> Vec<Member> {
63+
self.members
64+
.values()
65+
.filter(|e| e.added_by_entity.eq(&Some(parent_id.clone())))
66+
.cloned()
67+
.collect()
68+
}
69+
70+
pub fn members_by_kind(&self, kind: MemberKind) -> Vec<Member> {
71+
self.members
72+
.values()
73+
.filter(|e| e.kind() == kind)
74+
.cloned()
75+
.collect()
76+
}
77+
78+
pub fn new(account_address: String, nonce: u64) -> Self {
79+
let xid = generate_xid(&account_address, &nonce);
80+
let identifier = MemberIdentifier::Address(account_address.clone());
81+
let new_member = Member::new(identifier.clone(), None);
82+
Self {
83+
members: {
84+
let mut members = HashMap::new();
85+
members.insert(identifier, new_member);
86+
members
87+
},
88+
seen_signatures: HashSet::new(),
89+
recovery_address: account_address,
90+
xid,
91+
}
92+
}
93+
}
94+
95+
#[cfg(test)]
96+
mod tests {
97+
use crate::associations::test_utils::rand_string;
98+
99+
use super::*;
100+
101+
#[test]
102+
fn can_add_remove() {
103+
let starting_state = AssociationState::new(rand_string(), 0);
104+
let new_entity = Member::default();
105+
let with_add = starting_state.add(new_entity.clone());
106+
assert!(with_add.get(&new_entity.identifier).is_some());
107+
assert!(starting_state.get(&new_entity.identifier).is_none());
108+
}
109+
}
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use rand::{distributions::Alphanumeric, Rng};
2+
3+
pub fn rand_string() -> String {
4+
let v: String = rand::thread_rng()
5+
.sample_iter(&Alphanumeric)
6+
.take(32)
7+
.map(char::from)
8+
.collect();
9+
10+
v
11+
}
12+
13+
pub fn rand_u64() -> u64 {
14+
rand::thread_rng().gen()
15+
}
16+
17+
pub fn rand_vec() -> Vec<u8> {
18+
let mut buf = [0u8; 32];
19+
rand::thread_rng().fill(&mut buf[..]);
20+
buf.to_vec()
21+
}

xmtp_id/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod associations;
12
pub mod credential_verifier;
23
pub mod verified_key_package;
34

0 commit comments

Comments
 (0)