Skip to content

deps: @noble v3, proven byte-identical on the signing path - #85

Merged
github-actions[bot] merged 2 commits into
mainfrom
deps/noble-v3
Aug 14, 2026
Merged

deps: @noble v3, proven byte-identical on the signing path#85
github-actions[bot] merged 2 commits into
mainfrom
deps/noble-v3

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

@noble/secp256k1 2.3.0 → 3.1.0, @noble/hashes 1.8.0 → 2.3.0.

Both are breaking majors, and they land on the code that decides whether a
vote is real. So the interesting part of this PR is not the upgrade — it is
the evidence that it changed nothing.

The tests could not have caught a break

Every existing test signs and verifies with the same code. A change that
altered the wire format on both sides would have passed all fifteen of them
while silently invalidating every signature a real wallet produces — and every
signature already sitting in the audit trail.

Four known-answer tests now close that hole. All four were written and run
against v2 first
, so they are known to hold before the upgrade rather than
fitted to it afterwards:

Test What it anchors to
Address derivation BIP173's own published vector. The key is secret exponent 1, so its public key is the secp256k1 generator and its P2WPKH address is the one written into the spec: bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
Signed-message digest Rebuilt from the spec inside the test rather than imported, compared byte for byte
Signature bytes Asserted literally. RFC6979 makes signing deterministic, so the base64 is stable across versions unless nonce derivation or encoding moved
ECDSA validity OpenSSL, via node:crypto, verifies it independently — the only check in the repo that isn't noble verifying noble

They pass unchanged on v3. The golden signature is the same base64, so every
signature already stored still verifies.

The API moves

  • hmac wiring is now secp.hashes.{sha256,hmacSha256}, and hmacSha256
    takes a single message instead of varargs.
  • sign() returns bytes, not a Signature. Recovery comes from
    format: 'recovered' (65 bytes, recovery || r || s), and prehash: false
    is load-bearing — the Bitcoin digest is already double-sha256, and letting
    the library hash again would sign the wrong value.
  • Signature.fromCompact().recoverPublicKey() became the top-level
    recoverPublicKey(), so verification rebuilds the recovered encoding from
    the Bitcoin header || r || s layout.
  • randomPrivateKeyrandomSecretKey; secret keys are bytes now, not hex.
  • @noble/hashes v2 moved its submodules: sha256sha2.js,
    ripemd160legacy.js, and every deep import needs the .js extension.

Verification

  • npm run verify — exit 0 (37 passed, 2 skipped)
  • npm run build — compiled successfully
  • Golden signature base64 identical on v2 and v3; OpenSSL agrees on both

catomean and others added 2 commits August 14, 2026 16:25
@noble/secp256k1 2.3.0 -> 3.1.0 and @noble/hashes 1.8.0 -> 2.3.0. Both are
breaking majors on the code that decides whether a vote is real, so the
interesting part is not the upgrade — it is the evidence that it changed
nothing.

The tests could not supply that evidence. Every one of them signed and
verified with the same code, so a change that altered the wire format on both
sides would have passed all fifteen while silently invalidating every
signature a real wallet produces. Four known-answer tests now close that:

- Address derivation is anchored to BIP173's own published vector. The key is
  secret exponent 1, so its public key is the secp256k1 generator and its
  P2WPKH address is the one written into the spec.
- The signed-message digest is rebuilt from the spec inside the test rather
  than imported, and compared byte for byte.
- The signature base64 is asserted literally. RFC6979 makes signing
  deterministic, so this is stable across versions unless nonce derivation or
  encoding moved.
- OpenSSL, via node:crypto, verifies the signature independently. It is the
  only check in the repo that is not noble verifying noble.

All four were written and run against v2 first, so they are known to hold
before the upgrade rather than fitted to it afterwards. They pass unchanged on
v3: the golden signature is the same base64, so every signature already stored
in the treasury's audit trail still verifies.

The API moves themselves:

- hmac wiring is now secp.hashes.{sha256,hmacSha256}, and hmacSha256 takes a
  single message instead of varargs.
- sign() returns bytes, not a Signature. Recovery comes from
  format: 'recovered' (65 bytes, recovery || r || s), and prehash: false is
  now load-bearing — the Bitcoin digest is already double-sha256, and letting
  the library hash again would sign the wrong value.
- Signature.fromCompact().recoverPublicKey() became the top-level
  recoverPublicKey(), so verification rebuilds the recovered encoding from the
  Bitcoin header || r || s layout.
- randomPrivateKey -> randomSecretKey; secret keys are bytes, no longer hex.
- @noble/hashes v2 moved its submodules: sha256 to sha2.js, ripemd160 to
  legacy.js, and every deep import now needs the .js extension.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	package-lock.json
#	package.json
@github-actions
github-actions Bot merged commit 55f993c into main Aug 14, 2026
2 checks passed
@github-actions
github-actions Bot deleted the deps/noble-v3 branch August 14, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant