Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

feat: swap js implementation of Ed25519 for native module or wasm in the browser #215

Closed
wants to merge 9 commits into from

Conversation

achingbrain
Copy link
Member

@achingbrain achingbrain commented Nov 29, 2021

When the DHT is enabled, the amount of network traffic nodes send is vastly increased. At that point our crypto implementations become a performance bottleneck as we are signing and verifying a greatly increased number of messages due to the increased number of peers we have.

Under these conditions pure-js implementations of Ed25519 are just not fast enough.

Switching to native/WASM versions drops ambient CPU usage for js-IPFS from ~80% to ~2% while DHT table refreshes are ongoing in my local testing.

Uses ed25519 in node, falling back to ed25519-wasm-pro in the browser.

ed25519-wasm-pro is basically the same as supercop.wasm except the published module is a bit easier to use (no messing around with global Module['wasmBinary'] values).

Benchmarks:

@noble/ed25519 x 191 ops/sec ±24.37% (78 runs sampled)
@stablelib/ed25519 x 62.10 ops/sec ±2.14% (73 runs sampled)
node-forge/ed25519 x 63.00 ops/sec ±2.62% (71 runs sampled)
supercop.wasm x 3,433 ops/sec ±1.77% (83 runs sampled)
ed25519-wasm-pro x 3,562 ops/sec ±1.07% (80 runs sampled)
ed25519 (native module) x 4,625 ops/sec ±0.96% (84 runs sampled)
node.js web-crypto x 3,344 ops/sec ±2.61% (78 runs sampled)

Nb. increases the bundle size by 50kb (though only 5kb bigger than when we used secp256k1 instead of @noble/secp256k1)

When the DHT is enabled, the amount of network traffic nodes send is
vastly increased.  At that point our crypto implmenetations fast
become a performance bottleneck as we are signing and verifying a
greatly increased number of messages due to the increased number of
peers we have.

Pure-js implementations of Ed25519 are just not fast enough for use.

Switching to native/WASM versions drops ambient CPU usage for js-IPFS
from ~80% to ~2% while DHT table refreshes are ongoing in my local
testing.

