@synoi/sraid 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/PROJECTION_SPEC.md +245 -0
  3. package/README.md +149 -0
  4. package/SPEC.md +216 -0
  5. package/dist/attestation.d.ts +92 -0
  6. package/dist/attestation.d.ts.map +1 -0
  7. package/dist/attestation.js +155 -0
  8. package/dist/attestation.js.map +1 -0
  9. package/dist/authority.d.ts +351 -0
  10. package/dist/authority.d.ts.map +1 -0
  11. package/dist/authority.js +563 -0
  12. package/dist/authority.js.map +1 -0
  13. package/dist/canonicalize.d.ts +70 -0
  14. package/dist/canonicalize.d.ts.map +1 -0
  15. package/dist/canonicalize.js +151 -0
  16. package/dist/canonicalize.js.map +1 -0
  17. package/dist/ed25519.d.ts +26 -0
  18. package/dist/ed25519.d.ts.map +1 -0
  19. package/dist/ed25519.js +53 -0
  20. package/dist/ed25519.js.map +1 -0
  21. package/dist/index.d.ts +28 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +46 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/internal/base64.d.ts +28 -0
  26. package/dist/internal/base64.d.ts.map +1 -0
  27. package/dist/internal/base64.js +45 -0
  28. package/dist/internal/base64.js.map +1 -0
  29. package/dist/internal/key-cache.d.ts +47 -0
  30. package/dist/internal/key-cache.d.ts.map +1 -0
  31. package/dist/internal/key-cache.js +77 -0
  32. package/dist/internal/key-cache.js.map +1 -0
  33. package/dist/lineage.d.ts +104 -0
  34. package/dist/lineage.d.ts.map +1 -0
  35. package/dist/lineage.js +0 -0
  36. package/dist/lineage.js.map +1 -0
  37. package/dist/mldsa.d.ts +41 -0
  38. package/dist/mldsa.d.ts.map +1 -0
  39. package/dist/mldsa.js +119 -0
  40. package/dist/mldsa.js.map +1 -0
  41. package/dist/oid.d.ts +109 -0
  42. package/dist/oid.d.ts.map +1 -0
  43. package/dist/oid.js +140 -0
  44. package/dist/oid.js.map +1 -0
  45. package/dist/sensitivity.d.ts +104 -0
  46. package/dist/sensitivity.d.ts.map +1 -0
  47. package/dist/sensitivity.js +117 -0
  48. package/dist/sensitivity.js.map +1 -0
  49. package/dist/signature.d.ts +46 -0
  50. package/dist/signature.d.ts.map +1 -0
  51. package/dist/signature.js +89 -0
  52. package/dist/signature.js.map +1 -0
  53. package/dist/types.d.ts +313 -0
  54. package/dist/types.d.ts.map +1 -0
  55. package/dist/types.js +16 -0
  56. package/dist/types.js.map +1 -0
  57. package/dist/validate.d.ts +88 -0
  58. package/dist/validate.d.ts.map +1 -0
  59. package/dist/validate.js +339 -0
  60. package/dist/validate.js.map +1 -0
  61. package/package.json +69 -0
  62. package/src/attestation.ts +204 -0
  63. package/src/authority.ts +849 -0
  64. package/src/canonicalize.ts +167 -0
  65. package/src/ed25519.ts +60 -0
  66. package/src/index.ts +118 -0
  67. package/src/internal/base64.ts +44 -0
  68. package/src/internal/key-cache.ts +79 -0
  69. package/src/lineage.ts +0 -0
  70. package/src/mldsa.ts +131 -0
  71. package/src/oid.ts +146 -0
  72. package/src/sensitivity.ts +154 -0
  73. package/src/signature.ts +119 -0
  74. package/src/types.ts +351 -0
  75. package/src/validate.ts +402 -0
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @synoi/sraid — canonicalize.ts
3
+ *
4
+ * Strict RFC 8785 (JCS) serializer for SRAID objects. Produces
5
+ * byte-identical output to any other conformant RFC 8785 implementation
6
+ * given the same input, making this the normative canonical form for OID
7
+ * derivation and signing.
8
+ *
9
+ * Rules (strict RFC 8785 JCS):
10
+ * - Primitives are emitted via JSON.stringify (strings are escaped with
11
+ * standard JSON string escaping; booleans and null are literals).
12
+ * - Numbers must be FINITE INTEGERS (ADR_019 decision 2). NaN, Infinity
13
+ * (positive or negative), and any non-integer (float) are REJECTED with
14
+ * a thrown TypeError BEFORE hashing. Forbidding floats everywhere matches
15
+ * GAP-TS / GAP-Python and removes the hardest RFC 8785 cross-language
16
+ * serialization trap from the signed byte string; the product only emits
17
+ * integer minor-unit money and integer ms timestamps. Legal integers use
18
+ * the ECMAScript number-to-string serialization of RFC 8785 §3.2.2.3
19
+ * (equivalent to JS's default toString for integer values).
20
+ * - Arrays preserve element order; elements are recursively canonicalized.
21
+ * - Objects emit their keys sorted in ascending lexicographic
22
+ * (UTF-16 code-unit) order — this is the order produced by
23
+ * Array.prototype.sort(), which is exactly what RFC 8785 §3.2.3
24
+ * specifies for implementations whose host language uses UTF-16
25
+ * string encoding (including ECMAScript/TypeScript).
26
+ * - Object properties whose value is `undefined` are OMITTED entirely.
27
+ * (JSON has no undefined; this matches JSON.stringify semantics.)
28
+ * - No whitespace anywhere. Separators are bare `,` and `:`.
29
+ * - `null` is preserved as `null`.
30
+ *
31
+ * Stability invariant: any change to this function breaks every signed
32
+ * receipt, every OID, every grant — so it must NEVER be changed without
33
+ * a coordinated migration. The output bytes for a given input are a
34
+ * cross-package contract.
35
+ */
36
+ /**
37
+ * Canonicalize an arbitrary JSON-compatible value into a deterministic
38
+ * UTF-8 string following RFC 8785 (JCS). The output is suitable as input
39
+ * to SHA-256 (or any other hash) for OID derivation and as the byte string
40
+ * signed by Ed25519 / ML-DSA-65 envelopes.
41
+ *
42
+ * Reject-loud contract: a TypeError is thrown for any value that is not a
43
+ * JSON value, rather than silently producing a wrong or invalid canonical
44
+ * form (which would corrupt an OID or signature):
45
+ * - NaN / Infinity / -Infinity (RFC 8785 forbids non-finite numbers).
46
+ * - non-integer numbers / floats (ADR_019 forbids them; use integer minor
47
+ * units). e.g. 1.5, 0.1, 2e-3 all throw.
48
+ * - undefined / function / symbol / bigint anywhere they appear as a value,
49
+ * including as an ARRAY element (previously these produced invalid JSON
50
+ * like "[1,,2]").
51
+ * - objects with a toJSON() method (e.g. Date), which a bare keys-walk would
52
+ * silently mis-serialize (a Date became "{}"). Convert to a JSON value
53
+ * (e.g. an ISO string) first.
54
+ * Object properties whose value is `undefined` are OMITTED (matches
55
+ * JSON.stringify); an undefined array element throws (it cannot be omitted).
56
+ *
57
+ * @example
58
+ * canonicalize({ b: 2, a: 1 }) // '{"a":1,"b":2}'
59
+ * canonicalize({ a: undefined, b: 1 }) // '{"b":1}' (object undefined omitted)
60
+ * canonicalize([3, 1, 2]) // '[3,1,2]'
61
+ * canonicalize([1, null, 2]) // '[1,null,2]'
62
+ * canonicalize({ foo: 1, bar: 'hi' }) // '{"bar":"hi","foo":1}'
63
+ * canonicalize(NaN) // throws TypeError
64
+ * canonicalize(Infinity) // throws TypeError
65
+ * canonicalize(1.5) // throws TypeError (floats forbidden, ADR_019)
66
+ * canonicalize([1, undefined, 2]) // throws TypeError (no empty slots)
67
+ * canonicalize(new Date()) // throws TypeError (serialize first)
68
+ */
69
+ export function canonicalize(value) {
70
+ const t = typeof value;
71
+ if (t === 'number') {
72
+ if (!isFinite(value)) {
73
+ throw new TypeError(`canonicalize: RFC 8785 forbids non-finite numbers; received ${String(value)}`);
74
+ }
75
+ // ADR_019 decision 2: FORBID non-integer numbers everywhere. A number is
76
+ // legal iff it is a finite integer. Floats are rejected BEFORE hashing,
77
+ // matching GAP-TS / GAP-Python, so a float-bearing object cannot mint an
78
+ // OID on one surface that another surface declares malformed. This kills
79
+ // the hardest RFC 8785 cross-language trap (float shortest-round-trip
80
+ // serialization) on a signed byte string; the product only ever emits
81
+ // integer minor-unit money and integer millisecond timestamps.
82
+ //
83
+ // Number.isInteger(-0) is true and, with floats forbidden, -0 can only
84
+ // arise as an explicit input; JSON.stringify(-0) === '0', so the former
85
+ // -0 special-case is now redundant and is DELETED (removing a divergence
86
+ // source per ADR_019 rather than carrying a rule that can never fire on
87
+ // legal input).
88
+ if (!Number.isInteger(value)) {
89
+ throw new TypeError(`canonicalize: non-integer numbers are forbidden (ADR_019); received ${String(value)}. ` +
90
+ 'Represent fractional quantities as integer minor units (e.g. cents) before canonicalizing.');
91
+ }
92
+ return JSON.stringify(value);
93
+ }
94
+ if (value === null)
95
+ return 'null';
96
+ if (t === 'string' || t === 'boolean')
97
+ return JSON.stringify(value);
98
+ // Reject-loud: a value that is not a JSON value MUST throw, never silently
99
+ // produce wrong or invalid output. Previously these fell through to
100
+ // JSON.stringify, which returns the JS value `undefined` for undefined /
101
+ // function / symbol — corrupting an array element into invalid JSON like
102
+ // "[1,,2]" — and throws for bigint. An OID/signature must never be derived
103
+ // from a corrupted or out-of-domain canonical form.
104
+ if (t === 'undefined' || t === 'function' || t === 'symbol' || t === 'bigint') {
105
+ throw new TypeError(`canonicalize: value of type "${t}" is not a JSON value and cannot be canonicalized`);
106
+ }
107
+ // value is a non-null object from here.
108
+ if (Array.isArray(value)) {
109
+ // Each element is canonicalized recursively. We walk by index rather than
110
+ // Array.prototype.map because map SKIPS holes in a sparse array — a hole
111
+ // (`[1, , 2]`, distinct from an explicit `[1, undefined, 2]`) is never
112
+ // visited by map, so it would slip past the reject-loud element checks and
113
+ // join() would emit invalid, index-shifting JSON like "[1,,2]". A hole is
114
+ // out-of-domain input (JSON has no concept of a missing element); per the
115
+ // reject-loud contract it throws, exactly as an explicit undefined element
116
+ // does, rather than silently coercing to null.
117
+ const parts = [];
118
+ for (let i = 0; i < value.length; i++) {
119
+ if (!(i in value)) {
120
+ throw new TypeError(`canonicalize: sparse array hole at index ${i} is not a JSON value and ` +
121
+ 'cannot be canonicalized (it would produce invalid JSON like "[1,,2]"); ' +
122
+ 'fill the slot with an explicit value (e.g. null) before canonicalizing');
123
+ }
124
+ parts.push(canonicalize(value[i]));
125
+ }
126
+ return '[' + parts.join(',') + ']';
127
+ }
128
+ // Reject objects that define their own JSON projection (Date, Buffer,
129
+ // Decimal, BigNumber, ...). RFC 8785 / JSON.stringify call toJSON() first; a
130
+ // bare keys-walk does not, so a Date would silently canonicalize to "{}" and
131
+ // fork the OID. Callers must convert to a JSON value (e.g. an ISO string)
132
+ // before canonicalizing.
133
+ if (typeof value.toJSON === 'function') {
134
+ throw new TypeError('canonicalize: objects with a toJSON() method (e.g. Date) are not accepted; ' +
135
+ 'serialize them to a JSON value (e.g. an ISO string) before canonicalizing');
136
+ }
137
+ const obj = value;
138
+ // Object properties whose value is `undefined` are OMITTED (matches
139
+ // JSON.stringify and the JSON data model). This is the one omission we honor;
140
+ // an undefined ARRAY element throws (above), because dropping it would shift
141
+ // indices and silently change meaning.
142
+ const keys = Object.keys(obj)
143
+ .filter((k) => obj[k] !== undefined)
144
+ .sort();
145
+ return ('{' +
146
+ keys
147
+ .map((k) => JSON.stringify(k) + ':' + canonicalize(obj[k]))
148
+ .join(',') +
149
+ '}');
150
+ }
151
+ //# sourceMappingURL=canonicalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalize.js","sourceRoot":"","sources":["../src/canonicalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,MAAM,CAAC,GAAG,OAAO,KAAK,CAAA;IAEtB,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnB,IAAI,CAAC,QAAQ,CAAC,KAAe,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,SAAS,CACjB,+DAA+D,MAAM,CAAC,KAAK,CAAC,EAAE,CAC/E,CAAA;QACH,CAAC;QACD,yEAAyE;QACzE,wEAAwE;QACxE,yEAAyE;QACzE,yEAAyE;QACzE,sEAAsE;QACtE,sEAAsE;QACtE,+DAA+D;QAC/D,EAAE;QACF,uEAAuE;QACvE,wEAAwE;QACxE,yEAAyE;QACzE,wEAAwE;QACxE,gBAAgB;QAChB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAe,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,SAAS,CACjB,uEAAuE,MAAM,CAAC,KAAK,CAAC,IAAI;gBACtF,4FAA4F,CAC/F,CAAA;QACH,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC9B,CAAC;IAED,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAA;IACjC,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAyB,CAAC,CAAA;IAEvF,2EAA2E;IAC3E,oEAAoE;IACpE,yEAAyE;IACzE,yEAAyE;IACzE,2EAA2E;IAC3E,oDAAoD;IACpD,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC9E,MAAM,IAAI,SAAS,CACjB,gCAAgC,CAAC,mDAAmD,CACrF,CAAA;IACH,CAAC;IAED,wCAAwC;IACxC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,0EAA0E;QAC1E,yEAAyE;QACzE,uEAAuE;QACvE,2EAA2E;QAC3E,0EAA0E;QAC1E,0EAA0E;QAC1E,2EAA2E;QAC3E,+CAA+C;QAC/C,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBAClB,MAAM,IAAI,SAAS,CACjB,4CAA4C,CAAC,2BAA2B;oBACtE,yEAAyE;oBACzE,wEAAwE,CAC3E,CAAA;YACH,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACpC,CAAC;QACD,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;IACpC,CAAC;IAED,sEAAsE;IACtE,6EAA6E;IAC7E,6EAA6E;IAC7E,0EAA0E;IAC1E,yBAAyB;IACzB,IAAI,OAAQ,KAA8B,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACjE,MAAM,IAAI,SAAS,CACjB,6EAA6E;YAC3E,2EAA2E,CAC9E,CAAA;IACH,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAA;IAC5C,oEAAoE;IACpE,8EAA8E;IAC9E,6EAA6E;IAC7E,uCAAuC;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;SACnC,IAAI,EAAE,CAAA;IACT,OAAO,CACL,GAAG;QACH,IAAI;aACD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;aAC1D,IAAI,CAAC,GAAG,CAAC;QACZ,GAAG,CACJ,CAAA;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @synoi/sraid — ed25519.ts
3
+ *
4
+ * Native Ed25519 verification via node:crypto (OpenSSL). Verifying RFC 8032
5
+ * Ed25519 signatures with the platform crypto is materially faster than a
6
+ * pure-JS implementation (~30x on the verify op) and produces the same
7
+ * accept/reject result for all standard signatures.
8
+ *
9
+ * NOTE on verification semantics: node:crypto / OpenSSL uses RFC 8032
10
+ * cofactored verification. Some pure-JS libraries (e.g. @noble) default to
11
+ * ZIP-215 batch-compatible rules, which accept a slightly larger set of
12
+ * non-canonical edge-case points. The two agree on every well-formed
13
+ * signature; they can differ only on deliberately malformed/malleable points
14
+ * that real signers never produce. SynOI signs canonically, so this is a
15
+ * tightening, not a behavior change, but it is called out for maintainers.
16
+ *
17
+ * The public API takes a RAW 32-byte Ed25519 public key; this module wraps it
18
+ * in the fixed SPKI DER prefix to build a KeyObject.
19
+ */
20
+ /**
21
+ * Verify a raw 64-byte Ed25519 signature over `message` against a raw 32-byte
22
+ * public key. Returns false (never throws) on any malformed input or
23
+ * verification failure, so callers get a clean boolean.
24
+ */
25
+ export declare function verifyEd25519(signature: Uint8Array, message: Uint8Array, publicKeyRaw: Uint8Array): boolean;
26
+ //# sourceMappingURL=ed25519.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ed25519.d.ts","sourceRoot":"","sources":["../src/ed25519.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA0BH;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,UAAU,EACnB,YAAY,EAAE,UAAU,GACvB,OAAO,CAMT"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @synoi/sraid — ed25519.ts
3
+ *
4
+ * Native Ed25519 verification via node:crypto (OpenSSL). Verifying RFC 8032
5
+ * Ed25519 signatures with the platform crypto is materially faster than a
6
+ * pure-JS implementation (~30x on the verify op) and produces the same
7
+ * accept/reject result for all standard signatures.
8
+ *
9
+ * NOTE on verification semantics: node:crypto / OpenSSL uses RFC 8032
10
+ * cofactored verification. Some pure-JS libraries (e.g. @noble) default to
11
+ * ZIP-215 batch-compatible rules, which accept a slightly larger set of
12
+ * non-canonical edge-case points. The two agree on every well-formed
13
+ * signature; they can differ only on deliberately malformed/malleable points
14
+ * that real signers never produce. SynOI signs canonically, so this is a
15
+ * tightening, not a behavior change, but it is called out for maintainers.
16
+ *
17
+ * The public API takes a RAW 32-byte Ed25519 public key; this module wraps it
18
+ * in the fixed SPKI DER prefix to build a KeyObject.
19
+ */
20
+ import { createPublicKey, verify as nodeVerify } from 'node:crypto';
21
+ import { BoundedKeyCache, KEY_CACHE_MAX } from './internal/key-cache.js';
22
+ // Fixed DER prefix for an Ed25519 SubjectPublicKeyInfo (RFC 8410):
23
+ // SEQUENCE { SEQUENCE { OID 1.3.101.112 } BIT STRING (32 raw bytes) }
24
+ const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
25
+ // Small bounded LRU: building a KeyObject parses DER, so cache by raw-key hex.
26
+ // Bounded so an attacker streaming distinct keys cannot grow it without limit.
27
+ const keyCache = new BoundedKeyCache(KEY_CACHE_MAX);
28
+ function keyObjectFromRaw(raw) {
29
+ if (raw.length !== 32)
30
+ throw new Error('ed25519 public key must be 32 bytes');
31
+ const hex = Buffer.from(raw).toString('hex');
32
+ let ko = keyCache.get(hex);
33
+ if (!ko) {
34
+ const der = Buffer.concat([ED25519_SPKI_PREFIX, Buffer.from(raw)]);
35
+ ko = createPublicKey({ key: der, format: 'der', type: 'spki' });
36
+ keyCache.set(hex, ko);
37
+ }
38
+ return ko;
39
+ }
40
+ /**
41
+ * Verify a raw 64-byte Ed25519 signature over `message` against a raw 32-byte
42
+ * public key. Returns false (never throws) on any malformed input or
43
+ * verification failure, so callers get a clean boolean.
44
+ */
45
+ export function verifyEd25519(signature, message, publicKeyRaw) {
46
+ try {
47
+ return nodeVerify(null, message, keyObjectFromRaw(publicKeyRaw), signature);
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ //# sourceMappingURL=ed25519.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ed25519.js","sourceRoot":"","sources":["../src/ed25519.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,UAAU,EAAkB,MAAM,aAAa,CAAA;AAEnF,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAExE,mEAAmE;AACnE,wEAAwE;AACxE,MAAM,mBAAmB,GAAG,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAA;AAE1E,+EAA+E;AAC/E,+EAA+E;AAC/E,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAY,aAAa,CAAC,CAAA;AAE9D,SAAS,gBAAgB,CAAC,GAAe;IACvC,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IAC7E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC5C,IAAI,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC1B,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAClE,EAAE,GAAG,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QAC/D,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IACvB,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAqB,EACrB,OAAmB,EACnB,YAAwB;IAExB,IAAI,CAAC;QACH,OAAO,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,CAAA;IAC7E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @synoi/sraid — public surface.
3
+ *
4
+ * SRAID (Self-Routing Addressable Identity Data) is L0 of the SynOI SRAID Stack:
5
+ *
6
+ * L0 SRAID ← this package
7
+ * L1 Vault / Resolver
8
+ * L2 Inference Broker / Resonance
9
+ * L3 GAP (Governed Action Protocol)
10
+ * L4 Apps
11
+ *
12
+ * This package is intentionally small: types, canonical serializer,
13
+ * OID computation, hybrid signature verification, and shape validators.
14
+ * No storage, no HTTP, no governance — those are higher layers.
15
+ */
16
+ export type { AttestationEnvelope, AttestationSignature, AuthorityBlock, AuthorityDecision, CDRO, LineageLink, LinkRel, SRO, SROBody, SensitivityTier, SignatureEnvelope, } from './types.js';
17
+ export { canonicalize } from './canonicalize.js';
18
+ export { oidOf, oidOfCanonical, cdroOid, cdroContentCore, CDRO_ENVELOPE_FIELDS } from './oid.js';
19
+ export { verifySignature, type VerifySignatureInput, type VerifySignatureResult, } from './signature.js';
20
+ export { verifyMlDsa65, isNativeMlDsaAvailable } from './mldsa.js';
21
+ export { evictKeyFromCaches } from './internal/key-cache.js';
22
+ export { verifyAttestation, pae, ALG_ED25519, ALG_ML_DSA_65, type VerifyAttestationInput, type VerifyAttestationResult, } from './attestation.js';
23
+ export { lineageLinks, supersededOids, latestWins, type LatestWinsResult, } from './lineage.js';
24
+ export { SENSITIVITY_TIERS, SENSITIVITY_DEFAULT, isSensitivityTier, sensitivityRank, sensitivityMax, sensitivityCarryForward, sensitivityMonotoneCheck, } from './sensitivity.js';
25
+ export { verifyAuthority, capabilityCovers, type VerifyAuthorityInput, type VerifyAuthorityResult, type AuthorityResolver, type GrantStatus, type GrantBodyShape, } from './authority.js';
26
+ export { verifyDelegationChain, MAX_DELEGATION_DEPTH, type VerifyDelegationChainInput, type VerifyDelegationChainResult, type HopResult, type LinkPubkeys, } from './authority.js';
27
+ export { validateCdro, validateSro, validateSignatureEnvelope, validateAttestationEnvelope, validateAuthorityBlock, validateLineageLink, type ValidationResult, } from './validate.js';
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,iBAAiB,EACjB,IAAI,EACJ,WAAW,EACX,OAAO,EACP,GAAG,EACH,OAAO,EACP,eAAe,EACf,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAGhG,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,gBAAgB,CAAA;AAKvB,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAMlE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAK5D,OAAO,EACL,iBAAiB,EACjB,GAAG,EACH,WAAW,EACX,aAAa,EACb,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAA;AAGrB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAA;AAIvB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,KAAK,SAAS,EACd,KAAK,WAAW,GACjB,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,yBAAyB,EACzB,2BAA2B,EAC3B,sBAAsB,EACtB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @synoi/sraid — public surface.
3
+ *
4
+ * SRAID (Self-Routing Addressable Identity Data) is L0 of the SynOI SRAID Stack:
5
+ *
6
+ * L0 SRAID ← this package
7
+ * L1 Vault / Resolver
8
+ * L2 Inference Broker / Resonance
9
+ * L3 GAP (Governed Action Protocol)
10
+ * L4 Apps
11
+ *
12
+ * This package is intentionally small: types, canonical serializer,
13
+ * OID computation, hybrid signature verification, and shape validators.
14
+ * No storage, no HTTP, no governance — those are higher layers.
15
+ */
16
+ // Canonical serializer — byte-identical to synoi-gateway + @synoi/vault.
17
+ export { canonicalize } from './canonicalize.js';
18
+ // OID computation — value-level and full-CDRO content-core helpers.
19
+ export { oidOf, oidOfCanonical, cdroOid, cdroContentCore, CDRO_ENVELOPE_FIELDS } from './oid.js';
20
+ // Hybrid Ed25519 + ML-DSA-65 signature verification (LEGACY bare-bytes path).
21
+ export { verifySignature, } from './signature.js';
22
+ // ML-DSA-65 verification with a native node:crypto fast path (OpenSSL 3.5+ /
23
+ // Node 24+) and a transparent @noble fallback on older runtimes. Both paths
24
+ // are byte-identical; `isNativeMlDsaAvailable()` reports which is active.
25
+ export { verifyMlDsa65, isNativeMlDsaAvailable } from './mldsa.js';
26
+ // Coordinated key-cache revocation. The internal bounded LRU key caches
27
+ // (ed25519 + ml-dsa-65) are keyed by lowercase hex of the RAW public-key
28
+ // bytes; `evictKeyFromCaches(keyId)` drops that entry from every cache so a
29
+ // rotated or compromised key cannot be served stale on a later verify.
30
+ export { evictKeyFromCaches } from './internal/key-cache.js';
31
+ // L2 DSSE attestation — PAE type-binding, hybrid Ed25519 + ML-DSA-65 both
32
+ // required. The preferred signing path (replaces the legacy bare-bytes
33
+ // SignatureEnvelope; closes the cross-type confusion gap SRAID F7 / A4).
34
+ export { verifyAttestation, pae, ALG_ED25519, ALG_ML_DSA_65, } from './attestation.js';
35
+ // L3 lineage (Merkle-DAG) — unify supersedes/SRO/prev; latest-wins resolution.
36
+ export { lineageLinks, supersededOids, latestWins, } from './lineage.js';
37
+ // L4 propagating sensitivity — coarse, opaque tier + monotone max() carry-forward.
38
+ export { SENSITIVITY_TIERS, SENSITIVITY_DEFAULT, isSensitivityTier, sensitivityRank, sensitivityMax, sensitivityCarryForward, sensitivityMonotoneCheck, } from './sensitivity.js';
39
+ // L4 authority verification (the authorized axis).
40
+ export { verifyAuthority, capabilityCovers, } from './authority.js';
41
+ // L4 delegation-chain verification (K2) — VERIFY-ONLY, offline, no enforcement.
42
+ // Always reports revocation_checked/existence_checked false (no live claim).
43
+ export { verifyDelegationChain, MAX_DELEGATION_DEPTH, } from './authority.js';
44
+ // Shape validators.
45
+ export { validateCdro, validateSro, validateSignatureEnvelope, validateAttestationEnvelope, validateAuthorityBlock, validateLineageLink, } from './validate.js';
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiBH,yEAAyE;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,oEAAoE;AACpE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAEhG,8EAA8E;AAC9E,OAAO,EACL,eAAe,GAGhB,MAAM,gBAAgB,CAAA;AAEvB,6EAA6E;AAC7E,4EAA4E;AAC5E,0EAA0E;AAC1E,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAElE,wEAAwE;AACxE,yEAAyE;AACzE,4EAA4E;AAC5E,uEAAuE;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5D,0EAA0E;AAC1E,uEAAuE;AACvE,yEAAyE;AACzE,OAAO,EACL,iBAAiB,EACjB,GAAG,EACH,WAAW,EACX,aAAa,GAGd,MAAM,kBAAkB,CAAA;AAEzB,+EAA+E;AAC/E,OAAO,EACL,YAAY,EACZ,cAAc,EACd,UAAU,GAEX,MAAM,cAAc,CAAA;AAErB,mFAAmF;AACnF,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,uBAAuB,EACvB,wBAAwB,GACzB,MAAM,kBAAkB,CAAA;AAEzB,mDAAmD;AACnD,OAAO,EACL,eAAe,EACf,gBAAgB,GAMjB,MAAM,gBAAgB,CAAA;AAEvB,gFAAgF;AAChF,6EAA6E;AAC7E,OAAO,EACL,qBAAqB,EACrB,oBAAoB,GAKrB,MAAM,gBAAgB,CAAA;AAEvB,oBAAoB;AACpB,OAAO,EACL,YAAY,EACZ,WAAW,EACX,yBAAyB,EACzB,2BAA2B,EAC3B,sBAAsB,EACtB,mBAAmB,GAEpB,MAAM,eAAe,CAAA"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @synoi/sraid — internal/base64.ts
3
+ *
4
+ * Strict standard-base64 (RFC 4648 §4, with `=` padding) decode.
5
+ *
6
+ * INTERNAL: not exported from index.ts.
7
+ *
8
+ * Why strict: `Buffer.from(s, 'base64')` silently TRUNCATES at the first byte
9
+ * outside the alphabet and tolerates missing/extra padding. On a signature-
10
+ * verification path that is dangerous — a malformed envelope must be rejected
11
+ * loudly, not decoded to a short prefix that then "verifies" against something
12
+ * unintended. `decodeBase64Strict` validates the alphabet, length, and padding
13
+ * BEFORE decoding and throws on any deviation.
14
+ *
15
+ * Canonical inputs carry no surrounding whitespace, so no trim is applied;
16
+ * leading/trailing whitespace is itself a rejection.
17
+ */
18
+ /**
19
+ * Validate strict standard base64. Throws Error('base64-malformed') on any
20
+ * non-conforming input.
21
+ */
22
+ export declare function assertBase64(s: string): void;
23
+ /**
24
+ * Strictly decode standard base64 to raw bytes. Throws
25
+ * Error('base64-malformed') on any non-conforming input.
26
+ */
27
+ export declare function decodeBase64Strict(s: string): Uint8Array;
28
+ //# sourceMappingURL=base64.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../src/internal/base64.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAQ5C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAGxD"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @synoi/sraid — internal/base64.ts
3
+ *
4
+ * Strict standard-base64 (RFC 4648 §4, with `=` padding) decode.
5
+ *
6
+ * INTERNAL: not exported from index.ts.
7
+ *
8
+ * Why strict: `Buffer.from(s, 'base64')` silently TRUNCATES at the first byte
9
+ * outside the alphabet and tolerates missing/extra padding. On a signature-
10
+ * verification path that is dangerous — a malformed envelope must be rejected
11
+ * loudly, not decoded to a short prefix that then "verifies" against something
12
+ * unintended. `decodeBase64Strict` validates the alphabet, length, and padding
13
+ * BEFORE decoding and throws on any deviation.
14
+ *
15
+ * Canonical inputs carry no surrounding whitespace, so no trim is applied;
16
+ * leading/trailing whitespace is itself a rejection.
17
+ */
18
+ // Standard base64 alphabet; `=` only as 1-2 trailing pad chars. Total length
19
+ // must be a multiple of 4.
20
+ const STD_B64 = /^[A-Za-z0-9+/]*={0,2}$/;
21
+ /**
22
+ * Validate strict standard base64. Throws Error('base64-malformed') on any
23
+ * non-conforming input.
24
+ */
25
+ export function assertBase64(s) {
26
+ if (typeof s !== 'string')
27
+ throw new Error('base64-malformed');
28
+ if (s.length % 4 !== 0)
29
+ throw new Error('base64-malformed');
30
+ if (!STD_B64.test(s))
31
+ throw new Error('base64-malformed');
32
+ // `=` may appear only in the final 1-2 positions. The regex `={0,2}` anchored
33
+ // at end already guarantees pad chars are contiguous and trailing, but a pad
34
+ // char earlier in the body would have been rejected by the alphabet class —
35
+ // so an in-body `=` cannot pass STD_B64. No extra check needed.
36
+ }
37
+ /**
38
+ * Strictly decode standard base64 to raw bytes. Throws
39
+ * Error('base64-malformed') on any non-conforming input.
40
+ */
41
+ export function decodeBase64Strict(s) {
42
+ assertBase64(s);
43
+ return new Uint8Array(Buffer.from(s, 'base64'));
44
+ }
45
+ //# sourceMappingURL=base64.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.js","sourceRoot":"","sources":["../../src/internal/base64.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,6EAA6E;AAC7E,2BAA2B;AAC3B,MAAM,OAAO,GAAG,wBAAwB,CAAA;AAExC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IAC9D,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IAC3D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAA;IACzD,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,gEAAgE;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAS;IAC1C,YAAY,CAAC,CAAC,CAAC,CAAA;IACf,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAA;AACjD,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @synoi/sraid — internal/key-cache.ts
3
+ *
4
+ * Bounded, insertion-order LRU for cached KeyObjects, plus a registry so a
5
+ * single revocation call can fan out across every live cache.
6
+ *
7
+ * INTERNAL: not exported from index.ts. Only `evictKeyFromCaches` is public.
8
+ *
9
+ * Why a hand-rolled LRU and not `lru-cache`: this package is deliberately
10
+ * zero-runtime-dependency (only @noble/* dev/peer surface). Pulling an npm LRU
11
+ * to bound a key cache would widen the supply-chain attack surface of the exact
12
+ * L0 library whose value is a minimal, auditable core. A ~40-line insertion-
13
+ * order LRU costs nothing and keeps that posture.
14
+ *
15
+ * The cache key is the lowercase hex of the RAW public-key bytes — the same
16
+ * string mldsa.ts and ed25519.ts already use as their Map key. It is NOT an
17
+ * OID, DID, or kid. ed25519 keys (32B hex) and ml-dsa-65 keys (1952B hex)
18
+ * never collide, so the two caches share one key namespace safely and a
19
+ * cross-cache evict is a harmless no-op when the key is absent.
20
+ */
21
+ /** Per-cache capacity. At 1952 bytes/ml-dsa-key this bounds raw-key-equivalent
22
+ * residency near ~2MB plus KeyObject overhead — bounded and safe. */
23
+ export declare const KEY_CACHE_MAX = 1000;
24
+ /**
25
+ * Insertion-order LRU. Map preserves insertion order, so the oldest live entry
26
+ * is always `keys().next()`. A `get` hit re-inserts (delete + set) to promote
27
+ * the entry to most-recently-used. `set` evicts the oldest when at capacity.
28
+ */
29
+ export declare class BoundedKeyCache<V> {
30
+ readonly max: number;
31
+ private readonly map;
32
+ constructor(max?: number);
33
+ get size(): number;
34
+ get(key: string): V | undefined;
35
+ set(key: string, value: V): void;
36
+ delete(key: string): boolean;
37
+ }
38
+ /**
39
+ * Evict a key from every live cache. Use for coordinated key revocation: when a
40
+ * signing key is rotated or compromised, drop any cached KeyObject for it so a
41
+ * later verify cannot be served a stale entry.
42
+ *
43
+ * @param keyId lowercase hex of the RAW public-key bytes (the cache key). Not
44
+ * an OID/DID/kid. Deleting an absent key is a harmless no-op.
45
+ */
46
+ export declare function evictKeyFromCaches(keyId: string): void;
47
+ //# sourceMappingURL=key-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-cache.d.ts","sourceRoot":"","sources":["../../src/internal/key-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;qEACqE;AACrE,eAAO,MAAM,aAAa,OAAO,CAAA;AAKjC;;;;GAIG;AACH,qBAAa,eAAe,CAAC,CAAC;IAGhB,QAAQ,CAAC,GAAG,EAAE,MAAM;IAFhC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuB;gBAEtB,GAAG,GAAE,MAAsB;IAIhD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAS/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAUhC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAG7B;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEtD"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @synoi/sraid — internal/key-cache.ts
3
+ *
4
+ * Bounded, insertion-order LRU for cached KeyObjects, plus a registry so a
5
+ * single revocation call can fan out across every live cache.
6
+ *
7
+ * INTERNAL: not exported from index.ts. Only `evictKeyFromCaches` is public.
8
+ *
9
+ * Why a hand-rolled LRU and not `lru-cache`: this package is deliberately
10
+ * zero-runtime-dependency (only @noble/* dev/peer surface). Pulling an npm LRU
11
+ * to bound a key cache would widen the supply-chain attack surface of the exact
12
+ * L0 library whose value is a minimal, auditable core. A ~40-line insertion-
13
+ * order LRU costs nothing and keeps that posture.
14
+ *
15
+ * The cache key is the lowercase hex of the RAW public-key bytes — the same
16
+ * string mldsa.ts and ed25519.ts already use as their Map key. It is NOT an
17
+ * OID, DID, or kid. ed25519 keys (32B hex) and ml-dsa-65 keys (1952B hex)
18
+ * never collide, so the two caches share one key namespace safely and a
19
+ * cross-cache evict is a harmless no-op when the key is absent.
20
+ */
21
+ /** Per-cache capacity. At 1952 bytes/ml-dsa-key this bounds raw-key-equivalent
22
+ * residency near ~2MB plus KeyObject overhead — bounded and safe. */
23
+ export const KEY_CACHE_MAX = 1000;
24
+ /** Registry of every live cache, for coordinated eviction. */
25
+ const registry = [];
26
+ /**
27
+ * Insertion-order LRU. Map preserves insertion order, so the oldest live entry
28
+ * is always `keys().next()`. A `get` hit re-inserts (delete + set) to promote
29
+ * the entry to most-recently-used. `set` evicts the oldest when at capacity.
30
+ */
31
+ export class BoundedKeyCache {
32
+ max;
33
+ map = new Map();
34
+ constructor(max = KEY_CACHE_MAX) {
35
+ this.max = max;
36
+ registry.push(this);
37
+ }
38
+ get size() {
39
+ return this.map.size;
40
+ }
41
+ get(key) {
42
+ const v = this.map.get(key);
43
+ if (v === undefined)
44
+ return undefined;
45
+ // Promote to most-recently-used.
46
+ this.map.delete(key);
47
+ this.map.set(key, v);
48
+ return v;
49
+ }
50
+ set(key, value) {
51
+ // If present, drop first so re-insert lands at the MRU end.
52
+ if (this.map.has(key))
53
+ this.map.delete(key);
54
+ else if (this.map.size >= this.max) {
55
+ const oldest = this.map.keys().next().value;
56
+ if (oldest !== undefined)
57
+ this.map.delete(oldest);
58
+ }
59
+ this.map.set(key, value);
60
+ }
61
+ delete(key) {
62
+ return this.map.delete(key);
63
+ }
64
+ }
65
+ /**
66
+ * Evict a key from every live cache. Use for coordinated key revocation: when a
67
+ * signing key is rotated or compromised, drop any cached KeyObject for it so a
68
+ * later verify cannot be served a stale entry.
69
+ *
70
+ * @param keyId lowercase hex of the RAW public-key bytes (the cache key). Not
71
+ * an OID/DID/kid. Deleting an absent key is a harmless no-op.
72
+ */
73
+ export function evictKeyFromCaches(keyId) {
74
+ for (const cache of registry)
75
+ cache.delete(keyId);
76
+ }
77
+ //# sourceMappingURL=key-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-cache.js","sourceRoot":"","sources":["../../src/internal/key-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;qEACqE;AACrE,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAA;AAEjC,8DAA8D;AAC9D,MAAM,QAAQ,GAA+B,EAAE,CAAA;AAE/C;;;;GAIG;AACH,MAAM,OAAO,eAAe;IAGL;IAFJ,GAAG,GAAG,IAAI,GAAG,EAAa,CAAA;IAE3C,YAAqB,MAAc,aAAa;QAA3B,QAAG,GAAH,GAAG,CAAwB;QAC9C,QAAQ,CAAC,IAAI,CAAC,IAAgC,CAAC,CAAA;IACjD,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAA;IACtB,CAAC;IAED,GAAG,CAAC,GAAW;QACb,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QACrC,iCAAiC;QACjC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACpB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACpB,OAAO,CAAC,CAAA;IACV,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAQ;QACvB,4DAA4D;QAC5D,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;aACtC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;YAC3C,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACnD,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC1B,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC7B,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,KAAK,MAAM,KAAK,IAAI,QAAQ;QAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACnD,CAAC"}