feat(node): migrate @noble/curves from v1 to v2#1111
feat(node): migrate @noble/curves from v1 to v2#1111thinktanktom wants to merge 2 commits intohyperledger:mainfrom
Conversation
bestbeforetoday
left a comment
There was a problem hiding this comment.
Thank you very much for this contribution.
For some future v2 release, I am open to breaking backwards compatibility with CommonJS, but there are quite a few other API changes I would want to include in any v2 release. For now we need to maintain backwards compatibility with CommonJS consumers.
| // EC signatures have length of 2n according to the PKCS11 spec: | ||
| // https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/pkcs11-spec-v3.1.html |
There was a problem hiding this comment.
It would be great to retain the explanatory comment.
There was a problem hiding this comment.
Restored — sorry for the accidental removal.
node/src/identity/hsmsigner.ts
Outdated
| const sig = p256.Signature.fromBytes(compactSignature, 'compact'); | ||
| const normalizedSig = sig.hasHighS() ? new p256.Signature(sig.r, p256.Point.CURVE().n - sig.s) : sig; | ||
| return normalizedSig.toBytes('der'); |
There was a problem hiding this comment.
I prefer to avoid abbreviation where variable names a reasonably short anyway: signature instead of sig. I think I find this slightly more verbose structure more readable too, but the code is good so just a suggestion:
| const sig = p256.Signature.fromBytes(compactSignature, 'compact'); | |
| const normalizedSig = sig.hasHighS() ? new p256.Signature(sig.r, p256.Point.CURVE().n - sig.s) : sig; | |
| return normalizedSig.toBytes('der'); | |
| let signature = p256.Signature.fromBytes(compactSignature, 'compact'); | |
| if (signature.hasHighS()) { | |
| signature = new p256.Signature(signature.r, p256.Point.CURVE().n - signature.s); | |
| } | |
| return signature.toBytes('der'); |
There was a problem hiding this comment.
Good call, updated to use signature throughout and expanded the ternary to an if block for readability.
| "extends": "@tsconfig/node20/tsconfig.json", | ||
| "compilerOptions": { | ||
| "module": "esnext", | ||
| "moduleResolution": "bundler", |
There was a problem hiding this comment.
The TypeScript documentation advises against using "moduleResolution": "bundler".
There was a problem hiding this comment.
Replaced with "module": "node18" as recommended by the TypeScript library authoring docs. The google-protobuf deep import path is unresolvable under strict node18 resolution since it has no exports field — addressed this with targeted eslint-disable-next-line comments on the two affected import lines, with an explanatory comment so the workaround is visible and self-documenting.
|
Thanks for the context on the v2 plans. I'll leave the CJS compatibility question for that future work. For now the exports field in this PR is ESM-only. |
Summary
Upgrades
@noble/curvesfrom^1.9.4to^2.0.0in the Node.js SDK.Resolves #1109.
@noble/curvesv2 is ESM-only, which required migrating thenode/package to ESM output and updating all affected crypto API call sites.
Changes
Package
@noble/curvesto^2.0.0"type": "module"topackage.jsonexportsfield for ESM consumersTypeScript
moduleandmoduleResolutionintsconfig.jsontoesnext/bundler— see notes below.jsextensions to all relative imports (ESM requirement)Crypto API (v1 → v2 breaking changes)
sign()now returnsUint8Arraydirectly instead of aSignatureobject — updated
ecdsa.tswith{ format: 'der', prehash: false }normalizeS()removed — replaced withhasHighS()+ manual Snormalization in
hsmsigner.tsfromCompact()/fromDER()replaced withfromBytes(data, 'compact')and
fromBytes(data, 'der')randomPrivateKey()renamed torandomSecretKey()in testJest
jest.config.jsto ESMexport defaultsyntaxextensionsToTreatAsEsm,moduleNameMapper, and dualtransformrules to support ESM test executiontransformIgnorePatternsto allow Jest to transform@noble/*packages which are ESM-onlyKnown tradeoffs — maintainer input welcome
1.
moduleResolution: "bundler"in tsconfig.jsongoogle-protobufhas noexportsfield, which causes a type errorunder strict
node16resolution on the deep import pathgoogle-protobuf/google/protobuf/timestamp_pb. Overriding tobundlerresolution fixes this but is not strictly correct for a Node.js library.
Alternatives considered: ambient module declaration (rejected by ESLint
rules), local shim (same issue). Happy to explore other approaches if
the maintainer has a preference.
2. CJS consumers
The
exportsfield currently only exposes an ESM path. Any consumerusing
require('@hyperledger/fabric-gateway')will break. If CJSsupport needs to be maintained, a dual build step producing
dist/index.cjswould be required. Happy to add this if needed, orthis could be treated as a conscious breaking change.
3. Dynamic
require()insigners.tsThe optional HSM dependency is lazy-loaded via
require(), which Jestpolyfills in ESM mode but will throw in a native ESM runtime. Converting
to
await import()is the correct long-term fix but is out of scope forthis PR. Flagging for awareness.
Test results