Repository navigation
perf: use the native crypto of Node.js for Curve25519 and Ed25519 - #1135
Conversation
Pair verify and pair setup did their X25519 and Ed25519 with tweetnacl, in JavaScript. On a Raspberry Pi one pair verify (key pair, shared secret, sign, verify) took about 115 ms, with the crypto built into Node.js (OpenSSL) about 2 ms. After a restart every controller connects to every bridge at once; on a Raspberry Pi with 7 bridges that kept the event loop busy for seconds (6.5 s of CPU in a profile of one start), and reads of the controllers timed out meanwhile. hapCrypto now has generateEd25519KeyPair, ed25519Sign and ed25519Verify next to the Curve25519 functions, all native. Ed25519 secret keys keep the 64-byte layout of tweetnacl (seed and public key), so the stored long-term keys of accessories work unchanged, and the signatures are the same (Ed25519 is deterministic). generateCurve25519SharedSecKey still returns zeros for a public key of small order, as tweetnacl did, where OpenSSL refuses it. ed25519Verify returns false for a signature or public key of the wrong size instead of throwing. tweetnacl is only used by the tests now (the test clients and the cross-checks), so it moves to the devDependencies. The engines field already requires Node.js 22, which has X25519 and Ed25519.
generateCurve25519SharedSecKey returned an all-zero secret for any error of OpenSSL. Only a public key of small order makes OpenSSL fail the derivation (ERR_OSSL_FAILED_DURING_DERIVATION) once the key sizes are checked; that one still gives the zeros tweetnacl returned, any other error is thrown. The test covers four points of small order (0, 1, a point of order 8, p - 1).
|
Thanks for the contribution - I’ll review this over the weekend. |
Coverage Report for CI Build 39374458514Coverage increased (+0.06%) to 67.026%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats💛 - Coveralls |
There was a problem hiding this comment.
🔵 Needs a closer look
Authentication cryptography, persisted-key compatibility, and low-order-key handling warrant final human security review.
1 open issue
What changed in this PR
Moves pairing cryptography from TweetNaCl to Node.js native crypto to reduce event-loop blocking while preserving stored accessory keys.
Changes:
- Adds native X25519 and Ed25519 helpers and updates pairing call sites.
- Adds standards-based vectors and TweetNaCl compatibility tests.
- Moves TweetNaCl to development dependencies.
| File | Description |
|---|---|
| src/lib/util/hapCrypto.ts | Implements native cryptography helpers. |
| src/lib/util/hapCrypto.spec.ts | Adds compatibility and invalid-input tests. |
| src/lib/model/AccessoryInfo.ts | Uses native accessory key generation. |
| src/lib/HAPServer.ts | Uses native pairing signatures and verification. |
| package.json | Makes TweetNaCl development-only. |
| package-lock.json | Updates dependency classification. |
Review effort: Balanced
…r key fallback preserving authentication only' Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@StefanRiese Code is solid - thanks for the contribution! Can you make the edit to the comments above (removing them) and I'll clear this for commit? |
Done. |
|
Hi @StefanRiese |

Problem
Pair verify and pair setup do their X25519 and Ed25519 with
tweetnacl, in JavaScript, on the event loop. That is slow on the small machines many bridges run on. Measured on a Raspberry Pi (OpenCCU, Node.js 22):cryptoMeanwhile the reads of the controllers waited, and hap-nodejs logged "didn't respond at all" for them.
The move to
tweetnacl(#664) came from the native ed25519/curve25519 add-ons that were hard to build. Node.js has had X25519 and Ed25519 built in for years (generateKeyPairSyncsince v12,crypto.diffieHellmansince v13.9), and theenginesfield already requires Node.js 22.Change
hapCryptouses the crypto built into Node.js (OpenSSL) forgenerateCurve25519KeyPairandgenerateCurve25519SharedSecKey, and getsgenerateEd25519KeyPair,ed25519Signanded25519Verify.HAPServer(pair setup M5, pair verify M1/M3) andAccessoryInfo.createuse them.generateCurve25519SharedSecKeystill returns zeros for a public key of small order, as tweetnacl did (OpenSSL refuses it). The controller still has to sign with its long-term key, so it cannot pair with it.ed25519Verifyreturnsfalsefor a signature or public key of the wrong size, or a public key that is no point on the curve, wheretweetnaclthrew for the wrong sizes. A malformed proof in pair setup M5 or pair verify M3 now gets the authentication error response.tweetnaclis only used by the tests now (the test clients insrc/test-utilsand the cross-checks), so it moves to thedevDependencies. The lockfile change is limited to that.Tests
hapCrypto.spec.ts:tweetnaclwith random keys: the same shared secrets, the same signatures, verification of tweetnacl's signatures, the same answer for random public keys that are no point on the curve, and key pairs in tweetnacl's layout (keyPair.fromSeed)HAPServer.spec.tsrun with thetweetnacl-based test clients against the native server side.npm run lintandnpm run buildpass.npx jest: 921 passed in 26 suites. In one of two full runs, one test inAccessory.spec.tsfailed under load; it passed alone three times and in the other full run.