Zum Inhalt springen
ecdsa.com
Node.js

elliptic

elliptic von Fedor Indutny, die reine JavaScript-Bibliothek für ECDSA und EdDSA hinter einem Jahrzehnt an Browser-Bundles und Wallets: standardmäßig deterministisch, aber nicht Low-S, nur Digests als Eingabe und ein offener Sicherheitshinweis zu falschen Nonces auf P-521.
Lizenz
MIT
Geprüfte Version
6.6.1 ·
Quellcode
github.com/indutny/elliptic
Dokumentation
github.com/indutny/elliptic#readme

Kompatibilität auf einen Blick

ECDSAEdDSA

ECDSA-Kurven
P-224P-256P-384P-521secp256k1
EdDSA
Ed25519
Formate öffentlicher Schlüssel
sec1-uncompressedsec1-compressed

elliptic implementiert ECDSA auf den NIST-Kurven und secp256k1 sowie Ed25519 in reinem JavaScript auf Basis von bn.js. Verbreitet hat sich die Bibliothek über crypto-browserify, ältere Ethereum- und Bitcoin-Bibliotheken und die Node.js-Polyfills, die Webpack 4 in Browser-Bundles einfügte – deshalb taucht sie bis heute in Lockfiles auf. ethers v6 und viem setzen stattdessen auf @noble/curves, das dieselben Kurven mit strengeren Standardeinstellungen abdeckt.

Die API unterscheidet sich in drei Punkten von den meisten Bibliotheken, und alle drei verursachen Interoperabilitätsfehler. sign() erwartet den Digest, nicht die Nachricht, und behandelt jede übergebene Bytefolge als diesen Digest – bei der Prüfung ergab das Signieren einer rohen Nachricht eine andere Signatur als das Signieren ihres SHA-256-Hashes. Zurück kommt ein Objekt Signature mit r, s und recoveryParam; das Übertragungsformat ist also das, was du serialisierst: toDER() für Gegenstellen im Stil von OpenSSL oder beide Werte auf 32 Byte aufgefüllt für rohes r‖s. Und s wird nur normalisiert, wenn du canonical: true übergibst, während die Signaturprüfung beide Hälften akzeptiert.

Die Nonces folgen RFC 6979 – der P-256-Testvektor wurde exakt reproduziert –, doch der Sicherheitshinweis GHSA-848j-6mx2-7j84 (CVE-2025-14505), der für 6.6.1 weiterhin offen ist, meldet, dass ein Zwischenwert gekürzt wird, wenn er mit Nullbytes beginnt. Die Prüfung hat das reproduziert: Auf P-521 mit SHA-512 ergaben 9 von 2.048 festen Eingaben eine andere Nonce als nach RFC 6979, während secp256k1 mit SHA-256 und P-256 mit SHA-512 bei jeweils 4.096 Eingaben keine Abweichung zeigten. Diese Signaturen bestehen die Prüfung weiterhin, doch wer dieselbe Nachricht mit demselben Schlüssel auch mit einer anderen Implementierung von RFC 6979 signiert, erhält verwandte Nonces, die laut dem Sicherheitshinweis den Schlüssel preisgeben können. Verwende elliptic nicht für P-521; warum verwandte Nonces gefährlich sind, zeigt der Nonce-Wiederverwendungs-Checker.

Frühere Sicherheitshinweise wurden in den Versionen 6.5.7 bis 6.6.1 behoben, darunter die Annahme BER-kodierter Signaturen und ein Fehler, durch den sich der Schlüssel extrahieren ließ, wenn ein String statt eines Hashes signiert wurde; 6.6.1 lehnte bei der Prüfung eine DER-Signatur mit einer überflüssigen führenden Null ab. Schlüssel sind Hex-Strings oder Byte-Arrays – SEC1-Punkte, komprimiert oder unkomprimiert, und rohe Skalare –, ohne Unterstützung für PEM, SPKI oder JWK.

