@claude-flow/security 3.0.0-alpha.1 → 3.0.0-alpha.11
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 +292 -234
- package/__tests__/acceptance/security-compliance.test.ts +674 -674
- package/__tests__/authorization-propagator.test.ts +155 -0
- package/__tests__/credential-generator.test.ts +310 -310
- package/__tests__/fixtures/configurations.ts +425 -419
- package/__tests__/fixtures/index.ts +21 -21
- package/__tests__/helpers/create-mock.ts +469 -469
- package/__tests__/helpers/index.ts +32 -32
- package/__tests__/input-validator.test.ts +382 -381
- package/__tests__/integration/security-flow.test.ts +606 -606
- package/__tests__/keychain-adapter.test.ts +60 -0
- package/__tests__/oauth-browser.test.ts +13 -0
- package/__tests__/oauth-export-surface.test.ts +21 -0
- package/__tests__/oauth-pkce.test.ts +37 -0
- package/__tests__/password-hasher.test.ts +239 -239
- package/__tests__/path-validator.test.ts +302 -302
- package/__tests__/plugin-integrity-verifier.test.ts +199 -0
- package/__tests__/safe-executor.test.ts +292 -292
- package/__tests__/token-generator.test.ts +371 -371
- package/__tests__/tool-output-guardrail.test.ts +256 -0
- package/__tests__/unit/credential-generator.test.ts +182 -182
- package/__tests__/unit/password-hasher.test.ts +359 -359
- package/__tests__/unit/path-validator.test.ts +509 -509
- package/__tests__/unit/safe-executor.test.ts +667 -667
- package/__tests__/unit/token-generator.test.ts +310 -310
- package/dist/CVE-REMEDIATION.d.ts +17 -6
- package/dist/CVE-REMEDIATION.d.ts.map +1 -1
- package/dist/CVE-REMEDIATION.js +328 -12
- package/dist/CVE-REMEDIATION.js.map +1 -1
- package/dist/authorization/propagator.d.ts +148 -0
- package/dist/authorization/propagator.d.ts.map +1 -0
- package/dist/authorization/propagator.js +182 -0
- package/dist/authorization/propagator.js.map +1 -0
- package/dist/credential-generator.js +9 -9
- package/dist/domain/services/security-domain-service.js +1 -1
- package/dist/domain/services/security-domain-service.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/keychain-adapter.d.ts +48 -0
- package/dist/keychain-adapter.d.ts.map +1 -0
- package/dist/keychain-adapter.js +0 -0
- package/dist/keychain-adapter.js.map +1 -0
- package/dist/oauth/browser.d.ts +31 -0
- package/dist/oauth/browser.d.ts.map +1 -0
- package/dist/oauth/browser.js +55 -0
- package/dist/oauth/browser.js.map +1 -0
- package/dist/oauth/callback-server.d.ts +37 -0
- package/dist/oauth/callback-server.d.ts.map +1 -0
- package/dist/oauth/callback-server.js +86 -0
- package/dist/oauth/callback-server.js.map +1 -0
- package/dist/oauth/client.d.ts +57 -0
- package/dist/oauth/client.d.ts.map +1 -0
- package/dist/oauth/client.js +120 -0
- package/dist/oauth/client.js.map +1 -0
- package/dist/oauth/pkce.d.ts +25 -0
- package/dist/oauth/pkce.d.ts.map +1 -0
- package/dist/oauth/pkce.js +36 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/password-hasher.d.ts.map +1 -1
- package/dist/password-hasher.js +8 -1
- package/dist/password-hasher.js.map +1 -1
- package/dist/plugins/integrity-verifier.d.ts +125 -0
- package/dist/plugins/integrity-verifier.d.ts.map +1 -0
- package/dist/plugins/integrity-verifier.js +186 -0
- package/dist/plugins/integrity-verifier.js.map +1 -0
- package/dist/safe-executor.d.ts +7 -0
- package/dist/safe-executor.d.ts.map +1 -1
- package/dist/safe-executor.js +21 -1
- package/dist/safe-executor.js.map +1 -1
- package/dist/tool-output-guardrail.d.ts +126 -0
- package/dist/tool-output-guardrail.d.ts.map +1 -0
- package/dist/tool-output-guardrail.js +257 -0
- package/dist/tool-output-guardrail.js.map +1 -0
- package/node_modules-incomplete-release-test/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/package.json +33 -28
- package/scripts/verify-oauth-exports.mjs +29 -0
- package/src/CVE-REMEDIATION.ts +591 -251
- package/src/application/index.ts +10 -10
- package/src/application/services/security-application-service.ts +193 -193
- package/src/authorization/propagator.ts +279 -0
- package/src/credential-generator.ts +368 -368
- package/src/domain/entities/security-context.ts +173 -173
- package/src/domain/index.ts +17 -17
- package/src/domain/services/security-domain-service.ts +296 -296
- package/src/index.ts +340 -271
- package/src/input-validator.ts +466 -466
- package/src/keychain-adapter.ts +0 -0
- package/src/oauth/browser.ts +58 -0
- package/src/oauth/callback-server.ts +97 -0
- package/src/oauth/client.ts +153 -0
- package/src/oauth/pkce.ts +45 -0
- package/src/password-hasher.ts +277 -270
- package/src/path-validator.ts +525 -525
- package/src/plugins/integrity-verifier.ts +265 -0
- package/src/safe-executor.ts +546 -525
- package/src/token-generator.ts +463 -463
- package/src/tool-output-guardrail.ts +360 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -9
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +20 -0
- package/.agentic-flow/intelligence.json +0 -16
- package/__tests__/coverage/base.css +0 -224
- package/__tests__/coverage/block-navigation.js +0 -87
- package/__tests__/coverage/coverage-final.json +0 -11
- package/__tests__/coverage/favicon.png +0 -0
- package/__tests__/coverage/index.html +0 -161
- package/__tests__/coverage/lcov-report/base.css +0 -224
- package/__tests__/coverage/lcov-report/block-navigation.js +0 -87
- package/__tests__/coverage/lcov-report/favicon.png +0 -0
- package/__tests__/coverage/lcov-report/index.html +0 -161
- package/__tests__/coverage/lcov-report/prettify.css +0 -1
- package/__tests__/coverage/lcov-report/prettify.js +0 -2
- package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/__tests__/coverage/lcov-report/sorter.js +0 -210
- package/__tests__/coverage/lcov-report/src/CVE-REMEDIATION.ts.html +0 -838
- package/__tests__/coverage/lcov-report/src/application/services/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/application/services/security-application-service.ts.html +0 -664
- package/__tests__/coverage/lcov-report/src/credential-generator.ts.html +0 -1189
- package/__tests__/coverage/lcov-report/src/domain/entities/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/domain/entities/security-context.ts.html +0 -604
- package/__tests__/coverage/lcov-report/src/domain/services/index.html +0 -116
- package/__tests__/coverage/lcov-report/src/domain/services/security-domain-service.ts.html +0 -973
- package/__tests__/coverage/lcov-report/src/index.html +0 -206
- package/__tests__/coverage/lcov-report/src/input-validator.ts.html +0 -1483
- package/__tests__/coverage/lcov-report/src/password-hasher.ts.html +0 -895
- package/__tests__/coverage/lcov-report/src/path-validator.ts.html +0 -1660
- package/__tests__/coverage/lcov-report/src/safe-executor.ts.html +0 -1660
- package/__tests__/coverage/lcov-report/src/token-generator.ts.html +0 -1474
- package/__tests__/coverage/lcov.info +0 -3650
- package/__tests__/coverage/prettify.css +0 -1
- package/__tests__/coverage/prettify.js +0 -2
- package/__tests__/coverage/sort-arrow-sprite.png +0 -0
- package/__tests__/coverage/sorter.js +0 -210
- package/__tests__/coverage/src/CVE-REMEDIATION.ts.html +0 -838
- package/__tests__/coverage/src/application/services/index.html +0 -116
- package/__tests__/coverage/src/application/services/security-application-service.ts.html +0 -664
- package/__tests__/coverage/src/credential-generator.ts.html +0 -1189
- package/__tests__/coverage/src/domain/entities/index.html +0 -116
- package/__tests__/coverage/src/domain/entities/security-context.ts.html +0 -604
- package/__tests__/coverage/src/domain/services/index.html +0 -116
- package/__tests__/coverage/src/domain/services/security-domain-service.ts.html +0 -973
- package/__tests__/coverage/src/index.html +0 -206
- package/__tests__/coverage/src/input-validator.ts.html +0 -1483
- package/__tests__/coverage/src/password-hasher.ts.html +0 -895
- package/__tests__/coverage/src/path-validator.ts.html +0 -1660
- package/__tests__/coverage/src/safe-executor.ts.html +0 -1660
- package/__tests__/coverage/src/token-generator.ts.html +0 -1474
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginIntegrityVerifier — install-layer security for plugin supply chain.
|
|
3
|
+
*
|
|
4
|
+
* Implements P1 of ADR-145 (ruvnet/ruflo#2254): Stage-1 Ed25519 signature
|
|
5
|
+
* verification at `plugins install`. Stage-2 (semantic-intent scan against
|
|
6
|
+
* SCH attacks) lands in P2; per-namespace write ACLs land in P3-P4.
|
|
7
|
+
*
|
|
8
|
+
* Threat model
|
|
9
|
+
* ------------
|
|
10
|
+
* Ruflo's plugin install path fetches manifests from IPFS via Pinata with
|
|
11
|
+
* NO signature verification and NO intent analysis. Two attacks land today:
|
|
12
|
+
*
|
|
13
|
+
* - DDIPE (arXiv:2604.03081): malicious logic embedded in plugin docs and
|
|
14
|
+
* config templates. 11.6-33.5% bypass rate across 4 frameworks/5 models;
|
|
15
|
+
* 2.5% evade both detection and alignment. Stage-1 signing blocks the
|
|
16
|
+
* static-payload variants.
|
|
17
|
+
*
|
|
18
|
+
* - SCH (arXiv:2605.14460): malicious intent wrapped as natural-language
|
|
19
|
+
* "compliance rules" in plugin descriptions. The agent generates the
|
|
20
|
+
* harmful code at runtime — no static payload exists. 77.67% breach
|
|
21
|
+
* success, 0.00% scanner detection. Stage-2 (P2) catches this; Stage-1
|
|
22
|
+
* does not.
|
|
23
|
+
*
|
|
24
|
+
* Scope (P1)
|
|
25
|
+
* ----------
|
|
26
|
+
* - Ed25519 signature verification of a detached signature over the manifest.
|
|
27
|
+
* - Trust-anchor allowlist: the publisher's signing key fingerprint must be
|
|
28
|
+
* in `trust-anchors.json`, gated on CODEOWNERS review.
|
|
29
|
+
* - Backwards compatible: default mode is warn-only. `CLAUDE_FLOW_STRICT_PLUGINS=true`
|
|
30
|
+
* makes verification a hard gate.
|
|
31
|
+
*
|
|
32
|
+
* Non-goals (P1)
|
|
33
|
+
* --------------
|
|
34
|
+
* - Semantic intent scan (SCH defence). Lands in P2 with a pattern-rule
|
|
35
|
+
* fallback for environments without LLM credentials.
|
|
36
|
+
* - Sandboxing the plugin runtime. Orthogonal blast-radius concern.
|
|
37
|
+
*
|
|
38
|
+
* Reference: ADR-145, arXiv:2605.14460 (SCH), arXiv:2604.03081 (DDIPE),
|
|
39
|
+
* arXiv:2604.16548 (Mnemonic Sovereignty survey).
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { createHash } from 'node:crypto';
|
|
43
|
+
import * as nodeCrypto from 'node:crypto';
|
|
44
|
+
|
|
45
|
+
export interface PluginManifest {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly version: string;
|
|
48
|
+
/** Anything else; only `id` + `version` matter for the integrity hash. */
|
|
49
|
+
readonly [extra: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface SignedPluginManifest {
|
|
53
|
+
readonly manifest: PluginManifest;
|
|
54
|
+
/** Hex-encoded SHA-256 of the canonical JSON serialisation of manifest. */
|
|
55
|
+
readonly manifestHash: string;
|
|
56
|
+
/** Hex-encoded detached Ed25519 signature over `manifestHash` bytes. */
|
|
57
|
+
readonly signature: string;
|
|
58
|
+
/** Hex-encoded Ed25519 public key of the signer. */
|
|
59
|
+
readonly publicKey: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface TrustAnchor {
|
|
63
|
+
/** Hex-encoded Ed25519 public key the publisher signs with. */
|
|
64
|
+
readonly publicKey: string;
|
|
65
|
+
/** Human-readable owner — for audit, not for trust decisions. */
|
|
66
|
+
readonly owner: string;
|
|
67
|
+
/** Optional ISO 8601 date after which this anchor is no longer trusted. */
|
|
68
|
+
readonly expiresAt?: string;
|
|
69
|
+
/** Optional plugin id glob this anchor is authoritative for (e.g. `@claude-flow/*`). */
|
|
70
|
+
readonly scope?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface TrustAnchors {
|
|
74
|
+
readonly version: 1;
|
|
75
|
+
readonly anchors: ReadonlyArray<TrustAnchor>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type VerificationStatus =
|
|
79
|
+
| 'pass'
|
|
80
|
+
| 'signature-missing'
|
|
81
|
+
| 'signature-invalid'
|
|
82
|
+
| 'manifest-hash-mismatch'
|
|
83
|
+
| 'unknown-signer'
|
|
84
|
+
| 'signer-expired'
|
|
85
|
+
| 'signer-out-of-scope';
|
|
86
|
+
|
|
87
|
+
export interface VerificationResult {
|
|
88
|
+
readonly status: VerificationStatus;
|
|
89
|
+
readonly pluginId: string;
|
|
90
|
+
readonly signerFingerprint?: string;
|
|
91
|
+
/** Verification timestamp — useful for telemetry correlation. */
|
|
92
|
+
readonly ts: number;
|
|
93
|
+
/** Operator-facing detail; do not parse — log only. */
|
|
94
|
+
readonly detail?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface VerifierConfig {
|
|
98
|
+
readonly trustAnchors: TrustAnchors;
|
|
99
|
+
/**
|
|
100
|
+
* When true, `verify` returns the actual failure status. When false (legacy
|
|
101
|
+
* mode), `verify` returns the failure status BUT callers may choose to
|
|
102
|
+
* proceed with a warning — the strictness gate lives at the caller, not
|
|
103
|
+
* here, so the verifier itself is always pure.
|
|
104
|
+
*/
|
|
105
|
+
readonly strict?: boolean;
|
|
106
|
+
/** Optional now-provider for tests. */
|
|
107
|
+
readonly now?: () => number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Canonical JSON serialisation for hashing. Deterministic key ordering at
|
|
112
|
+
* every nesting level. Pure; identical input → identical output bytes.
|
|
113
|
+
*/
|
|
114
|
+
export function canonicalize(value: unknown): string {
|
|
115
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
116
|
+
if (Array.isArray(value)) return '[' + value.map(canonicalize).join(',') + ']';
|
|
117
|
+
const obj = value as Record<string, unknown>;
|
|
118
|
+
const keys = Object.keys(obj).sort();
|
|
119
|
+
return '{' + keys.map(k => JSON.stringify(k) + ':' + canonicalize(obj[k])).join(',') + '}';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Hex-encoded SHA-256 of the canonical-JSON manifest. */
|
|
123
|
+
export function hashManifest(manifest: PluginManifest): string {
|
|
124
|
+
return createHash('sha256').update(canonicalize(manifest)).digest('hex');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Short fingerprint for telemetry — first 16 hex chars of the key. */
|
|
128
|
+
export function fingerprint(publicKey: string): string {
|
|
129
|
+
return publicKey.slice(0, 16);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Find the trust anchor that vouches for a manifest. Returns the matched
|
|
134
|
+
* anchor (with anchor.publicKey === signer.publicKey) or null.
|
|
135
|
+
*
|
|
136
|
+
* Scope-matching uses a minimal glob: `*` at the end of the scope string
|
|
137
|
+
* matches any suffix. Empty/missing scope matches every plugin id.
|
|
138
|
+
*/
|
|
139
|
+
export function findAnchor(
|
|
140
|
+
anchors: ReadonlyArray<TrustAnchor>,
|
|
141
|
+
pluginId: string,
|
|
142
|
+
signerPublicKey: string,
|
|
143
|
+
now: number,
|
|
144
|
+
): TrustAnchor | null {
|
|
145
|
+
for (const a of anchors) {
|
|
146
|
+
if (a.publicKey !== signerPublicKey) continue;
|
|
147
|
+
if (a.expiresAt && new Date(a.expiresAt).getTime() <= now) continue;
|
|
148
|
+
if (!a.scope || a.scope === '*' || a.scope === pluginId) return a;
|
|
149
|
+
if (a.scope.endsWith('*') && pluginId.startsWith(a.scope.slice(0, -1))) return a;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* `PluginIntegrityVerifier` — Stage-1 verifier (signature + trust anchor).
|
|
156
|
+
*
|
|
157
|
+
* Pure-ish: holds the trust-anchor list and clock, no other state. Safe to
|
|
158
|
+
* construct per-invocation or share.
|
|
159
|
+
*/
|
|
160
|
+
export class PluginIntegrityVerifier {
|
|
161
|
+
constructor(private readonly config: VerifierConfig) {}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Verify a signed manifest. Returns the verification status; the caller
|
|
165
|
+
* decides what to do with `signature-missing` / `manifest-hash-mismatch`
|
|
166
|
+
* etc. based on its strict-mode policy.
|
|
167
|
+
*/
|
|
168
|
+
async verify(signed: SignedPluginManifest): Promise<VerificationResult> {
|
|
169
|
+
const now = (this.config.now ?? Date.now)();
|
|
170
|
+
const pluginId = signed.manifest?.id ?? '<unknown>';
|
|
171
|
+
|
|
172
|
+
if (!signed.signature || !signed.publicKey) {
|
|
173
|
+
return { status: 'signature-missing', pluginId, ts: now };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Re-hash the manifest and compare to the signed hash.
|
|
177
|
+
const recomputed = hashManifest(signed.manifest);
|
|
178
|
+
if (recomputed !== signed.manifestHash) {
|
|
179
|
+
return {
|
|
180
|
+
status: 'manifest-hash-mismatch',
|
|
181
|
+
pluginId,
|
|
182
|
+
signerFingerprint: fingerprint(signed.publicKey),
|
|
183
|
+
ts: now,
|
|
184
|
+
detail: `expected ${signed.manifestHash}, got ${recomputed}`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Find a trust anchor that vouches for this signer + plugin.
|
|
189
|
+
const anchor = findAnchor(this.config.trustAnchors.anchors, pluginId, signed.publicKey, now);
|
|
190
|
+
if (!anchor) {
|
|
191
|
+
return {
|
|
192
|
+
status: 'unknown-signer',
|
|
193
|
+
pluginId,
|
|
194
|
+
signerFingerprint: fingerprint(signed.publicKey),
|
|
195
|
+
ts: now,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Ed25519 verify. Probe @noble/ed25519 (already a workspace dep); fall
|
|
200
|
+
// back to the failure status if it isn't resolvable in this environment
|
|
201
|
+
// (mirrors verify.mjs's #1880 precondition pattern).
|
|
202
|
+
const ed = await loadEd25519();
|
|
203
|
+
if (!ed) {
|
|
204
|
+
return {
|
|
205
|
+
status: 'signature-invalid',
|
|
206
|
+
pluginId,
|
|
207
|
+
signerFingerprint: fingerprint(signed.publicKey),
|
|
208
|
+
ts: now,
|
|
209
|
+
detail: '@noble/ed25519 not available — cannot verify',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Required by @noble/ed25519 v2 for sync sha512.
|
|
214
|
+
if (!ed.etc.sha512Sync) {
|
|
215
|
+
ed.etc.sha512Sync = (...m: Uint8Array[]): Uint8Array => {
|
|
216
|
+
const h = nodeCrypto.createHash('sha512');
|
|
217
|
+
for (const x of m) h.update(x);
|
|
218
|
+
return h.digest();
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
let ok = false;
|
|
223
|
+
try {
|
|
224
|
+
const sigBytes = hexToBytes(signed.signature);
|
|
225
|
+
const msgBytes = hexToBytes(signed.manifestHash);
|
|
226
|
+
const pubBytes = hexToBytes(signed.publicKey);
|
|
227
|
+
ok = await ed.verify(sigBytes, msgBytes, pubBytes);
|
|
228
|
+
} catch {
|
|
229
|
+
ok = false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!ok) {
|
|
233
|
+
return {
|
|
234
|
+
status: 'signature-invalid',
|
|
235
|
+
pluginId,
|
|
236
|
+
signerFingerprint: fingerprint(signed.publicKey),
|
|
237
|
+
ts: now,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { status: 'pass', pluginId, signerFingerprint: fingerprint(signed.publicKey), ts: now };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── helpers ────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
function hexToBytes(hex: string): Uint8Array {
|
|
248
|
+
const out = new Uint8Array(hex.length >>> 1);
|
|
249
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interface NobleEd25519 {
|
|
254
|
+
verify(sig: Uint8Array, msg: Uint8Array, pub: Uint8Array): Promise<boolean>;
|
|
255
|
+
etc: { sha512Sync?: (...m: Uint8Array[]) => Uint8Array };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function loadEd25519(): Promise<NobleEd25519 | null> {
|
|
259
|
+
try {
|
|
260
|
+
const mod = await import('@noble/ed25519');
|
|
261
|
+
return mod as unknown as NobleEd25519;
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|