Aller au contenu
ecdsa.com
Node.js

elliptic

elliptic de Fedor Indutny, la bibliothèque ECDSA et EdDSA en pur JavaScript présente depuis dix ans dans les bundles pour navigateur et les portefeuilles : déterministe mais pas low-S par défaut, une entrée qui doit être un condensat, et un avis de sécurité ouvert concernant des nonces erronés sur P-521.
Licence
MIT
Version vérifiée
6.6.1 ·
Code source
github.com/indutny/elliptic
Documentation
github.com/indutny/elliptic#readme

Compatibilité en un coup d’œil

ECDSAEdDSA

Courbes ECDSA
P-224P-256P-384P-521secp256k1
EdDSA
Ed25519
Formats de clé publique
sec1-uncompressedsec1-compressed

elliptic implémente ECDSA sur les courbes NIST et secp256k1, ainsi qu’Ed25519, en JavaScript pur au-dessus de bn.js. Cette bibliothèque s’est répandue via crypto-browserify, d’anciennes bibliothèques Ethereum et Bitcoin et les polyfills Node.js que Webpack 4 injectait dans les bundles pour navigateur, ce qui explique qu’elle apparaisse encore dans les fichiers de verrouillage. ethers v6 et viem reposent plutôt sur @noble/curves, qui couvre les mêmes courbes avec des valeurs par défaut plus strictes.

L’API se distingue de la plupart des bibliothèques sur trois points qui provoquent des bugs d’interopérabilité. sign() prend le condensat, pas le message, et traite comme condensat tous les octets qu’elle reçoit : les essais ont signé un message brut et obtenu une signature différente de celle de son SHA-256. Cette fonction renvoie un objet Signature avec r, s et recoveryParam, si bien que le format échangé est celui que vous sérialisez : toDER() pour les pairs de type OpenSSL, ou les deux valeurs complétées à 32 octets pour du r‖s brut. Enfin, s n’est pas normalisé, sauf si vous passez canonical: true, alors que la vérification accepte les deux moitiés.

Les nonces suivent la RFC 6979 (le vecteur de test P-256 a été reproduit exactement), mais l’avis de sécurité GHSA-848j-6mx2-7j84 (CVE-2025-14505), toujours ouvert pour la 6.6.1, signale qu’une valeur intermédiaire est tronquée lorsqu’elle commence par des octets nuls. Les essais l’ont reproduit : sur P-521 avec SHA-512, 9 entrées fixes sur 2048 ont produit un nonce différent de celui de la RFC 6979, alors que secp256k1 avec SHA-256 et P-256 avec SHA-512 n’ont montré aucune différence sur 4096 entrées chacune. Ces signatures passent toujours la vérification, mais signer le même message avec la même clé à l’aide d’une autre implémentation de la RFC 6979 donne des nonces liés, ce qui, selon l’avis, peut exposer la clé. Évitez elliptic pour P-521 et consultez le vérificateur de réutilisation de nonce pour comprendre pourquoi des nonces liés posent problème.

Des avis antérieurs ont été corrigés entre la 6.5.7 et la 6.6.1, parmi lesquels l’acceptation de signatures encodées en BER et un bug d’extraction de clé quand une chaîne était signée à la place d’un condensat ; lors des essais, la 6.6.1 a rejeté une signature DER comportant un zéro initial superflu. Les clés sont des chaînes hexadécimales ou des tableaux d’octets (points SEC1, compressés ou non, et scalaires bruts), sans prise en charge de PEM, SPKI ou JWK.

L’exemple hache d’abord, demande une signature canonique low-S et affiche les deux sérialisations. Lorsqu’une signature issue d’elliptic échoue ailleurs, comparez ses deux encodages dans le convertisseur DER ⇄ brut.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/elliptic/run.sh a exécuté elliptic 6.6.1 sur Node.js 24.22.0 dans l’image de conteneur node:24.22.0-bookworm-slim le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Encodage des signatures ECDSA

    structured

    sign() renvoie un objet avec r, s et recoveryParam ; toDER() produit du DER ASN.1, et le r‖s brut doit être assemblé à partir des deux valeurs complétées à la taille de la courbe.

    Relevé par la vérification
    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
  • Ce que l’appel de signature hache

    takes-digest

    L’entrée de sign() et de verify() est utilisée comme condensat ; signer le message brut a produit une autre signature que signer son SHA-256.

    Relevé par la vérification
    fact ecdsa-prehash = takes-digest
  • Nonce ECDSA

    rfc6979

    RFC 6979 avec HMAC-DRBG : le vecteur de test P-256 de l’annexe A.2.5 a été reproduit exactement, y compris sa valeur de s élevée (high-S).

    Relevé par la vérification
    fact ecdsa-nonce = rfc6979
    evidence ecdsa-nonce: r and s equal RFC 6979 A.2.5 (high s kept): true
  • Nonces différents de la RFC 6979

    P-521/SHA-512

    Sur la courbe P-521 avec SHA-512, 9 entrées fixes sur 2048 ont utilisé un nonce différent de celui de la RFC 6979 (la référence était @noble/curves) ; aucune différence n’est apparue sur secp256k1/SHA-256 ni sur P-256/SHA-512, sur 4096 entrées chacune. Cela correspond à l’avis GHSA-848j-6mx2-7j84, ouvert pour toutes les versions jusqu’à la 6.6.1.

    Relevé par la vérification
    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 à la signature

    either

    Pas de normalisation par défaut : le vecteur de test de la RFC est ressorti avec sa valeur de s élevée (high-S). canonical: true renvoie n − s à la place.

    Relevé par la vérification
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: canonical: true returns n - s: true
  • High-S à la vérification

    accepts-high-s

    Les formes high-S et low-S d’une même signature ont toutes deux été vérifiées.

    Relevé par la vérification
    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
  • Rigueur de l’analyse DER

    rejects-non-minimal

    Une signature DER dont le r portait un zéro initial superflu a été refusée avec Signature without r or s, alors que l’encodage minimal produit par toDER() a été vérifié.

    Relevé par la vérification
    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
  • Signature ECDSA mal formée

    throws

    Un blob r‖s brut de 64 octets ou un tableau de 3 octets passé à verify() lève Signature without r or s au lieu de renvoyer false.

    Relevé par la vérification
    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
  • Courbes ECDSA

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

    Signature et vérification réussies avec P-224, P-256, P-384, P-521 et secp256k1 ; il n’y a pas de courbes Brainpool.

    Relevé par la vérification
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1
  • EdDSA

    Ed25519

    Ed25519 via new elliptic.eddsa("ed25519").

    Relevé par la vérification
    fact eddsa = Ed25519
  • Algorithmes post-quantiques

    none

    Aucun algorithme post-quantique.

    Relevé par la vérification
    fact pqc = none
  • Formats de clé publique

    sec1-uncompressedsec1-compressed

    keyFromPublic() a lu des points SEC1 en hexadécimal, compressés ou non, ainsi qu’un objet de coordonnées x et y en hexadécimal ; un x‖y de 64 octets sans le préfixe 0x04 a été refusé.

    Relevé par la vérification
    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
  • Formats de clé privée

    raw-scalar

    Les clés privées sont le scalaire brut, en hexadécimal ou en octets ; il n’existe pas d’analyseur PEM, PKCS#8 ou JWK.

    Relevé par la vérification
    fact private-key-formats = raw-scalar

Utilisation

Hacher le message, le signer sur secp256k1 sous forme de signature canonique low-S, puis la sérialiser en DER et en r‖s brut.
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);

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur node:24.22.0-bookworm-slim, et l’exécution échoue si la sortie ne correspond pas.

Mise en place
npm init -y >/dev/null && npm install --no-audit --no-fund elliptic@6.6.1
Commande
node usage.mjs
La sortie doit correspondre à cette expression régulière
low-S: true\nverified: true raw bytes: 64

Bibliothèques