Skip to content

Conversation

Chandelier-02
Copy link
Contributor

@Chandelier-02 Chandelier-02 commented May 12, 2025

Title

feat(Ed25519-Key): Improve validation speeds in browsers

Description

In this PR, I've replaced most Ed25519 key operations with Web Crypto alternatives. With many browsers soon rolling out full support for the Ed25519 key type, this is an opportune time to integrate the new API into Libp2p. The primary benefit is the significant performance improvement that WebCrypto offers.

Simple benchmarks show substantially higher throughput compared to the @noble/curves library implementation of the Ed25519 key.
Screenshot from 2025-05-12 15-12-38

I've also tested the performance difference in one of my projects that receives numerous messages over gossipsub (verified via Ed25519). The results show a dramatic reduction in main thread usage during high message rates, especially with large messages.

Notes & open questions

I could not figure out how to derive a Ed25519 public key from a private key (don't think it's supported in the WebCrypto API), so I am utilizing the Noble Curves library to handle that. Not certain of the security implications of this.

Since Ed25519 key support is currently behind experimental flags in Chromium browsers, I've implemented fallback detection that reverts to the previous Noble curves implementation when the API is unavailable.

There are two tests in the random walk test file that I haven't been able to resolve due to timing conditions: 'should continue random walk until all consumers satisfied' and 'should not block walk on slow consumers'. All other tests are passing.

Change checklist

  • [ x] I have performed a self-review of my own code
  • I have made corresponding changes to the documentation if necessary (this includes comments as well)
  • I have added tests that prove my fix is effective or that my feature works

@Chandelier-02 Chandelier-02 requested a review from a team as a code owner May 12, 2025 20:26
@Chandelier-02 Chandelier-02 changed the title perf(Ed25519-Key): Improve validation speeds in browsers feat(Ed25519-Key): Improve validation speeds in browsers May 12, 2025
@Chandelier-02
Copy link
Contributor Author

I have spent some time looking into the reason for why those two specific tests are failing in the 'random-walk.spec.ts' file. The specific test that's failing is this one:

it('should continue random walk until all consumers satisfied', async () => {
    let yielded = 0

    peerRouting.getClosestPeers
      .onFirstCall().callsFake(async function * (key, options?: AbortOptions) {
        for (let i = 0; i < 100; i++) {
          options?.signal?.throwIfAborted()
          yielded++
          yield await createRandomPeerInfo()
        }
      })
      .onSecondCall().returns(slowIterator())

    await Promise.all([
      drain(take(randomWalk.walk(), 1)),
      drain(take(randomWalk.walk(), 2))
    ])

    expect(yielded).to.equal(3)
  })

After adding a bunch of logging in the random-walk file, I do see that each consumer is getting satisfied, but the yield is only occurring twice.

First, PeerInfo-A gets generated, and both consumers see it. The first consumer is then finished as it saw its one peer it wanted. Then, PeerInfo-B gets generated. The second consumer is then finished.

When comparing the old PeerId implementation vs the new one in this PR, the only difference I see that would change test behavior is that the new PR's PeerIds get generated much faster. So, I am thinking there is a race condition in the test. Maybe the test is trying to gauge the wrong metric (number of peer infos yielded vs satisfied consumers).

Maybe I am misunderstanding what this test is trying to do.

@p-shahi
Copy link
Member

p-shahi commented May 27, 2025

@Chandelier-02 can you take a look at the test failures?

Did this change mostly to get the tests in the random-walk.spec to work. However, it is probably also best to not mix and match key generation/derivation functions across libraries. ALl key generation is done via Noble Curves and all signing/verification uses those keys.
@Chandelier-02
Copy link
Contributor Author

Chandelier-02 commented May 27, 2025

Okay so the problem had to do with the typing of the Uint8Array's buffer in one of the functions I had changed. I think this is due to the swap over to es2024 in the aegir config. However, fixing that created a new problem. Now, the tests that use this new function, which now has to check if the buffer is a SAB instance, fail because "SharedArrayBuffer is not defined."

Edit: Fixed this with the below commit

@achingbrain
Copy link
Member

I could not figure out how to derive a Ed25519 public key from a private key

Me neither. I put a little demo together here but it fails to export a private key previously imported from PKCS#8 data, even though the same code works with a newly generated keypair 🤷

Not certain of the security implications of this.

Given that the current release uses @noble/curves to derive the key from the seed, it's no less secure that the latest version.

I think this is due to the swap over to es2024 in the aegir config

It was caused by the typescript upgrade from 5.4.x to 5.8.x, Uint8Arrays became generic which seems to cause all manner of weirdness.

Thanks for putting this together!

@achingbrain achingbrain merged commit 8e87be9 into libp2p:main Jun 3, 2025
56 of 64 checks passed
@achingbrain achingbrain mentioned this pull request Jun 3, 2025
@achingbrain
Copy link
Member

achingbrain commented Jun 4, 2025

I put a little demo together here but it fails to export a private key previously imported from PKCS#8 data

It turns out this is a bug in Firefox as the same demo works fine in Safari and Chrome (with Ed25519 support enabled).

I've filed a bug here: https://bugzilla.mozilla.org/show_bug.cgi?id=1970321 (possibly a dupe of https://bugzilla.mozilla.org/show_bug.cgi?id=1932396)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants