@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.
- package/LICENSE +21 -0
- package/PROJECTION_SPEC.md +245 -0
- package/README.md +149 -0
- package/SPEC.md +216 -0
- package/dist/attestation.d.ts +92 -0
- package/dist/attestation.d.ts.map +1 -0
- package/dist/attestation.js +155 -0
- package/dist/attestation.js.map +1 -0
- package/dist/authority.d.ts +351 -0
- package/dist/authority.d.ts.map +1 -0
- package/dist/authority.js +563 -0
- package/dist/authority.js.map +1 -0
- package/dist/canonicalize.d.ts +70 -0
- package/dist/canonicalize.d.ts.map +1 -0
- package/dist/canonicalize.js +151 -0
- package/dist/canonicalize.js.map +1 -0
- package/dist/ed25519.d.ts +26 -0
- package/dist/ed25519.d.ts.map +1 -0
- package/dist/ed25519.js +53 -0
- package/dist/ed25519.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/base64.d.ts +28 -0
- package/dist/internal/base64.d.ts.map +1 -0
- package/dist/internal/base64.js +45 -0
- package/dist/internal/base64.js.map +1 -0
- package/dist/internal/key-cache.d.ts +47 -0
- package/dist/internal/key-cache.d.ts.map +1 -0
- package/dist/internal/key-cache.js +77 -0
- package/dist/internal/key-cache.js.map +1 -0
- package/dist/lineage.d.ts +104 -0
- package/dist/lineage.d.ts.map +1 -0
- package/dist/lineage.js +0 -0
- package/dist/lineage.js.map +1 -0
- package/dist/mldsa.d.ts +41 -0
- package/dist/mldsa.d.ts.map +1 -0
- package/dist/mldsa.js +119 -0
- package/dist/mldsa.js.map +1 -0
- package/dist/oid.d.ts +109 -0
- package/dist/oid.d.ts.map +1 -0
- package/dist/oid.js +140 -0
- package/dist/oid.js.map +1 -0
- package/dist/sensitivity.d.ts +104 -0
- package/dist/sensitivity.d.ts.map +1 -0
- package/dist/sensitivity.js +117 -0
- package/dist/sensitivity.js.map +1 -0
- package/dist/signature.d.ts +46 -0
- package/dist/signature.d.ts.map +1 -0
- package/dist/signature.js +89 -0
- package/dist/signature.js.map +1 -0
- package/dist/types.d.ts +313 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +88 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +339 -0
- package/dist/validate.js.map +1 -0
- package/package.json +69 -0
- package/src/attestation.ts +204 -0
- package/src/authority.ts +849 -0
- package/src/canonicalize.ts +167 -0
- package/src/ed25519.ts +60 -0
- package/src/index.ts +118 -0
- package/src/internal/base64.ts +44 -0
- package/src/internal/key-cache.ts +79 -0
- package/src/lineage.ts +0 -0
- package/src/mldsa.ts +131 -0
- package/src/oid.ts +146 -0
- package/src/sensitivity.ts +154 -0
- package/src/signature.ts +119 -0
- package/src/types.ts +351 -0
- package/src/validate.ts +402 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — lineage.ts
|
|
3
|
+
*
|
|
4
|
+
* L3 lineage helpers (the Merkle-DAG layer). Two jobs:
|
|
5
|
+
*
|
|
6
|
+
* 1. UNIFY the three legacy supersession mechanisms — the self-asserted
|
|
7
|
+
* `supersedes` string on a CDRO, the standalone SRO object, and the
|
|
8
|
+
* `prev` edge — into ONE set of typed lineage edges, so every consumer
|
|
9
|
+
* reads supersession the same way (SRAID_FOUNDATION_PUNCHLIST A2,
|
|
10
|
+
* OBJECT_MODEL_CLEANSHEET §3.3).
|
|
11
|
+
*
|
|
12
|
+
* 2. Give a verifier a LATEST-WINS / MONOTONE rule over a set of versions.
|
|
13
|
+
* The legacy `supersedes` string was an unwitnessed pointer: nothing
|
|
14
|
+
* stopped an attacker from re-presenting a superseded object as current
|
|
15
|
+
* (SRAID F10 / Adversary A3 rollback-replay). Because `prev`/`links`
|
|
16
|
+
* are hashed into the OID (see oid.ts `cdroContentCore`), the lineage
|
|
17
|
+
* edges are identity-bound and tamper-evident, and `latestWins` resolves
|
|
18
|
+
* a version set to its single head by following those edges — given the
|
|
19
|
+
* complete in-set version history, a superseded version is detectably NOT
|
|
20
|
+
* the head. Full rollback-replay resistance additionally requires that the
|
|
21
|
+
* superseding object cannot be withheld; that is a Resolver / transparency-
|
|
22
|
+
* log property (DESIGN, not yet deployed in this package). `set_complete`
|
|
23
|
+
* on `LatestWinsResult` is the local signal for callers to detect an
|
|
24
|
+
* incomplete (potentially withheld) set.
|
|
25
|
+
*
|
|
26
|
+
* Pure functions, no I/O. Reachability/coverage of the referenced objects
|
|
27
|
+
* (do they exist, are they signed, are they unrevoked) is a resolver concern
|
|
28
|
+
* and out of scope here, exactly as for `verifyAuthority`.
|
|
29
|
+
*/
|
|
30
|
+
import type { CDRO, LineageLink } from './types.js';
|
|
31
|
+
/**
|
|
32
|
+
* Coalesce a CDRO's three legacy supersession mechanisms into one
|
|
33
|
+
* de-duplicated list of typed lineage edges:
|
|
34
|
+
*
|
|
35
|
+
* - `prev` → `{ rel: 'supersedes', oid: prev }`
|
|
36
|
+
* - `supersedes` → `{ rel: 'supersedes', oid: supersedes }`
|
|
37
|
+
* - each `links[]` edge → kept as-is
|
|
38
|
+
*
|
|
39
|
+
* `prev` and `supersedes` that point at the same OID collapse to a single
|
|
40
|
+
* `supersedes` edge, and an explicit `{ rel: 'supersedes' }` link to the same
|
|
41
|
+
* OID is not duplicated. The result is the canonical typed-edge view of an
|
|
42
|
+
* object's lineage that every consumer (verifier, resolver, drift engine)
|
|
43
|
+
* should read instead of poking at the three fields individually.
|
|
44
|
+
*
|
|
45
|
+
* Order: synthesized supersession edges (from `prev`/`supersedes`) first,
|
|
46
|
+
* then the object's own `links[]` in declared order, minus duplicates.
|
|
47
|
+
*/
|
|
48
|
+
export declare function lineageLinks(cdro: Pick<CDRO, 'prev' | 'supersedes' | 'links'>): LineageLink[];
|
|
49
|
+
/**
|
|
50
|
+
* The set of predecessor OIDs this object claims to supersede, drawn from the
|
|
51
|
+
* unified edge view (`prev`, legacy `supersedes`, and any
|
|
52
|
+
* `{ rel: 'supersedes' }` links). De-duplicated.
|
|
53
|
+
*/
|
|
54
|
+
export declare function supersededOids(cdro: Pick<CDRO, 'prev' | 'supersedes' | 'links'>): string[];
|
|
55
|
+
/** A CDRO that already carries its own OID (needed to resolve a version set). */
|
|
56
|
+
type Identified = Pick<CDRO, 'oid' | 'prev' | 'supersedes' | 'links'>;
|
|
57
|
+
export interface LatestWinsResult {
|
|
58
|
+
/** True iff the version set resolves to exactly one head with no cycle. */
|
|
59
|
+
ok: boolean;
|
|
60
|
+
/** The head object's OID (the latest version), when `ok`. */
|
|
61
|
+
head?: string;
|
|
62
|
+
/**
|
|
63
|
+
* True iff every in-set version is reachable from the head via supersedes
|
|
64
|
+
* edges, i.e. the set is one connected monotone chain/DAG with no unreachable
|
|
65
|
+
* fragment. Always false when `ok` is false. `ok && set_complete` is the
|
|
66
|
+
* strict signal that this set IS the complete version history from a single
|
|
67
|
+
* unambiguous head.
|
|
68
|
+
*/
|
|
69
|
+
set_complete: boolean;
|
|
70
|
+
/** Machine-readable reasons when not ok or not set_complete. */
|
|
71
|
+
reasons: string[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* LATEST-WINS / MONOTONE resolution over a set of versions of the same
|
|
75
|
+
* logical record. Given the candidate objects, find the single head: the one
|
|
76
|
+
* object that is not superseded by any other object in the set, where the
|
|
77
|
+
* supersession edges form an acyclic DAG.
|
|
78
|
+
*
|
|
79
|
+
* Given the complete in-set version history, an attacker who re-presents a
|
|
80
|
+
* superseded version cannot make it the head, because some other object in the
|
|
81
|
+
* set carries a hashed (identity-bound) `supersedes` edge pointing at it. The
|
|
82
|
+
* rule is monotone: presenting MORE of the version set can only move the head
|
|
83
|
+
* forward (toward a newer object), never backward. Note: this guarantee is
|
|
84
|
+
* scoped to the provided set. Withholding the superseding object is a separate
|
|
85
|
+
* concern (Resolver / transparency-log layer; DESIGN, not yet deployed).
|
|
86
|
+
* `set_complete` signals whether every in-set node is reachable from the head.
|
|
87
|
+
*
|
|
88
|
+
* - 0 heads → a supersession cycle (no monotone order); `ok: false`.
|
|
89
|
+
* - 1 head → the unambiguous latest; `ok: true, head`.
|
|
90
|
+
* - >1 heads → a fork (concurrent versions, no single latest); `ok: false`
|
|
91
|
+
* with all fork heads listed in `reasons`.
|
|
92
|
+
*
|
|
93
|
+
* Diamond merges (a head supersedes two predecessors that share a common
|
|
94
|
+
* ancestor) are legal and do not trigger a cycle error. Only a back-edge in
|
|
95
|
+
* the DFS (a node encountered on the active stack) is a true cycle.
|
|
96
|
+
*
|
|
97
|
+
* Edges that point OUTSIDE the provided set are ignored for head-finding (the
|
|
98
|
+
* predecessor may simply not be in hand); they do not make an in-set object a
|
|
99
|
+
* non-head. Only an in-set object being superseded by another in-set object
|
|
100
|
+
* removes it from head candidacy.
|
|
101
|
+
*/
|
|
102
|
+
export declare function latestWins(versions: readonly Identified[]): LatestWinsResult;
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=lineage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../src/lineage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAW,MAAM,YAAY,CAAA;AAE5D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC,GAAG,WAAW,EAAE,CAwB7F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC,GAAG,MAAM,EAAE,CAU1F;AAED,iFAAiF;AACjF,KAAK,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC,CAAA;AAErE,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,EAAE,EAAE,OAAO,CAAA;IACX,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,YAAY,EAAE,OAAO,CAAA;IACrB,gEAAgE;IAChE,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,GAAG,gBAAgB,CA4F5E"}
|
package/dist/lineage.js
ADDED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lineage.js","sourceRoot":"","sources":["../src/lineage.ts"],"names":[],"mappings":"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"}
|
package/dist/mldsa.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — mldsa.ts
|
|
3
|
+
*
|
|
4
|
+
* ML-DSA-65 (FIPS 204) verification with a native fast path.
|
|
5
|
+
*
|
|
6
|
+
* When the runtime ships OpenSSL 3.5+ (Node 24+), node:crypto exposes a
|
|
7
|
+
* native `ml-dsa-65` key type whose verify is materially faster than the
|
|
8
|
+
* pure-JS @noble/post-quantum implementation: ~207µs vs ~1.6ms verify, a
|
|
9
|
+
* ~7.8x op-level speedup measured on Node 24.16.0 / OpenSSL 3.5.6
|
|
10
|
+
* (bench/mldsa-verify.mjs).
|
|
11
|
+
*
|
|
12
|
+
* This module feature-detects native ML-DSA-65 support ONCE at first use and
|
|
13
|
+
* routes verification accordingly:
|
|
14
|
+
* • native available → node:crypto (OpenSSL)
|
|
15
|
+
* • native absent → @noble/post-quantum fallback
|
|
16
|
+
*
|
|
17
|
+
* Both paths are byte-for-byte interoperable: a signature minted by either
|
|
18
|
+
* implementation verifies identically under the other, and tampered or
|
|
19
|
+
* wrong-key inputs fail under both (see test/mldsa-kat.test.ts cross-impl
|
|
20
|
+
* known-answer vectors). The native path is therefore a transparent
|
|
21
|
+
* performance swap, NOT a behavior change. Node < 24 keeps working unchanged.
|
|
22
|
+
*
|
|
23
|
+
* The public API takes a RAW ML-DSA-65 public key (1952 bytes) and a RAW
|
|
24
|
+
* signature (3309 bytes), matching the @noble surface. The native path wraps
|
|
25
|
+
* the raw key in the fixed SubjectPublicKeyInfo DER prefix to build a
|
|
26
|
+
* KeyObject; @noble consumes the raw bytes directly.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* True when this runtime verifies ML-DSA-65 via native node:crypto (OpenSSL
|
|
30
|
+
* 3.5+, Node 24+). False when it falls back to @noble/post-quantum. Exposed
|
|
31
|
+
* for benchmarks, diagnostics, and tests that must exercise both paths.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isNativeMlDsaAvailable(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Verify a raw ML-DSA-65 signature over `message` against a raw 1952-byte
|
|
36
|
+
* public key. Uses the native node:crypto path when available, otherwise
|
|
37
|
+
* @noble/post-quantum. Returns false (never throws) on any malformed input or
|
|
38
|
+
* verification failure, so callers get a clean boolean.
|
|
39
|
+
*/
|
|
40
|
+
export declare function verifyMlDsa65(signature: Uint8Array, message: Uint8Array, publicKeyRaw: Uint8Array): boolean;
|
|
41
|
+
//# sourceMappingURL=mldsa.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mldsa.d.ts","sourceRoot":"","sources":["../src/mldsa.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAsDH;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAoBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,UAAU,EACrB,OAAO,EAAE,UAAU,EACnB,YAAY,EAAE,UAAU,GACvB,OAAO,CAaT"}
|
package/dist/mldsa.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — mldsa.ts
|
|
3
|
+
*
|
|
4
|
+
* ML-DSA-65 (FIPS 204) verification with a native fast path.
|
|
5
|
+
*
|
|
6
|
+
* When the runtime ships OpenSSL 3.5+ (Node 24+), node:crypto exposes a
|
|
7
|
+
* native `ml-dsa-65` key type whose verify is materially faster than the
|
|
8
|
+
* pure-JS @noble/post-quantum implementation: ~207µs vs ~1.6ms verify, a
|
|
9
|
+
* ~7.8x op-level speedup measured on Node 24.16.0 / OpenSSL 3.5.6
|
|
10
|
+
* (bench/mldsa-verify.mjs).
|
|
11
|
+
*
|
|
12
|
+
* This module feature-detects native ML-DSA-65 support ONCE at first use and
|
|
13
|
+
* routes verification accordingly:
|
|
14
|
+
* • native available → node:crypto (OpenSSL)
|
|
15
|
+
* • native absent → @noble/post-quantum fallback
|
|
16
|
+
*
|
|
17
|
+
* Both paths are byte-for-byte interoperable: a signature minted by either
|
|
18
|
+
* implementation verifies identically under the other, and tampered or
|
|
19
|
+
* wrong-key inputs fail under both (see test/mldsa-kat.test.ts cross-impl
|
|
20
|
+
* known-answer vectors). The native path is therefore a transparent
|
|
21
|
+
* performance swap, NOT a behavior change. Node < 24 keeps working unchanged.
|
|
22
|
+
*
|
|
23
|
+
* The public API takes a RAW ML-DSA-65 public key (1952 bytes) and a RAW
|
|
24
|
+
* signature (3309 bytes), matching the @noble surface. The native path wraps
|
|
25
|
+
* the raw key in the fixed SubjectPublicKeyInfo DER prefix to build a
|
|
26
|
+
* KeyObject; @noble consumes the raw bytes directly.
|
|
27
|
+
*/
|
|
28
|
+
import { createPublicKey, verify as nodeVerify } from 'node:crypto';
|
|
29
|
+
import { webcrypto } from 'node:crypto';
|
|
30
|
+
// Node 18 doesn't expose globalThis.crypto.getRandomValues by default, and the
|
|
31
|
+
// @noble libraries expect it during module initialization. Idempotent polyfill
|
|
32
|
+
// — MUST run BEFORE the @noble/post-quantum import below so ml_dsa65's
|
|
33
|
+
// module-init sees a populated globalThis.crypto.
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
if (!globalThis.crypto)
|
|
36
|
+
globalThis.crypto = webcrypto;
|
|
37
|
+
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
|
|
38
|
+
import { BoundedKeyCache, KEY_CACHE_MAX } from './internal/key-cache.js';
|
|
39
|
+
// Fixed DER prefix for an ML-DSA-65 SubjectPublicKeyInfo
|
|
40
|
+
// (draft-ietf-lamps-dilithium-certificates):
|
|
41
|
+
// SEQUENCE {
|
|
42
|
+
// SEQUENCE { OID 2.16.840.1.101.3.4.3.18 (id-ml-dsa-65) }
|
|
43
|
+
// BIT STRING (0 unused bits || 1952 raw public-key bytes)
|
|
44
|
+
// }
|
|
45
|
+
// The only variable part is the 1952-byte key, so the 22-byte header is
|
|
46
|
+
// constant for every ML-DSA-65 key.
|
|
47
|
+
const MLDSA65_SPKI_PREFIX = Buffer.from('308207b2300b0609608648016503040312038207a100', 'hex');
|
|
48
|
+
const MLDSA65_PUBKEY_LEN = 1952;
|
|
49
|
+
// One-time capability detection. `undefined` = not yet probed.
|
|
50
|
+
let nativeAvailable;
|
|
51
|
+
/**
|
|
52
|
+
* Detect (once, then cache) whether this runtime's node:crypto can build a
|
|
53
|
+
* native ML-DSA-65 public key. We probe by constructing a KeyObject from a
|
|
54
|
+
* structurally-valid dummy SPKI: OpenSSL 3.5+ accepts it and reports
|
|
55
|
+
* asymmetricKeyType === 'ml-dsa-65'; older OpenSSL throws ("Failed to read
|
|
56
|
+
* asymmetric key"). The probe does NOT verify anything, so a dummy key body
|
|
57
|
+
* is fine — only the algorithm support is being detected.
|
|
58
|
+
*/
|
|
59
|
+
function detectNative() {
|
|
60
|
+
if (nativeAvailable !== undefined)
|
|
61
|
+
return nativeAvailable;
|
|
62
|
+
try {
|
|
63
|
+
const dummy = Buffer.concat([MLDSA65_SPKI_PREFIX, Buffer.alloc(MLDSA65_PUBKEY_LEN)]);
|
|
64
|
+
const ko = createPublicKey({ key: dummy, format: 'der', type: 'spki' });
|
|
65
|
+
// @types/node ^20 predates the ml-dsa-* key types, so widen to string.
|
|
66
|
+
nativeAvailable = ko.asymmetricKeyType === 'ml-dsa-65';
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
nativeAvailable = false;
|
|
70
|
+
}
|
|
71
|
+
return nativeAvailable;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* True when this runtime verifies ML-DSA-65 via native node:crypto (OpenSSL
|
|
75
|
+
* 3.5+, Node 24+). False when it falls back to @noble/post-quantum. Exposed
|
|
76
|
+
* for benchmarks, diagnostics, and tests that must exercise both paths.
|
|
77
|
+
*/
|
|
78
|
+
export function isNativeMlDsaAvailable() {
|
|
79
|
+
return detectNative();
|
|
80
|
+
}
|
|
81
|
+
// Small bounded LRU: building a KeyObject parses DER, so cache by raw-key hex.
|
|
82
|
+
// Bounded so an attacker streaming distinct keys cannot grow it without limit.
|
|
83
|
+
const keyCache = new BoundedKeyCache(KEY_CACHE_MAX);
|
|
84
|
+
function keyObjectFromRaw(raw) {
|
|
85
|
+
if (raw.length !== MLDSA65_PUBKEY_LEN) {
|
|
86
|
+
throw new Error(`ml-dsa-65 public key must be ${MLDSA65_PUBKEY_LEN} bytes`);
|
|
87
|
+
}
|
|
88
|
+
const hex = Buffer.from(raw).toString('hex');
|
|
89
|
+
let ko = keyCache.get(hex);
|
|
90
|
+
if (!ko) {
|
|
91
|
+
const der = Buffer.concat([MLDSA65_SPKI_PREFIX, Buffer.from(raw)]);
|
|
92
|
+
ko = createPublicKey({ key: der, format: 'der', type: 'spki' });
|
|
93
|
+
keyCache.set(hex, ko);
|
|
94
|
+
}
|
|
95
|
+
return ko;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Verify a raw ML-DSA-65 signature over `message` against a raw 1952-byte
|
|
99
|
+
* public key. Uses the native node:crypto path when available, otherwise
|
|
100
|
+
* @noble/post-quantum. Returns false (never throws) on any malformed input or
|
|
101
|
+
* verification failure, so callers get a clean boolean.
|
|
102
|
+
*/
|
|
103
|
+
export function verifyMlDsa65(signature, message, publicKeyRaw) {
|
|
104
|
+
if (detectNative()) {
|
|
105
|
+
try {
|
|
106
|
+
return nodeVerify(null, message, keyObjectFromRaw(publicKeyRaw), signature);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return ml_dsa65.verify(signature, message, publicKeyRaw);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=mldsa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mldsa.js","sourceRoot":"","sources":["../src/mldsa.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,UAAU,EAAkB,MAAM,aAAa,CAAA;AAEnF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,+EAA+E;AAC/E,+EAA+E;AAC/E,uEAAuE;AACvE,kDAAkD;AAClD,8DAA8D;AAC9D,IAAI,CAAE,UAAkB,CAAC,MAAM;IAAG,UAAkB,CAAC,MAAM,GAAG,SAAS,CAAA;AAEvE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAA;AAExD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAExE,yDAAyD;AACzD,6CAA6C;AAC7C,eAAe;AACf,+DAA+D;AAC/D,8DAA8D;AAC9D,MAAM;AACN,wEAAwE;AACxE,oCAAoC;AACpC,MAAM,mBAAmB,GAAG,MAAM,CAAC,IAAI,CACrC,8CAA8C,EAC9C,KAAK,CACN,CAAA;AACD,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAE/B,+DAA+D;AAC/D,IAAI,eAAoC,CAAA;AAExC;;;;;;;GAOG;AACH,SAAS,YAAY;IACnB,IAAI,eAAe,KAAK,SAAS;QAAE,OAAO,eAAe,CAAA;IACzD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,mBAAmB,EAAE,MAAM,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAA;QACpF,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;QACvE,uEAAuE;QACvE,eAAe,GAAI,EAAE,CAAC,iBAA4B,KAAK,WAAW,CAAA;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,eAAe,GAAG,KAAK,CAAA;IACzB,CAAC;IACD,OAAO,eAAe,CAAA;AACxB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,YAAY,EAAE,CAAA;AACvB,CAAC;AAED,+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,kBAAkB,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,kBAAkB,QAAQ,CAAC,CAAA;IAC7E,CAAC;IACD,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;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAqB,EACrB,OAAmB,EACnB,YAAwB;IAExB,IAAI,YAAY,EAAE,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,OAAO,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,CAAA;QAC7E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAA;QACd,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,YAAY,CAAC,CAAA;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
|
package/dist/oid.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — oid.ts
|
|
3
|
+
*
|
|
4
|
+
* OID (Object IDentifier) computation. An OID is a content-addressed
|
|
5
|
+
* identifier for a CDRO object:
|
|
6
|
+
*
|
|
7
|
+
* OID = "sha256:" + hex(sha256(canonicalize(content)))
|
|
8
|
+
*
|
|
9
|
+
* The hash input is the canonical form of the OBJECT MINUS the six detached
|
|
10
|
+
* signature / envelope fields (see `CDRO_ENVELOPE_FIELDS`). Higher-layer code
|
|
11
|
+
* (e.g. GAP) typically passes the object's `body` directly; this function
|
|
12
|
+
* accepts any value and canonicalizes it without further interpretation, so
|
|
13
|
+
* callers stay in control of what enters the hash.
|
|
14
|
+
*
|
|
15
|
+
* This module is the SINGLE NORMATIVE SOURCE of the CDRO OID projection
|
|
16
|
+
* (ADR_019). The strip-set and number rule are specified in prose in
|
|
17
|
+
* PROJECTION_SPEC.md; all other surfaces derive from here.
|
|
18
|
+
*
|
|
19
|
+
* This matches the gateway's `computeGapOid` (src/gap/oid.ts) and
|
|
20
|
+
* `payloadOid` (src/inference/receipts.ts) byte-for-byte.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Compute an OID over an arbitrary canonical-compatible value.
|
|
24
|
+
*
|
|
25
|
+
* oidOf({ a: 1, b: 2 }) === oidOf({ b: 2, a: 1 })
|
|
26
|
+
*
|
|
27
|
+
* Returns `sha256:` followed by 64 lowercase hex characters.
|
|
28
|
+
*
|
|
29
|
+
* Uses the platform SHA-256 (node:crypto / OpenSSL), which is byte-identical
|
|
30
|
+
* to any conformant SHA-256 and materially faster than a pure-JS hash. The
|
|
31
|
+
* output contract is unchanged.
|
|
32
|
+
*/
|
|
33
|
+
export declare function oidOf(canonical: unknown): string;
|
|
34
|
+
/**
|
|
35
|
+
* Compute an OID directly from already-canonicalized bytes. Useful when
|
|
36
|
+
* the caller has produced the canonical string itself (for example, when
|
|
37
|
+
* the same bytes will also feed into a signature) and wants to avoid
|
|
38
|
+
* canonicalizing twice.
|
|
39
|
+
*/
|
|
40
|
+
export declare function oidOfCanonical(canonical: string | Uint8Array): string;
|
|
41
|
+
/**
|
|
42
|
+
* The detached-signature / envelope fields removed by `cdroContentCore`
|
|
43
|
+
* before hashing. This is the SINGLE NORMATIVE strip-set for the CDRO OID
|
|
44
|
+
* projection (ADR_019 decision 1); every other surface (GAP SDKs, the
|
|
45
|
+
* gateway signer, IMPLEMENTING.md) derives from THIS set, never re-lists it.
|
|
46
|
+
*
|
|
47
|
+
* The set is defined SEMANTICALLY: it is "every field produced BY the signer
|
|
48
|
+
* after canonicalization, plus the OID output itself." Concretely:
|
|
49
|
+
*
|
|
50
|
+
* oid — the projection OUTPUT (cannot be an input to itself).
|
|
51
|
+
* signature — legacy hybrid SignatureEnvelope (attaches after hash).
|
|
52
|
+
* ml_dsa_signature — detached PQ signature (attaches after hash).
|
|
53
|
+
* signature_key_id — signer-stamped key id (produced by the signer).
|
|
54
|
+
* signature_algorithm — signer-stamped alg id (produced by the signer).
|
|
55
|
+
* attestation — DSSE AttestationEnvelope (attaches after hash).
|
|
56
|
+
*
|
|
57
|
+
* EVERYTHING ELSE IS KEPT and hashed into the OID, including in particular:
|
|
58
|
+
* - `gap_version` — IN identity so a protocol downgrade is OID-detectable.
|
|
59
|
+
* - `supersedes` — IN identity because the SRAID Merkle-DAG head-proves-
|
|
60
|
+
* history property requires every lineage edge inside the
|
|
61
|
+
* hash. Superseding mints a NEW object; it never mutates
|
|
62
|
+
* the old one's bytes, so keeping it here is safe and
|
|
63
|
+
* makes the lineage edge tamper-evident.
|
|
64
|
+
* - `type`, `sraid_version`, `tenant_id`, `created_at_ms`, `created_by`,
|
|
65
|
+
* `body`, `authority`, `sensitivity`, `prev`, `links`, and any other
|
|
66
|
+
* content field.
|
|
67
|
+
*
|
|
68
|
+
* This is the ONE projection that yields the SAME OID whether the object is
|
|
69
|
+
* pre- or post-attestation: attaching an `attestation` (or `signature`,
|
|
70
|
+
* `ml_dsa_signature`, `signature_key_id`, `signature_algorithm`) after hashing
|
|
71
|
+
* is stripped back out here, so `cdroOid(obj)` is invariant across signing.
|
|
72
|
+
*
|
|
73
|
+
* It is FROZEN so no caller can mutate the normative set at runtime.
|
|
74
|
+
*/
|
|
75
|
+
export declare const CDRO_ENVELOPE_FIELDS: readonly string[];
|
|
76
|
+
/**
|
|
77
|
+
* Build the OID content core of a full CDRO: the object with EXACTLY the six
|
|
78
|
+
* detached-signature / envelope fields in `CDRO_ENVELOPE_FIELDS` removed at
|
|
79
|
+
* the top level, and everything else kept.
|
|
80
|
+
*
|
|
81
|
+
* This is the mechanism that makes the L4 `authority` block, the L3 lineage
|
|
82
|
+
* edges (`prev`, `links`, `supersedes`), the propagating `sensitivity` tier,
|
|
83
|
+
* and `gap_version` tamper-evident: they are hashed into identity by
|
|
84
|
+
* construction, so a field cannot be added, stripped, re-pointed, or
|
|
85
|
+
* downgraded without producing a different OID (and invalidating the
|
|
86
|
+
* signature, which is computed over these same bytes). Because `prev`/`links`/
|
|
87
|
+
* `supersedes` OIDs are inside the hash, a node's OID transitively commits its
|
|
88
|
+
* whole reachable history (the Merkle-DAG "head proves history" property).
|
|
89
|
+
*
|
|
90
|
+
* Returns a plain object suitable for `canonicalize` / `oidOf`.
|
|
91
|
+
*/
|
|
92
|
+
export declare function cdroContentCore(cdro: unknown): Record<string, unknown>;
|
|
93
|
+
/**
|
|
94
|
+
* Compute the OID of a full CDRO over its content core (see
|
|
95
|
+
* `cdroContentCore`). This is the correct way to derive identity for a
|
|
96
|
+
* complete CDRO: it hashes `authority`, `supersedes`, `gap_version`, `body`,
|
|
97
|
+
* and every other content field, so the L4 authority block is identity-bound
|
|
98
|
+
* and cannot be silently dropped.
|
|
99
|
+
*
|
|
100
|
+
* INVARIANT (ADR_019): `cdroOid(obj)` yields the SAME OID whether `obj` is
|
|
101
|
+
* pre- or post-attestation, because the detached envelope fields are stripped
|
|
102
|
+
* (see `CDRO_ENVELOPE_FIELDS`). This is what lets a third party recompute the
|
|
103
|
+
* OID of a signed receipt and match the value the signer stamped.
|
|
104
|
+
*
|
|
105
|
+
* Note this differs from `oidOf(cdro.body)`: a CDRO's identity is over the
|
|
106
|
+
* whole content core, not just its body.
|
|
107
|
+
*/
|
|
108
|
+
export declare function cdroOid(cdro: unknown): string;
|
|
109
|
+
//# sourceMappingURL=oid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oid.d.ts","sourceRoot":"","sources":["../src/oid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAGhD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAMrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAOhD,CAAA;AAIF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUtE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAE7C"}
|
package/dist/oid.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — oid.ts
|
|
3
|
+
*
|
|
4
|
+
* OID (Object IDentifier) computation. An OID is a content-addressed
|
|
5
|
+
* identifier for a CDRO object:
|
|
6
|
+
*
|
|
7
|
+
* OID = "sha256:" + hex(sha256(canonicalize(content)))
|
|
8
|
+
*
|
|
9
|
+
* The hash input is the canonical form of the OBJECT MINUS the six detached
|
|
10
|
+
* signature / envelope fields (see `CDRO_ENVELOPE_FIELDS`). Higher-layer code
|
|
11
|
+
* (e.g. GAP) typically passes the object's `body` directly; this function
|
|
12
|
+
* accepts any value and canonicalizes it without further interpretation, so
|
|
13
|
+
* callers stay in control of what enters the hash.
|
|
14
|
+
*
|
|
15
|
+
* This module is the SINGLE NORMATIVE SOURCE of the CDRO OID projection
|
|
16
|
+
* (ADR_019). The strip-set and number rule are specified in prose in
|
|
17
|
+
* PROJECTION_SPEC.md; all other surfaces derive from here.
|
|
18
|
+
*
|
|
19
|
+
* This matches the gateway's `computeGapOid` (src/gap/oid.ts) and
|
|
20
|
+
* `payloadOid` (src/inference/receipts.ts) byte-for-byte.
|
|
21
|
+
*/
|
|
22
|
+
import { createHash } from 'node:crypto';
|
|
23
|
+
import { canonicalize } from './canonicalize.js';
|
|
24
|
+
/**
|
|
25
|
+
* Compute an OID over an arbitrary canonical-compatible value.
|
|
26
|
+
*
|
|
27
|
+
* oidOf({ a: 1, b: 2 }) === oidOf({ b: 2, a: 1 })
|
|
28
|
+
*
|
|
29
|
+
* Returns `sha256:` followed by 64 lowercase hex characters.
|
|
30
|
+
*
|
|
31
|
+
* Uses the platform SHA-256 (node:crypto / OpenSSL), which is byte-identical
|
|
32
|
+
* to any conformant SHA-256 and materially faster than a pure-JS hash. The
|
|
33
|
+
* output contract is unchanged.
|
|
34
|
+
*/
|
|
35
|
+
export function oidOf(canonical) {
|
|
36
|
+
const bytes = new TextEncoder().encode(canonicalize(canonical));
|
|
37
|
+
return 'sha256:' + createHash('sha256').update(bytes).digest('hex');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Compute an OID directly from already-canonicalized bytes. Useful when
|
|
41
|
+
* the caller has produced the canonical string itself (for example, when
|
|
42
|
+
* the same bytes will also feed into a signature) and wants to avoid
|
|
43
|
+
* canonicalizing twice.
|
|
44
|
+
*/
|
|
45
|
+
export function oidOfCanonical(canonical) {
|
|
46
|
+
const bytes = typeof canonical === 'string'
|
|
47
|
+
? new TextEncoder().encode(canonical)
|
|
48
|
+
: canonical;
|
|
49
|
+
return 'sha256:' + createHash('sha256').update(bytes).digest('hex');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The detached-signature / envelope fields removed by `cdroContentCore`
|
|
53
|
+
* before hashing. This is the SINGLE NORMATIVE strip-set for the CDRO OID
|
|
54
|
+
* projection (ADR_019 decision 1); every other surface (GAP SDKs, the
|
|
55
|
+
* gateway signer, IMPLEMENTING.md) derives from THIS set, never re-lists it.
|
|
56
|
+
*
|
|
57
|
+
* The set is defined SEMANTICALLY: it is "every field produced BY the signer
|
|
58
|
+
* after canonicalization, plus the OID output itself." Concretely:
|
|
59
|
+
*
|
|
60
|
+
* oid — the projection OUTPUT (cannot be an input to itself).
|
|
61
|
+
* signature — legacy hybrid SignatureEnvelope (attaches after hash).
|
|
62
|
+
* ml_dsa_signature — detached PQ signature (attaches after hash).
|
|
63
|
+
* signature_key_id — signer-stamped key id (produced by the signer).
|
|
64
|
+
* signature_algorithm — signer-stamped alg id (produced by the signer).
|
|
65
|
+
* attestation — DSSE AttestationEnvelope (attaches after hash).
|
|
66
|
+
*
|
|
67
|
+
* EVERYTHING ELSE IS KEPT and hashed into the OID, including in particular:
|
|
68
|
+
* - `gap_version` — IN identity so a protocol downgrade is OID-detectable.
|
|
69
|
+
* - `supersedes` — IN identity because the SRAID Merkle-DAG head-proves-
|
|
70
|
+
* history property requires every lineage edge inside the
|
|
71
|
+
* hash. Superseding mints a NEW object; it never mutates
|
|
72
|
+
* the old one's bytes, so keeping it here is safe and
|
|
73
|
+
* makes the lineage edge tamper-evident.
|
|
74
|
+
* - `type`, `sraid_version`, `tenant_id`, `created_at_ms`, `created_by`,
|
|
75
|
+
* `body`, `authority`, `sensitivity`, `prev`, `links`, and any other
|
|
76
|
+
* content field.
|
|
77
|
+
*
|
|
78
|
+
* This is the ONE projection that yields the SAME OID whether the object is
|
|
79
|
+
* pre- or post-attestation: attaching an `attestation` (or `signature`,
|
|
80
|
+
* `ml_dsa_signature`, `signature_key_id`, `signature_algorithm`) after hashing
|
|
81
|
+
* is stripped back out here, so `cdroOid(obj)` is invariant across signing.
|
|
82
|
+
*
|
|
83
|
+
* It is FROZEN so no caller can mutate the normative set at runtime.
|
|
84
|
+
*/
|
|
85
|
+
export const CDRO_ENVELOPE_FIELDS = Object.freeze([
|
|
86
|
+
'oid',
|
|
87
|
+
'signature',
|
|
88
|
+
'ml_dsa_signature',
|
|
89
|
+
'signature_key_id',
|
|
90
|
+
'signature_algorithm',
|
|
91
|
+
'attestation',
|
|
92
|
+
]);
|
|
93
|
+
const CDRO_ENVELOPE_FIELD_SET = new Set(CDRO_ENVELOPE_FIELDS);
|
|
94
|
+
/**
|
|
95
|
+
* Build the OID content core of a full CDRO: the object with EXACTLY the six
|
|
96
|
+
* detached-signature / envelope fields in `CDRO_ENVELOPE_FIELDS` removed at
|
|
97
|
+
* the top level, and everything else kept.
|
|
98
|
+
*
|
|
99
|
+
* This is the mechanism that makes the L4 `authority` block, the L3 lineage
|
|
100
|
+
* edges (`prev`, `links`, `supersedes`), the propagating `sensitivity` tier,
|
|
101
|
+
* and `gap_version` tamper-evident: they are hashed into identity by
|
|
102
|
+
* construction, so a field cannot be added, stripped, re-pointed, or
|
|
103
|
+
* downgraded without producing a different OID (and invalidating the
|
|
104
|
+
* signature, which is computed over these same bytes). Because `prev`/`links`/
|
|
105
|
+
* `supersedes` OIDs are inside the hash, a node's OID transitively commits its
|
|
106
|
+
* whole reachable history (the Merkle-DAG "head proves history" property).
|
|
107
|
+
*
|
|
108
|
+
* Returns a plain object suitable for `canonicalize` / `oidOf`.
|
|
109
|
+
*/
|
|
110
|
+
export function cdroContentCore(cdro) {
|
|
111
|
+
if (cdro === null || typeof cdro !== 'object' || Array.isArray(cdro)) {
|
|
112
|
+
throw new TypeError('cdroContentCore: argument must be a CDRO object');
|
|
113
|
+
}
|
|
114
|
+
const core = {};
|
|
115
|
+
for (const [k, v] of Object.entries(cdro)) {
|
|
116
|
+
if (CDRO_ENVELOPE_FIELD_SET.has(k))
|
|
117
|
+
continue;
|
|
118
|
+
core[k] = v;
|
|
119
|
+
}
|
|
120
|
+
return core;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Compute the OID of a full CDRO over its content core (see
|
|
124
|
+
* `cdroContentCore`). This is the correct way to derive identity for a
|
|
125
|
+
* complete CDRO: it hashes `authority`, `supersedes`, `gap_version`, `body`,
|
|
126
|
+
* and every other content field, so the L4 authority block is identity-bound
|
|
127
|
+
* and cannot be silently dropped.
|
|
128
|
+
*
|
|
129
|
+
* INVARIANT (ADR_019): `cdroOid(obj)` yields the SAME OID whether `obj` is
|
|
130
|
+
* pre- or post-attestation, because the detached envelope fields are stripped
|
|
131
|
+
* (see `CDRO_ENVELOPE_FIELDS`). This is what lets a third party recompute the
|
|
132
|
+
* OID of a signed receipt and match the value the signer stamped.
|
|
133
|
+
*
|
|
134
|
+
* Note this differs from `oidOf(cdro.body)`: a CDRO's identity is over the
|
|
135
|
+
* whole content core, not just its body.
|
|
136
|
+
*/
|
|
137
|
+
export function cdroOid(cdro) {
|
|
138
|
+
return oidOf(cdroContentCore(cdro));
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=oid.js.map
|
package/dist/oid.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oid.js","sourceRoot":"","sources":["../src/oid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,KAAK,CAAC,SAAkB;IACtC,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAA;IAC/D,OAAO,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACrE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,SAA8B;IAC3D,MAAM,KAAK,GACT,OAAO,SAAS,KAAK,QAAQ;QAC3B,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC;QACrC,CAAC,CAAC,SAAS,CAAA;IACf,OAAO,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AACrE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACnE,KAAK;IACL,WAAW;IACX,kBAAkB;IAClB,kBAAkB;IAClB,qBAAqB;IACrB,aAAa;CACd,CAAC,CAAA;AAEF,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAC,oBAAoB,CAAC,CAAA;AAElF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAA;IACxE,CAAC;IACD,MAAM,IAAI,GAA4B,EAAE,CAAA;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC,EAAE,CAAC;QACrE,IAAI,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,SAAQ;QAC5C,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACb,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,OAAO,CAAC,IAAa;IACnC,OAAO,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAA;AACrC,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @synoi/sraid — sensitivity.ts
|
|
3
|
+
*
|
|
4
|
+
* L4 GOVERNANCE — the propagating sensitivity label (OBJECT_MODEL_CLEANSHEET
|
|
5
|
+
* §3.4, SRAID_FOUNDATION_PUNCHLIST A5). A single coarse, OPAQUE tier on the
|
|
6
|
+
* CDRO that retrieval, summarization, and consolidation MUST carry forward
|
|
7
|
+
* monotonically: a derived object inherits the HIGHEST tier among its sources,
|
|
8
|
+
* so a summary of high-sensitivity inputs cannot be downgraded.
|
|
9
|
+
*
|
|
10
|
+
* ── Why the tiers are OPAQUE ORDINALS, not literal categories ───────────────
|
|
11
|
+
*
|
|
12
|
+
* SPEC §7 is normative and explicit: the envelope is a NON-encrypted, signed,
|
|
13
|
+
* publicly visible surface. A literal classification label (e.g. `"phi"`,
|
|
14
|
+
* `"health"`, `"financial"`) on that surface would LEAK the nature of the
|
|
15
|
+
* encrypted `body` — an observer who cannot read the ciphertext could still
|
|
16
|
+
* read "this is health data" off the envelope. SPEC §7 therefore requires a
|
|
17
|
+
* "coarse, OPAQUE sensitivity classifier", and the §9 reserved note says the
|
|
18
|
+
* same.
|
|
19
|
+
*
|
|
20
|
+
* So the tier set here is a small, COARSE, ORDERED ladder of opaque levels
|
|
21
|
+
* (`s0` < `s1` < `s2` < `s3` < `s4`). It carries an ORDERING (needed for the
|
|
22
|
+
* monotone `max()` lattice — standard lattice-based information-flow labeling,
|
|
23
|
+
* Denning 1976) but NO semantic category. The mapping from a regulatory
|
|
24
|
+
* category (PHI, PII, secret, …) to a tier is a higher-layer POLICY concern
|
|
25
|
+
* kept OFF the public envelope: an operator's policy decides "PHI → s3"
|
|
26
|
+
* privately; the wire only ever shows `s3`. This is the "coarse, opaque tier
|
|
27
|
+
* (or opaque policy-scope handle)" required by the task and SPEC §7.
|
|
28
|
+
*
|
|
29
|
+
* The field is hashed into the OID (it is a content-core field; see
|
|
30
|
+
* `cdroContentCore` in oid.ts), so it cannot be silently stripped or
|
|
31
|
+
* downgraded without changing the OID and breaking the signature.
|
|
32
|
+
*
|
|
33
|
+
* NOTE on maturity (CLAIMS_DISCIPLINE): this module ships the FIELD, the
|
|
34
|
+
* lattice, the monotone `max()` carry-forward, and the validator, with
|
|
35
|
+
* vectors. The commitment-based selective-disclosure scheme that would
|
|
36
|
+
* CONCEAL the tier itself (SPEC §7 / §9) is reserved and NOT built here.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* The coarse, opaque sensitivity tiers, lowest to highest. These are ORDINAL
|
|
40
|
+
* and OPAQUE by design — they convey relative sensitivity for the monotone
|
|
41
|
+
* propagation lattice WITHOUT naming a content category (per SPEC §7, a
|
|
42
|
+
* literal category on the public envelope would leak what the encrypted body
|
|
43
|
+
* is). The mapping of a real-world classification to a tier is private policy,
|
|
44
|
+
* not part of this taxonomy.
|
|
45
|
+
*
|
|
46
|
+
* s0 — lowest / unclassified (the default floor; freely shareable)
|
|
47
|
+
* s1 — low
|
|
48
|
+
* s2 — moderate
|
|
49
|
+
* s3 — high
|
|
50
|
+
* s4 — highest / most restricted
|
|
51
|
+
*
|
|
52
|
+
* Five coarse levels is deliberate: enough granularity for a useful lattice,
|
|
53
|
+
* few enough to stay coarse (so the tier alone is weakly distinguishing).
|
|
54
|
+
*/
|
|
55
|
+
export type SensitivityTier = 's0' | 's1' | 's2' | 's3' | 's4';
|
|
56
|
+
/**
|
|
57
|
+
* The ordered tier ladder, lowest-first. The index IS the rank used by the
|
|
58
|
+
* `max()` lattice. Frozen so the ordering is immutable at runtime.
|
|
59
|
+
*/
|
|
60
|
+
export declare const SENSITIVITY_TIERS: readonly SensitivityTier[];
|
|
61
|
+
/** The default tier for an object that declares none — the lattice floor. */
|
|
62
|
+
export declare const SENSITIVITY_DEFAULT: SensitivityTier;
|
|
63
|
+
/** True iff `x` is a defined `SensitivityTier`. */
|
|
64
|
+
export declare function isSensitivityTier(x: unknown): x is SensitivityTier;
|
|
65
|
+
/**
|
|
66
|
+
* The ordinal rank of a tier (0 = lowest). Throws on an unknown tier so a
|
|
67
|
+
* typo can never silently rank as the floor (which would be a downgrade
|
|
68
|
+
* footgun in the lattice).
|
|
69
|
+
*/
|
|
70
|
+
export declare function sensitivityRank(tier: SensitivityTier): number;
|
|
71
|
+
/**
|
|
72
|
+
* The monotone lattice join: the HIGHER (more restricted) of two tiers.
|
|
73
|
+
* `max('s1','s3') === 's3'`. This is the per-pair operation behind
|
|
74
|
+
* carry-forward.
|
|
75
|
+
*/
|
|
76
|
+
export declare function sensitivityMax(a: SensitivityTier, b: SensitivityTier): SensitivityTier;
|
|
77
|
+
/**
|
|
78
|
+
* Carry-forward over a set of source tiers: the highest tier present.
|
|
79
|
+
*
|
|
80
|
+
* This is the rule that makes sensitivity MONOTONE under derivation
|
|
81
|
+
* (retrieval / summarization / consolidation): a derived object's tier =
|
|
82
|
+
* max(tiers of all its `consolidated_from` / `derived_from` sources). It can
|
|
83
|
+
* only ever go UP, never down — a summary of high-sensitivity inputs stays at
|
|
84
|
+
* the highest input tier.
|
|
85
|
+
*
|
|
86
|
+
* `undefined`/absent source tiers are treated as the floor (`s0`): a source
|
|
87
|
+
* that declared no tier cannot pull the result down, and an explicit higher
|
|
88
|
+
* source always wins. An empty input set returns the floor.
|
|
89
|
+
*/
|
|
90
|
+
export declare function sensitivityCarryForward(sources: ReadonlyArray<SensitivityTier | undefined | null>): SensitivityTier;
|
|
91
|
+
/**
|
|
92
|
+
* Guard a proposed sensitivity assignment against monotone violation: the
|
|
93
|
+
* `proposed` tier of a derived object MUST be at least as high as the
|
|
94
|
+
* carry-forward of its `sources`. Returns the floor the derived object is NOT
|
|
95
|
+
* allowed to fall below, plus whether `proposed` honors it.
|
|
96
|
+
*
|
|
97
|
+
* Use this to REJECT an attempt to label a summary of `s3` inputs as `s1`.
|
|
98
|
+
*/
|
|
99
|
+
export declare function sensitivityMonotoneCheck(proposed: SensitivityTier, sources: ReadonlyArray<SensitivityTier | undefined | null>): {
|
|
100
|
+
ok: boolean;
|
|
101
|
+
floor: SensitivityTier;
|
|
102
|
+
proposed: SensitivityTier;
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=sensitivity.d.ts.map
|