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

Add detail and address feedback on identity doc #353

Merged
merged 11 commits into from
Dec 5, 2023
152 changes: 118 additions & 34 deletions xmtp_mls/IDENTITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ Amal's account (Ethereum wallet address)
├── Converse app (mobile phone)
│ └── Installation key bundle 1
├── Coinbase Wallet (mobile phone)
├── Coinbase Wallet app (mobile phone)
│ └── Installation key bundle 2
├── Lenster (tablet)
├── Lenster app (tablet)
│ └── Installation key bundle 3
└── Coinbase Wallet (tablet)
└── Coinbase Wallet app (tablet)
└── Installation key bundle 4
```

Expand All @@ -24,59 +24,143 @@ Using per-installation keys provides the following benefits:
- The user may enumerate the installations that have messaging access to their account.
- The user may revoke keys on a per-installation level.

**Installation provisioning**
## Identity lifecycle

Every new app installation gains messaging access as follows:
### Ethereum wallet

1. A new Ed25519 signature key pair is generated and stored on the device, representing the installation's identity.
2. The app prompts the user to sign the public key with their Ethereum wallet, establishing an association between the installation's identity and the user’s account. Example text:
As of Nov 30 2023, an Ethereum wallet consists of a secp256k1 keypair, and is identified by a public address, which is the hex-encoding of the last 20 bytes of the Keccak-256 hash of the public key, prepended by `0x`. Wallet keys do not expire and are not rotatable - in the event of a compromise, the user must create a new wallet. The user is expected to have a pre-existing Ethereum wallet prior to onboarding with XMTP.

The wallet keys can be used to sign arbitrary text, with most wallet software requiring explicit [user acceptance](https://docs.metamask.io/wallet/how-to/sign-data/#use-personal_sign) of the signature text. The signature text is formatted according to version `0x45` of [EIP-191](https://eips.ethereum.org/EIPS/eip-191), and is signed via a recoverable ECDSA signature.

Wallet signature requests originating from XMTP will additionally prepend context to the EIP-191 `message` field to prevent collisions between signatures in different contexts:

```
XMTP: <Label>\n\n
```

| Label | Described in section |
| ----------------------- | ------------------------------------------------------- |
| Grant messaging access | [Installation registration](#installation-registration) |
| Revoke messaging access | [Installation revocation](#installation-revocation) |

### Installation registration

XMTP installations consist of a long-lived Ed25519 key-pair (the 'installation key') and are identified via the Ethereum addressing format. The public installation key is used as the `signature_key` in all MLS leaf nodes, and is associated with the account's wallet via a wallet-signed credential. Every new app installation gains messaging access as follows:

1. The new Ed25519 key pair (installation key) is generated and stored on the device.
2. The app prompts the user to sign the public key with their Ethereum wallet. The user is expected to inspect the text and reject the signing request if the data is invalid, for example if the displayed time is incorrect. The format for version 1 of the association text is as follows:

```
XMTP: Grant Messaging Access

Current Time: <current time and local timezone>
Installation Key: <hex(last_20_bytes(keccak256(Ed25519PublicKey)))>
Current Time: <ISO 8601 date and time with local UTC offset>
Installation ID: <hex(last_20_bytes(keccak256(Ed25519PublicKey)))>
```

3. The signature and related data is then protobuf-serialized to form the MLS Credential:

```
struct {
association_text_version: i32,
signature: bytes,
iso8601_time: string,
wallet_address: string,
} Eip191Association;


struct {
installation_public_key: bytes,
eip191_association: Eip191Association
} MlsCredential;
```

4. A last resort KeyPackage (containing the credential and signed by the installation key per the [MLS spec](https://www.rfc-editor.org/rfc/rfc9420.html#name-key-packages)) is generated and stored on the device.
5. The app publishes the last resort key package to the server, which implicitly serves as a registration. The server will provide all identity updates (registrations and revocations) for a given wallet address to any client that requests it.

### Credential validation

Apps built on XMTP have a reduced need for safety numbers to be shown, as clients can locally validate that a credential is valid.

Credential validation must be performed by clients at the [events described by the MLS spec](https://www.rfc-editor.org/rfc/rfc9420.html#name-credential-validation) as follows:

1. Verify that the referenced `installation_public_key` has not been revoked (see [Installation revocation](#installation-revocation)).
1. Verify that the referenced `installation_public_key` matches the `signature_key` of the leaf node.
1. Derive the association text using the `association_text_version`, `creation_iso8601_time`, `installation_public_key`, with a label of `Grant Messaging Access`.
1. Recover the wallet public key from the recoverable ECDSA `signature` on the association text.
1. Derive the wallet address from the public key and verify that it matches the `wallet_address` on the association.

Currently, verifying revocations require a degree of server trust, however this is not the long-term goal - see [Server trust](#server-trust).

### Installation revocation

_Note: Revocation is not scheduled to be built until Q2 2024 or later_

Users may revoke an installation as follows:

1. Enumerate active installations by querying for all identity updates under the account. The user may identify each installation by the creation time as well as the installation public key of the credential.
1. Select the installation to revoke.
1. The app prompts the user to sign the revocation with their Ethereum wallet. The user is expected to inspect the text and reject the signing request if the data is invalid, for example if the displayed time is incorrect. The format for version 1 of the association text is as follows:

```
XMTP: Revoke Messaging Access