Das Beispiel hasht zuerst, fordert eine kanonische Low-S-Signatur an und zeigt beide Serialisierungen. Scheitert eine Signatur aus elliptic anderswo, vergleiche ihre beiden Kodierungen im Konverter DER ⇄ Raw.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/elliptic/run.sh hat elliptic 6.6.1 unter Node.js 24.22.0 am 20. Oktober 2026 im Container-Image node:24.22.0-bookworm-slim ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • Kodierung von ECDSA-Signaturen

    structured

    sign() gibt ein Objekt mit r, s und recoveryParam zurück; toDER() erzeugt ASN.1-DER, und rohes r‖s muss aus den beiden auf die Kurvengröße aufgefüllten Werten zusammengesetzt werden.

    Von der Prüfung aufgezeichnet
    fact ecdsa-signature-encoding = structured
    evidence ecdsa-signature-encoding: sign() returns an object with r, s and recoveryParam 0; toDER() gives 72 bytes that parse as DER: true
  • Was der Signaturaufruf hasht

    takes-digest

    Die Eingabe von sign() und verify() wird als Digest verwendet; das Signieren der rohen Nachricht ergab eine andere Signatur als das Signieren ihres SHA-256-Hashes.

    Von der Prüfung aufgezeichnet
    fact ecdsa-prehash = takes-digest
  • ECDSA-Nonce

    rfc6979

    RFC 6979 mit HMAC-DRBG: Der P-256-Testvektor aus Anhang A.2.5 wurde exakt reproduziert, einschließlich seines High-S-Werts.

    Von der Prüfung aufgezeichnet
    fact ecdsa-nonce = rfc6979
    evidence ecdsa-nonce: r and s equal RFC 6979 A.2.5 (high s kept): true
  • Von RFC 6979 abweichende Nonces

    P-521/SHA-512

    Auf P-521 mit SHA-512 verwendeten 9 von 2.048 festen Eingaben eine andere Nonce als nach RFC 6979 (Referenz war @noble/curves); auf secp256k1/SHA-256 und P-256/SHA-512 zeigte sich bei jeweils 4.096 Eingaben keine Abweichung. Das deckt sich mit dem Sicherheitshinweis GHSA-848j-6mx2-7j84, der für alle Versionen bis 6.6.1 offen ist.

    Von der Prüfung aufgezeichnet
    fact rfc6979-divergence = P-521/SHA-512
    evidence rfc6979-divergence: secp256k1 with SHA-256, 4096 fixed inputs: 0 nonces differ from RFC 6979, 0 of those signatures still verify
    evidence rfc6979-divergence: P-256 with SHA-512, 4096 fixed inputs: 0 nonces differ from RFC 6979, 0 of those signatures still verify
    evidence rfc6979-divergence: P-521 with SHA-512, 2048 fixed inputs: 9 nonces differ from RFC 6979, 9 of those signatures still verify
  • Low-S beim Signieren

    either

    Standardmäßig nicht normalisiert: Der Testvektor des RFC kam mit seinem High-S-Wert zurück. canonical: true liefert stattdessen n − s.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: canonical: true returns n - s: true
  • High-S bei der Prüfung

    accepts-high-s

    Sowohl die High-S- als auch die Low-S-Form derselben Signatur bestanden die Prüfung.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: the RFC signature (high s) and its low-s twin both verify: true
  • Strenge beim Parsen von DER

    rejects-non-minimal

    Eine DER-Signatur, deren r eine überflüssige führende Null enthielt, wurde mit Signature without r or s abgelehnt, während die minimale Kodierung aus toDER() die Prüfung bestand.

    Von der Prüfung aufgezeichnet
    fact der-strictness = rejects-non-minimal
    evidence der-strictness: the minimal DER from toDER() verifies when passed back as bytes: true
    evidence der-strictness: non-minimal INTEGER encoding: throws Signature without r or s
  • Fehlerhafte ECDSA-Signatur

    throws

    Ein roher 64-Byte-Blob r‖s oder ein 3-Byte-Array, an verify() übergeben, löst Signature without r or s aus, statt false zurückzugeben.

    Von der Prüfung aufgezeichnet
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: 64-byte raw r||s: throws Signature without r or s
    evidence ecdsa-malformed-signature: 3 bytes: throws Signature without r or s
  • ECDSA-Kurven

    P-224P-256P-384P-521secp256k1

    Signieren und Prüfen funktionierten mit P-224, P-256, P-384, P-521 und secp256k1; Brainpool-Kurven gibt es nicht.

    Von der Prüfung aufgezeichnet
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1
  • EdDSA

    Ed25519

    Ed25519 über new elliptic.eddsa("ed25519").

    Von der Prüfung aufgezeichnet
    fact eddsa = Ed25519
  • Post-Quanten-Algorithmen

    none

    Keine Post-Quanten-Algorithmen.

    Von der Prüfung aufgezeichnet
    fact pqc = none
  • Formate öffentlicher Schlüssel

    sec1-uncompressedsec1-compressed

    keyFromPublic() las SEC1-Punkte als Hex, komprimiert oder unkomprimiert, sowie ein Objekt mit x- und y-Koordinate als Hex; ein 64 Byte langes x‖y ohne das Präfix 0x04 wurde abgelehnt.

    Von der Prüfung aufgezeichnet
    fact public-key-formats = sec1-uncompressed, sec1-compressed
    evidence public-key-formats: an object of hex x and y coordinates also works: true; there is no PEM, SPKI, PKCS#8 or JWK parser
  • Formate privater Schlüssel

    raw-scalar

    Private Schlüssel sind der rohe Skalar als Hex oder Bytes; einen Parser für PEM, PKCS#8 oder JWK gibt es nicht.

    Von der Prüfung aufgezeichnet
    fact private-key-formats = raw-scalar

Verwendung

Hashe die Nachricht, signiere sie auf secp256k1 als kanonische Low-S-Signatur und serialisiere die Signatur als DER und als rohes r‖s.
import { createHash } from "node:crypto";
import elliptic from "elliptic";

const ec = new elliptic.ec("secp256k1");
const key = ec.genKeyPair();

// elliptic signs exactly the bytes it is given: hash the message yourself,
// and pass bytes — never a string.
const digest = createHash("sha256").update("order #1042 approved").digest();

// canonical: true keeps s in the lower half, which Bitcoin and noble verifiers require.
const signature = key.sign(digest, { canonical: true });

const der = Buffer.from(signature.toDER());
const raw = Buffer.concat([signature.r.toArrayLike(Buffer, "be", 32), signature.s.toArrayLike(Buffer, "be", 32)]);
console.log("low-S:", signature.s.cmp(ec.nh) <= 0);
console.log("verified:", key.verify(digest, der), "raw bytes:", raw.length);

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image node:24.22.0-bookworm-slim aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Einrichtung
npm init -y >/dev/null && npm install --no-audit --no-fund elliptic@6.6.1
Befehl
node usage.mjs
Ausgabe muss passen (regulärer Ausdruck)
low-S: true\nverified: true raw bytes: 64

Bibliotheken