@unknownncat/curve25519-node 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +332 -0
- package/README.md +222 -115
- package/dist/axlsign.d.ts +31 -0
- package/dist/axlsign.d.ts.map +1 -0
- package/dist/axlsign.js +96 -0
- package/dist/axlsign.js.map +1 -0
- package/dist/cjs/axlsign.js +105 -0
- package/dist/cjs/axlsign.js.map +1 -0
- package/dist/cjs/index.js +15 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/axlsign-wasm/LICENSE +21 -0
- package/dist/cjs/internal/axlsign-wasm/axlsign_wasm.d.ts +12 -0
- package/dist/cjs/internal/axlsign-wasm/axlsign_wasm.js +171 -0
- package/dist/cjs/internal/axlsign-wasm/axlsign_wasm_bg.wasm +0 -0
- package/dist/cjs/internal/axlsign-wasm/axlsign_wasm_bg.wasm.d.ts +13 -0
- package/dist/cjs/internal/axlsign-wasm/package.json +17 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/axlsign-wasm/LICENSE +21 -0
- package/dist/internal/axlsign-wasm/axlsign_wasm.d.ts +12 -0
- package/dist/internal/axlsign-wasm/axlsign_wasm.js +171 -0
- package/dist/internal/axlsign-wasm/axlsign_wasm_bg.wasm +0 -0
- package/dist/internal/axlsign-wasm/axlsign_wasm_bg.wasm.d.ts +13 -0
- package/dist/internal/axlsign-wasm/package.json +17 -0
- package/package.json +12 -2
package/README.en.md
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# @unknownncat/curve25519-node
|
|
2
|
+
|
|
3
|
+
> 🇧🇷 Versão em português (principal): [README.md](./README.md)
|
|
4
|
+
|
|
5
|
+
Zero-runtime-dependency implementation of:
|
|
6
|
+
|
|
7
|
+
- X25519 + Ed25519 (modern mode via OpenSSL in `node:crypto`)
|
|
8
|
+
- legacy axlsign (optional WASM mode, compatible with `curve25519-js`)
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/@unknownncat/curve25519-node)
|
|
11
|
+
[](https://nodejs.org/)
|
|
12
|
+
[](./dist/index.d.ts)
|
|
13
|
+

|
|
14
|
+

|
|
15
|
+
[](./LICENSE)
|
|
16
|
+
|
|
17
|
+
- Node: `>= 20`
|
|
18
|
+
- Runtime dependencies: `0`
|
|
19
|
+
- TypeScript: `strict`
|
|
20
|
+
- Module formats: ESM + CJS
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm i @unknownncat/curve25519-node
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick Usage
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { randomBytes } from "node:crypto";
|
|
36
|
+
import { asBytes32, x25519, ed25519 } from "@unknownncat/curve25519-node";
|
|
37
|
+
|
|
38
|
+
const aliceSeed = asBytes32(randomBytes(32));
|
|
39
|
+
const bobSeed = asBytes32(randomBytes(32));
|
|
40
|
+
|
|
41
|
+
const aliceX = x25519.generateKeyPair(aliceSeed);
|
|
42
|
+
const bobX = x25519.generateKeyPair(bobSeed);
|
|
43
|
+
|
|
44
|
+
const secret1 = x25519.sharedKey(aliceX.private, bobX.public);
|
|
45
|
+
const secret2 = x25519.sharedKey(bobX.private, aliceX.public);
|
|
46
|
+
// secret1 === secret2
|
|
47
|
+
|
|
48
|
+
const signerSeed = asBytes32(randomBytes(32));
|
|
49
|
+
const signer = ed25519.generateKeyPair(signerSeed);
|
|
50
|
+
const msg = new TextEncoder().encode("hello");
|
|
51
|
+
|
|
52
|
+
const sig = ed25519.sign(signerSeed, msg);
|
|
53
|
+
const ok = ed25519.verify(signer.public, msg, sig);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
CommonJS:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
const { x25519, ed25519, asBytes32 } = require("@unknownncat/curve25519-node");
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Legacy axlsign via WASM:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { asBytes32, axlsign } from "@unknownncat/curve25519-node";
|
|
66
|
+
|
|
67
|
+
const seed = asBytes32(new Uint8Array(32));
|
|
68
|
+
const kp = axlsign.generateKeyPair(seed); // curve25519-js-compatible X25519 keypair
|
|
69
|
+
const sig = axlsign.sign(
|
|
70
|
+
kp.private,
|
|
71
|
+
new TextEncoder().encode("hello"),
|
|
72
|
+
new Uint8Array(64),
|
|
73
|
+
);
|
|
74
|
+
const ok = axlsign.verify(kp.public, new TextEncoder().encode("hello"), sig);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
### `x25519`
|
|
82
|
+
|
|
83
|
+
- `publicKey(secretKey32: Bytes32): Bytes32`
|
|
84
|
+
- `sharedKey(secretKey32: Bytes32, publicKey32: Bytes32): Bytes32`
|
|
85
|
+
- `generateKeyPair(seed32: Bytes32): { public: Bytes32; private: Bytes32 }`
|
|
86
|
+
|
|
87
|
+
### `ed25519`
|
|
88
|
+
|
|
89
|
+
- `publicKey(secretSeed32: Bytes32): Bytes32`
|
|
90
|
+
- `generateKeyPair(seed32: Bytes32): { public: Bytes32; private: Bytes32 }`
|
|
91
|
+
- `sign(secretSeed32: Bytes32, msg: Uint8Array): Bytes64`
|
|
92
|
+
- `verify(publicKey32: Bytes32, msg: Uint8Array, signature64: Bytes64): boolean`
|
|
93
|
+
- `signMessage(secretSeed32: Bytes32, msg: Uint8Array): Uint8Array` (`signature || message`)
|
|
94
|
+
- `openMessage(publicKey32: Bytes32, signedMsg: Uint8Array): Uint8Array | null`
|
|
95
|
+
|
|
96
|
+
### `axlsign` (legacy compatibility via WASM)
|
|
97
|
+
|
|
98
|
+
- `publicKey(secretKey32: Bytes32): Bytes32`
|
|
99
|
+
- `sharedKey(secretKey32: Bytes32, publicKey32: Bytes32): Bytes32`
|
|
100
|
+
- `generateKeyPair(seed32: Bytes32): { public: Bytes32; private: Bytes32 }`
|
|
101
|
+
- `sign(secretKey32: Bytes32, msg: Uint8Array, opt_random?: Bytes64): Bytes64`
|
|
102
|
+
- `verify(publicKey32: Bytes32, msg: Uint8Array, signature64: Bytes64): boolean`
|
|
103
|
+
- `signMessage(secretKey32: Bytes32, msg: Uint8Array, opt_random?: Bytes64): Uint8Array`
|
|
104
|
+
- `openMessage(publicKey32: Bytes32, signedMsg: Uint8Array): Uint8Array | null`
|
|
105
|
+
|
|
106
|
+
### Top-level compatibility aliases
|
|
107
|
+
|
|
108
|
+
- `sharedKey = x25519.sharedKey`
|
|
109
|
+
- `generateKeyPair = x25519.generateKeyPair`
|
|
110
|
+
- `sign`, `verify`, `signMessage`, `openMessage` (Ed25519 semantics)
|
|
111
|
+
- `generateKeyPairX25519`, `generateKeyPairEd25519`
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Compatibility Notes
|
|
116
|
+
|
|
117
|
+
This package provides two modes:
|
|
118
|
+
|
|
119
|
+
- **modern (recommended):** `x25519` + `ed25519` via `node:crypto`
|
|
120
|
+
- **legacy:** `axlsign` via WASM for `curve25519-js` compatibility
|
|
121
|
+
|
|
122
|
+
| Feature | `curve25519-js` | `curve25519-node` |
|
|
123
|
+
| ---------------------------- | --------------- | ------------------------------------------- |
|
|
124
|
+
| Signature scheme (modern) | axlsign | Ed25519 (standard) |
|
|
125
|
+
| Signature scheme (legacy) | axlsign | axlsign (namespace `axlsign`) |
|
|
126
|
+
| Key agreement | X25519 | X25519 |
|
|
127
|
+
| Same key for signing + ECDH | yes | only in `axlsign` namespace |
|
|
128
|
+
| `opt_random` in signing APIs | yes | yes in `axlsign`, no in top-level/`ed25519` |
|
|
129
|
+
| OpenSSL backend | no | yes |
|
|
130
|
+
|
|
131
|
+
Important:
|
|
132
|
+
|
|
133
|
+
- X25519 public keys and Ed25519 public keys are different.
|
|
134
|
+
- `node:crypto` does not expose an API to convert X25519 public keys to/from Ed25519 public keys.
|
|
135
|
+
- Top-level `sign`/`signMessage` and `ed25519` keep Ed25519 semantics and reject `opt_random`.
|
|
136
|
+
- For `curve25519-js` compatibility (including `opt_random`), use namespace `axlsign`.
|
|
137
|
+
- Ed25519 signatures here are deterministic (OpenSSL default behavior).
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Why This Exists
|
|
142
|
+
|
|
143
|
+
`curve25519-js` is an important project, but it relies on manual finite-field arithmetic in JS (`Float64Array`, TweetNaCl style internals).
|
|
144
|
+
|
|
145
|
+
This package targets modern Node using OpenSSL primitives:
|
|
146
|
+
|
|
147
|
+
- safer implementation path by default
|
|
148
|
+
- better performance on Node >= 20
|
|
149
|
+
- smaller, explicit API surface
|
|
150
|
+
- strong typing with zero runtime dependencies
|
|
151
|
+
|
|
152
|
+
In addition, the WASM `axlsign` namespace enables progressive migration of legacy code without reintroducing manual curve arithmetic in JavaScript.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Branded Types
|
|
157
|
+
|
|
158
|
+
- `Bytes32`
|
|
159
|
+
- `Bytes64`
|
|
160
|
+
|
|
161
|
+
Validation helpers (no copy):
|
|
162
|
+
|
|
163
|
+
- `asBytes32(u8)`
|
|
164
|
+
- `asBytes64(u8)`
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## RFC Map (what this project uses)
|
|
169
|
+
|
|
170
|
+
| RFC | Sections used | How it is used | Where in code |
|
|
171
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | ----------------------- |
|
|
172
|
+
| RFC 7748 (X25519) | Section 5 (`The X25519 and X448 Functions`) | Scalar decoding/clamping and X25519 behavior (clear low 3 bits, clear top bit, set second-top bit). | `src/x25519.ts` |
|
|
173
|
+
| RFC 7748 (X25519) | Section 5.2 (`Test Vectors`), Section 6.1 (`Diffie-Hellman / Curve25519`) | Official vectors for interoperability and correctness checks. | `test/x25519.test.mjs` |
|
|
174
|
+
| RFC 8032 (Ed25519) | Section 5.1.5 (`Key Generation`), 5.1.6 (`Sign`), 5.1.7 (`Verify`) | Ed25519 keygen/sign/verify semantics (performed by OpenSSL via `node:crypto`). | `src/ed25519.ts` |
|
|
175
|
+
| RFC 8032 (Ed25519) | Section 7.1 (`Test Vectors for Ed25519`) | Deterministic vector checks for public key and signature correctness. | `test/ed25519.test.mjs` |
|
|
176
|
+
| RFC 8410 (X25519/Ed25519 in PKIX) | Section 3 (algorithm identifiers), Section 4 (`Subject Public Key Fields`), Section 7 (`Private Key Format`) | DER layout for raw 32-byte key import/export to SPKI/PKCS#8 with X25519/Ed25519 OIDs. | `src/internal/der.ts` |
|
|
177
|
+
|
|
178
|
+
Indirect references via RFC 8410 structures:
|
|
179
|
+
|
|
180
|
+
- RFC 5958 (OneAsymmetricKey / PKCS#8 family)
|
|
181
|
+
- RFC 5280 Section 4.1.2.7 (`Subject Public Key Info`)
|
|
182
|
+
|
|
183
|
+
Notes:
|
|
184
|
+
|
|
185
|
+
- This project does not reimplement curve arithmetic in JS; cryptographic operations are delegated to OpenSSL via `node:crypto`.
|
|
186
|
+
- Tests include official vectors from RFC 7748 and RFC 8032.
|
|
187
|
+
|
|
188
|
+
Run tests:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
npm test
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Technical Details (DER / RFC 8410)
|
|
197
|
+
|
|
198
|
+
Raw 32-byte keys are imported/exported using fixed DER prefixes:
|
|
199
|
+
|
|
200
|
+
- X25519 PKCS#8: `302e020100300506032b656e04220420`
|
|
201
|
+
- X25519 SPKI: `302a300506032b656e032100`
|
|
202
|
+
- Ed25519 PKCS#8: `302e020100300506032b657004220420`
|
|
203
|
+
- Ed25519 SPKI: `302a300506032b6570032100`
|
|
204
|
+
|
|
205
|
+
Implementation notes:
|
|
206
|
+
|
|
207
|
+
- preallocated buffers + `.set`
|
|
208
|
+
- zero-copy `Uint8Array` views when safe
|
|
209
|
+
- no `Buffer.concat` in hot paths
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Performance Notes
|
|
214
|
+
|
|
215
|
+
- Avoids unnecessary byte copies in critical paths.
|
|
216
|
+
- `signMessage` builds `signature || message` with a single preallocated `Uint8Array`.
|
|
217
|
+
- For high-throughput loops, caching `KeyObject` at application level reduces ASN.1 parse overhead.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Security Notes
|
|
222
|
+
|
|
223
|
+
- strict type/length validation on public APIs
|
|
224
|
+
- no secret logging
|
|
225
|
+
- `timingSafeEqual` for internal fixed-size comparisons where needed
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Benchmarks
|
|
230
|
+
|
|
231
|
+
Benchmark suite is isolated in `bench/` (separate subproject) and compares against `curve25519-js`.
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npm run build
|
|
235
|
+
cd bench
|
|
236
|
+
npm install
|
|
237
|
+
npm run bench
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Real benchmark snapshot (`npm run bench:ci`) on GitHub Codespaces
|
|
241
|
+
|
|
242
|
+
Command:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
node --expose-gc bench.mjs --rounds=16 --roundMs=350 --warmupMs=500 --vectors=64 --variants=raw,cached --strict --verifyEvery=64 --jsonFile=results/bench-results.json
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Environment:
|
|
249
|
+
|
|
250
|
+
- Node: `v24.11.1`
|
|
251
|
+
- OpenSSL: `3.5.4`
|
|
252
|
+
- CPU: `AMD EPYC 7763 64-Core Processor`
|
|
253
|
+
- Logical cores: `4`
|
|
254
|
+
- Vectors: `64`
|
|
255
|
+
|
|
256
|
+
### Table 1 - Modern API (`x25519` + `ed25519`)
|
|
257
|
+
|
|
258
|
+
`sign`/`verify` rows below compare API throughput, not cryptographic equivalence (Ed25519 vs legacy axlsign).
|
|
259
|
+
|
|
260
|
+
| Operation | Modern raw | Legacy raw (`curve25519-js`) | Raw speedup | Modern cached | Legacy cached (`curve25519-js`) | Cached speedup |
|
|
261
|
+
| ------------------------------ | ---------: | ---------------------------: | ----------: | ------------: | ------------------------------: | -------------: |
|
|
262
|
+
| `x25519.generateKeyPair` | 14,378 | 1,591 | 9.04x | 41,120 | 1,478 | 27.83x |
|
|
263
|
+
| `x25519.sharedKey` | 9,970 | 1,591 | 6.27x | 23,995 | 1,554 | 15.44x |
|
|
264
|
+
| `ed25519.sign (msg32)` | 11,273 | 143 | 78.95x | 23,696 | 133 | 178.10x |
|
|
265
|
+
| `ed25519.sign (msg1024)` | 10,800 | 138 | 78.07x | 22,502 | 147 | 152.92x |
|
|
266
|
+
| `ed25519.verify (msg32)` | 7,280 | 136 | 53.36x | 8,271 | 155 | 53.37x |
|
|
267
|
+
| `ed25519.verify (msg1024)` | 7,160 | 132 | 54.33x | 8,159 | 154 | 52.90x |
|
|
268
|
+
| `ed25519.signMessage (msg256)` | 10,624 | 131 | 81.09x | 23,304 | 148 | 156.97x |
|
|
269
|
+
| `ed25519.openMessage (msg256)` | 6,574 | 124 | 52.93x | 8,129 | 154 | 52.64x |
|
|
270
|
+
|
|
271
|
+
### Table 2 - `axlsign` compatibility mode (equivalent to `curve25519-js`)
|
|
272
|
+
|
|
273
|
+
This table compares the same cryptographic scheme (equivalence + throughput).
|
|
274
|
+
|
|
275
|
+
| Operation | Modern raw | Legacy raw (`curve25519-js`) | Raw speedup | Modern cached | Legacy cached (`curve25519-js`) | Cached speedup |
|
|
276
|
+
| ----------------------------------------- | ---------: | ---------------------------: | ----------: | ------------: | ------------------------------: | -------------: |
|
|
277
|
+
| `axlsign.generateKeyPair` | 8,429 | 1,583 | 5.33x | 8,384 | 1,585 | 5.29x |
|
|
278
|
+
| `axlsign.sharedKey` | 8,452 | 1,583 | 5.34x | 8,396 | 1,570 | 5.35x |
|
|
279
|
+
| `axlsign.sign (msg32)` | 3,973 | 144 | 27.61x | 3,952 | 140 | 28.28x |
|
|
280
|
+
| `axlsign.sign (msg32,opt_random)` | 3,969 | 147 | 27.03x | 3,984 | 139 | 28.58x |
|
|
281
|
+
| `axlsign.sign (msg1024)` | 3,881 | 143 | 27.16x | 3,864 | 139 | 27.72x |
|
|
282
|
+
| `axlsign.verify (msg32)` | 6,527 | 146 | 44.70x | 6,534 | 143 | 45.72x |
|
|
283
|
+
| `axlsign.verify (msg32,opt_random)` | 6,506 | 144 | 45.07x | 6,469 | 141 | 45.80x |
|
|
284
|
+
| `axlsign.verify (msg1024)` | 6,361 | 141 | 45.03x | 6,337 | 135 | 46.92x |
|
|
285
|
+
| `axlsign.signMessage (msg256)` | 3,902 | 140 | 27.79x | 3,935 | 141 | 27.98x |
|
|
286
|
+
| `axlsign.signMessage (msg256,opt_random)` | 3,885 | 142 | 27.40x | 3,864 | 145 | 26.60x |
|
|
287
|
+
| `axlsign.openMessage (msg256)` | 6,441 | 138 | 46.57x | 6,300 | 131 | 47.93x |
|
|
288
|
+
| `axlsign.openMessage (msg256,opt_random)` | 6,362 | 141 | 45.24x | 6,285 | 130 | 48.22x |
|
|
289
|
+
|
|
290
|
+
Notes:
|
|
291
|
+
|
|
292
|
+
- `raw` includes end-to-end API cost.
|
|
293
|
+
- `cached` reduces setup overhead to better expose cryptographic throughput.
|
|
294
|
+
- Numbers are sourced from the `bench:ci` JSON output (`results/bench-results.json`).
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Building `axlsign`
|
|
299
|
+
|
|
300
|
+
In the npm package, WASM artifacts are already prebuilt under `dist/`.
|
|
301
|
+
|
|
302
|
+
To build from source, you need:
|
|
303
|
+
|
|
304
|
+
- Rust toolchain
|
|
305
|
+
- `wasm-pack` installed
|
|
306
|
+
|
|
307
|
+
Then `npm run build` runs:
|
|
308
|
+
|
|
309
|
+
1. `wasm-pack build` (`wasm/axlsign`)
|
|
310
|
+
2. TypeScript ESM + CJS build
|
|
311
|
+
3. copy of WASM artifacts to `dist/internal/axlsign-wasm`
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
MIT
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Credits
|
|
322
|
+
|
|
323
|
+
- [curve25519-js](https://github.com/harveyconnor/curve25519-js) (Harvey Connor, Dmitry Chestnykh)
|
|
324
|
+
- [TweetNaCl.js](https://tweetnacl.js.org/)
|
|
325
|
+
- Trevor Perrin, Curve25519 signatures idea: <https://moderncrypto.org/mail-archive/curves/2014/000205.html>
|
|
326
|
+
- [Node.js `crypto` docs](https://nodejs.org/api/crypto.html)
|
|
327
|
+
- [OpenSSL](https://www.openssl.org/)
|
|
328
|
+
- [RFC 7748](https://www.rfc-editor.org/rfc/rfc7748)
|
|
329
|
+
- [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032)
|
|
330
|
+
- [RFC 8410](https://www.rfc-editor.org/rfc/rfc8410)
|
|
331
|
+
- [RFC 5958](https://www.rfc-editor.org/rfc/rfc5958)
|
|
332
|
+
- [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280)
|