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

IdentityUpdate serialization #633

Merged
merged 6 commits into from
Apr 11, 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
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
Loading