Uses [ed25519](https://www.npmjs.com/package/ed25519) in node, falling
back to [ed25519-wasm-pro](https://www.npmjs.com/package/ed25519-wasm-pro)
in the browser.

Benchmarks:

```
@noble/ed25519 x 191 ops/sec ±24.37% (78 runs sampled)
@stablelib/ed25519 x 62.10 ops/sec ±2.14% (73 runs sampled)
node-forge/ed25519 x 63.00 ops/sec ±2.62% (71 runs sampled)
supercop.wasm x 3,433 ops/sec ±1.77% (83 runs sampled)
ed25519-wasm-pro x 3,562 ops/sec ±1.07% (80 runs sampled)
ed25519 (native module) x 4,625 ops/sec ±0.96% (84 runs sampled)
node.js web-crypto x 3,344 ops/sec ±2.61% (78 runs sampled)
```
@achingbrain achingbrain changed the title feat: swap js implementation of Ed25519 for wasm feat: swap js implementation of Ed25519 for native module or wasm in the browser Nov 29, 2021
@paulmillr
Copy link
Contributor

paulmillr commented Nov 29, 2021

How do you know ed25519-wasm-pro is not malware? It has 4 dependents and is maintained by a relatively unknown person, the last release was 2 years ago. The binaries and releases are unsigned. And supercop.wasm has exactly 1 download per week.

@paulmillr
Copy link
Contributor

paulmillr commented Nov 29, 2021

Switching to native/WASM versions drops ambient CPU usage for js-IPFS from ~80% to ~2% while DHT table refreshes are ongoing in my local testing.

This sounds cool, but where exactly does that happen, and how long does ~80% cpu load last? I've tried to dig through your code, but was not able to find references to ed25519. What does DHT table refresh do, ed25519-wise? Is it sign, or verify?

I'm asking this because 3.2k signatures per second is pretty fast; basically wondering whether noble-ed is used properly.

@paulmillr
Copy link
Contributor

Also note that libp2p/js-libp2p#106 depends on pure-js libs, and cannot use wasm.

@achingbrain
Copy link
Member Author

How do you know [dep] is not malware?

This is a valid concern. If we go the wasm route we'd probably be best off vendoring in the c code and build the blobs during CI.

We only need RSA, Ed25519 and secp256k1 for Peer IDs. RSA is available in web-crypto everywhere we support (I think), Ed25519 is pretty small and has a portable version that is easy to build as wasm, and secp256k1 is relatively uncommon on the network so could stay as js for now.

I'm asking this because 3.2k signatures per second is pretty fast; basically wondering whether noble-ed is used properly.

I may have got a bit excited, I'm looking at other places the bottlenecks are so I'm going to mark this as a draft for the time being.

What does DHT table refresh do, ed25519-wise? Is it sign, or verify?

It searches the DHT for peers with KadIDs near to ours and tries to connect to them. This kicks off lots of handshakes which use RSA/Ed25519/secp256k1 to sign outgoing data and verify incoming data. Basically it does what libp2p does, it just causes it to happen a lot more so any inefficiencies become a lot more pronounced.

Also note that libp2p/js-libp2p#106 depends on pure-js libs, and cannot use wasm.

That's from 2017, I don't know if it's still correct.

@achingbrain achingbrain marked this pull request as draft November 30, 2021 10:40
@paulmillr
Copy link
Contributor

paulmillr commented Nov 30, 2021

That's still correct, I've talked to Kumavis a few weeks ago.

As for

This kicks off lots of handshakes which use RSA/Ed25519/secp256k1 to sign outgoing data and verify incoming data

I'm curious how many requests per second are done, once you'll have some time to investigate it!

@achingbrain
Copy link
Member Author

Closing as I'm not convinced this is a bottleneck any more. Will re-evaluate as necessary.

@achingbrain achingbrain closed this Jan 4, 2022
@achingbrain achingbrain deleted the feat/swap-js-for-native-or-wasm-ed25519 branch January 4, 2022 09:15
@dapplion
Copy link

dapplion commented Feb 4, 2022

Closing as I'm not convinced this is a bottleneck any more. Will re-evaluate as necessary.

Historically for Lodestar it has been a bottleneck, contributing to 5% of total CPU time in the main thread. 5% puts it in the top 1-3 most expensive ops in a regular beacon node

@dapplion
Copy link

dapplion commented Feb 4, 2022

Would be great to offer a pure-js option for consumers that don't require very high performance, and a another for high performance consumers.

Agree with @paulmillr concerns on security but should be doable to mantain a CI pipeline that builds in-house

@paulmillr
Copy link
Contributor

mantain a CI pipeline that builds in-house

That's a tough part. How would users know the CI hasn't been hacked? We're just two months away since log4j CVE that allowed to break into all sorts of CIs.

If you have the real need for ed25519 (is it just your particular project vs all libp2p consumers?), I suggest to compile the rare releases of ed25519 by hand, on your machine. Not on CI. Then, sign it with your GPG keys. Preferably with reproducible builds. There is zero need in having frequent releases of ed25519 since it's stable, so this can totally be done manually.

This is how WASM security should be done.

@paulmillr
Copy link
Contributor

By the way, if you think i'm saying something theoretical, check out this blog post by NCC group: https://research.nccgroup.com/2022/01/13/10-real-world-stories-of-how-weve-compromised-ci-cd-pipelines/

CI / CD pipeline hacks happen all the time.

@dapplion
Copy link

dapplion commented Feb 6, 2022

That's a tough part. How would users know the CI hasn't been hacked? We're just two months away since log4j CVE that allowed to break into all sorts of CIs.

That's a fair point. Tho to attack Lodestar instead of going for the wasm I would just inject some JS that sends the private keys somewhere. That should be doable by attacking the build process of any code that we consume. How do other projects with sensitive users guard against this possibility?

@paulmillr
Copy link
Contributor

Steal BLS private staking keys and do what? Launch a beacon node and double-sign some condition, which would slash the validator and lose a couple eths? Seems like a stupid thing to do. No profit in that.

@dapplion
Copy link

dapplion commented Feb 7, 2022

Steal BLS private staking keys and do what? Launch a beacon node and double-sign some condition, which would slash the validator and lose a couple eths? Seems like a stupid thing to do. No profit in that.

If you steal enough keys you can ransom the operator. If you don't qualify that as a concern what damage can be done to a beacon node with a malicious WASM payload?

@paulmillr
Copy link
Contributor

paulmillr commented Feb 7, 2022

I agree it's possible and it should be defended against.

The ransom can be asked for, but this has never happened yet, and seems like stealing private non-beacon keys from users / breaking defi projects with 300M$ in treasury is more interesting target to attackers.

It's not a simple thing to do, but I'd fixate build tool versions, as a first step. Only upgrade for major security release etc. there is no need to run towards new and new deps all the time. Overall, a comprehensive review of build process is required. I don't think security auditors focus on this step? Which is bad.

@dapplion
Copy link

dapplion commented Feb 8, 2022

stealing private non-beacon keys from users / breaking defi projects with 300M$ in treasury is more interesting target to attackers

Right, the security landscape of protocol vs user layer is very different. We'll do benchmarks first and consider the build infrastructure if necessary. Thanks for the comments tho, appreciate the input

@achingbrain achingbrain restored the feat/swap-js-for-native-or-wasm-ed25519 branch September 20, 2022 07:49
@achingbrain achingbrain deleted the feat/swap-js-for-native-or-wasm-ed25519 branch September 20, 2022 07:51
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants