From 03eb399b63a7576e7b884750afe77ba39b7a0728 Mon Sep 17 00:00:00 2001 From: Stefan Riese Date: Wed, 14 Oct 2026 13:25:26 +0200 Subject: [PATCH 1/4] perf: use the native crypto of Node.js for Curve25519 and Ed25519 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. --- package-lock.json | 5 +- package.json | 4 +- src/lib/HAPServer.ts | 9 ++- src/lib/model/AccessoryInfo.ts | 4 +- src/lib/util/hapCrypto.spec.ts | 114 +++++++++++++++++++++++++++++++++ src/lib/util/hapCrypto.ts | 95 +++++++++++++++++++++++++-- 6 files changed, 216 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index b5704ee9b..6e7468b8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,7 @@ "fast-srp-hap": "^2.0.4", "futoin-hkdf": "^1.5.3", "source-map-support": "^0.5.21", - "tslib": "^2.8.1", - "tweetnacl": "^1.0.3" + "tslib": "^2.8.1" }, "devDependencies": { "@eslint/eslintrc": "^3.3.6", @@ -39,6 +38,7 @@ "simple-plist": "^1.4.0", "ts-jest": "^29.4.11", "ts-node": "^10.9.2", + "tweetnacl": "^1.0.3", "typedoc": "^0.28.20", "typescript": "^6.0.3" }, @@ -6306,6 +6306,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true, "license": "Unlicense" }, "node_modules/type-check": { diff --git a/package.json b/package.json index a126b39a0..06c9db482 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,7 @@ "fast-srp-hap": "^2.0.4", "futoin-hkdf": "^1.5.3", "source-map-support": "^0.5.21", - "tslib": "^2.8.1", - "tweetnacl": "^1.0.3" + "tslib": "^2.8.1" }, "devDependencies": { "@eslint/eslintrc": "^3.3.6", @@ -82,6 +81,7 @@ "simple-plist": "^1.4.0", "ts-jest": "^29.4.11", "ts-node": "^10.9.2", + "tweetnacl": "^1.0.3", "typedoc": "^0.28.20", "typescript": "^6.0.3" }, diff --git a/src/lib/HAPServer.ts b/src/lib/HAPServer.ts index b0e630a65..194185f77 100644 --- a/src/lib/HAPServer.ts +++ b/src/lib/HAPServer.ts @@ -3,7 +3,6 @@ import createDebug from "debug"; import { EventEmitter } from "events"; import { SRP, SrpServer } from "fast-srp-hap"; import { IncomingMessage, ServerResponse } from "http"; -import tweetnacl from "tweetnacl"; import { URL } from "url"; import { consideredTrue, HAPMimeTypes, PairingStates, PairMethods, TLVValues } from "../internal-types"; import { @@ -634,7 +633,7 @@ export class HAPServer extends EventEmitter { const controllerInfo = Buffer.from("Pair-Setup-Controller-Sign-Info"); const outputKey = hapCrypto.HKDF("sha512", controllerSalt, S_private, controllerInfo, 32); const completeData = Buffer.concat([outputKey, clientUsername, clientLTPK]); - if (!tweetnacl.sign.detached.verify(completeData, clientProof, clientLTPK)) { + if (!hapCrypto.ed25519Verify(completeData, clientProof, clientLTPK)) { debug("[%s] Invalid signature", this.accessoryInfo.username); response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" }); response.end(tlv.encode(TLVValues.SEQUENCE_NUM, PairingStates.M6, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); @@ -662,7 +661,7 @@ export class HAPServer extends EventEmitter { const usernameData = Buffer.from(this.accessoryInfo.username); const material = Buffer.concat([outputKey, usernameData, serverLTPK]); const privateKey = Buffer.from(this.accessoryInfo.signSk); - const serverProof = tweetnacl.sign.detached(material, privateKey); + const serverProof = hapCrypto.ed25519Sign(material, privateKey); const message = tlv.encode(TLVValues.USERNAME, usernameData, TLVValues.PUBLIC_KEY, serverLTPK, TLVValues.PROOF, serverProof); const encrypted = hapCrypto.chacha20_poly1305_encryptAndSeal(hkdfEncKey, Buffer.from("PS-Msg06"), null, message); @@ -723,7 +722,7 @@ export class HAPServer extends EventEmitter { const usernameData = Buffer.from(this.accessoryInfo.username); const material = Buffer.concat([publicKey, usernameData, clientPublicKey]); const privateKey = Buffer.from(this.accessoryInfo.signSk); - const serverProof = tweetnacl.sign.detached(material, privateKey); + const serverProof = hapCrypto.ed25519Sign(material, privateKey); const encSalt = Buffer.from("Pair-Verify-Encrypt-Salt"); const encInfo = Buffer.from("Pair-Verify-Encrypt-Info"); const outputKey = hapCrypto.HKDF("sha512", encSalt, sharedSec, encInfo, 32).subarray(0, 32); @@ -804,7 +803,7 @@ export class HAPServer extends EventEmitter { connection._pairVerifyState = undefined; return; } - if (!tweetnacl.sign.detached.verify(material, proof, clientPublicKey)) { + if (!hapCrypto.ed25519Verify(material, proof, clientPublicKey)) { debug("[%s] Client %s provided an invalid signature", this.accessoryInfo.username, clientUsername); response.writeHead(HAPPairingHTTPCode.OK, { "Content-Type": "application/pairing+tlv8" }); response.end(tlv.encode(TLVValues.STATE, PairingStates.M4, TLVValues.ERROR_CODE, TLVErrorCode.AUTHENTICATION)); diff --git a/src/lib/model/AccessoryInfo.ts b/src/lib/model/AccessoryInfo.ts index 51bca3b7a..ef3f81289 100644 --- a/src/lib/model/AccessoryInfo.ts +++ b/src/lib/model/AccessoryInfo.ts @@ -1,11 +1,11 @@ import assert from "assert"; import crypto from "crypto"; import createDebug from "debug"; -import tweetnacl from "tweetnacl"; import util from "util"; import { AccessoryJsonObject, CharacteristicJsonObject, MacAddress, ServiceJsonObject } from "../../types"; import { Categories } from "../Accessory"; import { EventedHTTPServer, HAPConnection, HAPUsername } from "../util/eventedhttp"; +import { generateEd25519KeyPair } from "../util/hapCrypto"; import { HAPStorage } from "./HAPStorage"; import { readFileSync } from "node:fs"; @@ -301,7 +301,7 @@ export class AccessoryInfo { accessoryInfo.lastFirmwareVersion = getVersion(); // Create a new unique key pair for this accessory. - const keyPair = tweetnacl.sign.keyPair(); + const keyPair = generateEd25519KeyPair(); accessoryInfo.signSk = Buffer.from(keyPair.secretKey); accessoryInfo.signPk = Buffer.from(keyPair.publicKey); diff --git a/src/lib/util/hapCrypto.spec.ts b/src/lib/util/hapCrypto.spec.ts index d4bfeb9d6..bfda9f9bf 100644 --- a/src/lib/util/hapCrypto.spec.ts +++ b/src/lib/util/hapCrypto.spec.ts @@ -1,6 +1,7 @@ import { HAPEncryption } from "./eventedhttp"; import * as hapCrypto from "./hapCrypto"; import crypto from "crypto"; +import tweetnacl from "tweetnacl"; function fromHex(hex: string): Buffer { return Buffer.from(hex.trim().replace(/ /g, ""), "hex"); @@ -163,4 +164,117 @@ describe("hapCrypto", () => { expect(S_decrypted1).toEqual(Buffer.concat([message1, message2])); }); }); + + describe("curve25519", () => { + // RFC 7748, section 6.1 + const alicePrivate = fromHex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"); + const alicePublic = fromHex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); + const bobPrivate = fromHex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"); + const bobPublic = fromHex("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"); + const shared = fromHex("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"); + + it("should compute the shared secret of the RFC 7748 test vector", () => { + expect(Buffer.from(hapCrypto.generateCurve25519SharedSecKey(alicePrivate, bobPublic))).toEqual(shared); + expect(Buffer.from(hapCrypto.generateCurve25519SharedSecKey(bobPrivate, alicePublic))).toEqual(shared); + }); + + it("should generate key pairs whose public key belongs to the secret key", () => { + for (let i = 0; i < 10; i++) { + const keyPair = hapCrypto.generateCurve25519KeyPair(); + expect(keyPair.secretKey.length).toBe(32); + expect(Buffer.from(keyPair.publicKey)).toEqual(Buffer.from(tweetnacl.scalarMult.base(keyPair.secretKey))); + } + }); + + it("should compute the same shared secret as tweetnacl", () => { + for (let i = 0; i < 10; i++) { + const ours = hapCrypto.generateCurve25519KeyPair(); + const theirs = tweetnacl.box.keyPair(); + expect(Buffer.from(hapCrypto.generateCurve25519SharedSecKey(ours.secretKey, theirs.publicKey))) + .toEqual(Buffer.from(tweetnacl.scalarMult(theirs.secretKey, ours.publicKey))); + } + }); + + it("should return zeros for a public key of small order, like tweetnacl", () => { + const secretKey = hapCrypto.generateCurve25519KeyPair().secretKey; + expect(Buffer.from(hapCrypto.generateCurve25519SharedSecKey(secretKey, Buffer.alloc(32)))).toEqual(Buffer.alloc(32)); + expect(Buffer.from(tweetnacl.scalarMult(secretKey, Buffer.alloc(32)))).toEqual(Buffer.alloc(32)); + }); + + it("should reject keys of the wrong size", () => { + expect(() => hapCrypto.generateCurve25519SharedSecKey(Buffer.alloc(31), alicePublic)).toThrow(); + expect(() => hapCrypto.generateCurve25519SharedSecKey(alicePrivate, Buffer.alloc(33))).toThrow(); + }); + }); + + describe("ed25519", () => { + // RFC 8032, section 7.1, TEST 1 and TEST 2 + const vectors = [ + { + seed: fromHex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"), + publicKey: fromHex("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"), + message: Buffer.alloc(0), + signature: fromHex( + "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b", + ), + }, + { + seed: fromHex("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb"), + publicKey: fromHex("3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c"), + message: fromHex("72"), + signature: fromHex( + "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00", + ), + }, + ]; + + it("should sign and verify the RFC 8032 test vectors", () => { + for (const vector of vectors) { + const secretKey = Buffer.concat([vector.seed, vector.publicKey]); + expect(hapCrypto.ed25519Sign(vector.message, secretKey)).toEqual(vector.signature); + expect(hapCrypto.ed25519Verify(vector.message, vector.signature, vector.publicKey)).toBe(true); + } + }); + + it("should generate key pairs in the layout of tweetnacl", () => { + const keyPair = hapCrypto.generateEd25519KeyPair(); + expect(keyPair.secretKey.length).toBe(64); + const fromSeed = tweetnacl.sign.keyPair.fromSeed(keyPair.secretKey.subarray(0, 32)); + expect(Buffer.from(keyPair.publicKey)).toEqual(Buffer.from(fromSeed.publicKey)); + expect(Buffer.from(keyPair.secretKey)).toEqual(Buffer.from(fromSeed.secretKey)); + }); + + it("should sign like tweetnacl and verify the signatures of tweetnacl", () => { + for (let i = 0; i < 10; i++) { + const keyPair = tweetnacl.sign.keyPair(); + const message = crypto.randomBytes(i * 17); + const signature = tweetnacl.sign.detached(message, keyPair.secretKey); + expect(hapCrypto.ed25519Sign(message, keyPair.secretKey)).toEqual(Buffer.from(signature)); + expect(hapCrypto.ed25519Verify(message, signature, keyPair.publicKey)).toBe(true); + } + }); + + it("should not verify a changed message, another key or a public key that is no point on the curve", () => { + const keyPair = hapCrypto.generateEd25519KeyPair(); + const message = crypto.randomBytes(64); + const signature = hapCrypto.ed25519Sign(message, keyPair.secretKey); + const changed = Buffer.from(message); + changed[0] ^= 1; + expect(hapCrypto.ed25519Verify(changed, signature, keyPair.publicKey)).toBe(false); + expect(hapCrypto.ed25519Verify(message, signature, hapCrypto.generateEd25519KeyPair().publicKey)).toBe(false); + for (let i = 0; i < 10; i++) { + const publicKey = crypto.randomBytes(32); + expect(hapCrypto.ed25519Verify(message, signature, publicKey)).toBe(tweetnacl.sign.detached.verify(message, signature, publicKey)); + } + }); + + it("should not verify a signature or public key of the wrong size", () => { + const keyPair = hapCrypto.generateEd25519KeyPair(); + const message = crypto.randomBytes(16); + const signature = hapCrypto.ed25519Sign(message, keyPair.secretKey); + expect(hapCrypto.ed25519Verify(message, signature.subarray(0, 63), keyPair.publicKey)).toBe(false); + expect(hapCrypto.ed25519Verify(message, signature, keyPair.publicKey.subarray(0, 31))).toBe(false); + expect(() => hapCrypto.ed25519Sign(message, keyPair.secretKey.subarray(0, 32))).toThrow(); + }); + }); }); diff --git a/src/lib/util/hapCrypto.ts b/src/lib/util/hapCrypto.ts index 9cf56217f..2a681e871 100644 --- a/src/lib/util/hapCrypto.ts +++ b/src/lib/util/hapCrypto.ts @@ -1,7 +1,6 @@ import assert from "assert"; import crypto from "crypto"; import hkdf from "futoin-hkdf"; -import tweetnacl, { BoxKeyPair } from "tweetnacl"; import { HAPEncryption } from "./eventedhttp"; if (!crypto.getCiphers().includes("chacha20-poly1305")) { @@ -9,18 +8,106 @@ if (!crypto.getCiphers().includes("chacha20-poly1305")) { "At least a nodejs version of v10.17.0 (excluding v11.0 and v11.1) is required!"); } +// Curve25519 and Ed25519 use the crypto built into Node.js (OpenSSL). The JavaScript implementation used before +// (tweetnacl) needed about 115 ms for the X25519 and Ed25519 operations of one pair verify on a Raspberry Pi, about +// 2 ms natively; a bridge reconnecting all its controllers at once spent seconds on the event loop in them. + +// raw 32-byte keys in the DER structures node's crypto takes (RFC 8410) +const X25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b656e04220420", "hex"); +const X25519_SPKI_PREFIX = Buffer.from("302a300506032b656e032100", "hex"); +const ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex"); +const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex"); + +function privateKeyObject(prefix: Buffer, key: Uint8Array): crypto.KeyObject { + return crypto.createPrivateKey({ key: Buffer.concat([prefix, key]), format: "der", type: "pkcs8" }); +} + +function publicKeyObject(prefix: Buffer, key: Uint8Array): crypto.KeyObject { + return crypto.createPublicKey({ key: Buffer.concat([prefix, key]), format: "der", type: "spki" }); +} + +function rawKey(key: crypto.KeyObject): Buffer { + return key.export({ format: "der", type: key.type === "private" ? "pkcs8" : "spki" }).subarray(-32); +} + +/** + * @group Cryptography + */ +export interface Curve25519KeyPair { + publicKey: Uint8Array; + secretKey: Uint8Array; +} + +/** + * An Ed25519 key pair. The secret key has 64 bytes: the 32-byte seed followed by the public key + * (the layout of tweetnacl, in which the long-term keys of accessories are stored). + * + * @group Cryptography + */ +export interface Ed25519KeyPair { + publicKey: Uint8Array; + secretKey: Uint8Array; +} + /** * @group Cryptography */ -export function generateCurve25519KeyPair(): BoxKeyPair { - return tweetnacl.box.keyPair(); +export function generateCurve25519KeyPair(): Curve25519KeyPair { + const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519"); + return { publicKey: rawKey(publicKey), secretKey: rawKey(privateKey) }; } /** * @group Cryptography */ export function generateCurve25519SharedSecKey(priKey: Uint8Array, pubKey: Uint8Array): Uint8Array { - return tweetnacl.scalarMult(priKey, pubKey); + assert(priKey.length === 32, "bad private key size"); + assert(pubKey.length === 32, "bad public key size"); + try { + return crypto.diffieHellman({ + privateKey: privateKeyObject(X25519_PKCS8_PREFIX, priKey), + publicKey: publicKeyObject(X25519_SPKI_PREFIX, pubKey), + }); + } catch (error) { + // OpenSSL refuses a public key of small order, whose shared secret is all zeros; tweetnacl returned the zeros. + // The peer cannot complete the pairing with it anyway: it still has to sign with its long-term key. + return Buffer.alloc(32); + } +} + +/** + * @group Cryptography + */ +export function generateEd25519KeyPair(): Ed25519KeyPair { + const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519"); + const rawPublicKey = rawKey(publicKey); + return { publicKey: rawPublicKey, secretKey: Buffer.concat([rawKey(privateKey), rawPublicKey]) }; +} + +/** + * Signs with the 64-byte secret key of an {@link Ed25519KeyPair}. + * + * @group Cryptography + */ +export function ed25519Sign(message: Uint8Array, secretKey: Uint8Array): Buffer { + assert(secretKey.length === 64, "bad secret key size"); + return crypto.sign(null, message, privateKeyObject(ED25519_PKCS8_PREFIX, secretKey.subarray(0, 32))); +} + +/** + * False also for a signature or public key of the wrong size, and for a public key that is no point on the curve. + * + * @group Cryptography + */ +export function ed25519Verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean { + if (signature.length !== 64 || publicKey.length !== 32) { + return false; + } + try { + return crypto.verify(null, message, publicKeyObject(ED25519_SPKI_PREFIX, publicKey), signature); + } catch (error) { + return false; + } } /** From 78a3c81fa461de1acfc6c58d26b5fd613e8a8a51 Mon Sep 17 00:00:00 2001 From: Stefan Riese Date: Wed, 14 Oct 2026 13:44:25 +0200 Subject: [PATCH 2/4] fix: return zeros only for a Curve25519 public key of small order 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). --- src/lib/util/hapCrypto.spec.ts | 13 +++++++++++-- src/lib/util/hapCrypto.ts | 5 ++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib/util/hapCrypto.spec.ts b/src/lib/util/hapCrypto.spec.ts index bfda9f9bf..73065c1c0 100644 --- a/src/lib/util/hapCrypto.spec.ts +++ b/src/lib/util/hapCrypto.spec.ts @@ -197,8 +197,17 @@ describe("hapCrypto", () => { it("should return zeros for a public key of small order, like tweetnacl", () => { const secretKey = hapCrypto.generateCurve25519KeyPair().secretKey; - expect(Buffer.from(hapCrypto.generateCurve25519SharedSecKey(secretKey, Buffer.alloc(32)))).toEqual(Buffer.alloc(32)); - expect(Buffer.from(tweetnacl.scalarMult(secretKey, Buffer.alloc(32)))).toEqual(Buffer.alloc(32)); + // points of small order (0, 1, a point of order 8, p - 1) + const smallOrder = [ + "0000000000000000000000000000000000000000000000000000000000000000", + "0100000000000000000000000000000000000000000000000000000000000000", + "e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800", + "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + ].map(fromHex); + for (const publicKey of smallOrder) { + expect(Buffer.from(hapCrypto.generateCurve25519SharedSecKey(secretKey, publicKey))).toEqual(Buffer.alloc(32)); + expect(Buffer.from(tweetnacl.scalarMult(secretKey, publicKey))).toEqual(Buffer.alloc(32)); + } }); it("should reject keys of the wrong size", () => { diff --git a/src/lib/util/hapCrypto.ts b/src/lib/util/hapCrypto.ts index 2a681e871..c8e2cea9f 100644 --- a/src/lib/util/hapCrypto.ts +++ b/src/lib/util/hapCrypto.ts @@ -71,7 +71,10 @@ export function generateCurve25519SharedSecKey(priKey: Uint8Array, pubKey: Uint8 } catch (error) { // OpenSSL refuses a public key of small order, whose shared secret is all zeros; tweetnacl returned the zeros. // The peer cannot complete the pairing with it anyway: it still has to sign with its long-term key. - return Buffer.alloc(32); + if ((error as NodeJS.ErrnoException).code === "ERR_OSSL_FAILED_DURING_DERIVATION") { + return Buffer.alloc(32); + } + throw error; } } From 5f4bfbfbf6ac8264e617b4ee45cb99835dc38d99 Mon Sep 17 00:00:00 2001 From: hjdhjd Date: Mon, 19 Oct 2026 18:20:09 -0500 Subject: [PATCH 3/4] Potential fix for pull request finding 'Correct comment on small-order key fallback preserving authentication only' Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/lib/util/hapCrypto.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/util/hapCrypto.ts b/src/lib/util/hapCrypto.ts index c8e2cea9f..669fae03e 100644 --- a/src/lib/util/hapCrypto.ts +++ b/src/lib/util/hapCrypto.ts @@ -70,7 +70,8 @@ export function generateCurve25519SharedSecKey(priKey: Uint8Array, pubKey: Uint8 }); } catch (error) { // OpenSSL refuses a public key of small order, whose shared secret is all zeros; tweetnacl returned the zeros. - // The peer cannot complete the pairing with it anyway: it still has to sign with its long-term key. + // A paired peer can still complete pair verify by signing with its long-term key, but the resulting + // session keys are derived from a publicly known zero secret; authentication does not preserve secrecy here. if ((error as NodeJS.ErrnoException).code === "ERR_OSSL_FAILED_DURING_DERIVATION") { return Buffer.alloc(32); } From 782ad283b6ad4ade857a3a280a7fb168c345dd66 Mon Sep 17 00:00:00 2001 From: Stefan Riese Date: Tue, 20 Oct 2026 07:19:17 +0200 Subject: [PATCH 4/4] style: remove the comments at the top of hapCrypto.ts --- src/lib/util/hapCrypto.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/util/hapCrypto.ts b/src/lib/util/hapCrypto.ts index 669fae03e..67928587b 100644 --- a/src/lib/util/hapCrypto.ts +++ b/src/lib/util/hapCrypto.ts @@ -8,11 +8,6 @@ if (!crypto.getCiphers().includes("chacha20-poly1305")) { "At least a nodejs version of v10.17.0 (excluding v11.0 and v11.1) is required!"); } -// Curve25519 and Ed25519 use the crypto built into Node.js (OpenSSL). The JavaScript implementation used before -// (tweetnacl) needed about 115 ms for the X25519 and Ed25519 operations of one pair verify on a Raspberry Pi, about -// 2 ms natively; a bridge reconnecting all its controllers at once spent seconds on the event loop in them. - -// raw 32-byte keys in the DER structures node's crypto takes (RFC 8410) const X25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b656e04220420", "hex"); const X25519_SPKI_PREFIX = Buffer.from("302a300506032b656e032100", "hex"); const ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");