Skip to main content

React Native Support

CreatedAuthor(s)Status
2022-05-25@neekolasDraft

Background & Motivation

Neither xmtp-js nor js-waku currently work in React Native due to missing dependencies in the React Native environment. A number of potential partners require React Native support before they can integrate with XMTP.

The Status team made an unsuccessful attempt at making js-waku work in React Native in pure Javascript that is both a useful resource and a cautionary tale here.

Goals / Non-goals

Goals

  • A working SDK in React Native with feature parity to the existing app
  • Not compromise on the quality of implementation for crypto libraries
  • Minimize developer overhead to support all platforms

Non-goals

  • Performance optimization is a secondary concern. We can measure and optimize this later if needed.

The Problem

I'm going to break this up into the groups of issues faced with installing xmtp-js in a React Native application today. This list was compiled by looking at the rn-waku-relay-poc list of issues, as well as attempting to install various libraries into a clean React Native app and working through the issues that popped up.

BigInt

React Native's Javascript runtime does not natively support the BigInt datatype. Several of the underlying cryptographic libraries used by LibP2P involve BigInt datatypes. While polyfills exist for BigInts, there is currently a conflict between these polyfills and the default React Native babel presets. Namely, that @babel/plugin-transform-exponentiation-operator will attempt to use BigInts in a Math.pow() when combined using **, which does not work.

Apparently Babel doesn't let you disable a specific plugin used in a preset, so developers wanting to fix this issue will have to recreate all Babel Presets as a list of plugins and then remove @babel/plugin-transform-exponentiation-operator. While doable, this will have a cost to developer experience. I've investigated proposed solutions on StackOverflow, but they do not seem to work for React Native apps. More investigation is needed to find a cleaner way of handling this.

I checked to see if there was any usage of the BigInt.randBetween, which is considered insecure as it relies on Math.random(). I could not find any usage in the current dependency stack.

ESM Modules

LibP2P is currently moving all of it's libraries to ESM, and js-waku is following the trend. The Metro bundler in React Native does not currently support the package.json exports field, which these libraries rely on instead of the main field. This makes all full ESM packages incompatible with React Native.

We should encourage the libp2p team to add a react-native field to their package.json, and do the same for js-waku to resolve this issue upstream.

Another solution to this would be to maintain forks of any packages relying on the exports field and add a react-native field to their package.json.

We could also do a postinstall hack to just modify the package.json of apps in the node_modules folder to include a main or react-native field that maps to the . export.

Hopefully the first option works, since the rest are pretty terrible.

TextEncoder

A bunch of libraries, as well as our own, rely on access to a TextEncoder which is not included in the React Native runtime. There is a polyfill for react-native that I can confirm works fine.

Random Number Generators

We, along with many of our dependencies, rely extensively on the browser/node crypto.getRandomValues function to generate keys. Polyfilling this functionality in pure Javascript is problematic, since the runtime does not have access to native functionality like /dev/urandom or other sources of entropy. While there are Javascript polyfills, they rely on CPRNG's designed for browser applications which get entropy from things like mouse movements and keystrokes, as was researched here.

The good news is that as long as we are alright with generating random numbers asynchronously, react-native-randombytes offers a solution that calls out to the native platform's RNG (SecRandomCopyBytes on iOS and SecureRandom on Android). This can be polyfilled to provide secure random numbers to dependencies as well, so long as they are generating random numbers asynchronously.

It's worth investigating all of our dependencies use of RNGs, as bad random number generators seem to be common in React Native.

SubtleCrypto

This is the big one. xmtp-js, js-waku, @libp2p/crypto (relied on by libp2p-gossipsub and other libp2p libraries), and the multiformats package all rely on the crypto.subtle suite of cryptographic functions. I've taken a quick inventory of what we need from SubtleCrypto:

functionjs-wakuxmtp-js
importKey (hkdf)
encrypt (AES-GCM or AES-CTR)
decrypt (AES-GCM or AES-CTR)
sign
verify
digest
deriveKey

Pure Javascript

The only polyfill I can find that replicates the full SubtleCrypto API is @peculiar/webcrypto. This, in turn, is reliant on crypto-browserify in RN for some basic cryptographic functions.

Webcrypto comes with the following warning in big bold text:

At this time this solution should be considered suitable for research and experimentation, further code and security review is needed before utilization in a production application.

This is going to be a deal-breaker for any serious mobile wallet application to use our library and is unlikely to change in the near future. I don't see any plans for a security audit of these libraries reading through the GitHub Issues.

If we didn't care so much about replicating the SubtleCrypto API exactly, but wanted to piece together the necessary APIs from different sources (possibly repackaged into something offering a subset of the SubtleCrypto API), we have a few more options:

  • Paul Miller has an AES-GCM library. Unfortunately, this also relies on the crypto package which means we would need to polyfill that with crypto-browserify
  • We are already using Paul Miller's secp256k1 library, as are many of our dependencies
  • HKDF is possible with Paul Miller's noble hashes library (also reliant on crypto-browserify or other polyfill).

Whether crypto-browserify meets our security standards requires further investigation.

Proposed Solution

For the BigInt, ESM, and TextEncoder problems we just need to hack away at errors using polyfills and shims until all affected libraries are able to load.

We should decouple the RNG problem from the broader crypto issues, and allow the SDK to take an override of the RNG that can allow react-native-randombytes to be passed in and used asynchronously in React Native implementations and use the default crypto.getRandomValues() for all other environments.

