Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Native and Mobile OS (iOS and Android) Compatibility #155

Open
joshuakarp opened this issue May 26, 2021 · 11 comments
Open

React Native and Mobile OS (iOS and Android) Compatibility #155

joshuakarp opened this issue May 26, 2021 · 11 comments
Assignees
Labels
development Standard development epic Big issue with multiple subissues r&d:polykey:core activity 2 Cross Platform Cryptography for JavaScript Platforms

Comments

@joshuakarp
Copy link
Contributor

joshuakarp commented May 26, 2021

Created by @CMCDragonkai

No js-polykey cannot be used in browsers. However for NS, we may need to polyfill alot of our dependencies.

The js-polykey doesn't need to do this. But in Polykey-NativeScript we are going to we webpack5 and have to resolve some packages.

Webpack 5 no longer polyfills node core modules, but we can do it manually:

These are:

	assert: "assert",
	buffer: "buffer",
	console: "console-browserify",
	constants: "constants-browserify",
	crypto: "crypto-browserify",
	domain: "domain-browser",
	events: "events",
	http: "stream-http",
	https: "https-browserify",
	os: "os-browserify/browser",
	path: "path-browserify",
	punycode: "punycode",
	process: "process/browser",
	querystring: "querystring-es3",
	stream: "stream-browserify",
	_stream_duplex: "readable-stream/duplex",
	_stream_passthrough: "readable-stream/passthrough",
	_stream_readable: "readable-stream/readable",
	_stream_transform: "readable-stream/transform",
	_stream_writable: "readable-stream/writable",
	string_decoder: "string_decoder",
	sys: "util",
	timers: "timers-browserify",
	tty: "tty-browserify",
	url: "url",
	util: "util",
	vm: "vm-browserify",
	zlib: "browserify-zlib"

Some of these can be polyfilled for NS.

In other cases, we will need to write alternative implementations when they don't exist, like for example the usage of fs - https://docs.nativescript.org/ns-framework-modules/file-system

Related:

@CMCDragonkai CMCDragonkai changed the title Using js-polykey in NativeScript/Browsers NativeScript and Mobile OS (iOS and Android) Compatibility Jul 3, 2021
@CMCDragonkai CMCDragonkai self-assigned this Aug 24, 2021
@CMCDragonkai CMCDragonkai added epic Big issue with multiple subissues development Standard development labels Aug 24, 2021
@CMCDragonkai
Copy link
Member

In working on core activity 2 of our R&D report, I've discovered some prior context for this particular issue.

This comment #43 (comment) explains how we came to be using node-forge as opposed to other cryptographic libraries.