Current Time: <ISO 8601 date and time with local UTC offset>
Installation ID: <hex(last_20_bytes(keccak256(Ed25519PublicKey)))>
```

3. The following data is protobuf-serialized to form the MLS Credential. The credential can be presented alongside the installation key to prove an association with an account:
1. The signature and related data is then protobuf-serialized to form the revocation:

```
MlsCredential {
Eip191Association {
association_text_version: i32,
signature: bytes,
wallet_address: string,
creation_time_ns: i64,
}
}
struct {
installation_public_key: bytes,
eip191_association: Eip191Association
} InstallationRevocation;
```

4. A last resort KeyPackage (signed by the installation key per the MLS spec) is generated and stored on the device.
5. The app publishes the public signing key, credential, and last resort key package to the server, which stores it under the account. Other apps may query for this information to understand that the installation is on the network, associated with the account, and how to contact it.
1. The app publishes the revocation to the server. The server will provide all identity updates (registrations and revocations) for a given wallet address to any client that requests it.
1. The installation performing the revocation enumerates all known groups that it is a member of, and submits proposals to remove the revoked installation. Additional removals may also occur via the process described in [Installation synchronization](#installation-synchronization).

Validation of revocation payloads is identical to the process described in [Credential Validation](#credential-validation), except that a label of `Revoke Messaging Access` is used.

Once an installation is revoked, it cannot be re-registered or re-provisioned. The time displayed on the revocation is for informational purposes only.

Revocations may not apply immediately on all groups. In order to ensure transcript consistency, payloads from revoked installations are considered valid if they were published before the installation was removed from the group.

### Installation synchronization

At any time in the course of a conversation, the list of valid installations for the participating wallet addresses may change via registration or revocation.

**Installation management**
Clients must perform the following validation prior to publishing each payload on the group, as well as periodically:

At any time, the user may enumerate active installations by querying for all identity updates under the account. The user may identify each installation by the creation time as well as the installation key from the signing text.
1. Assemble a list of wallet addresses in the conversation from the leaf nodes.
1. Fetch all identity updates on those wallet addresses.
1. Validate the credentials and revocations and construct a list of valid installations (registered installations minus revoked installations).
1. Publish a commit to remove nodes from the conversation that are not in the list of valid installations.
1. Publish a commit to add nodes to the conversation that are in the list of valid installations and not already present.

In the event of a compromise, malicious app, or no longer used installation, the user may revoke an installation’s messaging access going forward by signing a revocation payload containing the installation’s identity keys using their wallet and publishing it to the server. This will subsequently be surfaced in the identity update list for clients to validate.
XMTP clients may perform performance optimizations, such as caching installation lists with a short TTL.

**Authentication service**
### Server trust

Unlike in other messaging providers, XMTP accounts are key-pairs. This means that the link between an installation and an account can be achieved via a signature that can be programmatically validated on any client device. There is no need for a centralized service to validate credentials, nor for safety numbers to be shown in apps built on top of XMTP. Additionally, apps built on top of XMTP may choose to layer on decentralized name resolution protocols such as ENS in order to display a user-friendly name.
We currently rely on trust in centralized XMTP servers, which could do the following if malicious.

Although registrations and revocations cannot be forged, we currently rely on trust in centralized XMTP servers not to maliciously hide installation registration and revocation payloads from clients that request them. Current decentralization efforts within XMTP will eventually produce a trustless public immutable record of registrations and revocations that does not rely on any single entity.
1. **Omit payloads**. A malicious server could hide registrations (and application messages) from valid installations in order to censor them, or revocations for invalid installations in order to prevent post-compromise recovery.
- Decentralization efforts within XMTP aim to produce an immutable log of identity updates (H1 2024), with immutability of conversation payloads coming later.
1. **Reorder payloads**. A malicious server could reorder messages and commits within a conversation.
- Decentralization efforts within XMTP aim to produce a consensus-driven ordering of commits at minimum (H2+ 2024).

## Synchronizing MLS group membership
Note, however, that a malicious server is unable to forge registration and revocation payloads without access to the wallet keys used to sign them.

At the user level, messages are exchanged between accounts, however at the cryptographic level, messages are exchanged between installations. This poses the question of how MLS group membership can be kept up-to-date as installations are registered and revoked and members are added and removed. This requires two components which will be addressed in reverse order:
## Account-level membership

1. How to know which accounts are members of a group
2. How to know which installations belong to each account
At the user level, messages are exchanged between accounts, however at the cryptographic level, messages are exchanged between installations. Because of this two-layer system, there must exist a mechanism for mapping between accounts and installations in a conversation.

**Mapping from accounts to installations**
We use an implicit mapping - an account is considered a participant of a conversation if one or more installations from the account are present in the tree.

The latter can be addressed using the mechanism described in the earlier section - any participant in a conversation can query for updates on any account in the conversation and construct the current list of valid installations. If the current installation list does not match what is in the group, the participant may add or remove installations in the group to match the latest state, and all other participants may perform the same verification. This can be performed at any frequency (for example before every message send), with various performance optimizations possible.
- **Listing accounts**. To list accounts, we enumerate unique wallet addresses from the credentials of the leaf nodes in the conversation.
- **Adding accounts**. To add an account, publish a commit adding one or more installations belonging to the account to the conversation. Note that if we fail to include all valid installations belonging to the account, it will be resolved via the process described in [Installation Synchronization](#installation-synchronization).
- **Removing accounts**. To remove an account, publish a commit removing _all_ installations belonging to the account from the conversation. It is important that all installations belonging to the account that are present in the tree during the epoch in which the commit is published are removed, and we ensure this is the case by enforcing linear ordering of epochs.

**Mapping from conversations to accounts**
### Other approaches

Will add this in next PR.
Account/user trees are another approach for solving this problem, however we assume the complexity of managing key packages and welcome messages in this system is higher than the current system, and have therefore deferred this. We are open to feedback otherwise!
Loading