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.
Inboxes are the core identity primitive in XMTP. Each inbox has a unique identifier and can be associated with multiple wallets and device installations.
InboxId and InstallationId
InboxId
The InboxId is a unique identifier for an inbox, generated from a wallet address and nonce:
pub type InboxIdRef<'a> = &'a str; // Borrowed reference
pub type InboxId = String; // Owned string
Generation:
use xmtp_id::associations::Identifier;
let wallet_address = Identifier::eth("0x1234...")?;
let nonce = 0u64;
let inbox_id = wallet_address.inbox_id(nonce)?;
// Returns SHA256 hash: sha256(wallet_address + nonce)
See member.rs:168-176 for the implementation.
InstallationId
An InstallationId represents a specific device or application installation:
pub type InstallationId = Vec<u8>;
// Get from identity
let installation_id = identity.installation_id();
// Create member identifier
let member = MemberIdentifier::installation(installation_key_bytes);
Installation IDs are Ed25519 public keys that sign messages on behalf of the inbox.
InboxOwner Trait
The InboxOwner trait defines how wallets interact with the identity system:
pub trait InboxOwner {
/// Get the identifier of this wallet
fn get_identifier(&self) -> Result<Identifier, IdentifierValidationError>;
/// Sign text with this wallet
fn sign(&self, text: &str) -> Result<UnverifiedSignature, SignatureError>;
}
See lib.rs:90-123 for the trait definition and implementations.
Implementation Example
impl InboxOwner for PrivateKeySigner {
fn get_identifier(&self) -> Result<Identifier, IdentifierValidationError> {
Identifier::eth(h160addr_to_string(self.address()))
}
fn sign(&self, text: &str) -> Result<UnverifiedSignature, SignatureError> {
let signature_bytes = self.sign_message_sync(text.as_bytes())?;
Ok(UnverifiedSignature::RecoverableEcdsa(
UnverifiedRecoverableEcdsaSignature {
signature_bytes: signature_bytes.into(),
}
))
}
}
The InboxOwner trait is automatically implemented for references:
impl<T: InboxOwner> InboxOwner for &T { ... }
Identity Structure
The Identity struct represents a user’s complete identity within the XMTP network:
pub struct Identity {
pub(crate) inbox_id: InboxId,
pub(crate) installation_keys: XmtpInstallationCredential,
pub(crate) credential: OpenMlsCredential,
pub(crate) signature_request: Option<SignatureRequest>,
pub(crate) is_ready: AtomicBool,
}
See identity.rs:294-301 for the struct definition.
Key Methods
impl Identity {
/// Get the inbox ID
pub fn inbox_id(&self) -> InboxIdRef<'_>;
/// Get the installation ID (public key bytes)
pub fn installation_id(&self) -> InstallationId;
/// Check if identity is ready for use
pub fn is_ready(&self) -> bool;
/// Get pending signature request
pub fn signature_request(&self) -> Option<SignatureRequest>;
/// Sign text with installation key
pub(crate) fn sign_identity_update<Text: AsRef<str>>(
&self,
text: Text,
) -> Result<Vec<u8>, IdentityError>;
/// Generate new key package for this identity
pub(crate) fn new_key_package(
&self,
provider: &impl MlsProviderExt,
include_post_quantum: bool,
) -> Result<NewKeyPackageResult, IdentityError>;
}
Creating an Identity
Identities are created through the IdentityStrategy system:
For New Inboxes
use xmtp_id::associations::Identifier;
let wallet_address = Identifier::eth("0x1234...")?;
let nonce = 0;
let inbox_id = wallet_address.inbox_id(nonce)?;
let strategy = IdentityStrategy::new(
inbox_id,
wallet_address,
nonce,
None, // No legacy key
);
For Existing Inboxes
If a wallet is already associated with an inbox:
// The system will:
// 1. Check if wallet has an associated inbox_id
// 2. Load identity updates from network
// 3. Verify installation count is below limit
// 4. Create signature request to add new installation
// 5. Return Identity with pending signature request
See identity.rs:358-441 for the creation flow.
With Legacy Key (V2 Migration)
let strategy = IdentityStrategy::new(
inbox_id,
wallet_address,
0, // Must be 0 for legacy keys
Some(legacy_signed_private_key),
);
Legacy keys can only be used once during inbox creation with nonce 0.
Installation Management
Querying Installations
use xmtp_id::associations::AssociationState;
let state: AssociationState = /* ... */;
// Get all installation IDs
let installation_ids: Vec<Vec<u8>> = state.installation_ids();
// Get installations with metadata
let installations: Vec<Installation> = state.installations();
for installation in installations {
println!("ID: {:?}", installation.id);
println!("Added at: {:?}", installation.client_timestamp_ns);
}
Adding an Installation
let signature_request = SignatureRequestBuilder::new(inbox_id)
.add_association(
MemberIdentifier::installation(new_installation_key),
existing_wallet_identifier.into(),
)
.build();
// Sign with wallet
signature_request.add_signature(wallet_signature, scw_verifier).await?;
// Sign with installation key
signature_request.add_signature(installation_signature, scw_verifier).await?;
// Apply the update
let identity_update = signature_request.build_identity_update()?;
api_client.publish_identity_update(identity_update).await?;
See builder.rs:83-99 for the add_association method.
Revoking Installations
let signature_request = SignatureRequestBuilder::new(inbox_id)
.revoke_association(
recovery_wallet_identifier.into(),
MemberIdentifier::installation(installation_to_revoke),
)
.build();
// Sign with recovery wallet
signature_request.add_signature(recovery_signature, scw_verifier).await?;
// Apply revocation
apply_signature_request_with_verifier(
api_client,
signature_request,
scw_verifier,
).await?;
See identity_updates.rs:125-140 for the revoke helper.
Key Package Management
Key packages are cryptographic bundles that allow others to add the identity to groups:
Generating Key Packages
let result = identity.new_key_package(provider, include_post_quantum)?;
// Key package for MLS protocol
let key_package = result.key_package;
// Optional post-quantum public key
let pq_pub_key = result.pq_pub_key;
Rotating Key Packages
Key packages should be rotated periodically:
identity.rotate_and_upload_key_package(
api_client,
mls_storage,
true, // Include post-quantum keys
).await?;
Key packages are automatically rotated based on KEY_PACKAGE_ROTATION_INTERVAL_NS.
See identity.rs:640-675 for rotation implementation.
Registration Flow
Registering an identity involves:
- Creating or loading the identity
- Generating and uploading key packages
- Storing the identity locally
impl Identity {
pub(crate) async fn register<ApiClient: XmtpApi, S: XmtpMlsStorageProvider>(
&self,
api_client: &ApiClientWrapper<ApiClient>,
mls_storage: &S,
) -> Result<(), IdentityError> {
// Check if already registered
let stored_identity: Option<StoredIdentity> = mls_storage.db().fetch(&())?;
if stored_identity.is_some() {
return Ok(());
}
// Upload key package
self.rotate_and_upload_key_package(
api_client,
mls_storage,
CREATE_PQ_KEY_PACKAGE_EXTENSION,
)
.await?;
// Store identity
StoredIdentity::try_from(self)?.store(&mls_storage.db())?;
Ok(())
}
}
See identity.rs:609-628 for the registration method.
Identity Errors
Common errors when working with identities:
pub enum IdentityError {
/// Identity not found in cache
RequiredIdentityNotFound,
/// InboxId mismatch between provided and stored
InboxIdMismatch { id: InboxId, stored: InboxId },
/// Too many installations for inbox
TooManyInstallations {
inbox_id: String,
count: usize,
max: usize,
},
/// Legacy key can only be used once
LegacyKeyReuse,
/// Installation key errors
InstallationKey(String),
// ... other variants
}
See identity.rs:188-272 for all error types.
Best Practices
- Cache identities - Use
IdentityStrategy::CachedOnly when possible to avoid network calls
- Rotate key packages - Implement periodic rotation for forward secrecy
- Limit installations - Be aware of
MAX_INSTALLATIONS_PER_INBOX (typically 100)
- Handle legacy migration - Legacy keys can only be used once with nonce 0
- Verify inbox associations - Always load and verify identity updates from the network
Related Pages
References
- Source:
crates/xmtp_id/src/lib.rs
- Source:
crates/xmtp_mls/src/identity.rs
- Source:
crates/xmtp_id/src/associations/member.rs