@forgezero/runtime 0.1.16 → 0.1.18
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 +26 -2
- package/dist/jobs.d.ts +8 -3
- package/dist/jobs.js +22 -11
- package/dist/vault-runtime.d.ts +191 -0
- package/dist/vault-runtime.js +272 -0
- package/package.json +18 -4
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
and commit the generator and rendered files together.
|
|
6
6
|
-->
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# Platform runtime
|
|
9
9
|
|
|
10
|
-
The machinery behind a request handler — jobs, queues, an outbox, a hash-chained audit trail, mail, backups, schema validation and exact money.
|
|
10
|
+
The machinery behind a request handler — jobs, queues, an outbox, a hash-chained audit trail, mail, backups, schema validation and exact money. 31 public modules, each imported on its own.
|
|
11
11
|
|
|
12
12
|
## Package overview
|
|
13
13
|
|
|
@@ -53,6 +53,7 @@ These are supported consumer entry points, not every internal module shipped for
|
|
|
53
53
|
| @forgezero/runtime/ssh-cert | OpenSSH certificates, so access expires instead of having to be hunted down. Takes a signing FUNCTION rather than a secret key, which is what lets the CA live in a vault that never hands it out. | portable | [Reference + usage](#forgezero-runtime-ssh-cert) |
|
|
54
54
|
| @forgezero/runtime/slip10 | SLIP-0010 derivation for ed25519, hardened-only — BIP-32 does not work on this curve and produces halves that do not correspond. | portable | [Reference + usage](#forgezero-runtime-slip10) |
|
|
55
55
|
| @forgezero/runtime/identity | Hybrid Ed25519 + ML-DSA-65 request signing. One canonical string, so the compute agent that signs inside a guest and the API that verifies cannot drift — which two implementations of it certainly would. | portable | [Reference + usage](#forgezero-runtime-identity) |
|
|
56
|
+
| @forgezero/runtime/vault-runtime | Provider-neutral algorithms for non-exportable Vault runtime records: passwords, DEK/KEK operations, HMAC/JWS, Ed25519/secp256k1 and Ethereum/Solana address encoding. | portable | [Reference + usage](#forgezero-runtime-vault-runtime) |
|
|
56
57
|
| @forgezero/runtime/schema | Validate against JSON Schema, restrict what a caller may declare, and describe a schema as a form. | portable | [Reference + usage](#forgezero-runtime-schema) |
|
|
57
58
|
| @forgezero/runtime/schema/typebox | The TypeBox validator behind that interface. | portable | [Reference + usage](#forgezero-runtime-schema-typebox) |
|
|
58
59
|
| @forgezero/runtime/finance/discounts | Promotions as arithmetic over integer minor units. They never stack — one winner — and a percentage rounds down, because rounding a discount up gives away a unit of currency per invoice forever. | portable | [Reference + usage](#forgezero-runtime-finance-discounts) |
|
|
@@ -509,6 +510,29 @@ import {
|
|
|
509
510
|
export const selectedCapability = CLOCK_SKEW_SECONDS;
|
|
510
511
|
```
|
|
511
512
|
|
|
513
|
+
<a id="forgezero-runtime-vault-runtime"></a>
|
|
514
|
+
## @forgezero/runtime/vault-runtime
|
|
515
|
+
|
|
516
|
+
Provider-neutral algorithms for non-exportable Vault runtime records: passwords, DEK/KEK operations, HMAC/JWS, Ed25519/secp256k1 and Ethereum/Solana address encoding. This is a supported entry point. Import only the named values used by the adjacent task example; the declaration file remains the complete API reference.
|
|
517
|
+
|
|
518
|
+
```text
|
|
519
|
+
import {
|
|
520
|
+
DEFAULT_PASSWORD_ALPHABET,
|
|
521
|
+
} from '@forgezero/runtime/vault-runtime';
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## @forgezero/runtime/vault-runtime — Use this entry point
|
|
525
|
+
|
|
526
|
+
This minimal executable use imports one concrete value from this exact entry point without a wildcard or package-root detour. Keep the value or values the application actually needs; the full named inventory remains directly above it.
|
|
527
|
+
|
|
528
|
+
```text
|
|
529
|
+
import {
|
|
530
|
+
DEFAULT_PASSWORD_ALPHABET,
|
|
531
|
+
} from '@forgezero/runtime/vault-runtime';
|
|
532
|
+
|
|
533
|
+
export const selectedCapability = DEFAULT_PASSWORD_ALPHABET;
|
|
534
|
+
```
|
|
535
|
+
|
|
512
536
|
<a id="forgezero-runtime-schema"></a>
|
|
513
537
|
## @forgezero/runtime/schema
|
|
514
538
|
|
package/dist/jobs.d.ts
CHANGED
|
@@ -58,7 +58,12 @@ export interface Lease {
|
|
|
58
58
|
until: number;
|
|
59
59
|
}
|
|
60
60
|
export interface JobLock {
|
|
61
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Acquire the job. A scheduled occurrence is a durable once-only token: after
|
|
63
|
+
* the holder releases its lease, another replica still cannot execute that
|
|
64
|
+
* same due slot. Manual runs omit it and remain deliberately repeatable.
|
|
65
|
+
*/
|
|
66
|
+
acquire(key: string, ttlMs: number, occurrence?: string): Promise<Lease | undefined>;
|
|
62
67
|
/** Extend a lease held by this fence. False means it was lost. */
|
|
63
68
|
renew(key: string, fence: number, ttlMs: number): Promise<boolean>;
|
|
64
69
|
release(key: string, fence: number): Promise<void>;
|
|
@@ -72,7 +77,7 @@ export declare function memoryLock(clock?: Clock): JobLock;
|
|
|
72
77
|
* the same lease to two processes under exactly the load that makes it matter.
|
|
73
78
|
*/
|
|
74
79
|
export interface LockStore {
|
|
75
|
-
claim(key: string, until: number, now: number): Promise<Lease | undefined>;
|
|
80
|
+
claim(key: string, until: number, now: number, occurrence?: string): Promise<Lease | undefined>;
|
|
76
81
|
extend(key: string, fence: number, until: number): Promise<boolean>;
|
|
77
82
|
clear(key: string, fence: number): Promise<void>;
|
|
78
83
|
}
|
|
@@ -199,7 +204,7 @@ export interface CursorJobSpec<T> extends Omit<JobSpec, 'run'> {
|
|
|
199
204
|
* item three of ten throws, the whole batch is retried from the same cursor on
|
|
200
205
|
* the next run. That means `process` must be idempotent — items one and two will
|
|
201
206
|
* be seen again — and idempotent retries are strictly better than the
|
|
202
|
-
* alternative, which is
|
|
207
|
+
* alternative, which is records that are never processed at all.
|
|
203
208
|
*
|
|
204
209
|
* The lease is re-checked between batches. A run that stalled past its lease and
|
|
205
210
|
* woke up must not keep writing while another process is doing the same work.
|
package/dist/jobs.js
CHANGED
|
@@ -379,15 +379,20 @@ function nextWallClockAt(schedule, afterMs) {
|
|
|
379
379
|
}
|
|
380
380
|
function memoryLock(clock = systemClock) {
|
|
381
381
|
const held = new Map;
|
|
382
|
+
const claimedOccurrences = new Map;
|
|
382
383
|
let fences = 0;
|
|
383
384
|
return {
|
|
384
|
-
async acquire(key, ttlMs) {
|
|
385
|
+
async acquire(key, ttlMs, occurrence) {
|
|
386
|
+
if (occurrence !== undefined && claimedOccurrences.get(key) === occurrence)
|
|
387
|
+
return;
|
|
385
388
|
const current = held.get(key);
|
|
386
389
|
if (current && current.until > clock.now())
|
|
387
390
|
return;
|
|
388
391
|
fences += 1;
|
|
389
392
|
const lease = { fence: fences, until: clock.now() + ttlMs };
|
|
390
393
|
held.set(key, lease);
|
|
394
|
+
if (occurrence !== undefined)
|
|
395
|
+
claimedOccurrences.set(key, occurrence);
|
|
391
396
|
return lease;
|
|
392
397
|
},
|
|
393
398
|
async renew(key, fence, ttlMs) {
|
|
@@ -405,7 +410,7 @@ function memoryLock(clock = systemClock) {
|
|
|
405
410
|
}
|
|
406
411
|
function storeLock(store, clock = systemClock) {
|
|
407
412
|
return {
|
|
408
|
-
acquire: (key, ttlMs) => store.claim(key, clock.now() + ttlMs, clock.now()),
|
|
413
|
+
acquire: (key, ttlMs, occurrence) => store.claim(key, clock.now() + ttlMs, clock.now(), occurrence),
|
|
409
414
|
renew: (key, fence, ttlMs) => store.extend(key, fence, clock.now() + ttlMs),
|
|
410
415
|
release: (key, fence) => store.clear(key, fence)
|
|
411
416
|
};
|
|
@@ -465,10 +470,10 @@ function createScheduler(options) {
|
|
|
465
470
|
let paused = false;
|
|
466
471
|
let running = false;
|
|
467
472
|
const outstanding = new Map;
|
|
468
|
-
const submit = async (job) => {
|
|
473
|
+
const submit = async (job, occurrence) => {
|
|
469
474
|
outstanding.set(job.key, (outstanding.get(job.key) ?? 0) + 1);
|
|
470
475
|
try {
|
|
471
|
-
await work.run(job.key, execute, job).result;
|
|
476
|
+
await work.run(job.key, execute, job, occurrence).result;
|
|
472
477
|
} finally {
|
|
473
478
|
const left = (outstanding.get(job.key) ?? 1) - 1;
|
|
474
479
|
if (left === 0)
|
|
@@ -477,14 +482,14 @@ function createScheduler(options) {
|
|
|
477
482
|
outstanding.set(job.key, left);
|
|
478
483
|
}
|
|
479
484
|
};
|
|
480
|
-
async function execute(job) {
|
|
485
|
+
async function execute(job, occurrence) {
|
|
481
486
|
const report = reports.get(job.key);
|
|
482
487
|
const leaseMs = job.leaseMs ?? (job.every ? everyMs(job.every) * 4 : 60000);
|
|
483
488
|
let lease;
|
|
484
489
|
let leaseHeld = true;
|
|
485
490
|
let renewTimer;
|
|
486
491
|
if (!job.unlocked) {
|
|
487
|
-
lease = await lock.acquire(job.key, leaseMs);
|
|
492
|
+
lease = await lock.acquire(job.key, leaseMs, occurrence);
|
|
488
493
|
if (!lease) {
|
|
489
494
|
report.skippedLocked += 1;
|
|
490
495
|
return;
|
|
@@ -553,8 +558,11 @@ function createScheduler(options) {
|
|
|
553
558
|
}
|
|
554
559
|
}
|
|
555
560
|
function nextDelay(job) {
|
|
556
|
-
if (job.every !== undefined)
|
|
557
|
-
|
|
561
|
+
if (job.every !== undefined) {
|
|
562
|
+
const interval = everyMs(job.every);
|
|
563
|
+
const now = clock.now();
|
|
564
|
+
return Math.max(0, (Math.floor(now / interval) + 1) * interval - now);
|
|
565
|
+
}
|
|
558
566
|
if (job.schedule)
|
|
559
567
|
return Math.max(0, nextWallClockAt(job.schedule, clock.now()) - clock.now());
|
|
560
568
|
return;
|
|
@@ -563,7 +571,8 @@ function createScheduler(options) {
|
|
|
563
571
|
const next = delayMs ?? nextDelay(job);
|
|
564
572
|
if (!running || next === undefined)
|
|
565
573
|
return;
|
|
566
|
-
|
|
574
|
+
const dueAt = clock.now() + next;
|
|
575
|
+
reports.get(job.key).nextRunAtMs = dueAt;
|
|
567
576
|
timers.set(job.key, setTimeout(() => {
|
|
568
577
|
timers.delete(job.key);
|
|
569
578
|
if (!running || paused)
|
|
@@ -574,12 +583,14 @@ function createScheduler(options) {
|
|
|
574
583
|
reports.get(job.key).skippedOverlap += 1;
|
|
575
584
|
return;
|
|
576
585
|
}
|
|
577
|
-
|
|
586
|
+
const occurrence2 = job.every === undefined ? `wall:${dueAt}` : `interval:${Math.floor(clock.now() / everyMs(job.every))}`;
|
|
587
|
+
submit(job, occurrence2).catch(() => {
|
|
578
588
|
return;
|
|
579
589
|
});
|
|
580
590
|
return;
|
|
581
591
|
}
|
|
582
|
-
|
|
592
|
+
const occurrence = job.every === undefined ? `wall:${dueAt}` : `interval:${Math.floor(clock.now() / everyMs(job.every))}`;
|
|
593
|
+
submit(job, occurrence).then(() => schedule(job), () => {
|
|
583
594
|
return;
|
|
584
595
|
});
|
|
585
596
|
}, next));
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-neutral Vault runtime primitives.
|
|
3
|
+
*
|
|
4
|
+
* This module never derives from a realm seed and never stores a key. The API
|
|
5
|
+
* owns those two security boundaries and lends this module one short-lived key
|
|
6
|
+
* for one operation. Keeping the algorithms here makes their byte formats
|
|
7
|
+
* independently testable without making `@forgezero/vault.get()` a signer.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VAULT_RUNTIME_KINDS: readonly ["random-password", "data-encryption-key", "key-encryption-key", "hmac-key", "jwt-hmac-key", "ed25519-key", "secp256k1-key", "ssh-ca-key", "chain-key"];
|
|
10
|
+
export type VaultRuntimeKind = typeof VAULT_RUNTIME_KINDS[number];
|
|
11
|
+
export declare const VAULT_RUNTIME_ALGORITHM_IDS: readonly ["password-v1", "aes-256-gcm", "aes-256-kw", "hmac-sha256", "hmac-sha512", "jws-hs256", "ed25519", "secp256k1", "ssh-ed25519-ca", "ethereum-secp256k1", "solana-ed25519"];
|
|
12
|
+
export type VaultRuntimeAlgorithm = typeof VAULT_RUNTIME_ALGORITHM_IDS[number];
|
|
13
|
+
export type VaultRuntimeOperation = 'generate-password' | 'encrypt' | 'decrypt' | 'wrap' | 'unwrap' | 'sign' | 'verify' | 'sign-jwt' | 'verify-jwt' | 'issue-ssh-certificate' | 'derive-public-key' | 'derive-address';
|
|
14
|
+
export interface VaultRuntimeAlgorithmDefinition {
|
|
15
|
+
kind: VaultRuntimeKind;
|
|
16
|
+
algorithm: VaultRuntimeAlgorithm;
|
|
17
|
+
operations: readonly VaultRuntimeOperation[];
|
|
18
|
+
/** Private material is never a supported output. */
|
|
19
|
+
exports: 'password-only' | 'public-only' | 'none';
|
|
20
|
+
standard: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Typed algorithm truth shared by the API, package documentation and future UI.
|
|
24
|
+
* Adding another chain or credential generator requires a reviewed definition;
|
|
25
|
+
* callers cannot invent an algorithm string and reach an unreviewed primitive.
|
|
26
|
+
*/
|
|
27
|
+
export declare const VAULT_RUNTIME_ALGORITHMS: readonly [{
|
|
28
|
+
readonly kind: "random-password";
|
|
29
|
+
readonly algorithm: "password-v1";
|
|
30
|
+
readonly operations: readonly ["generate-password"];
|
|
31
|
+
readonly exports: "password-only";
|
|
32
|
+
readonly standard: "ForgeZero password-v1";
|
|
33
|
+
}, {
|
|
34
|
+
readonly kind: "data-encryption-key";
|
|
35
|
+
readonly algorithm: "aes-256-gcm";
|
|
36
|
+
readonly operations: readonly ["encrypt", "decrypt"];
|
|
37
|
+
readonly exports: "none";
|
|
38
|
+
readonly standard: "NIST SP 800-38D";
|
|
39
|
+
}, {
|
|
40
|
+
readonly kind: "key-encryption-key";
|
|
41
|
+
readonly algorithm: "aes-256-kw";
|
|
42
|
+
readonly operations: readonly ["wrap", "unwrap"];
|
|
43
|
+
readonly exports: "none";
|
|
44
|
+
readonly standard: "NIST SP 800-38F";
|
|
45
|
+
}, {
|
|
46
|
+
readonly kind: "hmac-key";
|
|
47
|
+
readonly algorithm: "hmac-sha256";
|
|
48
|
+
readonly operations: readonly ["sign", "verify"];
|
|
49
|
+
readonly exports: "none";
|
|
50
|
+
readonly standard: "RFC 2104 / SHA-256";
|
|
51
|
+
}, {
|
|
52
|
+
readonly kind: "hmac-key";
|
|
53
|
+
readonly algorithm: "hmac-sha512";
|
|
54
|
+
readonly operations: readonly ["sign", "verify"];
|
|
55
|
+
readonly exports: "none";
|
|
56
|
+
readonly standard: "RFC 2104 / SHA-512";
|
|
57
|
+
}, {
|
|
58
|
+
readonly kind: "jwt-hmac-key";
|
|
59
|
+
readonly algorithm: "jws-hs256";
|
|
60
|
+
readonly operations: readonly ["sign-jwt", "verify-jwt"];
|
|
61
|
+
readonly exports: "none";
|
|
62
|
+
readonly standard: "RFC 7515 / RFC 7518";
|
|
63
|
+
}, {
|
|
64
|
+
readonly kind: "ed25519-key";
|
|
65
|
+
readonly algorithm: "ed25519";
|
|
66
|
+
readonly operations: readonly ["derive-public-key", "sign", "verify"];
|
|
67
|
+
readonly exports: "public-only";
|
|
68
|
+
readonly standard: "RFC 8032";
|
|
69
|
+
}, {
|
|
70
|
+
readonly kind: "secp256k1-key";
|
|
71
|
+
readonly algorithm: "secp256k1";
|
|
72
|
+
readonly operations: readonly ["derive-public-key", "sign", "verify"];
|
|
73
|
+
readonly exports: "public-only";
|
|
74
|
+
readonly standard: "SEC 2 secp256k1";
|
|
75
|
+
}, {
|
|
76
|
+
readonly kind: "ssh-ca-key";
|
|
77
|
+
readonly algorithm: "ssh-ed25519-ca";
|
|
78
|
+
readonly operations: readonly ["derive-public-key", "issue-ssh-certificate"];
|
|
79
|
+
readonly exports: "public-only";
|
|
80
|
+
readonly standard: "OpenSSH PROTOCOL.certkeys";
|
|
81
|
+
}, {
|
|
82
|
+
readonly kind: "chain-key";
|
|
83
|
+
readonly algorithm: "ethereum-secp256k1";
|
|
84
|
+
readonly operations: readonly ["derive-public-key", "derive-address", "sign", "verify"];
|
|
85
|
+
readonly exports: "public-only";
|
|
86
|
+
readonly standard: "BIP-32/BIP-44/SLIP-44/EIP-55";
|
|
87
|
+
}, {
|
|
88
|
+
readonly kind: "chain-key";
|
|
89
|
+
readonly algorithm: "solana-ed25519";
|
|
90
|
+
readonly operations: readonly ["derive-public-key", "derive-address", "sign", "verify"];
|
|
91
|
+
readonly exports: "public-only";
|
|
92
|
+
readonly standard: "SLIP-10/BIP-44/SLIP-44";
|
|
93
|
+
}];
|
|
94
|
+
export declare const VAULT_RUNTIME_CHAINS: readonly [{
|
|
95
|
+
readonly chain: "ethereum";
|
|
96
|
+
readonly coinType: 60;
|
|
97
|
+
readonly curve: "secp256k1";
|
|
98
|
+
readonly path: "m/44'/60'/account'/change/index";
|
|
99
|
+
}, {
|
|
100
|
+
readonly chain: "solana";
|
|
101
|
+
readonly coinType: 501;
|
|
102
|
+
readonly curve: "ed25519";
|
|
103
|
+
readonly path: "m/44'/501'/account'/change'/index'";
|
|
104
|
+
}];
|
|
105
|
+
export interface PasswordPolicy {
|
|
106
|
+
length: number;
|
|
107
|
+
alphabet?: string;
|
|
108
|
+
}
|
|
109
|
+
export interface EthereumDerivation {
|
|
110
|
+
chain: 'ethereum';
|
|
111
|
+
network?: 'mainnet' | 'sepolia' | string;
|
|
112
|
+
account?: number;
|
|
113
|
+
change?: number;
|
|
114
|
+
index?: number;
|
|
115
|
+
}
|
|
116
|
+
export interface SolanaDerivation {
|
|
117
|
+
chain: 'solana';
|
|
118
|
+
network?: 'mainnet-beta' | 'devnet' | 'testnet' | string;
|
|
119
|
+
account?: number;
|
|
120
|
+
change?: number;
|
|
121
|
+
index?: number;
|
|
122
|
+
}
|
|
123
|
+
export type ChainDerivation = EthereumDerivation | SolanaDerivation;
|
|
124
|
+
type RuntimeDefinition = typeof VAULT_RUNTIME_ALGORITHMS[number];
|
|
125
|
+
/**
|
|
126
|
+
* A correlated creation contract: an algorithm cannot be paired with the wrong
|
|
127
|
+
* key kind, password keys require a policy, and chain keys require a reviewed
|
|
128
|
+
* chain derivation. The API repeats these checks at its trust boundary.
|
|
129
|
+
*/
|
|
130
|
+
export type VaultRuntimeCreateDescriptor = RuntimeDefinition extends infer Definition ? Definition extends {
|
|
131
|
+
kind: infer Kind extends VaultRuntimeKind;
|
|
132
|
+
algorithm: infer Algorithm extends VaultRuntimeAlgorithm;
|
|
133
|
+
} ? {
|
|
134
|
+
kind: Kind;
|
|
135
|
+
algorithm: Algorithm;
|
|
136
|
+
label: string;
|
|
137
|
+
} & (Kind extends 'random-password' ? {
|
|
138
|
+
password: PasswordPolicy;
|
|
139
|
+
chain?: never;
|
|
140
|
+
} : Kind extends 'chain-key' ? {
|
|
141
|
+
chain: ChainDerivation;
|
|
142
|
+
password?: never;
|
|
143
|
+
} : {
|
|
144
|
+
password?: never;
|
|
145
|
+
chain?: never;
|
|
146
|
+
}) : never : never;
|
|
147
|
+
export interface VaultRuntimeDescriptor {
|
|
148
|
+
format: 1;
|
|
149
|
+
kind: VaultRuntimeKind;
|
|
150
|
+
algorithm: VaultRuntimeAlgorithm;
|
|
151
|
+
label: string;
|
|
152
|
+
createdAtTs: number;
|
|
153
|
+
password?: PasswordPolicy;
|
|
154
|
+
chain?: ChainDerivation;
|
|
155
|
+
}
|
|
156
|
+
export interface RuntimeCiphertext {
|
|
157
|
+
algorithm: 'aes-256-gcm';
|
|
158
|
+
nonce: string;
|
|
159
|
+
ciphertext: string;
|
|
160
|
+
}
|
|
161
|
+
export interface RuntimeWrappedKey {
|
|
162
|
+
algorithm: 'aes-256-kw';
|
|
163
|
+
ciphertext: string;
|
|
164
|
+
}
|
|
165
|
+
export declare const DEFAULT_PASSWORD_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*()-_=+";
|
|
166
|
+
export declare function validatePasswordPolicy(policy: PasswordPolicy): void;
|
|
167
|
+
export declare const toBase64Url: (bytes: Uint8Array) => string;
|
|
168
|
+
export declare const fromBase64Url: (value: string) => Uint8Array;
|
|
169
|
+
export declare function passwordFromBytes(entropy: Uint8Array, policy: PasswordPolicy): string;
|
|
170
|
+
export declare function encryptWithDek(key: Uint8Array, plaintext: Uint8Array, aad: string): RuntimeCiphertext;
|
|
171
|
+
export declare function decryptWithDek(key: Uint8Array, box: RuntimeCiphertext, aad: string): Uint8Array;
|
|
172
|
+
export declare function wrapWithKek(key: Uint8Array, plaintextKey: Uint8Array): RuntimeWrappedKey;
|
|
173
|
+
export declare function unwrapWithKek(key: Uint8Array, wrapped: RuntimeWrappedKey): Uint8Array;
|
|
174
|
+
export declare function hmacSign(algorithm: 'hmac-sha256' | 'hmac-sha512', key: Uint8Array, message: Uint8Array): Uint8Array;
|
|
175
|
+
export declare function hmacVerify(algorithm: 'hmac-sha256' | 'hmac-sha512', key: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean;
|
|
176
|
+
export declare function signCompactJws(key: Uint8Array, claims: Record<string, unknown>): string;
|
|
177
|
+
export declare function verifyCompactJws(key: Uint8Array, token: string, options?: {
|
|
178
|
+
nowSec?: number;
|
|
179
|
+
issuer?: string;
|
|
180
|
+
audience?: string;
|
|
181
|
+
}): Record<string, unknown>;
|
|
182
|
+
export declare const ed25519PublicKey: (secret: Uint8Array) => Uint8Array;
|
|
183
|
+
export declare const ed25519Sign: (secret: Uint8Array, message: Uint8Array) => Uint8Array;
|
|
184
|
+
export declare const ed25519Verify: (publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => boolean;
|
|
185
|
+
export declare const secp256k1PublicKey: (secret: Uint8Array, compressed?: boolean) => Uint8Array;
|
|
186
|
+
export declare const secp256k1SignDigest: (secret: Uint8Array, digest: Uint8Array) => Uint8Array;
|
|
187
|
+
export declare const secp256k1VerifyDigest: (publicKey: Uint8Array, digest: Uint8Array, signature: Uint8Array) => boolean;
|
|
188
|
+
export declare function chainPath(chain: ChainDerivation): string;
|
|
189
|
+
export declare function ethereumAddress(uncompressedPublicKey: Uint8Array): string;
|
|
190
|
+
export declare const solanaAddress: (publicKey: Uint8Array) => string;
|
|
191
|
+
export {};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined")
|
|
5
|
+
return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/vault-runtime.ts
|
|
10
|
+
import { aeskw, gcm } from "@noble/ciphers/aes.js";
|
|
11
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
12
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
13
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
14
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
15
|
+
import { sha256, sha512 } from "@noble/hashes/sha2.js";
|
|
16
|
+
import { base58 } from "@scure/base";
|
|
17
|
+
var VAULT_RUNTIME_KINDS = [
|
|
18
|
+
"random-password",
|
|
19
|
+
"data-encryption-key",
|
|
20
|
+
"key-encryption-key",
|
|
21
|
+
"hmac-key",
|
|
22
|
+
"jwt-hmac-key",
|
|
23
|
+
"ed25519-key",
|
|
24
|
+
"secp256k1-key",
|
|
25
|
+
"ssh-ca-key",
|
|
26
|
+
"chain-key"
|
|
27
|
+
];
|
|
28
|
+
var VAULT_RUNTIME_ALGORITHM_IDS = [
|
|
29
|
+
"password-v1",
|
|
30
|
+
"aes-256-gcm",
|
|
31
|
+
"aes-256-kw",
|
|
32
|
+
"hmac-sha256",
|
|
33
|
+
"hmac-sha512",
|
|
34
|
+
"jws-hs256",
|
|
35
|
+
"ed25519",
|
|
36
|
+
"secp256k1",
|
|
37
|
+
"ssh-ed25519-ca",
|
|
38
|
+
"ethereum-secp256k1",
|
|
39
|
+
"solana-ed25519"
|
|
40
|
+
];
|
|
41
|
+
var VAULT_RUNTIME_ALGORITHMS = [
|
|
42
|
+
{ kind: "random-password", algorithm: "password-v1", operations: ["generate-password"], exports: "password-only", standard: "ForgeZero password-v1" },
|
|
43
|
+
{ kind: "data-encryption-key", algorithm: "aes-256-gcm", operations: ["encrypt", "decrypt"], exports: "none", standard: "NIST SP 800-38D" },
|
|
44
|
+
{ kind: "key-encryption-key", algorithm: "aes-256-kw", operations: ["wrap", "unwrap"], exports: "none", standard: "NIST SP 800-38F" },
|
|
45
|
+
{ kind: "hmac-key", algorithm: "hmac-sha256", operations: ["sign", "verify"], exports: "none", standard: "RFC 2104 / SHA-256" },
|
|
46
|
+
{ kind: "hmac-key", algorithm: "hmac-sha512", operations: ["sign", "verify"], exports: "none", standard: "RFC 2104 / SHA-512" },
|
|
47
|
+
{ kind: "jwt-hmac-key", algorithm: "jws-hs256", operations: ["sign-jwt", "verify-jwt"], exports: "none", standard: "RFC 7515 / RFC 7518" },
|
|
48
|
+
{ kind: "ed25519-key", algorithm: "ed25519", operations: ["derive-public-key", "sign", "verify"], exports: "public-only", standard: "RFC 8032" },
|
|
49
|
+
{ kind: "secp256k1-key", algorithm: "secp256k1", operations: ["derive-public-key", "sign", "verify"], exports: "public-only", standard: "SEC 2 secp256k1" },
|
|
50
|
+
{ kind: "ssh-ca-key", algorithm: "ssh-ed25519-ca", operations: ["derive-public-key", "issue-ssh-certificate"], exports: "public-only", standard: "OpenSSH PROTOCOL.certkeys" },
|
|
51
|
+
{ kind: "chain-key", algorithm: "ethereum-secp256k1", operations: ["derive-public-key", "derive-address", "sign", "verify"], exports: "public-only", standard: "BIP-32/BIP-44/SLIP-44/EIP-55" },
|
|
52
|
+
{ kind: "chain-key", algorithm: "solana-ed25519", operations: ["derive-public-key", "derive-address", "sign", "verify"], exports: "public-only", standard: "SLIP-10/BIP-44/SLIP-44" }
|
|
53
|
+
];
|
|
54
|
+
var VAULT_RUNTIME_CHAINS = [
|
|
55
|
+
{ chain: "ethereum", coinType: 60, curve: "secp256k1", path: "m/44'/60'/account'/change/index" },
|
|
56
|
+
{ chain: "solana", coinType: 501, curve: "ed25519", path: "m/44'/501'/account'/change'/index'" }
|
|
57
|
+
];
|
|
58
|
+
var DEFAULT_PASSWORD_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*()-_=+";
|
|
59
|
+
function validatePasswordPolicy(policy) {
|
|
60
|
+
const alphabet = policy.alphabet ?? DEFAULT_PASSWORD_ALPHABET;
|
|
61
|
+
if (!Number.isInteger(policy.length) || policy.length < 16 || policy.length > 256) {
|
|
62
|
+
throw new Error("vault-runtime: password length must be in 16..256");
|
|
63
|
+
}
|
|
64
|
+
if (alphabet.length < 16 || alphabet.length > 128 || new Set(alphabet).size !== alphabet.length) {
|
|
65
|
+
throw new Error("vault-runtime: password alphabet must contain 16..128 unique characters");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
var toBase64Url = (bytes) => {
|
|
69
|
+
let binary = "";
|
|
70
|
+
for (const byte of bytes)
|
|
71
|
+
binary += String.fromCharCode(byte);
|
|
72
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
73
|
+
};
|
|
74
|
+
var fromBase64Url = (value) => {
|
|
75
|
+
if (!/^[A-Za-z0-9_-]*$/.test(value))
|
|
76
|
+
throw new Error("vault-runtime: invalid base64url");
|
|
77
|
+
const padded = value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
78
|
+
let binary;
|
|
79
|
+
try {
|
|
80
|
+
binary = atob(padded);
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error("vault-runtime: invalid base64url");
|
|
83
|
+
}
|
|
84
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
85
|
+
};
|
|
86
|
+
var utf8 = (value) => new TextEncoder().encode(value);
|
|
87
|
+
function passwordFromBytes(entropy, policy) {
|
|
88
|
+
const alphabet = policy.alphabet ?? DEFAULT_PASSWORD_ALPHABET;
|
|
89
|
+
validatePasswordPolicy(policy);
|
|
90
|
+
const limit = 256 - 256 % alphabet.length;
|
|
91
|
+
let output = "";
|
|
92
|
+
for (const byte of entropy) {
|
|
93
|
+
if (byte >= limit)
|
|
94
|
+
continue;
|
|
95
|
+
output += alphabet[byte % alphabet.length];
|
|
96
|
+
if (output.length === policy.length)
|
|
97
|
+
return output;
|
|
98
|
+
}
|
|
99
|
+
throw new Error("vault-runtime: insufficient unbiased password entropy");
|
|
100
|
+
}
|
|
101
|
+
function encryptWithDek(key, plaintext, aad) {
|
|
102
|
+
if (key.length !== 32)
|
|
103
|
+
throw new Error("vault-runtime: DEK must be 32 bytes");
|
|
104
|
+
if (!aad)
|
|
105
|
+
throw new Error("vault-runtime: authenticated data is required");
|
|
106
|
+
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
|
107
|
+
const ciphertext = gcm(key, nonce, utf8(aad)).encrypt(plaintext);
|
|
108
|
+
return { algorithm: "aes-256-gcm", nonce: toBase64Url(nonce), ciphertext: toBase64Url(ciphertext) };
|
|
109
|
+
}
|
|
110
|
+
function decryptWithDek(key, box, aad) {
|
|
111
|
+
if (key.length !== 32)
|
|
112
|
+
throw new Error("vault-runtime: DEK must be 32 bytes");
|
|
113
|
+
if (!aad || box.algorithm !== "aes-256-gcm")
|
|
114
|
+
throw new Error("vault-runtime: invalid ciphertext");
|
|
115
|
+
return gcm(key, fromBase64Url(box.nonce), utf8(aad)).decrypt(fromBase64Url(box.ciphertext));
|
|
116
|
+
}
|
|
117
|
+
function wrapWithKek(key, plaintextKey) {
|
|
118
|
+
if (key.length !== 32)
|
|
119
|
+
throw new Error("vault-runtime: KEK must be 32 bytes");
|
|
120
|
+
if (plaintextKey.length < 16 || plaintextKey.length % 8 !== 0) {
|
|
121
|
+
throw new Error("vault-runtime: wrapped key must be a non-empty multiple of 8 bytes, at least 16 bytes");
|
|
122
|
+
}
|
|
123
|
+
return { algorithm: "aes-256-kw", ciphertext: toBase64Url(aeskw(key).encrypt(plaintextKey)) };
|
|
124
|
+
}
|
|
125
|
+
function unwrapWithKek(key, wrapped) {
|
|
126
|
+
if (key.length !== 32 || wrapped.algorithm !== "aes-256-kw") {
|
|
127
|
+
throw new Error("vault-runtime: invalid wrapped key");
|
|
128
|
+
}
|
|
129
|
+
return aeskw(key).decrypt(fromBase64Url(wrapped.ciphertext));
|
|
130
|
+
}
|
|
131
|
+
function hmacSign(algorithm, key, message) {
|
|
132
|
+
return hmac(algorithm === "hmac-sha256" ? sha256 : sha512, key, message);
|
|
133
|
+
}
|
|
134
|
+
function hmacVerify(algorithm, key, message, signature) {
|
|
135
|
+
const actual = hmacSign(algorithm, key, message);
|
|
136
|
+
if (actual.length !== signature.length)
|
|
137
|
+
return false;
|
|
138
|
+
let difference = 0;
|
|
139
|
+
for (let index = 0;index < actual.length; index += 1)
|
|
140
|
+
difference |= actual[index] ^ signature[index];
|
|
141
|
+
return difference === 0;
|
|
142
|
+
}
|
|
143
|
+
function signCompactJws(key, claims) {
|
|
144
|
+
if (!claims || Array.isArray(claims) || Object.getPrototypeOf(claims) !== Object.prototype) {
|
|
145
|
+
throw new Error("vault-runtime: JWT claims must be a plain object");
|
|
146
|
+
}
|
|
147
|
+
const header = toBase64Url(utf8(JSON.stringify({ alg: "HS256", typ: "JWT" })));
|
|
148
|
+
let serialized;
|
|
149
|
+
try {
|
|
150
|
+
serialized = JSON.stringify(claims);
|
|
151
|
+
} catch {
|
|
152
|
+
throw new Error("vault-runtime: JWT claims must be JSON serializable");
|
|
153
|
+
}
|
|
154
|
+
const payload = toBase64Url(utf8(serialized));
|
|
155
|
+
const input = `${header}.${payload}`;
|
|
156
|
+
return `${input}.${toBase64Url(hmac(sha256, key, utf8(input)))}`;
|
|
157
|
+
}
|
|
158
|
+
function verifyCompactJws(key, token, options = {}) {
|
|
159
|
+
const parts = token.split(".");
|
|
160
|
+
if (parts.length !== 3)
|
|
161
|
+
throw new Error("vault-runtime: malformed compact JWS");
|
|
162
|
+
const [headerPart, payloadPart, signaturePart] = parts;
|
|
163
|
+
let header;
|
|
164
|
+
try {
|
|
165
|
+
header = JSON.parse(new TextDecoder().decode(fromBase64Url(headerPart)));
|
|
166
|
+
} catch {
|
|
167
|
+
throw new Error("vault-runtime: malformed compact JWS");
|
|
168
|
+
}
|
|
169
|
+
if (!header || Array.isArray(header) || typeof header !== "object") {
|
|
170
|
+
throw new Error("vault-runtime: malformed compact JWS");
|
|
171
|
+
}
|
|
172
|
+
const headerRecord = header;
|
|
173
|
+
if (headerRecord.alg !== "HS256" || headerRecord.typ !== "JWT")
|
|
174
|
+
throw new Error("vault-runtime: unsupported JWS header");
|
|
175
|
+
if (!hmacVerify("hmac-sha256", key, utf8(`${headerPart}.${payloadPart}`), fromBase64Url(signaturePart))) {
|
|
176
|
+
throw new Error("vault-runtime: invalid JWS signature");
|
|
177
|
+
}
|
|
178
|
+
let parsedClaims;
|
|
179
|
+
try {
|
|
180
|
+
parsedClaims = JSON.parse(new TextDecoder().decode(fromBase64Url(payloadPart)));
|
|
181
|
+
} catch {
|
|
182
|
+
throw new Error("vault-runtime: malformed JWT claims");
|
|
183
|
+
}
|
|
184
|
+
if (!parsedClaims || Array.isArray(parsedClaims) || typeof parsedClaims !== "object") {
|
|
185
|
+
throw new Error("vault-runtime: JWT claims must be an object");
|
|
186
|
+
}
|
|
187
|
+
const claims = parsedClaims;
|
|
188
|
+
const now = options.nowSec ?? Math.floor(Date.now() / 1000);
|
|
189
|
+
if (claims.exp !== undefined && typeof claims.exp !== "number")
|
|
190
|
+
throw new Error("vault-runtime: JWT exp must be a NumericDate");
|
|
191
|
+
if (claims.nbf !== undefined && typeof claims.nbf !== "number")
|
|
192
|
+
throw new Error("vault-runtime: JWT nbf must be a NumericDate");
|
|
193
|
+
if (typeof claims.exp === "number" && now >= claims.exp)
|
|
194
|
+
throw new Error("vault-runtime: JWT expired");
|
|
195
|
+
if (typeof claims.nbf === "number" && now < claims.nbf)
|
|
196
|
+
throw new Error("vault-runtime: JWT not active");
|
|
197
|
+
if (options.issuer !== undefined && claims.iss !== options.issuer)
|
|
198
|
+
throw new Error("vault-runtime: JWT issuer mismatch");
|
|
199
|
+
if (options.audience !== undefined) {
|
|
200
|
+
const audiences = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
|
|
201
|
+
if (audiences.some((audience) => typeof audience !== "string"))
|
|
202
|
+
throw new Error("vault-runtime: JWT aud must contain strings");
|
|
203
|
+
if (!audiences.includes(options.audience))
|
|
204
|
+
throw new Error("vault-runtime: JWT audience mismatch");
|
|
205
|
+
}
|
|
206
|
+
return claims;
|
|
207
|
+
}
|
|
208
|
+
var ed25519PublicKey = (secret) => ed25519.getPublicKey(secret);
|
|
209
|
+
var ed25519Sign = (secret, message) => ed25519.sign(message, secret);
|
|
210
|
+
var ed25519Verify = (publicKey, message, signature) => ed25519.verify(signature, message, publicKey);
|
|
211
|
+
var secp256k1PublicKey = (secret, compressed = true) => secp256k1.getPublicKey(secret, compressed);
|
|
212
|
+
var secp256k1SignDigest = (secret, digest) => {
|
|
213
|
+
if (digest.length !== 32)
|
|
214
|
+
throw new Error("vault-runtime: secp256k1 requires a 32-byte digest");
|
|
215
|
+
return secp256k1.sign(digest, secret, { prehash: false, format: "recovered" });
|
|
216
|
+
};
|
|
217
|
+
var secp256k1VerifyDigest = (publicKey, digest, signature) => digest.length === 32 && secp256k1.verify(signature, digest, publicKey, { prehash: false, format: "recovered" });
|
|
218
|
+
function chainPath(chain) {
|
|
219
|
+
const bound = (value, label, fallback) => {
|
|
220
|
+
const result = value ?? fallback;
|
|
221
|
+
if (!Number.isInteger(result) || result < 0 || result >= 2147483648) {
|
|
222
|
+
throw new Error(`vault-runtime: ${label} must be in 0..2147483647`);
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
};
|
|
226
|
+
const account = bound(chain.account, "account", 0);
|
|
227
|
+
const change = bound(chain.change, "change", 0);
|
|
228
|
+
const index = bound(chain.index, "index", 0);
|
|
229
|
+
return chain.chain === "ethereum" ? `m/44'/60'/${account}'/${change}/${index}` : `m/44'/501'/${account}'/${change}'/${index}'`;
|
|
230
|
+
}
|
|
231
|
+
function ethereumAddress(uncompressedPublicKey) {
|
|
232
|
+
if (uncompressedPublicKey.length !== 65 || uncompressedPublicKey[0] !== 4) {
|
|
233
|
+
throw new Error("vault-runtime: Ethereum requires an uncompressed secp256k1 public key");
|
|
234
|
+
}
|
|
235
|
+
const lower = Array.from(keccak_256(uncompressedPublicKey.slice(1)).slice(-20), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
236
|
+
const checksum = Array.from(keccak_256(utf8(lower)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
237
|
+
let address = "0x";
|
|
238
|
+
for (let index = 0;index < lower.length; index += 1) {
|
|
239
|
+
const character = lower[index];
|
|
240
|
+
address += Number.parseInt(checksum[index], 16) >= 8 ? character.toUpperCase() : character;
|
|
241
|
+
}
|
|
242
|
+
return address;
|
|
243
|
+
}
|
|
244
|
+
var solanaAddress = (publicKey) => base58.encode(publicKey);
|
|
245
|
+
export {
|
|
246
|
+
wrapWithKek,
|
|
247
|
+
verifyCompactJws,
|
|
248
|
+
validatePasswordPolicy,
|
|
249
|
+
unwrapWithKek,
|
|
250
|
+
toBase64Url,
|
|
251
|
+
solanaAddress,
|
|
252
|
+
signCompactJws,
|
|
253
|
+
secp256k1VerifyDigest,
|
|
254
|
+
secp256k1SignDigest,
|
|
255
|
+
secp256k1PublicKey,
|
|
256
|
+
passwordFromBytes,
|
|
257
|
+
hmacVerify,
|
|
258
|
+
hmacSign,
|
|
259
|
+
fromBase64Url,
|
|
260
|
+
ethereumAddress,
|
|
261
|
+
encryptWithDek,
|
|
262
|
+
ed25519Verify,
|
|
263
|
+
ed25519Sign,
|
|
264
|
+
ed25519PublicKey,
|
|
265
|
+
decryptWithDek,
|
|
266
|
+
chainPath,
|
|
267
|
+
VAULT_RUNTIME_KINDS,
|
|
268
|
+
VAULT_RUNTIME_CHAINS,
|
|
269
|
+
VAULT_RUNTIME_ALGORITHM_IDS,
|
|
270
|
+
VAULT_RUNTIME_ALGORITHMS,
|
|
271
|
+
DEFAULT_PASSWORD_ALPHABET
|
|
272
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgezero/runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
"types": "./dist/identity.d.ts",
|
|
60
60
|
"default": "./dist/identity.js"
|
|
61
61
|
},
|
|
62
|
+
"./vault-runtime": {
|
|
63
|
+
"types": "./dist/vault-runtime.d.ts",
|
|
64
|
+
"default": "./dist/vault-runtime.js"
|
|
65
|
+
},
|
|
62
66
|
"./passkey-hybrid": {
|
|
63
67
|
"types": "./dist/passkey-hybrid.d.ts",
|
|
64
68
|
"default": "./dist/passkey-hybrid.js"
|
|
@@ -134,13 +138,14 @@
|
|
|
134
138
|
"prepublishOnly": "bun ../tools/package-task.ts prepublish runtime"
|
|
135
139
|
},
|
|
136
140
|
"dependencies": {
|
|
137
|
-
"@forgezero/access": "^0.1.
|
|
141
|
+
"@forgezero/access": "^0.1.13"
|
|
138
142
|
},
|
|
139
143
|
"peerDependencies": {
|
|
140
144
|
"@noble/ciphers": "^2.2.0",
|
|
141
145
|
"@noble/curves": "^2.2.0",
|
|
142
146
|
"@noble/post-quantum": "^0.6.1",
|
|
143
147
|
"@noble/hashes": "^2.2.0",
|
|
148
|
+
"@scure/base": "^2.2.0",
|
|
144
149
|
"@sinclair/typebox": "^0.34.0",
|
|
145
150
|
"@scure/bip39": "^2.2.0"
|
|
146
151
|
},
|
|
@@ -162,6 +167,9 @@
|
|
|
162
167
|
},
|
|
163
168
|
"@scure/bip39": {
|
|
164
169
|
"optional": true
|
|
170
|
+
},
|
|
171
|
+
"@scure/base": {
|
|
172
|
+
"optional": true
|
|
165
173
|
}
|
|
166
174
|
},
|
|
167
175
|
"devDependencies": {
|
|
@@ -170,9 +178,12 @@
|
|
|
170
178
|
"@sinclair/typebox": "^0.34.0",
|
|
171
179
|
"@noble/curves": "^2.2.0",
|
|
172
180
|
"@noble/post-quantum": "^0.6.1",
|
|
181
|
+
"@noble/ciphers": "^2.2.0",
|
|
182
|
+
"@noble/hashes": "^2.2.0",
|
|
183
|
+
"@scure/base": "^2.2.0",
|
|
173
184
|
"@scure/bip39": "^2.2.0"
|
|
174
185
|
},
|
|
175
|
-
"description": "
|
|
186
|
+
"description": "Server-neutral runtime primitives for jobs, queues, outbox, audit, Vault cryptography, finance, identity, recovery and deployment automation.",
|
|
176
187
|
"keywords": [
|
|
177
188
|
"jobs",
|
|
178
189
|
"scheduler",
|
|
@@ -186,7 +197,10 @@
|
|
|
186
197
|
"validation",
|
|
187
198
|
"calendar",
|
|
188
199
|
"billing-period",
|
|
189
|
-
"working-days"
|
|
200
|
+
"working-days",
|
|
201
|
+
"vault-runtime",
|
|
202
|
+
"envelope-encryption",
|
|
203
|
+
"signing"
|
|
190
204
|
],
|
|
191
205
|
"license": "MIT",
|
|
192
206
|
"homepage": "https://www.forgezero.net/docs/runtime",
|