@kokuin/crypto-napi 0.0.0-stage → 0.7.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Paul Le Cam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,62 @@
1
- # Temporary Holding Version
1
+ # @kokuin/crypto-napi
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Strict asynchronous crypto provider for Node.js, backed by the kokuin Rust core (`crates/kokuin-crypto`) through Node-API.
4
+ It implements the `@kokuin/crypto` `CryptoProvider` contract and falls back to `@kokuin/crypto-node` when no native binary loads.
5
+
6
+ ```ts
7
+ import { createNapiCrypto } from '@kokuin/crypto-napi'
8
+
9
+ const crypto = createNapiCrypto()
10
+ crypto.backend // 'napi', or 'node' after a fallback; `crypto.failure` then says why
11
+ const signature = await crypto.sign({ alg: 'EdDSA', privateKey: seed, message })
12
+ ```
13
+
14
+ | Call | Route |
15
+ | --- | --- |
16
+ | Ed25519 `sign`, Ed25519 `verify`, `blake3` | Native, on the calling thread |
17
+ | ES256 `verify` and ES256 items of `verifyBatch` | `nodeCrypto` (OpenSSL), about three times faster than the Rust P-256 verifier |
18
+ | `verifyBatch` with two or more Ed25519 items | Native on the libuv thread pool; inputs are copied before the Promise returns |
19
+ | One-item `verifyBatch`, `signBatch` up to 32 items, `blake3Batch` up to 1 MiB | Native, on the calling thread, where the pool hop costs more than it saves |
20
+ | Larger `signBatch` and `blake3Batch` | Native on the libuv thread pool |
21
+
22
+ The routes come from the [Node crypto benchmark gate](../../docs/reference/crypto-node-benchmark.md).
23
+
24
+ Measured through the provider on one x86_64 container with Node 22, against strict noble:
25
+ Ed25519 signing is about 18× faster, Ed25519 verification about 25×, and blake3 on 64 KiB about 110×.
26
+ A batch of 32 Ed25519 verifications costs about 113 µs per item and keeps the event loop free.
27
+
28
+ The backend is chosen once, when `createNapiCrypto()` runs. A missing binary, a binary built for another ABI version
29
+ or one that fails to load selects `nodeCrypto`. A native failure after construction rejects; it never falls back.
30
+
31
+ Arguments are validated in JavaScript exactly as `nobleCrypto` validates them, so every backend raises the same errors.
32
+ Every backend accepts and rejects exactly the same signatures: the shared corpus and the pinned upstream vectors run
33
+ against the Rust core, strict noble, `nodeCrypto` and this provider.
34
+
35
+ Prebuilt binaries are not published yet. In this repository, `pnpm --filter @kokuin/crypto-napi run build:native`
36
+ compiles one for the current host into `native/`. Requires Node.js 22 or later.
37
+
38
+ ## Named params migration
39
+
40
+ `CryptoProvider.verify` now accepts one `VerifyItem`: `{ alg, signature, message, publicKey }`.
41
+ `sign`, `signBatch`, `blake3` and `blake3Batch` take flat params objects.
42
+ `verifyBatch` retains its single array argument.
43
+
44
+ | Export | Named params type | Fields |
45
+ | --- | --- | --- |
46
+ | `NapiBinding.sign` | `NapiBindingSignParams` | `seed, message` |
47
+ | `NapiBinding.signBatch` | `Array<NapiBindingSignParams>` | `seeds, messages` |
48
+ | `NapiBinding.blake3` | `NapiBindingBlake3Params` | `input, length` |
49
+ | `NapiBinding.blake3Batch` | `NapiBindingBlake3BatchParams` | `inputs, length` |
50
+ | `NapiBinding.verify` | `NapiVerifyParams` | `es256, signature, message, publicKey` |
51
+ | `NapiBinding.verifyBatch` | `Array<NapiVerifyParams>` | `es256, signatures, messages, publicKeys` |
52
+
53
+ `NapiBinding` accepts named signing, hashing and verification objects. The loader adapts them to the unchanged positional native ABI.
54
+
55
+ These signature and typing changes preserve cryptographic results, error causes, ordering and storage behaviour.
56
+
57
+ | API | Named params type | Previous positional form |
58
+ | --- | --- | --- |
59
+ | `CryptoProvider.sign` | `CryptoProviderSignParams` | `sign(alg, privateKey, message)` |
60
+ | `CryptoProvider.signBatch` | `CryptoProviderSignBatchParams` | `signBatch(alg, items)` |
61
+ | `CryptoProvider.blake3` | `CryptoProviderBlake3Params` | `blake3(input, dkLen)` |
62
+ | `CryptoProvider.blake3Batch` | `CryptoProviderBlake3BatchParams` | `blake3Batch(inputs, dkLen)` |
@@ -0,0 +1,44 @@
1
+ export type NapiVerifyParams = {
2
+ es256: boolean;
3
+ signature: Uint8Array;
4
+ message: Uint8Array;
5
+ publicKey: Uint8Array;
6
+ };
7
+ /** The ABI the provider expects from `crates/kokuin-crypto-napi` (its `ABI_VERSION`). */
8
+ export declare const NAPI_ABI_VERSION = 1;
9
+ export type NapiBindingSignParams = {
10
+ seed: Uint8Array;
11
+ message: Uint8Array;
12
+ };
13
+ export type NapiBindingBlake3Params = {
14
+ input: Uint8Array;
15
+ length: number;
16
+ };
17
+ export type NapiBindingBlake3BatchParams = {
18
+ inputs: Array<Uint8Array>;
19
+ length: number;
20
+ };
21
+ export type NapiBinding = {
22
+ ABI_VERSION: number;
23
+ sign(params: NapiBindingSignParams): Uint8Array;
24
+ verify(params: NapiVerifyParams): boolean;
25
+ blake3(params: NapiBindingBlake3Params): Uint8Array;
26
+ signBatch(items: Array<NapiBindingSignParams>): Promise<Array<Uint8Array>>;
27
+ verifyBatch(items: Array<NapiVerifyParams>): Promise<Array<boolean>>;
28
+ blake3Batch(params: NapiBindingBlake3BatchParams): Promise<Array<Uint8Array>>;
29
+ };
30
+ /** Why no native binding loaded. `path` is absent when the binary's location did not resolve. */
31
+ export type NapiLoadFailure = {
32
+ reason: 'missing' | 'abi' | 'load';
33
+ path?: string;
34
+ cause?: unknown;
35
+ };
36
+ /** The platform suffix of the binary file name, such as `linux-x64-gnu` or `darwin-arm64`. */
37
+ export declare function napiTarget(): string;
38
+ /** Where the binary for this platform is expected: `native/kokuin-crypto-napi.<target>.node`. */
39
+ export declare function napiBindingPath(): string;
40
+ /** Load the native binding once per process. Never throws; reports why it could not load. */
41
+ export declare function loadNapiBinding(): {
42
+ binding: NapiBinding | null;
43
+ failure?: NapiLoadFailure;
44
+ };
package/lib/binding.js ADDED
@@ -0,0 +1,85 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ /** The ABI the provider expects from `crates/kokuin-crypto-napi` (its `ABI_VERSION`). */ export const NAPI_ABI_VERSION = 1;
6
+ function adaptBinding(native) {
7
+ return {
8
+ ABI_VERSION: native.ABI_VERSION,
9
+ sign: ({ seed, message })=>native.sign(seed, message),
10
+ // The native ABI takes parallel arrays. Split here so nothing above it can misalign entries.
11
+ signBatch: (items)=>{
12
+ return native.signBatch(items.map((item)=>item.seed), items.map((item)=>item.message));
13
+ },
14
+ blake3: ({ input, length })=>native.blake3(input, length),
15
+ blake3Batch: ({ inputs, length })=>native.blake3Batch(inputs, length),
16
+ verify: ({ es256, signature, message, publicKey })=>{
17
+ return native.verify(es256, signature, message, publicKey);
18
+ },
19
+ verifyBatch: (items)=>{
20
+ return native.verifyBatch(items.map((item)=>item.es256), items.map((item)=>item.signature), items.map((item)=>item.message), items.map((item)=>item.publicKey));
21
+ }
22
+ };
23
+ }
24
+ function libc() {
25
+ const header = process.report?.getReport()?.header;
26
+ return header?.glibcVersionRuntime ? 'gnu' : 'musl';
27
+ }
28
+ /** The platform suffix of the binary file name, such as `linux-x64-gnu` or `darwin-arm64`. */ export function napiTarget() {
29
+ const base = `${process.platform}-${process.arch}`;
30
+ return process.platform === 'linux' ? `${base}-${libc()}` : base;
31
+ }
32
+ /** Where the binary for this platform is expected: `native/kokuin-crypto-napi.<target>.node`. */ export function napiBindingPath() {
33
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..');
34
+ return join(root, 'native', `kokuin-crypto-napi.${napiTarget()}.node`);
35
+ }
36
+ let cached;
37
+ /** Load the native binding once per process. Never throws; reports why it could not load. */ export function loadNapiBinding() {
38
+ if (cached != null) return cached;
39
+ let path;
40
+ try {
41
+ path = napiBindingPath();
42
+ } catch (cause) {
43
+ // A CJS bundle leaves `import.meta.url` undefined.
44
+ cached = {
45
+ binding: null,
46
+ failure: {
47
+ reason: 'load',
48
+ cause
49
+ }
50
+ };
51
+ return cached;
52
+ }
53
+ if (!existsSync(path)) {
54
+ cached = {
55
+ binding: null,
56
+ failure: {
57
+ reason: 'missing',
58
+ path
59
+ }
60
+ };
61
+ return cached;
62
+ }
63
+ try {
64
+ const binding = createRequire(import.meta.url)(path);
65
+ cached = binding.ABI_VERSION === NAPI_ABI_VERSION ? {
66
+ binding: adaptBinding(binding)
67
+ } : {
68
+ binding: null,
69
+ failure: {
70
+ reason: 'abi',
71
+ path
72
+ }
73
+ };
74
+ } catch (cause) {
75
+ cached = {
76
+ binding: null,
77
+ failure: {
78
+ reason: 'load',
79
+ path,
80
+ cause
81
+ }
82
+ };
83
+ }
84
+ return cached;
85
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export type { NapiBinding, NapiBindingBlake3BatchParams, NapiBindingBlake3Params, NapiBindingSignParams, NapiLoadFailure, NapiVerifyParams, } from './binding.js';
2
+ export { loadNapiBinding, NAPI_ABI_VERSION, napiBindingPath, napiTarget } from './binding.js';
3
+ export type { NapiCrypto, NapiCryptoOptions } from './provider.js';
4
+ export { BLAKE3_SYNC_BATCH_BYTES, createNapiCrypto, SIGN_SYNC_BATCH_ITEMS } from './provider.js';
package/lib/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { loadNapiBinding, NAPI_ABI_VERSION, napiBindingPath, napiTarget } from './binding.js';
2
+ export { BLAKE3_SYNC_BATCH_BYTES, createNapiCrypto, SIGN_SYNC_BATCH_ITEMS } from './provider.js';
@@ -0,0 +1,30 @@
1
+ import type { CryptoProvider } from '@kokuin/crypto';
2
+ import { type NapiBinding, type NapiLoadFailure } from './binding.js';
3
+ export type NapiCrypto = CryptoProvider & {
4
+ /** `napi` when the Rust binding loaded, `node` when the provider fell back to `nodeCrypto`. */
5
+ readonly backend: 'napi' | 'node';
6
+ /** Why the binding did not load, when `backend` is `node`. */
7
+ readonly failure?: NapiLoadFailure;
8
+ };
9
+ export type NapiCryptoOptions = {
10
+ /** Use this binding instead of loading one. `null` forces the `nodeCrypto` fallback. */
11
+ binding?: NapiBinding | null;
12
+ };
13
+ /**
14
+ * blake3 batches with at most this much input in total hash on the calling thread. Below it the
15
+ * thread-pool hop and input copies cost more than the hashing (1 MiB hashes in about 135 µs on
16
+ * x86_64); above it, the pool keeps a large hash off the event loop.
17
+ */
18
+ export declare const BLAKE3_SYNC_BATCH_BYTES: number;
19
+ /**
20
+ * Ed25519 signing batches of at most this many items sign on the calling thread: about 27 µs per
21
+ * signature at 256 B and 61 µs at 12 KiB on x86_64, against a thread-pool hop that measured
22
+ * slower for every batch the gate covers. Larger batches go to the pool.
23
+ */
24
+ export declare const SIGN_SYNC_BATCH_ITEMS = 32;
25
+ /**
26
+ * The Rust core through Node-API when this platform's binary loads, otherwise `nodeCrypto`.
27
+ * The choice is made once, here; a native failure after construction rejects instead of
28
+ * falling back.
29
+ */
30
+ export declare function createNapiCrypto(options?: NapiCryptoOptions): NapiCrypto;
@@ -0,0 +1,198 @@
1
+ import { nodeCrypto } from '@kokuin/crypto-node';
2
+ import { loadNapiBinding } from './binding.js';
3
+ function assertBytes(value) {
4
+ if (!(value instanceof Uint8Array) || !(value.buffer instanceof ArrayBuffer)) {
5
+ throw new TypeError('Expected an unshared Uint8Array');
6
+ }
7
+ // Throws TypeError on a detached buffer.
8
+ new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
9
+ }
10
+ function assertSigning(alg, seed, message) {
11
+ if (alg !== 'EdDSA') throw new TypeError('Unsupported signing algorithm');
12
+ assertBytes(seed);
13
+ assertBytes(message);
14
+ if (seed.length !== 32) throw new TypeError('Expected a 32-byte Ed25519 seed');
15
+ }
16
+ function assertVerifying({ alg, signature, message, publicKey }) {
17
+ if (alg !== 'EdDSA' && alg !== 'ES256') throw new TypeError('Unsupported verification algorithm');
18
+ for (const bytes of [
19
+ signature,
20
+ message,
21
+ publicKey
22
+ ])assertBytes(bytes);
23
+ }
24
+ function assertOutputLength(dkLen) {
25
+ if (!Number.isInteger(dkLen) || dkLen < 1 || dkLen > 65536) throw new RangeError('Invalid blake3 output length');
26
+ }
27
+ /**
28
+ * blake3 batches with at most this much input in total hash on the calling thread. Below it the
29
+ * thread-pool hop and input copies cost more than the hashing (1 MiB hashes in about 135 µs on
30
+ * x86_64); above it, the pool keeps a large hash off the event loop.
31
+ */ export const BLAKE3_SYNC_BATCH_BYTES = 1024 * 1024;
32
+ /**
33
+ * Ed25519 signing batches of at most this many items sign on the calling thread: about 27 µs per
34
+ * signature at 256 B and 61 µs at 12 KiB on x86_64, against a thread-pool hop that measured
35
+ * slower for every batch the gate covers. Larger batches go to the pool.
36
+ */ export const SIGN_SYNC_BATCH_ITEMS = 32;
37
+ function assertResultCount(results, count) {
38
+ if (results.length !== count) throw new Error('Invalid native crypto response count');
39
+ return results;
40
+ }
41
+ /**
42
+ * Routes, set by the Node crypto benchmark gate (scripts/bench-crypto-node.mjs):
43
+ * - Ed25519 sign, Ed25519 verify and blake3 single calls run natively on the calling thread.
44
+ * - ES256 verification, single or batched, uses `nodeCrypto`: OpenSSL is about three times
45
+ * faster than the Rust P-256 verifier.
46
+ * - Ed25519 verify batches of two or more copy their inputs on the calling thread, then run
47
+ * natively on the libuv pool, keeping about 100 µs per item off the event loop. A one-item
48
+ * batch runs on the calling thread, where it is faster than the pool hop.
49
+ * - Ed25519 sign batches run natively on the calling thread up to `SIGN_SYNC_BATCH_ITEMS` items
50
+ * and on the pool above it.
51
+ * - blake3 batches run natively on the calling thread up to `BLAKE3_SYNC_BATCH_BYTES` of input
52
+ * and on the pool above it.
53
+ *
54
+ * Arguments are validated in JavaScript exactly as `nobleCrypto` validates them, so every
55
+ * backend raises the same error types. Every method consumes its inputs before its Promise
56
+ * returns.
57
+ */ function napiProvider(binding) {
58
+ return {
59
+ async sign ({ alg, privateKey: seed, message }) {
60
+ assertSigning(alg, seed, message);
61
+ return binding.sign({
62
+ seed,
63
+ message
64
+ });
65
+ },
66
+ async signBatch ({ alg, items }) {
67
+ if (alg !== 'EdDSA') throw new TypeError('Unsupported signing algorithm');
68
+ for (const item of items)assertSigning(alg, item.privateKey, item.message);
69
+ if (items.length === 0) return [];
70
+ // Signing is cheap enough that the pool hop costs more than it saves for ordinary batches,
71
+ // and the calling-thread route still takes half nodeCrypto's main-thread time.
72
+ if (items.length <= SIGN_SYNC_BATCH_ITEMS) {
73
+ return items.map((item)=>binding.sign({
74
+ seed: item.privateKey,
75
+ message: item.message
76
+ }));
77
+ }
78
+ // Capture the count before suspending: the caller may change the array afterwards.
79
+ const count = items.length;
80
+ const results = await binding.signBatch(items.map((item)=>({
81
+ seed: item.privateKey,
82
+ message: item.message
83
+ })));
84
+ return assertResultCount(results, count);
85
+ },
86
+ async verify ({ alg, signature, message, publicKey }) {
87
+ assertVerifying({
88
+ alg,
89
+ signature,
90
+ message,
91
+ publicKey
92
+ });
93
+ if (alg === 'ES256') return await nodeCrypto.verify({
94
+ alg,
95
+ signature,
96
+ message,
97
+ publicKey
98
+ });
99
+ return binding.verify({
100
+ es256: false,
101
+ signature,
102
+ message,
103
+ publicKey
104
+ });
105
+ },
106
+ async verifyBatch (items) {
107
+ for (const item of items){
108
+ assertVerifying({
109
+ alg: item.alg,
110
+ signature: item.signature,
111
+ message: item.message,
112
+ publicKey: item.publicKey
113
+ });
114
+ }
115
+ // Capture membership before suspending: the caller may change the array afterwards.
116
+ const algs = items.map((item)=>item.alg);
117
+ const ed25519 = items.filter((item)=>item.alg === 'EdDSA');
118
+ // Start both halves before awaiting either: nodeCrypto consumes its inputs synchronously
119
+ // and the native batch copies them, so caller mutations afterwards change nothing.
120
+ const es256 = items.filter((item)=>item.alg === 'ES256').map((item)=>{
121
+ return nodeCrypto.verify({
122
+ alg: 'ES256',
123
+ signature: item.signature,
124
+ message: item.message,
125
+ publicKey: item.publicKey
126
+ });
127
+ });
128
+ const native = ed25519.length === 0 ? Promise.resolve([]) : ed25519.length === 1 ? Promise.resolve([
129
+ binding.verify({
130
+ es256: false,
131
+ signature: ed25519[0].signature,
132
+ message: ed25519[0].message,
133
+ publicKey: ed25519[0].publicKey
134
+ })
135
+ ]) : binding.verifyBatch(ed25519.map((item)=>({
136
+ es256: false,
137
+ signature: item.signature,
138
+ message: item.message,
139
+ publicKey: item.publicKey
140
+ }))).then((results)=>assertResultCount(results, ed25519.length));
141
+ const [edResults, esResults] = await Promise.all([
142
+ native,
143
+ Promise.all(es256)
144
+ ]);
145
+ let ed = 0;
146
+ let es = 0;
147
+ return algs.map((alg)=>{
148
+ return alg === 'EdDSA' ? edResults[ed++] : esResults[es++];
149
+ });
150
+ },
151
+ async blake3 ({ input, dkLen }) {
152
+ assertBytes(input);
153
+ assertOutputLength(dkLen);
154
+ return binding.blake3({
155
+ input,
156
+ length: dkLen
157
+ });
158
+ },
159
+ async blake3Batch ({ inputs, dkLen }) {
160
+ assertOutputLength(dkLen);
161
+ for (const input of inputs)assertBytes(input);
162
+ if (inputs.length === 0) return [];
163
+ const total = inputs.reduce((sum, input)=>sum + input.length, 0);
164
+ if (total <= BLAKE3_SYNC_BATCH_BYTES) return inputs.map((input)=>binding.blake3({
165
+ input,
166
+ length: dkLen
167
+ }));
168
+ const count = inputs.length;
169
+ return assertResultCount(await binding.blake3Batch({
170
+ inputs,
171
+ length: dkLen
172
+ }), count);
173
+ }
174
+ };
175
+ }
176
+ /**
177
+ * The Rust core through Node-API when this platform's binary loads, otherwise `nodeCrypto`.
178
+ * The choice is made once, here; a native failure after construction rejects instead of
179
+ * falling back.
180
+ */ export function createNapiCrypto(options = {}) {
181
+ const loaded = options.binding !== undefined ? {
182
+ binding: options.binding
183
+ } : loadNapiBinding();
184
+ if (loaded.binding == null) {
185
+ const failure = 'failure' in loaded ? loaded.failure : undefined;
186
+ return {
187
+ ...nodeCrypto,
188
+ backend: 'node',
189
+ ...failure ? {
190
+ failure
191
+ } : {}
192
+ };
193
+ }
194
+ return {
195
+ ...napiProvider(loaded.binding),
196
+ backend: 'napi'
197
+ };
198
+ }
package/package.json CHANGED
@@ -1,6 +1,49 @@
1
1
  {
2
2
  "name": "@kokuin/crypto-napi",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.7.1",
4
+ "description": "Strict asynchronous crypto provider backed by the Rust core through Node-API, falling back to @kokuin/crypto-node",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/TairuFramework/kokuin",
8
+ "directory": "packages/crypto-napi"
9
+ },
10
+ "license": "MIT",
11
+ "sideEffects": false,
12
+ "type": "module",
13
+ "exports": {
14
+ ".": "./lib/index.js"
15
+ },
16
+ "main": "lib/index.js",
17
+ "types": "lib/index.d.ts",
18
+ "files": [
19
+ "lib/*",
20
+ "LICENSE"
21
+ ],
22
+ "dependencies": {
23
+ "@kokuin/crypto": "^0.7.0",
24
+ "@kokuin/crypto-node": "^0.7.0"
25
+ },
26
+ "devDependencies": {
27
+ "@kokuin/crypto-conformance": "^0.0.0",
28
+ "@noble/curves": "^2.4.0",
29
+ "@noble/hashes": "^2.4.0",
30
+ "@types/node": "^26.6.6"
31
+ },
32
+ "engines": {
33
+ "node": ">=22"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
40
+ "build:clean": "del-cli lib",
41
+ "build:js": "del-cli 'lib/**/*.js' && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
42
+ "build:native": "node ../../scripts/build-crypto-napi.mjs",
43
+ "build:types": "del-cli 'lib/**/*.d.ts' 'lib/**/*.d.ts.map' && tsc --emitDeclarationOnly --skipLibCheck",
44
+ "test": "pnpm run test:types && pnpm run test:unit",
45
+ "test:native": "vitest run test/native.test.ts",
46
+ "test:types": "tsc --noEmit --skipLibCheck -p tsconfig.test.json",
47
+ "test:unit": "vitest run --exclude test/native.test.ts"
48
+ }
6
49
  }