> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/xmtp/libxmtp/llms.txt
> Use this file to discover all available pages before exploring further.

# Credential Validation

> How LibXMTP validates credentials from Ethereum wallets, smart contracts, and V2 keys

XMTP uses a multi-layered validation system to link MLS installation keys with wallet addresses through the Inbox ID system defined in [XIP-46](https://github.com/xmtp/XIPs/blob/main/XIPs/xip-46-multi-wallet-identity.md).

## Validation Levels

LibXMTP performs validation at two levels:

1. **MLS protocol validation** - Implemented by OpenMLS
2. **Application-level validation** - XMTP-specific credential and identity validation

## MLS Credentials

The MLS credential used in key packages and leaf nodes contains a single field: `inbox_id`.

### Credential Structure

```rust theme={null}
pub struct MlsCredential {
    pub inbox_id: String,
}
```

**Validation requirements:**

1. Resolve the state of the `inbox_id` (as described in XIP-46)
2. Ensure the installation key that signed the credential is a current member of the inbox

<Note>
  XMTP does **not** implement credential rotation because credentials represent a long-lived connection between the MLS client and the `inbox_id`.
</Note>

## Identity Association Types

When linking installation keys to an inbox, XMTP supports three signature types:

### 1. EOA (Externally Owned Account) Wallet Signatures

Standard Ethereum wallet signatures using ECDSA.

**Validation process:**

1. Recover the signer address using ECDSA signature recovery
2. Verify the recovered address matches the expected address
3. Ensure the signature text matches exactly (prevents random address recovery)

```rust theme={null}
// Signature validation for EOA
let recovered_address = recover_signer(&signature, &signature_text)?;
assert_eq!(recovered_address, expected_address);
```

**Location in code:** `crates/xmtp_id/src/associations/ident/ethereum.rs`

### 2. Smart Contract Wallet Signatures

Smart contract wallets require on-chain validation per [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271).

**Key characteristics:**

* Signatures are **not recoverable** (unlike EOA)
* Validation requires calling the smart contract's `isValidSignature()` function
* Smart contract code is mutable - a valid signature may become invalid later

<Warning>
  Smart contract signatures are validated at a **specific block number** to ensure deterministic validation. If the contract changes later, the association remains valid until explicitly revoked. This is a deliberate design choice.
</Warning>

**Validation with EIP-6492:**

LibXMTP uses [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) with a "universal validator" smart contract, enabling verification of signatures from:

* Deployed smart contract wallets
* **Counterfactual** smart contract wallets (not yet deployed)

**Requirements:**

Client applications **must** provide RPC URLs for all XMTP-supported blockchains during client instantiation.

**Location in code:** `crates/xmtp_id/src/scw_verifier/`

### 3. V2 Legacy Key Signatures

XMTP V2 used a different identity model. To enable migration, V2 keys can create associations under **strict limitations**.

<Warning>
  **Critical constraint:** Legacy V2 keys may only be used to create **one association globally**.
</Warning>

**Enforcement mechanisms:**

1. V2 keys can only be used on an Inbox ID with `nonce = 0`
2. Replay protection prevents reuse of V2 keys on the same inbox

**Rationale:**

V2 keys were shared between multiple apps and devices. If compromised, all associated installations would need revocation. Limiting V2 keys to one association prevents cascading security issues.

**Validation chain:**

```
Wallet Signature → V2 Public Key → XMTP Identity Key
                 ↓
            Challenge Text (for association)
```

**Challenge text format:**

```
XMTP : Create Identity
$SERIALIZED_V2_PUBLIC_KEY

For more info: https://xmtp.org/signatures/
```

**Validation process:**

1. Recover wallet address from the signature on the V2 public key
2. Verify the signature on the association challenge recovers to the same V2 public key
3. If both validate, treat the association as if signed by the wallet

**Location in code:** `crates/xmtp_id/src/associations/signature.rs:194-267`

## Inbox ID Creation

Inbox IDs are deterministically derived from wallet addresses:

```rust theme={null}
inbox_id = SHA256(CONCAT(wallet_address, nonce))
```

### CreateInbox Action

When creating a new inbox, the signature must be from an address where:

```
SHA256(CONCAT(wallet_address, nonce)) == inbox_id
```

**Special case:** If `nonce = 0` and V2 keys are available, `CreateInbox` can be signed with V2 keys instead of the wallet (one-time migration).

### AddAssociation Action

When adding an installation to an existing inbox:

1. Installation keys sign the association text
2. An **existing member** of the inbox must also sign (wallet or installation key)
3. The existing member must not be revoked

<Note>
  V2 keys can only sign `AddAssociation` if `nonce = 0` and the V2 keys have not been used for any previous identity updates.
</Note>

## Signature Re-use Protection

The system prevents signature replay attacks:

### Between Inboxes

The `inbox_id` is included in all signature challenge text, making signatures inbox-specific.

### Within an Inbox

Raw signature bytes are stored in the `AssociationState`:

```rust theme={null}
pub struct AssociationState {
    pub(crate) inbox_id: String,
    pub(crate) members: HashMap<MemberIdentifier, Member>,
    pub(crate) recovery_identifier: Identifier,
    pub(crate) seen_signatures: HashSet<Vec<u8>>, // Prevents reuse
}
```

Each new identity action is checked against `seen_signatures` to prevent replay.

**Location:** `crates/xmtp_id/src/associations/state.rs:62-63`

## Key Package Validation

When validating another user's key package:

### Standard MLS Validation

* Signature verification
* Message authenticity checks
* Lifetime validation
* Protocol version compatibility

### XMTP-Specific Validation

1. **Extract the credential:**
   ```rust theme={null}
   let credential = MlsCredential::decode(basic_credential.identity())?;
   ```

2. **Download identity updates:**
   ```rust theme={null}
   let identity_updates = api_client.get_identity_updates(credential.inbox_id).await?;
   ```

3. **Resolve association state:**
   ```rust theme={null}
   let state = get_state(identity_updates)?;
   ```

4. **Verify installation key is associated:**
   ```rust theme={null}
   let installation_key = key_package.leaf_node().signature_key();
   assert!(state.installation_ids().contains(installation_key));
   ```

**Location:** `crates/xmtp_mls/src/verified_key_package_v2.rs`

## Commit Validation

In addition to OpenMLS validation, libxmtp validates commits by:

1. **Permission checks**: Ensure the commit is allowed per group policies
2. **Credential validation**: Validate credentials and key packages of new members
3. **Membership consistency**: Ensure MLS group member changes match the expected diff from the `GroupMembership` extension

<Accordion title="What happens if an invalid commit is received?">
  Currently, there is **no mechanism** to detect, report, or recover from group splits due to invalid commits. This may need to be handled at the application level.
</Accordion>

## Signature Normalization

To prevent signature malleability, XMTP normalizes ECDSA signatures to use the lower-s value:

```rust theme={null}
pub fn to_lower_s(sig_bytes: &[u8]) -> Result<Vec<u8>, SignatureError> {
    let sig = K256Signature::try_from(sig_data)?;
    match sig.normalize_s() {
        None => sig_data.to_vec(), // Already normalized
        Some(normalized) => normalized.to_bytes().to_vec(),
    }
}
```

**Location:** `crates/xmtp_id/src/associations/signature.rs:270-295`

## Related Documentation

* [Security Overview](/security/overview) - Overall security properties
* [Key Rotation](/security/key-rotation) - Key package lifecycle
* [Revocation](/security/revocation) - How to revoke installations
