Skip to content

Commit

Permalink
IdentityUpdate serialization (#633)
Browse files Browse the repository at this point in the history
## tl;dr

- Adds serialization and deserialization for IdentityUpdate
- Adds a missing `inbox_id` field to a few of the action types
- Scaffolds out the `Signature` types. Still need real verification of signatures and recovery addresses.
- Rename the `IdentityUpdateBuilder` to `SignatureRequestBuilder`
- Make current with final version of protos (moved the `inbox_id` to the `IdentityUpdate` instead of being on each action)
  • Loading branch information
neekolas authored Apr 11, 2024
1 parent 19d2601 commit 121a867
Show file tree
Hide file tree
Showing 20 changed files with 7,375 additions and 1,452 deletions.
1,377 changes: 1,246 additions & 131 deletions Cargo.lock

Large diffs are not rendered by default.

38 changes: 24 additions & 14 deletions xmtp_id/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
[package]
edition = "2021"
name = "xmtp_id"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow.workspace = true
async-trait.workspace = true
chrono.workspace = true
ethers.workspace = true
futures.workspace = true
hex.workspace = true
log.workspace = true
tracing.workspace = true
thiserror.workspace = true
xmtp_cryptography.workspace = true
xmtp_mls.workspace = true
xmtp_proto.workspace = true
openmls_traits.workspace = true
openmls.workspace = true
openmls_basic_credential.workspace = true
openmls_rust_crypto.workspace = true
openmls_traits.workspace = true
prost.workspace = true
rand.workspace = true
serde.workspace = true
async-trait.workspace = true
futures.workspace = true
sha2 = "0.10.8"
rand.workspace = true
hex.workspace = true
ethers.workspace = true
thiserror.workspace = true
tracing.workspace = true
xmtp_cryptography.workspace = true
xmtp_mls.workspace = true
xmtp_proto.workspace = true

[dev-dependencies]
tracing-subscriber.workspace = true
tokio = { workspace = true, features = ["time"] }
anyhow.workspace = true
ctor = "0.2.5"
ethers = { workspace = true, features = ["ws"] }
futures = "0.3"
jsonrpsee = { workspace = true, features = ["macros", "ws-client"] }
regex = "1.10"
serde_json.workspace = true
surf = "2.3"
tokio = { workspace = true, features = ["time"] }
tokio-test = "0.4"
tracing-subscriber.workspace = true
21 changes: 20 additions & 1 deletion xmtp_id/src/associations/association_log.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use super::hashes::generate_inbox_id;
use super::member::{Member, MemberIdentifier, MemberKind};
use super::serialization::{
from_identity_update_proto, to_identity_update_proto, DeserializationError, SerializationError,
};
use super::signature::{Signature, SignatureError, SignatureKind};
use super::state::AssociationState;

use thiserror::Error;
use xmtp_proto::xmtp::identity::associations::IdentityUpdate as IdentityUpdateProto;

#[derive(Debug, Error)]
pub enum AssociationError {
Expand All @@ -23,6 +27,8 @@ pub enum AssociationError {
LegacySignatureReuse,
#[error("The new member identifier does not match the signer")]
NewMemberIdSignatureMismatch,
#[error("Wrong inbox_id specified on association")]
WrongInboxId,
#[error("Signature not allowed for role {0:?} {1:?}")]
SignatureNotAllowed(String, String),
#[error("Replay detected")]
Expand Down Expand Up @@ -303,17 +309,27 @@ impl IdentityAction for Action {

/// An `IdentityUpdate` contains one or more Actions that can be applied to the AssociationState
pub struct IdentityUpdate {
pub inbox_id: String,
pub client_timestamp_ns: u64,
pub actions: Vec<Action>,
}

impl IdentityUpdate {
pub fn new(actions: Vec<Action>, client_timestamp_ns: u64) -> Self {
pub fn new(actions: Vec<Action>, inbox_id: String, client_timestamp_ns: u64) -> Self {
Self {
inbox_id,
actions,
client_timestamp_ns,
}
}

pub fn to_proto(&self) -> Result<IdentityUpdateProto, SerializationError> {
to_identity_update_proto(self)
}

pub fn from_proto(proto: IdentityUpdateProto) -> Result<Self, DeserializationError> {
from_identity_update_proto(proto)
}
}

impl IdentityAction for IdentityUpdate {
Expand All @@ -327,6 +343,9 @@ impl IdentityAction for IdentityUpdate {
}

let new_state = state.ok_or(AssociationError::NotCreated)?;
if new_state.inbox_id().ne(&self.inbox_id) {
return Err(AssociationError::WrongInboxId);
}

// After all the updates in the LogEntry have been processed, add the list of signatures to the state
// so that the signatures can not be re-used in subsequent updates
Expand Down
70 changes: 44 additions & 26 deletions xmtp_id/src/associations/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@ use super::{
Action, IdentityUpdate, MemberIdentifier, Signature, SignatureError,
};

#[derive(Error, Debug)]
pub enum IdentityBuilderError {
#[error("Missing signer")]
MissingSigner,
}

/// The SignatureField is used to map the signatures from a [SignatureRequest] back to the correct
/// field in an [IdentityUpdate]. It is used in the `pending_signatures` map in a [PendingIdentityAction]
#[derive(Clone, PartialEq, Hash, Eq)]
enum SignatureField {
InitialAddress,
Expand All @@ -33,13 +29,17 @@ pub struct PendingIdentityAction {
pending_signatures: HashMap<SignatureField, MemberIdentifier>,
}

pub struct IdentityUpdateBuilder {
/// The SignatureRequestBuilder is used to collect all of the actions in
/// an IdentityUpdate, but without the signatures.
/// It outputs a SignatureRequest, which can then collect the relevant signatures and be turned into
/// an IdentityUpdate.
pub struct SignatureRequestBuilder {
inbox_id: String,
client_timestamp_ns: u64,
actions: Vec<PendingIdentityAction>,
}

impl IdentityUpdateBuilder {
impl SignatureRequestBuilder {
/// Create a new IdentityUpdateBuilder for the given `inbox_id`
pub fn new(inbox_id: String) -> Self {
Self {
Expand Down Expand Up @@ -76,7 +76,6 @@ impl IdentityUpdateBuilder {
self.actions.push(PendingIdentityAction {
unsigned_action: UnsignedAction::AddAssociation(UnsignedAddAssociation {
new_member_identifier: new_member_identifier.clone(),
inbox_id: self.inbox_id.clone(),
}),
pending_signatures: HashMap::from([
(
Expand All @@ -101,7 +100,6 @@ impl IdentityUpdateBuilder {
recovery_address_identifier.clone(),
)]),
unsigned_action: UnsignedAction::RevokeAssociation(UnsignedRevokeAssociation {
inbox_id: self.inbox_id.clone(),
revoked_member,
}),
});
Expand All @@ -120,24 +118,32 @@ impl IdentityUpdateBuilder {
recovery_address_identifier.clone(),
)]),
unsigned_action: UnsignedAction::ChangeRecoveryAddress(UnsignedChangeRecoveryAddress {
inbox_id: self.inbox_id.clone(),
new_recovery_address,
}),
});

self
}

pub fn to_signature_request(self) -> SignatureRequest {
pub fn build(self) -> SignatureRequest {
let unsigned_actions: Vec<UnsignedAction> = self
.actions
.iter()
.map(|pending_action| pending_action.unsigned_action.clone())
.collect();

let signature_text = get_signature_text(unsigned_actions, self.client_timestamp_ns);
let signature_text = get_signature_text(
unsigned_actions,
self.inbox_id.clone(),
self.client_timestamp_ns,
);

SignatureRequest::new(self.actions, signature_text, self.client_timestamp_ns)
SignatureRequest::new(
self.actions,
signature_text,
self.inbox_id,
self.client_timestamp_ns,
)
}
}

Expand All @@ -161,15 +167,18 @@ pub struct SignatureRequest {
signature_text: String,
signatures: HashMap<MemberIdentifier, Box<dyn Signature>>,
client_timestamp_ns: u64,
inbox_id: String,
}

impl SignatureRequest {
pub fn new(
pending_actions: Vec<PendingIdentityAction>,
signature_text: String,
inbox_id: String,
client_timestamp_ns: u64,
) -> Self {
Self {
inbox_id,
pending_actions,
signature_text,
signatures: HashMap::new(),
Expand Down Expand Up @@ -220,7 +229,7 @@ impl SignatureRequest {
self.signature_text.clone()
}

pub fn build_identity_update(&self) -> Result<IdentityUpdate, SignatureRequestError> {
pub fn build_identity_update(self) -> Result<IdentityUpdate, SignatureRequestError> {
if !self.is_ready() {
return Err(SignatureRequestError::MissingSigner);
}
Expand All @@ -232,7 +241,11 @@ impl SignatureRequest {
.map(|pending_action| build_action(pending_action, &self.signatures))
.collect::<Result<Vec<Action>, SignatureRequestError>>()?;

Ok(IdentityUpdate::new(actions, self.client_timestamp_ns))
Ok(IdentityUpdate::new(
actions,
self.inbox_id,
self.client_timestamp_ns,
))
}
}

Expand Down Expand Up @@ -317,10 +330,15 @@ fn build_action(
}
}

fn get_signature_text(actions: Vec<UnsignedAction>, client_timestamp_ns: u64) -> String {
fn get_signature_text(
actions: Vec<UnsignedAction>,
inbox_id: String,
client_timestamp_ns: u64,
) -> String {
let identity_update = UnsignedIdentityUpdate {
client_timestamp_ns,
actions,
inbox_id,
};

identity_update.signature_text()
Expand All @@ -337,7 +355,7 @@ mod tests {

use super::*;

// Helper function to add all the missing signatures
// Helper function to add all the missing signatures, since we don't have real signers available
fn add_missing_signatures_to_request(signature_request: &mut SignatureRequest) {
let missing_signatures = signature_request.missing_signatures();
for member_identifier in missing_signatures {
Expand All @@ -362,9 +380,9 @@ mod tests {
let account_address = "account_address".to_string();
let nonce = 0;
let inbox_id = generate_inbox_id(&account_address, &nonce);
let mut signature_request = IdentityUpdateBuilder::new(inbox_id)
let mut signature_request = SignatureRequestBuilder::new(inbox_id)
.create_inbox(account_address.into(), nonce)
.to_signature_request();
.build();

add_missing_signatures_to_request(&mut signature_request);

Expand All @@ -383,10 +401,10 @@ mod tests {
let existing_member_identifier: MemberIdentifier = account_address.into();
let new_member_identifier: MemberIdentifier = rand_vec().into();

let mut signature_request = IdentityUpdateBuilder::new(inbox_id)
let mut signature_request = SignatureRequestBuilder::new(inbox_id)
.create_inbox(existing_member_identifier.clone(), nonce)
.add_association(new_member_identifier, existing_member_identifier)
.to_signature_request();
.build();

add_missing_signatures_to_request(&mut signature_request);

Expand All @@ -405,10 +423,10 @@ mod tests {
let inbox_id = generate_inbox_id(&account_address, &nonce);
let existing_member_identifier: MemberIdentifier = account_address.clone().into();

let mut signature_request = IdentityUpdateBuilder::new(inbox_id)
let mut signature_request = SignatureRequestBuilder::new(inbox_id)
.create_inbox(existing_member_identifier.clone(), nonce)
.revoke_association(existing_member_identifier.clone(), account_address.into())
.to_signature_request();
.build();

add_missing_signatures_to_request(&mut signature_request);

Expand All @@ -426,9 +444,9 @@ mod tests {
let account_address = "account_address".to_string();
let nonce = 0;
let inbox_id = generate_inbox_id(&account_address, &nonce);
let mut signature_request = IdentityUpdateBuilder::new(inbox_id)
let mut signature_request = SignatureRequestBuilder::new(inbox_id)
.create_inbox(account_address.into(), nonce)
.to_signature_request();
.build();

let attempt_to_add_random_member = signature_request.add_signature(
MockSignature::new_boxed(true, rand_string().into(), SignatureKind::Erc191, None),
Expand Down
Loading

0 comments on commit 121a867

Please sign in to comment.