title | description | authors | categories | created | updated | version |
---|
- Write a PoC DID Client for web-sdk (xmtp-js/xmtp-web) that is re-usable for xmtpv2 and xmtpv3
- minimal changes to websdk, current libxmtpv2 apis in order to encourage integrating wasm PoC into webSDK ASAP and get feedback
- minimize API surface area to reduce future breaking changes,
- a working prototype is prioritized since xmtp-mls/libxmtp is still in active development breaking changes matter less
Create a new crate in libxmtp, evm-contracts
(name tba) encapsulating logic for interacting and associating identities with decentralized networks. This crate should be wasm-compatible and have clear avenues of integration with other external bindings. The crate should have clear API boundaries and be extensible for future needs.
- We need a trait that libxmtpv2/v3 will interface with in order to manage associated installations with the deployed contract
- related contact registry discussion
- For wasm, wrapper functions will have to be created in order to correctly export to javascript.
/// Installation manager that interacts with some evm-compatible smart contract consensus system implementing VeramoLabs did:eth
pub trait EvmInstallationManager {
/// Declares an installation is associated with a wallet. Signs the installation keys using the wallet keys.
fn grant_installation(&self);
/// Declares that an installation is no longer associated with a wallet (e.g. if it is compromised, or no longer used).
fn revoke_installation(&self);
/// Fetch valid installations for a given wallet -- fetch granted installations, and subtract revoked installations
fn get_all_installations(&self);
}
// wrapper example
#[wasm_bindgen]
fn grant_installation_wrapper(instance: &EvmInstallationManager) {
instance.grant_installation();
}
- The 'Ethereum' struct needs to be passed through to the wasm blob. This allows us to call into ethereum contracts with maximum flexibility.
- Relies on the client SDK instantiating the Ethereum Object, which needs to be passed into from the frontend app.
- Most Ethereum clients have good support for ethers.js, for instance rainbow-kit uses WAGMI, and ethers is already in use in xmtp-js
- ethers-rs compiles into WASM and supports mocks and JSONRPC out-of-the box.
- alternatively, a faster approach would be to hardcode node + network, but this is fragile and will lead to tech debt.
- Complex types need to be boxed in order to cross ffi boundaries
- Unknowns
- We need a good way to pass a signer object into rust, that cooperates well with existing solutions (WalletConnect, Metamask, etc.) in order to allow transaction signing/sending.
- it's not enough to have
InboxOwner
, because we need some way to send transactions to the network, which requires theSigner
trait. This is complicated by the transaction-signing flow by frontend-apps like WalletConnect.
use ethers::Signer;
/// Struct holding state to interact with Ethereum
#[wasm_bindgen]
pub struct Ethereum {
/// name of the network, e.g 'ethereum', 'homestead'. if network is unknown, then 'unknown'.
name: String,
/// the ID of the network
chain_id: u32,
/// simple URL type to instantiate an Ethers instance
endpoint: Url,
/// Interact with a user wallet, i.e the owner of an inbox
wallet: Box<dyn InboxOwner>,
/// Interact with a users installation keys
installation_keys: Box<dyn KeyPackage>
}
impl Ethereum {
#[wasm_bindgen(constructor)]
pub fn new(/*...*/) -> Self {
/* ... */
}
}
/// Some other network
pub struct SomeOtherNetwork<Signer> {
provider: OtherNetworkSpecificLibraryProvider,
wallet: Signer
}
impl<WebProtocol, Signer> EvmInstallationManager for Ethereum<WebProtocol, Signer> {
// ...
}
impl<Signer> EvmInstallationManager for SomeOtherNetwork<Signer> {
// ...
}
/// InboxOwner needs to require an ethers::Signer, or a Signer must be passed separately
trait InboxOwner: ethers::Signer {
fn sign(/* .. */) {
/* ... */
}
- Need a way to sign wallet keys with MLS or Double Ratchet. InboxOwner can already sign things but does not exist in v2
- need to possibly port InboxOwner trait to v2, or create a custom trait for the Ethereum implementation.
- InboxOwner is currently implemented to sign a string of text as a 'RecoverableSignature' according to Eip191.
The current LibXMTPv3 Signature (non-MLS) solution consists of signing the combination of the public installation key with the blockchain_address:
fn sign_new_account(owner: &Owner) -> Result<Account, ClientBuilderError> {
let sign = |public_key_bytes: Vec<u8>| -> Result<Eip191Association, AssociationError> {
let assoc_text = AssociationText::Static {
blockchain_address: owner.get_address(),
installation_public_key: public_key_bytes.clone(),
};
let signature = owner.sign(&assoc_text.text())?;
Eip191Association::new(public_key_bytes.as_slice(), assoc_text, signature)
};
Account::generate(sign).map_err(ClientBuilderError::AccountInitialization)
}
impl AssociationText {
pub fn text(&self) -> String {
match self {
Self::Static {
blockchain_address,
installation_public_key,
} => gen_static_text_v1(blockchain_address, installation_public_key),
}
}
}
fn gen_static_text_v1(addr: &str, key_bytes: &[u8]) -> String {
format!(
"AccountAssociation(XMTPv3): {addr} -> keyBytes:{}",
&hex::encode(key_bytes)
)
}
- for the DID Registry Proof of Concept, we need to sign a message containing XMTP Installation keys as an Attribute in the registry. We can generalize this by introducing a KeyPackage trait as part of the Ethereum network object.
trait KeyPackage {
type Keys: Into<String> + Into<Vec<u8>>; // or Into<Hex> -- however the keys are currently represented
fn keys() -> Self::Keys;
}
impl KeyPackage for Account {
type Keys = Mls;
fn keys() -> Mls {
/* ... */
}
}
impl KeyPackage for DoubleRatchetAccount {
type Keys = DoubleRatchet;
fn keys() -> DoubleRatchet {
/* ... */
}
}
use wasm_bindgen::prelude::JsValue;
pub struct JsSdkKeys {
value: JsValue
}
impl KeyPackage for JsSdkKeys {
/* .. */
}
- Questions:
- Should we re-use eip191Signature for the PoC?
- If so, we could use the same trait but require a signature message instead of returning the keys.
- Should we re-use eip191Signature for the PoC?
- Need a way to know which contracts to interact with
- for the PoC, it might be best to hardcode contract addresses store abi along with the crate.
- Gives us the most control over contract addresses and handling of the contract output ABI, which can be updated internally to the crate.
- It's easy to update this implementation and pass the contract address/ABI in across the rust-wasm boundary, however we should answer these questions first:
- is it worth defining contract addresses outside of the decentralization crate?
- what is our strategy for updating contract addresses/abis once they are deployed?
- for the PoC, it might be best to hardcode contract addresses store abi along with the crate.
const REGISTRY_ADDRESS = "0x....";
abigen!(RegistryContract, "./abi/contract.json");
- For PoC purposes, we can include simple observability via tracing and tracing-subscriber
use evm_contracts::{EvmInstallationManager, networks::Ethereum};
// Instantiate the network somewhere that makes sense, or grab from across the ffi boundary, depending on where network primitives are coming from
Ethereum::new(/* .. */);
/* ... */
// the client can call these methods to attach an identity to the registry
impl Client {
/* ... */
fn some_inbox_operation(network: impl EvmInstallationManager) {
network.grant_installation()
}
}
import { Ethereum } from './eth_bindings';
const ethereum = new Ethereum(/* ... */);
grant_installation_wrapper(ethereum);
// create a libxmtp client including new information for decentralization
create_client(/* ... */, ethereum);
- XMTP Web Inbox app uses RainbowKit
- it's up to implementors what wallet/network configuration to use, and it has not been explicitly included in XMTP SDKS yet.
- we need to include:
- Signer Object (allow wallet signing procedure)
- Network Object
- We can use our own hard-coded network urls, but then must manually track network changes initiated by user
- we need to include:
- SDKS/Apps
- XMTP-JS
- JS SDK over xmtp
- XMTP-WEB
- Bundle of React components for building frontends
- XMTP-Web-Inbox
- Frontend using XMTP-WEB and Infura for ethereum interaction.
- XMTP-JS
- EIP-1193 Ethereum Provider Javascript API
- WalletConnect Ethers-rs support
- EIP-191
- it would help if we get some clarification/prototyping for passing a Wallet signer across the WASM boundary. We can currently sign things according to how InboxOwner is implemented in libxmtpv3, but it's unclear if this translates to signing/sending transactions with ether-rs, which traditionally uses Signer Middleware