I know that when Polykey project first started, we initially were thinking of using PGP and thus the kbpgp.js library (https://github.com/keybase/kbpgp). We ended up going away from PGP due to its limited usage in a number of scenarios that we want PK to deal with. Namely end to end encrypted network communication which is a TLS issue, that makes use of X.509 certificates rather than PGP certificates. Furthermore we also had symmetric encryption/decryption scenarios like js-encryptedfs that again would not make use of PGP standards. Therefore it just lacked interoperability with many other cryptographic scenarios, it seemed like its own island of standards, the library can still be brought back in in the future if we find usecases for PK using PGP.

However in choosing node-forge, we came across a few other problems. Mainly overall-cross platform compatibility planning for mobile devices. This is not just a problem with crypto, but also other libraries that are used in our networking domain such as utp-native.

Here are something I found that may be relevant to us proceeding here:

  • The webcrypto standard (https://w3c.github.io/webcrypto/) is now the official standard for cryptographic operations in browser environments. It is now being supported by all major browsers and electron
  • This webcrypto standard is now taking over other deployment platforms such as in Nodejs: https://nodejs.org/api/webcrypto.html & https://www.nearform.com/blog/implementing-the-web-cryptography-api-for-node-js-core/
  • As a standardised API, switching over to this API gives us more cross-platform opportunity as the rest of the world's development ecosystem catches up and migrates over to webcrypto standard.
  • In particular are nativescript and react-native ecosystems. Currently none of them have official crypto APIs, they expect the developer to use underlying native iOS or Android crypto APIs.
  • However I found out that the GUN project https://github.com/amark/gun#additional-cryptography-libraries claims to be able to use webcrypto on react-native and other platforms.
  • It does this through a "webview" trick. Basically this hooks into the browser runtimes that are on iOS and Android to perform the actual crypto and then return the results back to the main application. I have no idea about the performance characteristics of this trick. This trick is described here: https://gun.eco/docs/React-Native and https://github.com/webview-crypto/react-native-webview-crypto
  • The usage of webcrypto libraries is pioneered by "PeculiarVentures" which has these main libraries:
    • https://github.com/PeculiarVentures/PKI.js - this could be an alternative to node-forge for all of our PKI/X.509 and TLS related functionality (previously it was claimed this was overly complex compared to node-forge)
    • https://github.com/PeculiarVentures/webcrypto - this is a generic polyfill for webcrypto, not entirely sure how it wraps the native webcrypto inside nodejs or if smooths over the differences, there is a discussion about this library in relation to other webcrypto polyfills targeting nodejs https://github.com/PeculiarVentures/webcrypto, because this is also mentioned with respect to the webview trick, there may also be a relationship between the webview trick and this polyfill
    • They have many other crypto related libraries that we should investigate

All of this will mean that we either replace node-forge, or end up creating a adapter pattern where we plugin different crypto implementions depending on our environment. At this point in time, the keys domain abstracts over most(all?) crypto operations for all other domains. Except in the case of EFS which is currently pinned to node-forge (it may be a good idea to abstract that and expect an interface of functions for EFS).

Cross platform compatibility here isn't just about the fundamental crypto library. It's also about other parts of PK. One closely related situation is the JOSE libraries. As they involve cryptographic operations, they currently seem to "fix" their underlying crypto library as well. It would be ideal that if we standardise on a crypto library for cross-platform deployment, that we can also ensure that our JOSE library is using the same crypto library to reduce our crypto attack surface. We are currently using https://github.com/panva/jose which uses native crypto depending on the platform including webcrypto. Contenders include https://github.com/cisco/node-jose (which fixes on node-forge) and https://github.com/square/js-jose.

Still to research:

  • utp-native and networking libraries
  • others...?

@CMCDragonkai
Copy link
Member

Regarding the future of using web assembly, here are relevant resources:

The last link is interesting as it describes how to use webcrypto together with webassembly when you need a custom crytographic functionality not supported inside webcrypto.

@CMCDragonkai
Copy link
Member

One note about WASM. There are 2 well supported runtimes for WASM:

  • wasm3 - supports interpretation
  • wasmer - supports JIT and AOT compilation

So that does mean wasmer can compile WASM code to native code. But wasm3 is for interpretation. Why use interpetation?

It appears that in some cases interpretation can be more widely deployed. There are examples of iOS apps using wasm3. https://github.com/kateinoigakukun/wasmic-ios

It's becoming fast a standard target for many languages. Even TypeScript when ported to AssemblyScript can be compiled to WASM.

Once it is WASM, the only thing missing is broad adoption of WASI. If WASI is broadly adopted like it is in nodejs (https://github.com/nodejs/uvwasi), then pretty much we have a universal portable binary capable of doing relevant system operations. WASI is like a universal standard of system calls. Like a whole new POSIX standard.

Then one would just use WASM and WASI for all platforms.

@CMCDragonkai
Copy link
Member

CMCDragonkai commented Oct 6, 2021

Dynamic imports can be used to inject platform-specific implementation adapters.

async function test () {
  // dynamic imports work now!
  const p = await import('perf_hooks');
  console.log(p);
}

test();

So if we needed to supply different implementations for different platforms, we could do a feature/platform test, and then dynamically import the right module.

Important thing is that it is asynchronous, so it would have to be wrapped up in a class, or function.

TS compiler will translate the above into a promise that acquires modules using require in commonjs or other ways depending on the platform.

There is top level await in NodeJS as well, but I'm not sure it will work across all the platforms.

Could be important for all these areas:

Current attempt at using top level await to do this is located at the js-id work: MatrixAI/js-id#1 (comment). Notice that there's quite a few changes required, and we would need to prototype a working solution in TypeScript-Demo-Lib. I reckon, this is something that will require TS 4.5 to be released completely, and ts-node support in TypeStrong/ts-node#1007.

@CMCDragonkai
Copy link
Member

Moved the discussions about crypto into its own issue #270. All issues that relate to the webcrypto integration/migration should point to that.

@CMCDragonkai
Copy link
Member

This discussion at deno denoland/deno#6607 is interesting.

If you want to run deno on ios, you have to compile it on ios then:

You can already run Deno in V8 jitless mode: deno run --v8-flags="--jitless". It is just painfully slow. V8 lite mode is also supported via --v8-flags="--lite-mode". I am not sure everything works, but it is enough for initial testing.

The v8 runtime is the most important piece. Then higher level interfaces built around it like node or deno or nativescript. Which means as long as we stick to compatible Ecmascript, it should work, and for our native addons.

Note that deno will never support nodejs native addons. That's because nodejs native addons using things like n-api is based on the node wrapper around v8.

That doesn't mean C/C++ libraries won't work, it's just that the bindings would have to be written differently for deno vs for nodejs. Instead of n-api, you'd use https://deno.land/manual/runtime/ffi_api.

Anyway deno is still too early to be utilised, but it is very promising runtime for the future of TS/wasm applications.

@CMCDragonkai
Copy link
Member

CMCDragonkai commented May 30, 2022

Other than reliance on nodejs APIs, these are the libraries that are "native" that we rely upon:

  • fd-lock
  • leveldown
  • utp-native

The fd-lock is fairly easy to substitute, we are likely to replace it with @matrixai/js-file-locks.

The leveldown uses leveldb and we are replacing it with @matrixai/js-db, which itself may continue to use leveldb internally.

The utp-native is not easily substituted, and I find its code quite flaky, I'd prefer to use a QUIC implementation, and this may mean using something like MsQuic which is native code that can be compiled for different platforms.

In terms of porting for mobile, we must first figure out: Should we even try to get nodejs itself running on mobile? - The answer to this determines whether we can continue using native-addons for nodejs.

If nodejs worked on the mobile platforms, then any native addon should continue to work as long as the relevant platform-specific compilation is available, they would be compiled to a shared object node.napi.node file which will be dynamically loaded. This is the case for leveldown or classic-level, where there are prebuilt-binaries for android (not yet for ios). Because these addons all use the "napi" system, they are compatible from one node version to another. But also because they use the "napi" system, they are limited to working with nodejs runtime.

So how does one get the nodejs runtime on mobile? There are several options, and all are difficult because nodejs upstream does not maintain any releases to mobile, instead this has been done by third parties:

  1. Compile node as a native library to be embedded in the mobile application, where the calling interface is a direct FFI from the host-app written in Java or Swift directly into nodejs APIs
  2. Compile node to run as a separate executable thread, which then acts like a service/virtual-machine that's embedded inside the app

Option 1. is basically the idea of "embedding nodejs as a library". This seems to be possible simply because of https://nodejs.org/api/embedding.html. However it seems a rare approach, and I guess many nodejs applications won't work due to expectations on the operating environment.

Option 2. is basically like Electron, where there is a "nodejs" backend process runtime is running completely independently, and then you just have the host-app frontend call the backend process using network calls (usually localhost TCP). Electron does the same thing with the nodejs backend process with some message passing APIs/rpc to the frontend (where the frontend is a "browser" chromium process).

Option 2 is popular because it's the same architecture applied to mobile settings, and it appears that's how one would integrate a nodejs backend to react-native. React-native just provides the ability to use JS as the host-app.

Similarly nativescript is just allowing you to use JS as the host-app.

Now why is option 1. quite rare? I suspect it has to do with the fact that node relied on v8, and until recently v8 was not runnable on ios due to its JIT usage. Only recently did its jitless mode become available, and so both NativeScript and nodejs-mobile swapped their JS runtime to v8 for both android and ios. Prior to this, both ecosystems had to maintain separate JS runtimes for android and ios. So now that the v8 is runnable on ios with jitless mode (https://v8.dev/docs/cross-compile-arm), it should mean that it would be easier to implement option 1 and option 2.

In terms of feasibility, running node as a backend process is what we are planning to do for electron (there are issues here to be solved too: #235).

As for mobile, the only ones that seem possible is liquidcore or using termux patches for android, or taking nodejs-mobile and patching it up to support v16.

This is all so that nodejs can be the one running. If we were able to substitute all the nodejs APIs for portable JS/WASM, then we are left with only the native addons that require consideration.

I believe all the native addons could be separately implemented, by directly compiling native code for android/ios.

For example, the @matrixai/js-file-locks could just use direct android and ios API calls.

The @matrixai/js-db which uses leveldb can just directly compile leveldb to ios/android and use a native FFI to call them, the JS code can still run, but it would need to abstracted over so instead of calling the C++ native addon using NAPI, they call code provided via native FFI to android/ios specific code.

As for the network, that's the only complexity. I haven't explored how msquic work.

See also:

@CMCDragonkai
Copy link
Member

CMCDragonkai commented May 30, 2022

We can try to explore option 2 to see if it is feasible. And if we hit some roadblocks, we may just forget about getting nodejs running on mobile, and instead substitute our native libraries with android/ios compatible shim code which would likely require utilising the nativescript framework.

Outside of native addons, all other JS should be portable JS and thus we must stop relying on nodejs APIs (except where it involves the OS side-effects), in those cases those things must be substituted as well.

@CMCDragonkai
Copy link
Member

CMCDragonkai commented May 30, 2022

Another option is to target wasm from everything and then get a wasm interpreter that works on android/ios. That is WASM is then the portable intermediate code for everything. But this is all quite experimental. Right now there's no JS to WASM compiler, the closest is assemblyscript. Alternatively you compile the JS runtime to WASM, and then use that WASM program to run JS, like compiling v8 or quickjs to WASM.

Option 1 can be further explored, see if nodejs really can be compiled with jitless mode for android/ios. The only question is the calling interface would have to be mediated somehow.

@CMCDragonkai
Copy link
Member

CMCDragonkai commented Jun 2, 2022

I've just surveyed the situation with React Native and NativeScript again, and it seems React Native is the better option now. NS support across all the frameworks seems breaking.

The ability to call into native code is improving with turbomodules.

Nodejs would still not work so native addons on nodejs would have to be converted to native code specific to Android/iOS.

Webassembly support is not available yet on react native (some work arounds: https://github.com/inokawa/react-native-react-bridge), it may be available in the future, as we are looking to use this to replace some of the crypto APIs that would be missing.

NodeJS APIs would need to be written with portable JS. Only crypto would be the issue.

@CMCDragonkai CMCDragonkai changed the title NativeScript and Mobile OS (iOS and Android) Compatibility React Native and Mobile OS (iOS and Android) Compatibility Jul 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
development Standard development epic Big issue with multiple subissues r&d:polykey:core activity 2 Cross Platform Cryptography for JavaScript Platforms
Development

No branches or pull requests

2 participants