Skip to content

Instantly share code, notes, and snippets.

@insipx
Last active October 27, 2023 19:11
Show Gist options
  • Save insipx/3e5af11306fedc1ed6afb991f12fde57 to your computer and use it in GitHub Desktop.
Save insipx/3e5af11306fedc1ed6afb991f12fde57 to your computer and use it in GitHub Desktop.
title description authors categories created updated version

Overview

Objectives

  • 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

Try:

  • 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

Proposal

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.

Implementation

Traits

Installation Management

  • We need a trait that libxmtpv2/v3 will interface with in order to manage associated installations with the deployed contract
/// 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 the Signer 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.

Contract Location Storage

  • 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?
const REGISTRY_ADDRESS = "0x....";
abigen!(RegistryContract, "./abi/contract.json");

Observability/Monitoring

Examples

Use in future libxmtp

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()
  }
}

JS use

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);

Notes

  • 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
  • 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.
  • EIP-1193 Ethereum Provider Javascript API
  • WalletConnect Ethers-rs support
  • EIP-191

Improvements

  • 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment