📢 @steemit/steem-js@1.2.0 — elliptic is gone: secp256k1 now runs on @noble/curvessteemCreated with Sketch.

in #steemlast month

Release channel: latest
Version: 1.2.0
Date: 2026-08-16
Install: npm install @steemit/steem-js@latest


Headline

The elliptic dependency has been removed entirely and replaced with
@noble/curves for all secp256k1 point operations.
This closes
CVE-2025-14505 — an ECDSA
flaw with no upstream fix — while producing bit-identical signatures:
every signature generated by 1.2.0 is byte-for-byte the same as the one 1.1.2
generated for the same key and message.

The vulnerability

CVE-2025-14505 (GHSA-848j-6mx2-7j84,
rated Low, CVSS 4.0: 2.9) affects every published version of elliptic up to and
including 6.6.1 — which was the last release, published in November 2024. The bug
sits in elliptic's deterministic ECDSA signing: when an interim value of the
RFC 6979 nonce k has leading zeros, elliptic computes its byte length
incorrectly and truncates it. The resulting malformed signatures are susceptible
to cryptanalysis; a faulty signature and a correct signature over the same input
can, under certain conditions, reveal the private key.

There is no patched version to upgrade to, and no workaround. The only real
remediation is to stop depending on elliptic.

Were steem-js users exposed? No.

steem-js never called elliptic's signing path. Since the 2025 rewrite, the ECDSA
math has been implemented in-tree:

  • the RFC 6979 deterministic nonce is generated by a hand-written
    deterministicGenerateK (candidates come from the full 32-byte HMAC output,
    parsed as a number and range-checked against [1, n-1] — there is no
    byte-length-truncation step anywhere);
  • sign, verify, and public-key recovery are hand-implemented over raw curve
    primitives. elliptic was used purely as a point-arithmetic library
    (mul, add, encode, decodePoint) — with exactly one higher-level call,
    recoverPubKey, which does not touch nonce generation.

A deterministic signature scheme also means the same (key, hash, nonce)
always produces the same signature, so the "one good + one faulty signature over
the same input" attack pattern the CVE requires cannot arise. Downstream
consumers such as the Steem wallet sign through this same hand-written path.

So this is a dependency hygiene release, not an incident response. But an
advisory that pnpm audit reports forever, on a dependency chain
(hmac-drbg, brorand, …) that has been unmaintained since 2024, is a
permanent liability — and migrating aligns the elliptic-curve layer with the
@noble/hashes / @noble/ciphers stack the 2025 refactor already adopted
elsewhere.

What changed in 1.2.0

Only the point-arithmetic layer was swapped

The Steem protocol layer is untouched:

  • hand-written RFC 6979 deterministic nonce generation;
  • the canonical-signature retry loop (is_fc_canonical, matching the Steem C++
    node's fc library);
  • low-S normalization (BIP62);
  • the dsteem-compatible recovery byte convention (31–34).

API-level mapping (for orientation; all of it is internal):

elliptic@noble/curves v2
G.mul(bn)G.multiply(bigint)
G.mul(u1).add(Q.mul(u2))G.mulAddUnsafe(u1, Q, u2)
Q.getX() / Q.getY().isOdd()Q.x / Q.y & 1n
Q.encode('array', compressed)Q.toBytes(compressed)
curve.decodePoint(buffer)secp256k1.Point.fromBytes(bytes)
curve.recoverPubKey(...)manual SEC 1 recovery (in-tree)

All curve access is now funneled through a new shared module,
src/auth/ecc/src/curve.ts.

A latent bug found and fixed along the way

Migrating uncovered a real (if unreachable) bug in the old manual public-key
recovery fallback: it computed -e as e.neg().mod(n), but bn.js's mod
keeps the sign of the dividend — so the fallback produced a negative scalar.
It was unreachable because elliptic's built-in recoverPubKey always succeeded
first, and noble-curves's strict scalar range checks would have rejected it
immediately. The fix (umod()) ships in this release.

One type-level change to know about: PublicKey.Q

PublicKey.Q is now a @noble/curves point instead of an elliptic point:

1.1.2 (elliptic)1.2.0 (@noble/curves)
Q.mul(bn)Q.multiply(bigint)
Q.getX()Q.x (bigint)
Q.encode('array', b)Q.toBytes(b)
Q.isInfinity()Q.is0()

If you use the high-level steem.auth.* API with string keys (WIF, STM…
public keys) — which is everyone we know of, including the Steem wallet —
there is nothing to do. Only code that reaches into PublicKey.Q and calls
elliptic point methods directly needs the mechanical renames above. This is the
reason the release is 1.2.0 rather than 1.1.3.

How we verified it

GateResult
Full test suite279 passed, including Go cross-language serializer vectors
Bit-identical baseline25 signatures / 5 keys / transaction signing / child-key derivation / ECDH shared secrets — every output identical to the pre-migration build
Buildsall four artifacts (ESM / CJS / browser ESM / UMD); UMD smoke-tested in a simulated browser context
pnpm auditGHSA-848j-6mx2-7j84 no longer reported

Upgrade

npm install @steemit/steem-js@latest
# or pin exactly:
npm install @steemit/steem-js@1.2.0

No action needed beyond the version bump for string-level API consumers.
Signatures, transaction serialization, and cross-language compatibility are
unchanged — the fixtures that validate against the Steem C++ node and the Go
implementation are the same ones that were green before the migration.

Links

Sort:  

Upvoted! Thank you for supporting witness @jswit.