castrum 0.9.0 → 0.9.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/README.md +153 -630
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +1374 -1772
- package/dist/index.js +5570 -4169
- package/docs/API.md +337 -0
- package/docs/ARCHITECTURE.md +106 -14
- package/docs/BENCHMARKS.md +83 -47
- package/docs/CASE_STUDY.md +240 -0
- package/docs/ENVIRONMENT.md +8 -6
- package/docs/FFI_BUN_GUIDE.md +327 -0
- package/docs/GETTING_STARTED.md +15 -32
- package/docs/INGRESS-ROUTER.md +81 -0
- package/docs/INGRESS.md +5 -4
- package/docs/NATIVE-ROUTE.md +53 -0
- package/docs/REPO_MAP.md +46 -26
- package/docs/adr/0003-bun-builtins-delegation.md +6 -5
- package/docs/bun-builtins-decision-matrix.md +25 -18
- package/index.ts +9 -16
- package/package.json +36 -24
- package/src/ingress/body.ts +167 -27
- package/src/ingress/handlers.ts +91 -15
- package/src/ingress/health.ts +1 -0
- package/src/ingress/index.ts +39 -27
- package/src/ingress/packing/gather-raw-headers.ts +85 -21
- package/src/ingress/packing/header-packing.ts +5 -5
- package/src/ingress/packing/input-packer.ts +17 -3
- package/src/ingress/packing/select-headers.ts +12 -6
- package/src/ingress/router.ts +237 -0
- package/src/ingress/routes/common.ts +42 -1
- package/src/ingress/routes/fallback.ts +2 -2
- package/src/ingress/routes/head.ts +3 -13
- package/src/ingress/routes/index.ts +5 -4
- package/src/ingress/routes/json-write.ts +7 -31
- package/src/ingress/routes/options.ts +2 -12
- package/src/ingress/routes/read.ts +3 -13
- package/src/ingress/routes/responder.ts +159 -0
- package/src/ingress/server-node.ts +6 -6
- package/src/ingress/server.ts +45 -13
- package/src/ingress/types.ts +35 -1
- package/src/integration/pipeline.ts +3 -0
- package/src/integration/streaming.ts +2 -3
- package/src/integration/websocket.ts +4 -3
- package/src/loader/batch.ts +4 -0
- package/src/loader/cost.ts +1 -0
- package/src/loader/index.ts +43 -228
- package/src/loader/state.ts +84 -0
- package/src/loader/types.ts +145 -0
- package/src/native/ffi/constants.ts +58 -0
- package/src/native/ffi/selftest.ts +729 -0
- package/src/native/ffi/types.ts +474 -0
- package/src/native/ffi.ts +1063 -829
- package/src/native/loader.ts +52 -6
- package/src/native/types/instances.ts +274 -0
- package/src/native/types.ts +41 -238
- package/src/runtime/builtins.ts +131 -0
- package/src/runtime/codec.ts +147 -0
- package/src/runtime/detect.ts +36 -0
- package/src/runtime/index.ts +28 -0
- package/src/runtime/native.ts +35 -0
- package/src/runtime/server.ts +31 -0
- package/src/runtime/transport.ts +64 -0
- package/src/runtime/types.ts +173 -0
- package/src/runtime/uuid.ts +45 -0
- package/src/runtime/websocket.ts +30 -0
- package/src/rust-ffi/addon.ts +1 -1
- package/src/rust-ffi/batch/build.ts +155 -0
- package/src/rust-ffi/batch/index.ts +8 -0
- package/src/rust-ffi/batch/types.ts +92 -0
- package/src/rust-ffi/client.ts +5 -6
- package/src/rust-ffi/context.ts +39 -10
- package/src/rust-ffi/index.ts +28 -17
- package/src/rust-ffi/into.ts +21 -0
- package/src/rust-ffi/options.ts +1 -1
- package/src/rust-ffi/packed.ts +2 -2
- package/src/rust-ffi/proven.ts +16 -30
- package/src/rust-ffi/scalar/crypto.ts +188 -107
- package/src/rust-ffi/scalar/factories.ts +253 -27
- package/src/rust-ffi/scalar/hashing.ts +43 -41
- package/src/rust-ffi/scalar/http.ts +208 -77
- package/src/rust-ffi/scalar/index.ts +3 -3
- package/src/rust-ffi/scalar/interface.ts +88 -213
- package/src/rust-ffi/scalar/json-packed.ts +175 -0
- package/src/rust-ffi/scalar/json.ts +28 -12
- package/src/rust-ffi/scalar/payload.ts +159 -42
- package/src/rust-ffi/text.ts +31 -5
- package/src/selection.ts +27 -31
- package/src/shared/adaptive.ts +1 -2
- package/src/shared/buffer-pool.ts +72 -17
- package/src/shared/bytes.ts +75 -6
- package/src/shared/codec.ts +78 -0
- package/src/shared/env.ts +4 -4
- package/src/shared/log.ts +1 -0
- package/src/shared/packed/index.ts +10 -0
- package/src/shared/packed/parsers.ts +31 -0
- package/src/shared/packed/schema.ts +23 -0
- package/src/shared/{packed.ts → packed/wire.ts} +31 -39
- package/src/shared/proven.ts +189 -496
- package/src/shared/runtime.ts +20 -25
- package/src/shared/uuid.ts +12 -28
- package/src/data/json-rows.ts +0 -33
- package/src/rust-ffi/batch.ts +0 -431
- package/src/shared/bench-classify.ts +0 -209
|
@@ -2,48 +2,71 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Mirrors rust/crypto/*: base64/hex codecs, signed cookies, CSRF, random
|
|
4
4
|
// tokens, JWT, password hashing and AEAD.
|
|
5
|
+
//
|
|
6
|
+
// Runtime dispatch is centralized in the adapter (`ctx.runtime`): Bun built-in
|
|
7
|
+
// delegations (randomToken/base64*/hexEncode) come from `builtins.has(op)` and
|
|
8
|
+
// the native call from `transport.resolve(op)` / `transport.ffi` (bun:ffi
|
|
9
|
+
// first, napi fallback) — no inline `isBun()` / `getBunFFI()`.
|
|
5
10
|
|
|
6
11
|
import type { PasswordHashOptions } from '../../native'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import type
|
|
10
|
-
|
|
11
|
-
const encoder = new TextEncoder()
|
|
12
|
-
const decoder = new TextDecoder()
|
|
12
|
+
import { encoder } from '../../shared/bytes'
|
|
13
|
+
import { writeInto } from '../into'
|
|
14
|
+
import { memoizeFfi, type RustClientContext, resolveNative } from '../context'
|
|
13
15
|
|
|
14
16
|
/** Auth / crypto scalar methods (`Pick<RustScalar, ...>`). */
|
|
15
17
|
export function buildCrypto(ctx: RustClientContext) {
|
|
16
18
|
const { addon } = ctx
|
|
19
|
+
const { builtins, transport } = ctx.runtime
|
|
20
|
+
// Hoist the immutable per-runtime builtin-delegation decisions into a
|
|
21
|
+
// bind-time map (BUILTIN_OPS is a module constant — never mutated after
|
|
22
|
+
// load) so the hot path reads object properties instead of `Set.has`.
|
|
23
|
+
const HAS = {
|
|
24
|
+
randomToken: builtins.has('randomToken'),
|
|
25
|
+
base64Encode: builtins.has('base64Encode'),
|
|
26
|
+
base64UrlEncode: builtins.has('base64UrlEncode'),
|
|
27
|
+
hexEncode: builtins.has('hexEncode'),
|
|
28
|
+
}
|
|
29
|
+
// Lazy-memoized ffi surface: binds on first call, single local read after.
|
|
30
|
+
const ffi = memoizeFfi(transport)
|
|
17
31
|
|
|
18
32
|
return {
|
|
19
|
-
randomToken(byteLen: number): Uint8Array {
|
|
33
|
+
randomToken(byteLen: number): Uint8Array | string {
|
|
20
34
|
// Optimal by default under Bun: `crypto.getRandomValues` + native hex
|
|
21
|
-
// beats the rust+FFI crossing for token-sized draws (decision matrix)
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
// beats the rust+FFI crossing for token-sized draws (decision matrix);
|
|
36
|
+
// the builtin carries the 16 MiB guard and returns the hex STRING. Node
|
|
37
|
+
// keeps the addon.
|
|
38
|
+
if (HAS.randomToken) return builtins.randomToken(byteLen)
|
|
39
|
+
return resolveNative(ctx, 'randomToken')(byteLen) as Uint8Array | string
|
|
40
|
+
},
|
|
41
|
+
randomTokenInto(byteLen: number, output: Uint8Array): number {
|
|
42
|
+
// Pooled sibling — writes `byteLen*2` hex chars into `output`. FFI path
|
|
43
|
+
// uses the native `_into` (no cstring round-trip); without ffi the Bun
|
|
44
|
+
// builtin (or addon) hex is written into the caller buffer.
|
|
45
|
+
const f = ffi()
|
|
46
|
+
if (f) return f.randomTokenInto(byteLen, output)
|
|
47
|
+
if (HAS.randomToken) {
|
|
48
|
+
const need = byteLen * 2
|
|
49
|
+
if (output.length < need) throw new Error('random token: output buffer too small')
|
|
50
|
+
output.set(encoder.encode(builtins.randomToken(byteLen) as string))
|
|
51
|
+
return need
|
|
31
52
|
}
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
return addon.randomToken(byteLen)
|
|
53
|
+
const bytes = addon.randomToken(byteLen)
|
|
54
|
+
return writeInto('random token', output, bytes)
|
|
35
55
|
},
|
|
36
|
-
base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array {
|
|
56
|
+
base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array | string {
|
|
37
57
|
// Bun's Buffer base64 (SIMD) beats the rust+FFI crossing (~2x, measured)
|
|
38
|
-
// for the standard (url-safe=false, padded) case;
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
if (
|
|
42
|
-
|
|
58
|
+
// for the standard (url-safe=false, padded) case; the builtin returns
|
|
59
|
+
// `undefined` for url-safe/unpadded so those fall through to native.
|
|
60
|
+
// Parity pinned by test/unit/features/encoding.test.ts.
|
|
61
|
+
if (HAS.base64Encode) {
|
|
62
|
+
const encoded = builtins.base64Encode(input, urlSafe, padding)
|
|
63
|
+
if (encoded !== undefined) return encoded
|
|
43
64
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
return resolveNative(ctx, 'base64Encode')(
|
|
66
|
+
input,
|
|
67
|
+
urlSafe ?? undefined,
|
|
68
|
+
padding ?? undefined,
|
|
69
|
+
) as Uint8Array | string
|
|
47
70
|
},
|
|
48
71
|
base64EncodeInto(
|
|
49
72
|
input: Uint8Array,
|
|
@@ -51,14 +74,19 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
51
74
|
urlSafe?: boolean,
|
|
52
75
|
padding?: boolean,
|
|
53
76
|
): number {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
return resolveNative(ctx, 'base64EncodeInto')(
|
|
78
|
+
input,
|
|
79
|
+
output,
|
|
80
|
+
urlSafe ?? undefined,
|
|
81
|
+
padding ?? undefined,
|
|
82
|
+
) as number
|
|
57
83
|
},
|
|
58
84
|
base64Decode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
85
|
+
return resolveNative(ctx, 'base64Decode')(
|
|
86
|
+
input,
|
|
87
|
+
urlSafe ?? undefined,
|
|
88
|
+
padding ?? undefined,
|
|
89
|
+
) as Uint8Array
|
|
62
90
|
},
|
|
63
91
|
base64DecodeInto(
|
|
64
92
|
input: Uint8Array,
|
|
@@ -66,69 +94,73 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
66
94
|
urlSafe?: boolean,
|
|
67
95
|
padding?: boolean,
|
|
68
96
|
): number {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
97
|
+
return resolveNative(ctx, 'base64DecodeInto')(
|
|
98
|
+
input,
|
|
99
|
+
output,
|
|
100
|
+
urlSafe ?? undefined,
|
|
101
|
+
padding ?? undefined,
|
|
102
|
+
) as number
|
|
72
103
|
},
|
|
73
|
-
base64UrlEncode(input: Uint8Array): Uint8Array {
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
base64UrlEncode(input: Uint8Array): Uint8Array | string {
|
|
105
|
+
// Bun's native `Buffer.toString('base64url')` (SIMD) beats the rust+FFI
|
|
106
|
+
// crossing — same delegation rationale as `base64Encode`; verified
|
|
107
|
+
// byte-parity with the Rust `URL_SAFE_NO_PAD` engine
|
|
108
|
+
// (test/unit/features/encoding.test.ts). The ffi path reuses
|
|
109
|
+
// `base64Encode(input, true, false)` (no dedicated base64UrlEncode symbol).
|
|
110
|
+
if (HAS.base64UrlEncode) return builtins.base64UrlEncode(input)
|
|
111
|
+
const f = ffi()
|
|
112
|
+
if (f) return f.base64Encode(input, true, false)
|
|
76
113
|
return addon.base64UrlEncode(input)
|
|
77
114
|
},
|
|
78
115
|
base64UrlDecode(input: Uint8Array): Uint8Array {
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
116
|
+
const f = ffi()
|
|
117
|
+
if (f) return f.base64Decode(input, true, false)
|
|
81
118
|
return addon.base64UrlDecode(input)
|
|
82
119
|
},
|
|
83
|
-
hexEncode(input: Uint8Array): Uint8Array {
|
|
84
|
-
// Bun
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
return
|
|
120
|
+
hexEncode(input: Uint8Array): Uint8Array | string {
|
|
121
|
+
// Bun's native `Buffer.toString('hex')` (SIMD) beats the rust+FFI
|
|
122
|
+
// crossing, especially for large inputs. Verified byte-parity with the
|
|
123
|
+
// Rust hex encoder. Bun returns the hex STRING; Node keeps the addon bytes.
|
|
124
|
+
if (HAS.hexEncode) return builtins.hexEncode(input)
|
|
125
|
+
return resolveNative(ctx, 'hexEncode')(input) as Uint8Array | string
|
|
89
126
|
},
|
|
90
127
|
hexEncodeInto(input: Uint8Array, output: Uint8Array): number {
|
|
91
|
-
|
|
92
|
-
if (ffi) return ffi.hexEncodeInto(input, output)
|
|
93
|
-
return addon.hexEncodeInto(input, output)
|
|
128
|
+
return resolveNative(ctx, 'hexEncodeInto')(input, output) as number
|
|
94
129
|
},
|
|
95
130
|
hexDecode(input: Uint8Array): Uint8Array {
|
|
96
|
-
|
|
97
|
-
if (ffi) return ffi.hexDecode(input)
|
|
98
|
-
return addon.hexDecode(input)
|
|
131
|
+
return resolveNative(ctx, 'hexDecode')(input) as Uint8Array
|
|
99
132
|
},
|
|
100
133
|
hexDecodeInto(input: Uint8Array, output: Uint8Array): number {
|
|
101
|
-
|
|
102
|
-
if (ffi) return ffi.hexDecodeInto(input, output)
|
|
103
|
-
return addon.hexDecodeInto(input, output)
|
|
134
|
+
return resolveNative(ctx, 'hexDecodeInto')(input, output) as number
|
|
104
135
|
},
|
|
105
|
-
signCookie(value: Uint8Array, secret: Uint8Array): Uint8Array {
|
|
106
|
-
|
|
107
|
-
if (ffi) return ffi.signCookie(value, secret)
|
|
108
|
-
return addon.signCookie(value, secret)
|
|
136
|
+
signCookie(value: Uint8Array, secret: Uint8Array): Uint8Array | string {
|
|
137
|
+
return resolveNative(ctx, 'signCookie')(value, secret) as Uint8Array | string
|
|
109
138
|
},
|
|
110
139
|
signCookieInto(value: Uint8Array, secret: Uint8Array, output: Uint8Array): number {
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return
|
|
140
|
+
const f = ffi()
|
|
141
|
+
if (f) return f.signCookieInto(value, secret, output)
|
|
142
|
+
return writeInto('sign cookie', output, addon.signCookie(value, secret))
|
|
143
|
+
},
|
|
144
|
+
verifyCookie(signed: Uint8Array, secret: Uint8Array): Uint8Array | string | null {
|
|
145
|
+
return resolveNative(ctx, 'verifyCookie')(signed, secret) as Uint8Array | string | null
|
|
146
|
+
},
|
|
147
|
+
verifyCookieInto(signed: Uint8Array, secret: Uint8Array, output: Uint8Array): number | null {
|
|
148
|
+
const f = ffi()
|
|
149
|
+
if (f) return f.verifyCookieInto(signed, secret, output)
|
|
150
|
+
const value = addon.verifyCookie(signed, secret)
|
|
151
|
+
if (value === null) return null
|
|
152
|
+
return writeInto('verify cookie', output, value)
|
|
117
153
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (ffi) return ffi.verifyCookie(signed, secret)
|
|
121
|
-
return addon.verifyCookie(signed, secret)
|
|
154
|
+
csrfToken(secret: Uint8Array): Uint8Array | string {
|
|
155
|
+
return resolveNative(ctx, 'csrfToken')(secret) as Uint8Array | string
|
|
122
156
|
},
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
return addon.csrfToken(secret)
|
|
157
|
+
csrfTokenInto(secret: Uint8Array, output: Uint8Array): number {
|
|
158
|
+
const f = ffi()
|
|
159
|
+
if (f) return f.csrfTokenInto(secret, output)
|
|
160
|
+
return writeInto('csrf token', output, addon.csrfToken(secret))
|
|
127
161
|
},
|
|
128
162
|
csrfVerify(token: Uint8Array, secret: Uint8Array): boolean {
|
|
129
|
-
|
|
130
|
-
if (ffi) return ffi.csrfVerify(token, secret)
|
|
131
|
-
return addon.csrfVerify(token, secret)
|
|
163
|
+
return resolveNative(ctx, 'csrfVerify')(token, secret) as boolean
|
|
132
164
|
},
|
|
133
165
|
jwtSign(
|
|
134
166
|
claims: Record<string, unknown>,
|
|
@@ -136,6 +168,23 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
136
168
|
ttlSeconds?: number | null,
|
|
137
169
|
nowSeconds?: number,
|
|
138
170
|
): Uint8Array {
|
|
171
|
+
// FFI-first: route the object claims through the C-ABI `jwtSignBytes`
|
|
172
|
+
// (pre-serialized JSON). The engine returns the token as a string
|
|
173
|
+
// (native transfer); re-encode to bytes to keep the public BYTE contract
|
|
174
|
+
// (batch/benchmark consumers compare bytes). NOTE: JSON.stringify
|
|
175
|
+
// preserves JS insertion order while napi's serde_json sorts keys — for
|
|
176
|
+
// multi-key claims the two transports emit different-but-equally-valid
|
|
177
|
+
// tokens (both verify; parity tests cross-verify semantically).
|
|
178
|
+
const f = ffi()
|
|
179
|
+
if (f) {
|
|
180
|
+
const token = f.jwtSignBytes(
|
|
181
|
+
encoder.encode(JSON.stringify(claims)),
|
|
182
|
+
secret,
|
|
183
|
+
ttlSeconds ?? 0,
|
|
184
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
185
|
+
)
|
|
186
|
+
return encoder.encode(token)
|
|
187
|
+
}
|
|
139
188
|
return addon.jwtSign(
|
|
140
189
|
claims,
|
|
141
190
|
secret,
|
|
@@ -148,12 +197,12 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
148
197
|
secret: Uint8Array,
|
|
149
198
|
ttlSeconds?: number | null,
|
|
150
199
|
nowSeconds?: number,
|
|
151
|
-
): Uint8Array {
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
200
|
+
): Uint8Array | string {
|
|
201
|
+
const f = ffi()
|
|
202
|
+
if (f) {
|
|
154
203
|
// The C-ABI `ttl <= 0` sentinel matches napi's Option<i64> (inject only
|
|
155
204
|
// when positive), and nowSeconds defaults identically.
|
|
156
|
-
return
|
|
205
|
+
return f.jwtSignBytes(
|
|
157
206
|
claimsJson,
|
|
158
207
|
secret,
|
|
159
208
|
ttlSeconds ?? 0,
|
|
@@ -167,19 +216,55 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
167
216
|
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
168
217
|
)
|
|
169
218
|
},
|
|
219
|
+
jwtSignBytesInto(
|
|
220
|
+
claimsJson: Uint8Array,
|
|
221
|
+
secret: Uint8Array,
|
|
222
|
+
output: Uint8Array,
|
|
223
|
+
ttlSeconds?: number | null,
|
|
224
|
+
nowSeconds?: number,
|
|
225
|
+
): number {
|
|
226
|
+
const f = ffi()
|
|
227
|
+
if (f) {
|
|
228
|
+
return f.jwtSignBytesInto(
|
|
229
|
+
claimsJson,
|
|
230
|
+
secret,
|
|
231
|
+
ttlSeconds ?? 0,
|
|
232
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
233
|
+
output,
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
return writeInto(
|
|
237
|
+
'jwt sign',
|
|
238
|
+
output,
|
|
239
|
+
addon.jwtSignBytes(
|
|
240
|
+
claimsJson,
|
|
241
|
+
secret,
|
|
242
|
+
ttlSeconds ?? null,
|
|
243
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
},
|
|
170
247
|
jwtVerify(token: Uint8Array, secret: Uint8Array, nowSeconds?: number): unknown {
|
|
248
|
+
const f = ffi()
|
|
249
|
+
if (f) {
|
|
250
|
+
// Verify via the FFI cstring path (castrum_jwt_verify): the engine
|
|
251
|
+
// clones the claims JSON string at return (zero decode), and `null` =
|
|
252
|
+
// invalid signature / expired / malformed → null (napi Option parity).
|
|
253
|
+
const claims = f.jwtVerify(token, secret, nowSeconds ?? Math.floor(Date.now() / 1000))
|
|
254
|
+
return claims === null ? null : (JSON.parse(claims) as unknown)
|
|
255
|
+
}
|
|
171
256
|
return addon.jwtVerify(token, secret, nowSeconds ?? Math.floor(Date.now() / 1000))
|
|
172
257
|
},
|
|
173
258
|
passwordHash(
|
|
174
259
|
password: Uint8Array,
|
|
175
260
|
salt: Uint8Array,
|
|
176
261
|
options?: PasswordHashOptions | null,
|
|
177
|
-
): Uint8Array {
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
262
|
+
): Uint8Array | string {
|
|
263
|
+
const f = ffi()
|
|
264
|
+
if (f) {
|
|
180
265
|
// Resolve the napi defaults (rust/crypto/argon2.rs resolve_opts).
|
|
181
266
|
const o = options ?? {}
|
|
182
|
-
return
|
|
267
|
+
return f.passwordHash(
|
|
183
268
|
password,
|
|
184
269
|
salt,
|
|
185
270
|
o.mCost ?? 19456,
|
|
@@ -191,18 +276,16 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
191
276
|
return addon.passwordHash(password, salt, options ?? null)
|
|
192
277
|
},
|
|
193
278
|
passwordVerify(password: Uint8Array, phc: Uint8Array): boolean {
|
|
194
|
-
|
|
195
|
-
if (ffi) return ffi.passwordVerify(password, phc)
|
|
196
|
-
return addon.passwordVerify(password, phc)
|
|
279
|
+
return resolveNative(ctx, 'passwordVerify')(password, phc) as boolean
|
|
197
280
|
},
|
|
198
281
|
passwordHashBcrypt(password: Uint8Array, cost: number): string {
|
|
199
|
-
|
|
200
|
-
if (ffi) return decoder.decode(ffi.passwordHashBcrypt(password, cost))
|
|
201
|
-
return addon.passwordHashBcrypt(password, cost)
|
|
282
|
+
return resolveNative(ctx, 'passwordHashBcrypt')(password, cost) as string
|
|
202
283
|
},
|
|
203
284
|
passwordVerifyBcrypt(password: Uint8Array, hash: string): boolean {
|
|
204
|
-
|
|
205
|
-
|
|
285
|
+
// FFI-first: `hash` is a `cstring` ARG (the engine transcodes the PHC
|
|
286
|
+
// string in-engine — no JS encode); `password` stays `(ptr,len)` bytes.
|
|
287
|
+
const f = ffi()
|
|
288
|
+
if (f) return f.passwordVerifyBcrypt(password, hash)
|
|
206
289
|
return addon.passwordVerifyBcrypt(password, hash)
|
|
207
290
|
},
|
|
208
291
|
pbkdf2Sha256(
|
|
@@ -211,9 +294,7 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
211
294
|
rounds: number,
|
|
212
295
|
dkLen: number,
|
|
213
296
|
): Uint8Array {
|
|
214
|
-
|
|
215
|
-
if (ffi) return ffi.pbkdf2Sha256(password, salt, rounds, dkLen)
|
|
216
|
-
return addon.pbkdf2Sha256(password, salt, rounds, dkLen)
|
|
297
|
+
return resolveNative(ctx, 'pbkdf2Sha256')(password, salt, rounds, dkLen) as Uint8Array
|
|
217
298
|
},
|
|
218
299
|
aeadEncrypt(
|
|
219
300
|
key: Uint8Array,
|
|
@@ -221,9 +302,9 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
221
302
|
plaintext: Uint8Array,
|
|
222
303
|
algorithm?: string | null,
|
|
223
304
|
): Uint8Array {
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
return
|
|
305
|
+
const f = ffi()
|
|
306
|
+
if (f) {
|
|
307
|
+
return f.aeadEncrypt(key, nonce, plaintext, algorithm === 'chacha20-poly1305' ? 1 : 0)
|
|
227
308
|
}
|
|
228
309
|
return addon.aeadEncrypt(key, nonce, plaintext, algorithm ?? null)
|
|
229
310
|
},
|
|
@@ -234,9 +315,9 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
234
315
|
output: Uint8Array,
|
|
235
316
|
algorithm?: string | null,
|
|
236
317
|
): number {
|
|
237
|
-
const
|
|
238
|
-
if (
|
|
239
|
-
return
|
|
318
|
+
const f = ffi()
|
|
319
|
+
if (f) {
|
|
320
|
+
return f.aeadEncryptInto(
|
|
240
321
|
key,
|
|
241
322
|
nonce,
|
|
242
323
|
plaintext,
|
|
@@ -255,9 +336,9 @@ export function buildCrypto(ctx: RustClientContext) {
|
|
|
255
336
|
ciphertext: Uint8Array,
|
|
256
337
|
algorithm?: string | null,
|
|
257
338
|
): Uint8Array | null {
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
260
|
-
return
|
|
339
|
+
const f = ffi()
|
|
340
|
+
if (f) {
|
|
341
|
+
return f.aeadDecrypt(key, nonce, ciphertext, algorithm === 'chacha20-poly1305' ? 1 : 0)
|
|
261
342
|
}
|
|
262
343
|
return addon.aeadDecrypt(key, nonce, ciphertext, algorithm ?? null)
|
|
263
344
|
},
|