Replies: 3 comments 2 replies
-
This all looks rad, thanks for the update. If |
Beta Was this translation helpful? Give feedback.
-
After I "wasted" a week of decoupling the encode-decode part from the conversation, I'm really happy with this idea! If you start this refactor I generally propose to separate the functions and the data for every API (basically stop using classes). Like, why we have a fully functioning conversation in the |
Beta Was this translation helpful? Give feedback.
-
I hear you on this pain point. v8 of the SDK is definitely going to better in terms of serializability. For us, however, JSON isn't the target we are primarily focused on. Pretty much every object uses |
Beta Was this translation helpful? Give feedback.
-
I wanted to share with you an early preview of some work we are doing for v8 of the
xmtp-js
SDK.The first PR around this just landed, but there is still considerable work to be done before this is ready for usage.
Background & Motivation
Version 8 of the
xmtp-js
SDK is going to tackle a longstanding issue in our codebase. We currently don't have a strong separation of concerns between the cryptographic functions and the core business logic of interacting with the network. Cryptographic operations and crypto classes are sprinkled throughout the SDK, with a baked-in assumption that sensitive private key data is accessible. This makes it awkward and difficult to develop more secure methods for encrypting/decrypting/signing of data and for us to move to a model where key material is compartmented.The development of our Metamask Snap is not the only place where this is becoming an issue. As we develop a Rust based
libxmtp
to handle cryptographic operations, there are significant performance implications to passing data between the application context and WebAssembly. Having key material and cryptgraphic operations all live in the same context will make the code much simpler to reason about and lead to greater performance.Goals/Non Goals
Goals
xmtp-js
codebase to establish strict modularization of code that handles business logic/API calls and code that handles private key material/cryptographic operations.Client
code by handling all key management and cryptography in one placeNon goals
lixbxmtp
).Proposed Solution
I propose refactoring the codebase to move any code that interacts with private keys, topic keys, and encryption/decryption into a separate module with a strict API boundary and well defined interface. In the initial version, all calls to the Keystore service will be in the same process as the rest of the SDK. The interface should be designed in a way that requests can be easily JSON serializable to allow for future providers that are remote (Snap, Chrome Extension, Wallet, 1P mobile app).
The keystore will need to maintain some amount of state (either persisted between sessions or ephemeral) to access the
PrivateKeyBundle
and anyTopicKey
s that have been found from invitations.Components
In this new model, there are two distinct components of our SDK:
Client
The Client is responsible for all API calls to XMTP nodes, high level business logic (conversations abstraction), and handling of message encoding/decoding.
Keystore
The Keystore is responsible for holding the
PrivateKeyBundle
of the user (and any future delegated keys), encrypting/decrypting V1 messages, storingTopicKey
material from invitations, and encrypting/decrypting V2 messages.Separation of concerns
Types Of Keystore
There are a number of potential keystore types I can see us developing over time. These are listed in rough order of priority and timing.
InMemoryKeystore
The default Keystore, and the first one we will need to build, will simply be a module that implements the Keystore API locally. It will hold the user's
PrivateKeyBundle
andTopicKey
s and execute API requests using those keys.The
InMemoryKeystore
can run in the same process as the Client, or it can be used to implement some of the remote Keystores listed below.Effectively, this is just a refactor of our codebase.
Snaps Keystore
The Snaps Keystore will be a light wrapper around the Metamask Snaps API, where all requests to the Keystore are proxied as JSON-RPC calls to an installed Snap. The Snap will handle RPC requests using something similar to the
InMemoryKeystore
, but with the additional capability of persisting keys in the Metamask encrypted storage.libxmtp
KeystoreAs we develop
libxmtp
, we can include a Typescript wrapper class that sends Keystore API calls across the WASM bridge tolibxmtp
to fulfill them. This keystore can be used inside other Keystores (for example, the Snap could proxy calls tolibxmtp
while using the Snap encrypted storage for persistence). Effectively a replacement for the Base Keystore.Browser Extension Keystore
Any browser extension that supports XMTP could potentially become a Keystore. The Browser Extension would implement some version of the Base Keystore. We would likely use a
runtime.Port
to communicate back and forth between the extension and the browser session. This would work similarly towindow.ethereum
(window.xmtp
?) where a small bit of code would be injected into all webpages to handle communication.Users would have to approve a dialog in the Chrome Extension for each domain they wish to use.
Mobile Wallet Browser
Mobile wallets implementing XMTP can implement the Keystore API in the language of their choice. They can then inject
window.xmtp
into their mobile browser sessions, functioning in a similar way to the Browser Extension Keystore. That will allow any web page that uses XMTP to access the wallet's keystore (after the user grants permission to the domain)Service Worker
In browser contexts we could use Service Workers to move the Keystore out of the main thread. This would be a great performance boost, and improve the security of the SDK.
API Design
The Keystore API is specified in protocol buffers. While only some Keystore implementations require serialization, this gives us maximum flexibility. We can pass generated protobuf classes to the Keystore and use them as-is, easily serialize to JSON, or serialize to a binary format. And this can all be done in a type-safe way.
The Protobuf API will then be referenced in the Typescript implementation like this:
Common workflows
Loading V2 conversations
Listing messages
Sending a message
Breaking changes
Client.getKeys()
in the main SDK. While we may offer support for this feature for cases where the keys are in the browser context, I do not want to support any mechanism for extracting keys from secure contexts like Snaps or the Chrome Extension. Instead, the ability to export and cache keys would be supported in the Keystore implementation itself. That way, private key exports can stay inside the secure context the Keystore runs in.conversations.export()
. I would suggest we create a stateful keystore that uses LocalStorage to support the current use-cases of exporting Conversations. Keystore implementations can have pluggable storage providers to support a range of devices with differing storage capabilities (for example, on Node.js you may want to cache on the filesystem)Conversation
orDecodedMessage
Beta Was this translation helpful? Give feedback.
All reactions