ed25519.verify() does not reject public keys of small order, such as the identity point. Under such a key, a signature with S = 0 and a small-order R verifies for many messages, or for every message with the identity key, without any private key. Fixed in node-forge 1.4.3.
crypto_sign_open() in lib/ed25519.js decodes the public key A with unpackneg(), which only checks that the point is on the curve, and then checks R == [S]B - [k]A with k = SHA-512(R || A || M). Nothing rejects public keys of small order: the identity point, the other 7 points of order 2, 4 or 8, and their non-canonical encodings with y >= p.
With S = 0, the check becomes R == [k](-A). For a small-order A, [k](-A) is one of at most 8 small-order points, so an attacker can make a valid signature by trying the small-order values of R. For the identity key (01 00 … 00), R = identity and S = 0 verifies for every message. For the other small-order keys, a given message can be forged with high probability. No private key is involved. The S < L check added for CVE-2026-33895 (GHSA-q67f-28xg-22rw) doesn't apply, because S = 0 is canonical.
Keys produced by Ed25519 key generation (ed25519.generateKeyPair() or any RFC 8032 implementation) are never small-order, so signatures under them cannot be forged this way. Applications are affected when they verify signatures against a public key an attacker can choose (for example a key included with the signed data, a self-registered key, or a key in an attacker-controlled document) and rely on the signature being bound to the message: proof of possession, attribution, non-repudiation or deduplication.
RFC 8032 doesn't require rejecting small-order public keys, and implementations differ. libsodium rejects them, and Node.js crypto.verify() (OpenSSL) rejects these signatures.
The fix rejects a public key A when [8]A is the identity point. This covers all small-order points and their non-canonical encodings.
Fixed in node-forge 1.4.3. ed25519.verify() returns false for small-order public keys.
Before calling ed25519.verify() with a public key from untrusted input, reject keys whose 32-byte encoding is one of the small-order points (the identity 01 00 … 00 and the other encodings that libsodium blocks), or only verify against keys you generated or have otherwise validated.
CVSS 3.1: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N (3.7, Low) CVSS 4.0 (informational): CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N (6.3) CVSS 4.0 rates this Moderate because its integrity weighting is higher; the severity rating follows CVSS 3.1.
Reported by @analisaperlengkapan.
Identity public key A = 01 00 … 00 (32 bytes) and signature R = 01 00 … 00, S = 00 … 00 (64 bytes):
forge.ed25519.verify({message: 'admin=true', encoding: 'utf8',
signature: SIG, publicKey: IDENTITY});
// <= 1.4.2: true for any message; node:crypto.verify(): false
// 1.4.3: false
Other small-order public keys (orders 2, 4 and 8, and non-canonical encodings) give forgeries for many messages with S = 0 and a small-order R.
Regression tests are in tests/unit/ed25519.js, and the reporter's PoC is in tests/pocs/ghsa-6889-vf6x-f3g5.js.
Applications that verify Ed25519 signatures with node-forge against a public key that an attacker can choose are affected. An attacker can provide a small-order public key and then produce signatures that verify for many or all messages without a private key, defeating checks that rely on a signature being bound to its message under that key (proof of possession, attribution, non-repudiation, deduplication). Signatures under keys from honest key generation cannot be forged, and applications that only verify against their own or otherwise trusted keys are not affected.
{
"cwe_ids": [
"CWE-347"
],
"github_reviewed": true,
"github_reviewed_at": "2026-11-10T21:18:23Z",
"nvd_published_at": null,
"severity": "LOW"
}