For the crypto itself, I see three good solutions available:

  1. Create a native module for React Native wrapping the OS level crypto primitives.
  2. Create a shared crypto library in Rust that would be used in Browser/Node/React Native
  3. Use a webview to access browser native crypto modules

NativeModules

iOS and Android both have access to fast, strong, cryptographic primitives that can handle all of our needs.

There are already some libraries which expose these APIs in a React Native compatible wrapper.

Unless we want to embark on a long journey of fully replicating the crypto module API, the path to actually using this would be to create a smaller Crypto interface that encapsulates the functionality we care about. We would then create a wrapper for the standard crypto module to make that conform with the interface, and a wrapper of the NativeModule with an identical interface.

Developers could then pass in a ReactNativeCrypto module at client instantiation to override the default implementation (our wrapper of the browser/node crypto).

NativeModules Pros

  • The native iOS and Android cryptographic primitives are the most battle-tested and secure available
  • Library itself should be high performance

NativeModules Cons

  • Need to be very cautious about introducing differences between Browser/Node/RN implementations
  • Duplicate work of developing for web and RN separately if we want to add new cryptographic functionality/algorithms
  • Bunch of boilerplate work creating interfaces for crypto functions
  • Some performance cost to the React-Native bridge

Shared Rust Crypto Library

Instead of having different crypto implementations for Browser/Node/React Native we could build a single crypto library in Rust and compile it with WASM for the browser. This one is pretty tempting, actually. Rust has audited crates for important cryptographic functions like AES-GCM thanks to MobileCoin, and a generally mature set of crypto libraries.

I think this generally stays on the right side of "Don't Roll Your Own Crypto!", as all we would really be doing is creating a few layers of wrappers around established and audited sets of crypto libraries. Care would still need to be taken to ensure we don't screw anything up in our wrapping.

If we implement this well, I could see the Waku team getting on board with using this library in their implementation.

Rust Pros

  • One library for all environments, at least for our first-party crypto usage
  • Library iteself should be high performance
  • Well audited crypto crates available

Rust Cons

  • Maintenance burden of the library
  • Every bit of functionality we need will have to be implemented and exported from the package. Can't just get the full SubtleCrypto feature-set in one go.
  • Complexity of compling/building WASM and integrating with third party apps (some developer's Webpack config will have to change to support WASM)
  • Some performance cost to the React-Native bridge

WebView Crypto

Instantiate a webview and a bridge to proxy crypto calls to a hidden browser window using React Native Webview Crypto.

Webview Pros

  • Full API compatibility with SubtleCrypto
  • Same security guarantees as the browser, but with some additional risks of the bridge itself being an attack vector
  • Can be used as a global polyfill for all crypto instances in dependencies.

Webview Cons

  • Feels hacky. Not sure if third party developers are going to be on board
  • Performance impact. Need a whole browser engine running in the background of your app at all times
  • Poorly maintained libraries. Very low numbers of downloads, infrequent releases. Can't find major apps that rely on this approach.

What about all the other libraries

xmtp-js and js-waku are not the only users of cryptographic libraries in our stack. LibP2P has many usages of crypto. Unless we can get the maintainers of those libraries to get on board with our solution, I think we are likely to need to also include a browserify/peculiar polyfill for those use-cases. Or maybe the WebView Crypto approach.

While some of the usage of crypto are in modules unused by us, the one that worries me is the generation of PeerIDs used for encrypted connections with LibP2P nodes. Still, we may have to swallow that risk in the short term and start a dialogue with the LibP2P team about better solutions.

Alternative Solutions Considered

GoWaku

The Status Team is actively working on making GoWaku accessible as a native library in React Native environments and is creating C bindings for core functions. While this work is underway, it is unclear how long it will be until this is in a stable and (even remotely) production-ready state. The current focus is just to get basic use-cases like sending messages working. We would need complete coverage of all the Waku APIs we use (including the ability to add new LibP2P protocols to support our Auth features), as well as some assurance that the core networking layer is going to work reliably in this environment. My gut says this is at least 6 months away, and even then we probably will have to make some contributions to fill in gaps that are high priorities for us and low priorities for the Waku team.

If we were starting 6 months later, this may be a viable path. But it feels too early for this project.

Risks?

  • One of the more minor issues (like ESM support) turns out to be intractible and breaks our implementation even after we have the crypto problem fully solved
  • Maintaining high quality crypto libraries is a lot of work, and we will need to be constantly vigilant of CVEs and potential exploits for any of the dependencies of libraries we rely on.
  • An exploit of any polyfills used in our code (or that we recommend for RN), even if it doesn't directly compromise our security, may cause a loss of trust in XMTP
  • Are wallet app developers going to be willing to use global polyfills for the crypto library? Maybe not. If they aren't willing to polyfill crypto, then libp2p will continue to not work even with a perfect solution on our end.

Questions

  • Is this the complete set of issues with React Native, or are there more that will only appear after these blockers are solved?
  • Is it worthwhile investigating what it would take to get @peculiar/webcrypto into a state we would be comfortable using it?
  • If we do everything outlined in this plan, is the library going to be in a state where a wallet developer would actually use it? Or will there still be blockers (bundle size, security of poylfills)
  • Are we appropriately staffed to handle the maintenance of the libraries we would need to create here.

Useful Resources