@zanii/certify 0.0.0-stage → 0.6.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 (141) hide show
  1. package/LICENSE +159 -0
  2. package/README.md +144 -2
  3. package/dist/appliance-execution.d.ts +18 -0
  4. package/dist/appliance-execution.js +12 -0
  5. package/dist/appliance.d.ts +135 -0
  6. package/dist/appliance.js +253 -0
  7. package/dist/assurance-status.d.ts +58 -0
  8. package/dist/assurance-status.js +86 -0
  9. package/dist/audit-renewal.d.ts +39 -0
  10. package/dist/audit-renewal.js +62 -0
  11. package/dist/audit.d.ts +69 -0
  12. package/dist/audit.js +112 -0
  13. package/dist/beacon-client.d.ts +40 -0
  14. package/dist/beacon-client.js +151 -0
  15. package/dist/beacon.d.ts +84 -0
  16. package/dist/beacon.js +106 -0
  17. package/dist/bitstring-status.d.ts +56 -0
  18. package/dist/bitstring-status.js +135 -0
  19. package/dist/board.d.ts +218 -0
  20. package/dist/board.js +428 -0
  21. package/dist/bundle.d.ts +100 -0
  22. package/dist/bundle.js +122 -0
  23. package/dist/certificate.d.ts +161 -0
  24. package/dist/certificate.js +346 -0
  25. package/dist/cli-messages.json +148 -0
  26. package/dist/cli.d.ts +16 -0
  27. package/dist/cli.js +369 -0
  28. package/dist/current-issuer-vc.d.ts +20 -0
  29. package/dist/current-issuer-vc.js +60 -0
  30. package/dist/evidence-share.d.ts +89 -0
  31. package/dist/evidence-share.js +479 -0
  32. package/dist/federation.d.ts +103 -0
  33. package/dist/federation.js +240 -0
  34. package/dist/feed-snapshot.d.ts +97 -0
  35. package/dist/feed-snapshot.js +186 -0
  36. package/dist/fingerprint.d.ts +23 -0
  37. package/dist/fingerprint.js +48 -0
  38. package/dist/formats/status-v2.schema.json +31 -0
  39. package/dist/governance.d.ts +244 -0
  40. package/dist/governance.js +418 -0
  41. package/dist/identity.d.ts +79 -0
  42. package/dist/identity.js +146 -0
  43. package/dist/impartiality.d.ts +47 -0
  44. package/dist/impartiality.js +85 -0
  45. package/dist/index.d.ts +61 -0
  46. package/dist/index.js +63 -0
  47. package/dist/issuer-authorization.d.ts +4 -0
  48. package/dist/issuer-authorization.js +52 -0
  49. package/dist/issuer-resolution.d.ts +25 -0
  50. package/dist/issuer-resolution.js +89 -0
  51. package/dist/licence-owner-entry.d.ts +20 -0
  52. package/dist/licence-owner-entry.js +33 -0
  53. package/dist/licence-owner.d.ts +67 -0
  54. package/dist/licence-owner.js +180 -0
  55. package/dist/licence.d.ts +174 -0
  56. package/dist/licence.js +400 -0
  57. package/dist/live-evidence.d.ts +135 -0
  58. package/dist/live-evidence.js +210 -0
  59. package/dist/nitro-roots.d.ts +3 -0
  60. package/dist/nitro-roots.js +20 -0
  61. package/dist/nitro-x509.d.ts +16 -0
  62. package/dist/nitro-x509.js +141 -0
  63. package/dist/nitro.d.ts +74 -0
  64. package/dist/nitro.js +329 -0
  65. package/dist/normalization.d.ts +8 -0
  66. package/dist/normalization.js +10 -0
  67. package/dist/pq.d.ts +32 -0
  68. package/dist/pq.js +69 -0
  69. package/dist/profile.d.ts +15 -0
  70. package/dist/profile.js +165 -0
  71. package/dist/register.d.ts +79 -0
  72. package/dist/register.js +134 -0
  73. package/dist/rotation.d.ts +49 -0
  74. package/dist/rotation.js +124 -0
  75. package/dist/royalty-reconciliation.d.ts +24 -0
  76. package/dist/royalty-reconciliation.js +88 -0
  77. package/dist/runtime-client.d.ts +40 -0
  78. package/dist/runtime-client.js +190 -0
  79. package/dist/runtime-gateway.d.ts +26 -0
  80. package/dist/runtime-gateway.js +169 -0
  81. package/dist/runtime-knowledge.d.ts +28 -0
  82. package/dist/runtime-knowledge.js +75 -0
  83. package/dist/runtime-nitro.d.ts +30 -0
  84. package/dist/runtime-nitro.js +57 -0
  85. package/dist/runtime.d.ts +226 -0
  86. package/dist/runtime.js +254 -0
  87. package/dist/sandbox-delegation.d.ts +38 -0
  88. package/dist/sandbox-delegation.js +61 -0
  89. package/dist/schema-dialect.d.ts +1 -0
  90. package/dist/schema-dialect.js +97 -0
  91. package/dist/schemas/clause-map.schema.json +52 -0
  92. package/dist/schemas/profile.schema.json +81 -0
  93. package/dist/schemas/scenarios.schema.json +573 -0
  94. package/dist/schemas/suite.schema.json +138 -0
  95. package/dist/schemas/trajectory.schema.json +1396 -0
  96. package/dist/scheme-authority.d.ts +5 -0
  97. package/dist/scheme-authority.js +15 -0
  98. package/dist/scheme-entry.d.ts +25 -0
  99. package/dist/scheme-entry.js +85 -0
  100. package/dist/scheme-history.d.ts +5 -0
  101. package/dist/scheme-history.js +87 -0
  102. package/dist/scheme-log.d.ts +98 -0
  103. package/dist/scheme-log.js +266 -0
  104. package/dist/scheme-merkle.d.ts +2 -0
  105. package/dist/scheme-merkle.js +20 -0
  106. package/dist/scheme-text.d.ts +25 -0
  107. package/dist/scheme-text.js +46 -0
  108. package/dist/scheme-timing.d.ts +16 -0
  109. package/dist/scheme-timing.js +22 -0
  110. package/dist/scheme-transition.d.ts +47 -0
  111. package/dist/scheme-transition.js +101 -0
  112. package/dist/scoring.d.ts +35 -0
  113. package/dist/scoring.js +50 -0
  114. package/dist/shared.d.ts +16 -0
  115. package/dist/shared.js +20 -0
  116. package/dist/signer.d.ts +18 -0
  117. package/dist/signer.js +49 -0
  118. package/dist/signing-identity.d.ts +1 -0
  119. package/dist/signing-identity.js +11 -0
  120. package/dist/status.d.ts +113 -0
  121. package/dist/status.js +204 -0
  122. package/dist/succession.d.ts +21 -0
  123. package/dist/succession.js +67 -0
  124. package/dist/suite.d.ts +261 -0
  125. package/dist/suite.js +458 -0
  126. package/dist/trajectory.d.ts +216 -0
  127. package/dist/trajectory.js +409 -0
  128. package/dist/transcript-provenance.d.ts +25 -0
  129. package/dist/transcript-provenance.js +37 -0
  130. package/dist/transport-key.d.ts +3 -0
  131. package/dist/transport-key.js +16 -0
  132. package/dist/vc.d.ts +29 -0
  133. package/dist/vc.js +96 -0
  134. package/dist/verification-bounds.d.ts +5 -0
  135. package/dist/verification-bounds.js +78 -0
  136. package/dist/verification-policy.d.ts +32 -0
  137. package/dist/verification-policy.js +85 -0
  138. package/dist/witness.d.ts +19 -0
  139. package/dist/witness.js +80 -0
  140. package/package.json +65 -4
  141. package/src/cli.ts +331 -0
@@ -0,0 +1,5 @@
1
+ import { type SchemeLogsInput } from "./scheme-log.ts";
2
+ export declare function schemeAuthority(log: SchemeLogsInput, owner: string | undefined, groups: Record<string, readonly unknown[]>): {
3
+ reasons: string[];
4
+ canceled: Set<string>;
5
+ };
@@ -0,0 +1,15 @@
1
+ // Optional complete, checkpoint-authenticated context for standalone authority consumers.
2
+ import { jcsHash } from "@zanii/core";
3
+ import { verifySchemeLogs } from "./scheme-log.js";
4
+ import { isObj } from "./shared.js";
5
+ import { withinVerificationBudget } from "./verification-bounds.js";
6
+ export function schemeAuthority(log, owner, groups) {
7
+ if (!withinVerificationBudget({ log, groups }) || !isObj(log) || Object.keys(log).some(k => !["entries", "checkpoint", "previous", "witness", "appends", "ledgerReceipts"].includes(k)) || !Array.isArray(log?.entries) || log.entries.length > 10_000)
8
+ return { reasons: ["scheme authority context exceeds the verification budget"], canceled: new Set() };
9
+ const checked = verifySchemeLogs(log, { owner }), reasons = [...checked.reasons];
10
+ for (const [type, rows] of Object.entries(groups)) {
11
+ if (!Array.isArray(rows) || jcsHash(rows) !== jcsHash(log.entries.filter(e => isObj(e) && e.type === type)))
12
+ reasons.push(`supplied ${type} history differs from the authenticated scheme log`);
13
+ }
14
+ return { reasons, canceled: new Set(reasons.length ? [] : checked.canceled ?? []) };
15
+ }
@@ -0,0 +1,25 @@
1
+ import { type LicenceState } from "./licence.ts";
2
+ export declare const MAX_SCHEME_ENTRY_BYTES = 1048576;
3
+ export interface SchemeEntryContext {
4
+ owner: string;
5
+ /** Already authenticated newest board and the head of this entry's type. */
6
+ board: Record<string, unknown> | null;
7
+ head: {
8
+ size: number;
9
+ hash: string;
10
+ effective_at: string | null;
11
+ } | null;
12
+ maxSignedEffective: string | null;
13
+ release: (hash: string) => Record<string, unknown> | undefined;
14
+ released: (subject: string) => boolean;
15
+ withdrawn: (release: string) => boolean;
16
+ licenceState: (hash: string) => LicenceState;
17
+ statusIssuers?: readonly string[];
18
+ }
19
+ export interface SchemeEntryCheck {
20
+ ok: boolean;
21
+ reasons: string[];
22
+ effectiveAt?: string;
23
+ licenceState?: LicenceState;
24
+ }
25
+ export declare function verifySchemeEntry(doc: unknown, c: SchemeEntryContext): SchemeEntryCheck;
@@ -0,0 +1,85 @@
1
+ // One authenticated extension of a previously verified semantic prefix. The caller commits the
2
+ // entry and its returned state atomically; lookups must contain ONLY that verified prefix.
3
+ import { jcsHash, verifySignedObject } from "@zanii/core";
4
+ import { boardFieldError, boardSignedReasons, isBoard, isRelease, isWithdrawal, releaseFieldError, signersOf, subjectKey, withdrawalFieldError } from "./board.js";
5
+ import { licenceStatusFieldError } from "./licence.js";
6
+ import { isBodyRegister, registerFieldError } from "./register.js";
7
+ import { isObj, ms } from "./shared.js";
8
+ import { signingIdentityReasons } from "./signing-identity.js";
9
+ import { withinVerificationBudget } from "./verification-bounds.js";
10
+ export const MAX_SCHEME_ENTRY_BYTES = 1_048_576;
11
+ export function verifySchemeEntry(doc, c) {
12
+ const reasons = [];
13
+ const out = (extra = {}) => ({ ok: reasons.length === 0, reasons, ...extra });
14
+ if (!withinVerificationBudget(doc, { maxBytes: MAX_SCHEME_ENTRY_BYTES, maxNodes: 100_000 }))
15
+ return { ok: false, reasons: ["scheme entry exceeds the JSON verification budget"] };
16
+ if (!isObj(doc))
17
+ return { ok: false, reasons: ["not a scheme log entry"] };
18
+ const board = Boolean(isBoard(doc)), release = Boolean(isRelease(doc)), withdrawal = Boolean(isWithdrawal(doc)), register = Boolean(isBodyRegister(doc));
19
+ const status = doc.v === 1 && doc.type === "certify.licence-status" && Object.keys(doc).length === ("for_cause" in doc || "effective_at" in doc ? 12 : 10);
20
+ if (!board && !release && !withdrawal && !register && !status)
21
+ return { ok: false, reasons: ["not a scheme log entry (v1)"] };
22
+ if (c.statusIssuers !== undefined)
23
+ reasons.push(...signingIdentityReasons(c.owner, [], c.statusIssuers));
24
+ const err = board ? boardFieldError(doc) : release ? releaseFieldError(doc) : withdrawal ? withdrawalFieldError(doc) : register ? registerFieldError(doc) : licenceStatusFieldError(doc);
25
+ if (err) {
26
+ reasons.push(`entry is malformed: ${err}`);
27
+ return out();
28
+ }
29
+ const count = c.head?.size ?? 0;
30
+ if (!Number.isSafeInteger(count) || count < 0 || doc.seq !== count)
31
+ reasons.push(`entry is not at position ${count}`);
32
+ if (doc.prev !== (c.head?.hash ?? null))
33
+ reasons.push("entry does not name the previous entry of its type");
34
+ const effectiveAt = (status ? doc.at : doc.effective_from);
35
+ if (c.head?.effective_at && !(board || register ? ms(effectiveAt) > ms(c.head.effective_at) : ms(effectiveAt) >= ms(c.head.effective_at)))
36
+ reasons.push(board ? "board does not take effect after the previous board" : register ? "register does not take effect after the previous register" : "entry takes effect before the previous entry");
37
+ if (board) {
38
+ if (doc.owner !== c.owner)
39
+ reasons.push("the board log is not the trusted scheme owner's");
40
+ if (count && !c.board)
41
+ reasons.push("the authenticated previous board is missing");
42
+ if (c.maxSignedEffective && !(ms(effectiveAt) > ms(c.maxSignedEffective)))
43
+ reasons.push("the board does not take effect after every board-signed entry appended before it");
44
+ const allowed = count === 0 ? [c.owner] : (c.board?.members ?? []);
45
+ const need = count === 0 ? 1 : Number(c.board?.threshold ?? 1);
46
+ const signatures = signersOf(doc, allowed);
47
+ if (!signatures.wellFormed)
48
+ reasons.push("board carries a signature that is malformed, invalid or not by an authorised signer");
49
+ if (signatures.signers.length < need)
50
+ reasons.push(`board is signed by ${signatures.signers.length} of the ${need} previous board members required`);
51
+ }
52
+ else if (status) {
53
+ if (!verifySignedObject(doc) || doc.signer !== c.owner)
54
+ reasons.push("licence status entry is not signed by the scheme owner");
55
+ const previous = c.licenceState(doc.licence);
56
+ const allowed = doc.action === "suspend" ? previous === "active" : doc.action === "reinstate" ? previous === "suspended" : previous !== "withdrawn";
57
+ if (!allowed)
58
+ reasons.push(`entry cannot ${String(doc.action)} a licence that is ${previous}`);
59
+ return out({ effectiveAt, licenceState: doc.action === "suspend" ? "suspended" : doc.action === "reinstate" ? "active" : "withdrawn" });
60
+ }
61
+ else {
62
+ if (!c.board)
63
+ reasons.push("a board-signed entry is appended before any board");
64
+ else if (doc.board !== jcsHash(c.board))
65
+ reasons.push("entry is not signed by the newest board when it was appended");
66
+ else
67
+ reasons.push(...boardSignedReasons(doc, [c.board], release ? "release" : withdrawal ? "withdrawal" : "body register"));
68
+ if (release && c.released(subjectKey(doc.subject)))
69
+ reasons.push(`entry releases ${subjectKey(doc.subject)} again`);
70
+ if (withdrawal) {
71
+ const prior = c.release(doc.release);
72
+ if (!prior)
73
+ reasons.push("withdrawal names a release that is not in the release log");
74
+ else {
75
+ if (subjectKey(prior.subject) !== subjectKey(doc.subject))
76
+ reasons.push("withdrawal subject is not the release's subject");
77
+ if (!(ms(effectiveAt) >= ms(prior.effective_from)))
78
+ reasons.push("withdrawal takes effect before the release");
79
+ }
80
+ if (c.withdrawn(doc.release))
81
+ reasons.push("withdrawal withdraws a release again");
82
+ }
83
+ }
84
+ return out({ effectiveAt });
85
+ }
@@ -0,0 +1,5 @@
1
+ import { type SchemeAppendStatement, type SchemeLogsInput } from "./scheme-log.ts";
2
+ export declare function schemeHistoryReasons(input: SchemeLogsInput, owner: string | undefined, statusIssuers?: readonly string[], prepared?: readonly SchemeAppendStatement[]): {
3
+ reasons: string[];
4
+ canceled: string[];
5
+ };
@@ -0,0 +1,87 @@
1
+ // Transition-aware semantic replay. The enclosing verifier authenticates the final checkpoint.
2
+ import { jcsHash, receiptHash } from "@zanii/core";
3
+ import {} from "./licence.js";
4
+ import { verifySchemeEntry } from "./scheme-entry.js";
5
+ import {} from "./scheme-log.js";
6
+ import { schemePrefixStatements } from "./scheme-merkle.js";
7
+ import { verifySchemeTransition } from "./scheme-transition.js";
8
+ import { isObj, ms } from "./shared.js";
9
+ import { withinVerificationBudget } from "./verification-bounds.js";
10
+ export function schemeHistoryReasons(input, owner, statusIssuers, prepared) {
11
+ if (!owner || !Array.isArray(input.entries) || input.entries.length > 10_000 || !withinVerificationBudget(input))
12
+ return { reasons: ["transition-aware scheme history needs a trusted owner and at most 10000 bounded entries"], canceled: [] };
13
+ const reasons = [], canceled = new Set(), records = new Map();
14
+ const heads = new Map(), states = new Map();
15
+ const withdrawals = new Map();
16
+ const subjects = new Set();
17
+ let board = null, maximum = null;
18
+ const receipts = new Map((Array.isArray(input.ledgerReceipts) ? input.ledgerReceipts : []).filter(isObj).map((r) => [receiptHash(r), r]));
19
+ const statements = prepared ?? schemePrefixStatements(input.entries, owner);
20
+ const active = (hash) => canceled.has(hash) ? undefined : records.get(hash);
21
+ const withdrawn = (hash) => { const digest = withdrawals.get(hash); return digest !== undefined && !canceled.has(digest); };
22
+ const rebuildEffective = () => {
23
+ maximum = null;
24
+ for (const [type, head] of heads) {
25
+ if (!["certify.release", "certify.release-withdrawal", "certify.body-register"].includes(type))
26
+ continue;
27
+ head.effective_at = null;
28
+ }
29
+ for (const [hash, e] of records)
30
+ if (!canceled.has(hash) && ["certify.release", "certify.release-withdrawal", "certify.body-register"].includes(e.type)) {
31
+ const at = e.effective_from;
32
+ if (!maximum || ms(at) > ms(maximum))
33
+ maximum = at;
34
+ heads.get(e.type).effective_at = at;
35
+ }
36
+ };
37
+ const anchor = (index) => {
38
+ const append = input.appends?.[index];
39
+ const receipt = isObj(append) ? receipts.get(append.ledger_receipt) : undefined;
40
+ return statements[index] ? { statement: statements[index], append, receipt } : undefined;
41
+ };
42
+ for (let i = 0; i < input.entries.length; i++) {
43
+ const e = input.entries[i];
44
+ if (!isObj(e)) {
45
+ reasons.push(`entry ${i} is not a scheme entry`);
46
+ break;
47
+ }
48
+ if (e.type === "certify.board-transition") {
49
+ const next = input.entries[i + 1];
50
+ const previous = input.appends?.[i - 1];
51
+ const checked = verifySchemeTransition(e, { owner, board, nextBoard: next, index: i, previousAt: isObj(previous) ? previous.anchored_at : "", entry: active, anchor: anchor(i), nextAnchor: i + 1 < input.entries.length ? anchor(i + 1) : undefined });
52
+ reasons.push(...checked.reasons.map((r) => `entry ${i}: ${r}`));
53
+ if (!checked.ok)
54
+ break;
55
+ for (const hash of checked.canceled)
56
+ canceled.add(hash);
57
+ // A canceled release cannot leave an uncanceled withdrawal referring to it.
58
+ for (const [hash, prior] of records)
59
+ if (!canceled.has(hash) && prior.type === "certify.release-withdrawal" && canceled.has(prior.release))
60
+ reasons.push(`entry ${i}: dependent future withdrawal must be explicitly canceled with its release`);
61
+ rebuildEffective();
62
+ records.set(jcsHash(e), e);
63
+ continue;
64
+ }
65
+ const check = verifySchemeEntry(e, { owner, board, head: heads.get(e.type) ?? null, maxSignedEffective: maximum, statusIssuers,
66
+ release: active, released: (subject) => subjects.has(subject), withdrawn, licenceState: (hash) => states.get(hash) ?? "active" });
67
+ reasons.push(...check.reasons.map((r) => `entry ${i}: ${r}`));
68
+ if (!check.ok)
69
+ break;
70
+ const hash = jcsHash(e), type = e.type;
71
+ records.set(hash, e);
72
+ heads.set(type, { size: Number(e.seq) + 1, hash, effective_at: check.effectiveAt });
73
+ if (type === "certify.board")
74
+ board = e;
75
+ if (type === "certify.release") {
76
+ const s = e.subject;
77
+ subjects.add(`${s.kind} ${s.ref}`);
78
+ }
79
+ if (type === "certify.release-withdrawal")
80
+ withdrawals.set(e.release, hash);
81
+ if (type === "certify.licence-status")
82
+ states.set(e.licence, check.licenceState);
83
+ if (["certify.release", "certify.release-withdrawal", "certify.body-register"].includes(type) && (!maximum || ms(check.effectiveAt) > ms(maximum)))
84
+ maximum = check.effectiveAt;
85
+ }
86
+ return { reasons, canceled: reasons.length ? [] : [...canceled].sort() };
87
+ }
@@ -0,0 +1,98 @@
1
+ import { type Receipt, type SignedTreeHead } from "@zanii/core";
2
+ import { type Signer } from "./signer.ts";
3
+ export declare const SCHEME_LOG_TYPES: readonly ["certify.board", "certify.release", "certify.licence-status", "certify.release-withdrawal", "certify.body-register", "certify.board-transition"];
4
+ /** Section 2: the owner's signed tree head over the scheme log (leaf i = entry i). */
5
+ export declare function schemeCheckpoint(entries: readonly unknown[], ts: string, signer: Signer): Promise<SignedTreeHead>;
6
+ /** Section 2: the inclusion proof of entry `index` in the checkpoint over `entries`, hex. */
7
+ export declare const schemeInclusionProof: (entries: readonly unknown[], index: number) => string[];
8
+ /** Section 2: the proof that the log over `entries` extends its first `fromSize` entries, hex. */
9
+ export declare const schemeConsistencyProof: (entries: readonly unknown[], fromSize: number) => string[];
10
+ /** Section 2: one entry is at `index` of a checkpoint signed by the owner (a light client's check). */
11
+ export declare function verifySchemeInclusion(entry: unknown, index: number, checkpoint: unknown, proof: unknown, opts?: {
12
+ owner?: string;
13
+ }): {
14
+ ok: boolean;
15
+ reasons: string[];
16
+ };
17
+ /** Section 3: what the ledger receipt of an append commits to (`payload_hash` = jcsHash of it, `payload_encoding: jcs`). */
18
+ export interface SchemeAppendStatement {
19
+ v: 1;
20
+ type: "certify.scheme-append";
21
+ /** The scheme owner's DID, the log id. */
22
+ log: string;
23
+ index: number;
24
+ /** jcsHash of the entry appended. */
25
+ entry: string;
26
+ /** The log after the append: index + 1 entries and their Merkle root. */
27
+ size: number;
28
+ root: string;
29
+ }
30
+ export type SchemeAppend = SchemeAppendStatement & {
31
+ /** receiptHash of the Zanii ledger receipt that anchors the append. */
32
+ ledger_receipt: string;
33
+ /** The ledger receipt's `ts`: the append time. */
34
+ anchored_at: string;
35
+ signer: string;
36
+ sig: string;
37
+ };
38
+ /** Section 3: the statement for entry `index` of `entries` (the server records it on the ledger). */
39
+ export declare function schemeAppendStatement(entries: readonly unknown[], index: number, owner: string): SchemeAppendStatement;
40
+ /** Section 3: the owner's append receipt for entry `index`, bound to the ledger receipt that anchors it. */
41
+ export declare function buildSchemeAppend(entries: readonly unknown[], index: number, ledgerReceipt: Receipt, signer: Signer): Promise<SchemeAppend>;
42
+ /** The bounded equivalent when the caller maintains authenticated Merkle subtrees. */
43
+ export declare function buildSchemeAppendFromStatement(statement: SchemeAppendStatement, ledgerReceipt: Receipt, signer: Signer): Promise<SchemeAppend>;
44
+ /** One append against the authenticated entry/tree prefix; no history arrays are needed. */
45
+ export declare function verifySchemeAppend(statement: SchemeAppendStatement, entry: unknown, append: unknown, receipt: unknown, opts?: {
46
+ previousAt?: string | null;
47
+ ledger?: string | null;
48
+ }): {
49
+ ok: boolean;
50
+ reasons: string[];
51
+ };
52
+ export interface SchemeLogsInput {
53
+ /** The scheme log in append order. */
54
+ entries: readonly unknown[];
55
+ /** The owner's newest checkpoint, over all of `entries`. */
56
+ checkpoint: unknown;
57
+ /** Checkpoints this verifier saw before, each with the consistency proof from it to `checkpoint`. */
58
+ previous?: readonly {
59
+ checkpoint: unknown;
60
+ proof: unknown;
61
+ }[];
62
+ /** Require `checkpoint` co-signed by k trusted witnesses. */
63
+ witness?: {
64
+ cosignatures: readonly unknown[];
65
+ trusted: readonly string[];
66
+ k: number;
67
+ };
68
+ /** One append receipt per entry. */
69
+ appends?: readonly unknown[];
70
+ /** The Zanii ledger receipts the appends name: each append time is then the ledger's. */
71
+ ledgerReceipts?: readonly unknown[];
72
+ }
73
+ export interface SchemeLogsCheck {
74
+ ok: boolean;
75
+ reasons: string[];
76
+ size: number;
77
+ /** Entries whose append is bound to a supplied ledger receipt. */
78
+ anchored: number;
79
+ /** With `appends`: each entry's append time. */
80
+ appended?: string[];
81
+ /** With `witness`: the trusted witnesses that co-signed the checkpoint. */
82
+ witnesses?: string[];
83
+ /** Transition-aware logs only: authenticated cancellation hashes; empty on any refusal. */
84
+ canceled?: string[];
85
+ }
86
+ /**
87
+ * Section 4: the scheme log verifies: each kind of entry forms its own log (governance-board.md,
88
+ * licensing.md, body-register.md); the append rules hold in leaf order (each board-signed entry by
89
+ * the newest board appended before it, each later board taking effect after every board-signed
90
+ * entry before it); the checkpoint is the owner's over exactly these entries and extends every
91
+ * previous one; with `witness`, k trusted witnesses co-signed it; with `appends`, each entry has the
92
+ * owner's append receipt with append times never going back; with `ledgerReceipts`, each append is
93
+ * bound to its ledger receipt.
94
+ */
95
+ export declare function verifySchemeLogs(input: SchemeLogsInput, opts?: {
96
+ owner?: string;
97
+ statusIssuers?: readonly string[];
98
+ }): SchemeLogsCheck;
@@ -0,0 +1,266 @@
1
+ // spec/scheme-log.md: the scheme owner's logs (boards, releases, licence status, release withdrawals,
2
+ // body registers) as one checkpointed append-only log, like the status log (spec/witness.md):
3
+ // signed tree heads, inclusion and consistency proofs, witness co-signatures, and an append
4
+ // receipt binding each entry to the Zanii ledger receipt that anchors it.
5
+ import { canonicalBytes, consistencyProof, createSTHAsync, inclusionProof, jcsHash, leafHash, merkleRoot, receiptHash, verifyChain, verifyConsistency, verifyInclusion, verifyReceiptSignature, verifySignedObject, verifySTH, } from "@zanii/core";
6
+ import { verifyQuorum } from "@zanii/witness";
7
+ import { isBoard, isRelease, isWithdrawal, verifyBoardLog, verifyReleaseLog, verifyWithdrawalLog } from "./board.js";
8
+ import { verifyLicenceLog } from "./licence.js";
9
+ import { isBodyRegister, verifyBodyRegisterLog } from "./register.js";
10
+ import { isDid, isInt, isIso, isObj, isSha, ms } from "./shared.js";
11
+ import { signerDid, signWith } from "./signer.js";
12
+ import { signingIdentityReasons } from "./signing-identity.js";
13
+ import { schemeHistoryReasons } from "./scheme-history.js";
14
+ import { schemePrefixStatements } from "./scheme-merkle.js";
15
+ import { withinVerificationBudget } from "./verification-bounds.js";
16
+ export const SCHEME_LOG_TYPES = ["certify.board", "certify.release", "certify.licence-status", "certify.release-withdrawal", "certify.body-register", "certify.board-transition"];
17
+ const HEX32 = /^[0-9a-f]{64}$/;
18
+ const hex = (b) => Buffer.from(b).toString("hex");
19
+ const bytes = (h) => new Uint8Array(Buffer.from(h, "hex"));
20
+ const leaves = (entries) => entries.map((e) => leafHash(canonicalBytes(e)));
21
+ const rootOf = (entries) => `sha256:${hex(merkleRoot(leaves(entries)))}`;
22
+ const hexList = (p) => Array.isArray(p) && p.every((h) => typeof h === "string" && HEX32.test(h));
23
+ const known = (e) => isObj(e) && SCHEME_LOG_TYPES.includes(e.type);
24
+ /** Section 2: the owner's signed tree head over the scheme log (leaf i = entry i). */
25
+ export async function schemeCheckpoint(entries, ts, signer) {
26
+ if (!Array.isArray(entries) || entries.length === 0 || !entries.every(known))
27
+ throw new Error("the scheme log must be 1 or more scheme log entries");
28
+ if (!isIso(ts))
29
+ throw new Error("ts must be an ISO UTC timestamp");
30
+ return createSTHAsync({ logId: signerDid(signer), size: entries.length, root: rootOf(entries), ts }, signer.sign);
31
+ }
32
+ /** Section 2: the inclusion proof of entry `index` in the checkpoint over `entries`, hex. */
33
+ export const schemeInclusionProof = (entries, index) => inclusionProof(index, leaves(entries)).map(hex);
34
+ /** Section 2: the proof that the log over `entries` extends its first `fromSize` entries, hex. */
35
+ export const schemeConsistencyProof = (entries, fromSize) => consistencyProof(fromSize, leaves(entries)).map(hex);
36
+ const checkpointOk = (cp, owner) => isObj(cp) && verifySTH(cp) && (owner === undefined || cp.log_id === owner) && isInt(cp.size, 1, Number.MAX_SAFE_INTEGER) && isSha(cp.root);
37
+ /** Section 2: one entry is at `index` of a checkpoint signed by the owner (a light client's check). */
38
+ export function verifySchemeInclusion(entry, index, checkpoint, proof, opts = {}) {
39
+ const reasons = [];
40
+ if (opts.owner === undefined)
41
+ reasons.push("no trusted scheme owner supplied: the checkpoint signer was NOT checked");
42
+ if (!checkpointOk(checkpoint, opts.owner))
43
+ return { ok: false, reasons: [...reasons, "scheme checkpoint is not signed by the scheme owner"] };
44
+ const included = known(entry) && isInt(index, 0, checkpoint.size - 1) && hexList(proof) &&
45
+ verifyInclusion(leafHash(canonicalBytes(entry)), index, checkpoint.size, proof.map(bytes), bytes(checkpoint.root.slice(7)));
46
+ if (!included)
47
+ reasons.push("the entry is not in the scheme checkpoint at that index");
48
+ return { ok: reasons.length === 0, reasons };
49
+ }
50
+ /** Section 3: the statement for entry `index` of `entries` (the server records it on the ledger). */
51
+ export function schemeAppendStatement(entries, index, owner) {
52
+ if (!Array.isArray(entries) || !isInt(index, 0, entries.length - 1) || !known(entries[index]))
53
+ throw new Error("index is not an entry of the scheme log");
54
+ if (!isDid(owner))
55
+ throw new Error("owner must be a did:key");
56
+ const prefix = entries.slice(0, index + 1);
57
+ return { v: 1, type: "certify.scheme-append", log: owner, index, entry: jcsHash(entries[index]), size: index + 1, root: rootOf(prefix) };
58
+ }
59
+ /** Why a ledger receipt does not anchor `statement`; empty when it does. */
60
+ function ledgerReasons(statement, receipt, anchoredAt) {
61
+ const r = isObj(receipt) ? receipt : null;
62
+ if (!r || !verifyReceiptSignature(r))
63
+ return ["ledger receipt signature is invalid"];
64
+ if (r.payload_hash !== jcsHash(statement) || r.payload_encoding !== "jcs")
65
+ return ["ledger receipt does not commit to the append"];
66
+ if (anchoredAt !== undefined && r.ts !== anchoredAt)
67
+ return ["append time is not the ledger receipt's"];
68
+ return [];
69
+ }
70
+ /** Section 3: the receipt's agent is delegated, at the receipt's time, by the owner's ledger identity. */
71
+ const ledgerIdentity = (r, ledger) => Array.isArray(r.delegation) && isIso(r.ts) && verifyChain(r.delegation, { agentId: r.agent_id, expectedRoot: ledger, at: new Date(r.ts) }).ok;
72
+ /** Section 3: the owner's append receipt for entry `index`, bound to the ledger receipt that anchors it. */
73
+ export async function buildSchemeAppend(entries, index, ledgerReceipt, signer) {
74
+ const statement = schemeAppendStatement(entries, index, signerDid(signer));
75
+ return buildSchemeAppendFromStatement(statement, ledgerReceipt, signer);
76
+ }
77
+ /** The bounded equivalent when the caller maintains authenticated Merkle subtrees. */
78
+ export async function buildSchemeAppendFromStatement(statement, ledgerReceipt, signer) {
79
+ if (!withinVerificationBudget(ledgerReceipt, { maxBytes: 524_288, maxNodes: 50_000 }))
80
+ throw new Error("scheme ledger receipt exceeds the verification budget");
81
+ if (!isObj(statement) || Object.keys(statement).length !== 7 || statement.v !== 1 || statement.type !== "certify.scheme-append"
82
+ || statement.log !== signerDid(signer) || !isInt(statement.index, 0, Number.MAX_SAFE_INTEGER - 1) || statement.size !== statement.index + 1
83
+ || !isSha(statement.entry) || !isSha(statement.root))
84
+ throw new Error("invalid scheme append statement");
85
+ const bad = ledgerReasons(statement, ledgerReceipt);
86
+ if (bad.length)
87
+ throw new Error(bad[0]);
88
+ if (!isIso(ledgerReceipt.ts))
89
+ throw new Error("the ledger receipt ts is not an ISO UTC timestamp");
90
+ return signWith({ ...statement, ledger_receipt: receiptHash(ledgerReceipt), anchored_at: ledgerReceipt.ts }, signer);
91
+ }
92
+ /** One append against the authenticated entry/tree prefix; no history arrays are needed. */
93
+ export function verifySchemeAppend(statement, entry, append, receipt, opts = {}) {
94
+ const reasons = [];
95
+ if (!withinVerificationBudget(entry, { maxBytes: 1_048_576, maxNodes: 100_000 }) || !withinVerificationBudget(append, { maxBytes: 65_536, maxNodes: 10_000 })
96
+ || !withinVerificationBudget(receipt, { maxBytes: 524_288, maxNodes: 50_000 }))
97
+ return { ok: false, reasons: ["scheme append exceeds the verification budget"] };
98
+ if (!isObj(append) || Object.keys(append).length !== 11 || append.type !== "certify.scheme-append" || !verifySignedObject(append) || append.signer !== statement.log)
99
+ return { ok: false, reasons: ["entry has no append receipt signed by the scheme owner"] };
100
+ const { ledger_receipt, anchored_at, signer: _s, sig: _g, ...given } = append;
101
+ if (jcsHash(given) !== jcsHash(statement) || statement.entry !== jcsHash(entry))
102
+ reasons.push("the append receipt is not for this entry at this position");
103
+ if (!isIso(anchored_at) || opts.previousAt && ms(anchored_at) < ms(opts.previousAt))
104
+ reasons.push("the append time goes back");
105
+ if (opts.ledger && (isRelease(entry) || isWithdrawal(entry)) && isIso(anchored_at) && ms(entry.effective_from) < ms(anchored_at))
106
+ reasons.push("entry takes effect before it was appended");
107
+ if (!isObj(receipt) || ledger_receipt !== receiptHash(receipt))
108
+ reasons.push("its ledger receipt was not supplied");
109
+ else {
110
+ reasons.push(...ledgerReasons(statement, receipt, anchored_at));
111
+ if (opts.ledger && !ledgerIdentity(receipt, opts.ledger))
112
+ reasons.push("ledger receipt is not signed by the scheme owner's ledger identity");
113
+ }
114
+ return { ok: reasons.length === 0, reasons };
115
+ }
116
+ const NOUN = { "certify.release": "release", "certify.release-withdrawal": "withdrawal", "certify.body-register": "body register" };
117
+ const cosigShape = (c) => isObj(c) && typeof c.witnessed_log_id === "string" && isObj(c.cosignature) && typeof c.cosignature.log_id === "string";
118
+ /**
119
+ * Section 4: the scheme log verifies: each kind of entry forms its own log (governance-board.md,
120
+ * licensing.md, body-register.md); the append rules hold in leaf order (each board-signed entry by
121
+ * the newest board appended before it, each later board taking effect after every board-signed
122
+ * entry before it); the checkpoint is the owner's over exactly these entries and extends every
123
+ * previous one; with `witness`, k trusted witnesses co-signed it; with `appends`, each entry has the
124
+ * owner's append receipt with append times never going back; with `ledgerReceipts`, each append is
125
+ * bound to its ledger receipt.
126
+ */
127
+ export function verifySchemeLogs(input, opts = {}) {
128
+ const entries = Array.isArray(input?.entries) ? input.entries : [];
129
+ const out = (reasons, extra = {}) => {
130
+ const r = [...new Set(reasons)];
131
+ return { ok: r.length === 0, reasons: r, size: entries.length, anchored: 0, ...extra, ...(extra.canceled ? { canceled: r.length ? [] : extra.canceled } : {}) };
132
+ };
133
+ if (entries.length === 0)
134
+ return out(["the scheme log is empty"]);
135
+ const transitions = entries.some((e) => isObj(e) && e.type === "certify.board-transition");
136
+ if (transitions && (!withinVerificationBudget(input) || entries.length > 10_000))
137
+ return out(["transition-aware scheme history exceeds the verification budget"], { canceled: [] });
138
+ const reasons = [];
139
+ entries.forEach((e, i) => {
140
+ if (!known(e))
141
+ reasons.push(`entry ${i} is not a scheme log entry`);
142
+ });
143
+ const of = (t) => entries.filter((e) => isObj(e) && e.type === t);
144
+ const boards = of("certify.board");
145
+ const releases = of("certify.release");
146
+ const statuses = of("certify.licence-status");
147
+ const withdrawals = of("certify.release-withdrawal");
148
+ const registers = of("certify.body-register");
149
+ const prepared = transitions ? schemePrefixStatements(entries, opts.owner ?? "") : null;
150
+ const semantics = transitions ? schemeHistoryReasons(input, opts.owner, opts.statusIssuers, prepared) : null;
151
+ if (semantics)
152
+ reasons.push(...semantics.reasons);
153
+ if (!transitions) {
154
+ reasons.push(...verifyBoardLog(boards, opts).reasons);
155
+ if (releases.length)
156
+ reasons.push(...verifyReleaseLog(releases, boards, opts).reasons);
157
+ if (statuses.length)
158
+ reasons.push(...verifyLicenceLog(statuses, opts).reasons);
159
+ if (withdrawals.length)
160
+ reasons.push(...verifyWithdrawalLog(withdrawals, releases, boards, opts).reasons);
161
+ if (registers.length)
162
+ reasons.push(...verifyBodyRegisterLog(registers, boards, opts).reasons);
163
+ // the append rules, replayed in leaf order.
164
+ let newest = null;
165
+ const signedAt = [];
166
+ entries.forEach((e, i) => {
167
+ if (!isObj(e))
168
+ return;
169
+ const o = e;
170
+ const signed = isRelease(o) || isWithdrawal(o) || isBodyRegister(o);
171
+ if (isBoard(e)) {
172
+ if (newest !== null && signedAt.some((t) => !(ms(e.effective_from) > t)))
173
+ reasons.push(`entry ${i}: the board does not take effect after every board-signed entry appended before it`);
174
+ newest = e;
175
+ }
176
+ else if (signed) {
177
+ if (newest === null)
178
+ reasons.push(`entry ${i}: a ${NOUN[o.type]} is appended before any board`);
179
+ else if (o.board !== jcsHash(newest))
180
+ reasons.push(`entry ${i}: the ${NOUN[o.type]} is not signed by the newest board when it was appended`);
181
+ signedAt.push(ms(o.effective_from));
182
+ }
183
+ });
184
+ }
185
+ const owner = opts.owner ?? (isBoard(boards[0]) ? boards[0].owner : undefined);
186
+ if (opts.statusIssuers !== undefined)
187
+ reasons.push(...signingIdentityReasons(owner, [], opts.statusIssuers));
188
+ const cp = input.checkpoint;
189
+ if (!checkpointOk(cp, owner))
190
+ reasons.push("scheme checkpoint is not signed by the scheme owner");
191
+ else if (cp.size !== entries.length || cp.root !== rootOf(entries))
192
+ reasons.push("scheme checkpoint is not over these entries");
193
+ for (const [n, p] of (Array.isArray(input.previous) ? input.previous : []).entries()) {
194
+ const old = isObj(p) ? p.checkpoint : null;
195
+ if (!checkpointOk(old, owner))
196
+ reasons.push(`previous checkpoint ${n} is not signed by the scheme owner`);
197
+ else if (!checkpointOk(cp, owner) || !hexList(p.proof) || old.size > cp.size ||
198
+ !verifyConsistency(old.size, cp.size, bytes(old.root.slice(7)), bytes(cp.root.slice(7)), p.proof.map(bytes)))
199
+ reasons.push(`the scheme log does not extend previous checkpoint ${n}`);
200
+ }
201
+ let witnesses;
202
+ if (input.witness) {
203
+ const w = input.witness;
204
+ if (!isInt(w.k, 1, 1000))
205
+ reasons.push("witness policy k must be an integer from 1");
206
+ else if (checkpointOk(cp, owner)) {
207
+ const cosigs = (Array.isArray(w.cosignatures) ? w.cosignatures : []).filter(cosigShape);
208
+ const q = verifyQuorum(cp, cosigs, { trusted: Array.isArray(w.trusted) ? [...w.trusted] : [], k: w.k });
209
+ witnesses = q.witnesses;
210
+ if (!q.ok)
211
+ reasons.push(`scheme checkpoint has ${q.witnesses.length} of the ${w.k} trusted witness co-signatures required`);
212
+ }
213
+ }
214
+ const extra = { ...(witnesses ? { witnesses } : {}), ...(semantics ? { canceled: semantics.canceled } : {}) };
215
+ if (input.appends === undefined)
216
+ return out(reasons, extra);
217
+ const appends = Array.isArray(input.appends) ? input.appends : [];
218
+ if (appends.length !== entries.length)
219
+ reasons.push(`${appends.length} append receipts for ${entries.length} entries`);
220
+ const receipts = new Map();
221
+ for (const r of Array.isArray(input.ledgerReceipts) ? input.ledgerReceipts : [])
222
+ if (isObj(r))
223
+ receipts.set(receiptHash(r), r);
224
+ const appended = [];
225
+ let anchored = 0;
226
+ let last = Number.NEGATIVE_INFINITY;
227
+ // section 3: the owner's ledger identity named by the newest board appended at or before each entry
228
+ let ledger;
229
+ entries.forEach((e, i) => {
230
+ if (isBoard(e) && typeof e.ledger === "string")
231
+ ledger = e.ledger;
232
+ const a = appends[i];
233
+ if (!isObj(a) || a.type !== "certify.scheme-append" || Object.keys(a).length !== 11 || !verifySignedObject(a) || (owner !== undefined && a.signer !== owner)) {
234
+ reasons.push(`entry ${i} has no append receipt signed by the scheme owner`);
235
+ return;
236
+ }
237
+ if (!known(e) || !owner)
238
+ return;
239
+ const statement = prepared?.[i] ?? schemeAppendStatement(entries, i, owner);
240
+ const { ledger_receipt, anchored_at, signer: _s, sig: _g, ...given } = a;
241
+ if (jcsHash(given) !== jcsHash(statement))
242
+ reasons.push(`entry ${i}: the append receipt is not for this entry at this position`);
243
+ if (!isIso(anchored_at) || !(ms(anchored_at) >= last))
244
+ reasons.push(`entry ${i}: the append time goes back`);
245
+ else
246
+ last = ms(anchored_at);
247
+ appended.push(String(anchored_at));
248
+ const o = e;
249
+ if (ledger && (isRelease(o) || isWithdrawal(o)) && isIso(anchored_at) && ms(o.effective_from) < ms(anchored_at))
250
+ reasons.push(`entry ${i}: the ${NOUN[o.type]} takes effect before it was appended`);
251
+ if (input.ledgerReceipts === undefined)
252
+ return;
253
+ const r = typeof ledger_receipt === "string" ? receipts.get(ledger_receipt) : undefined;
254
+ if (r === undefined) {
255
+ reasons.push(`entry ${i}: its ledger receipt was not supplied`);
256
+ return;
257
+ }
258
+ const bad = ledgerReasons(statement, r, anchored_at);
259
+ if (!bad.length && ledger && !ledgerIdentity(r, ledger))
260
+ bad.push("ledger receipt is not signed by the scheme owner's ledger identity");
261
+ reasons.push(...bad.map((x) => `entry ${i}: ${x}`));
262
+ if (!bad.length)
263
+ anchored++;
264
+ });
265
+ return out(reasons, { ...extra, anchored, appended });
266
+ }
@@ -0,0 +1,2 @@
1
+ import type { SchemeAppendStatement } from "./scheme-log.ts";
2
+ export declare function schemePrefixStatements(entries: readonly unknown[], owner: string): SchemeAppendStatement[];
@@ -0,0 +1,20 @@
1
+ // One preparation for complete offline verification; the server uses persisted indexed nodes.
2
+ import { canonicalBytes, jcsHash, leafHash, merkleRoot } from "@zanii/core";
3
+ export function schemePrefixStatements(entries, owner) {
4
+ const frontier = [];
5
+ return entries.map((entry, index) => {
6
+ let level = 0, position = index, node = leafHash(canonicalBytes(entry));
7
+ while (position % 2 === 1) {
8
+ node = merkleRoot([frontier[level], node]);
9
+ frontier[level] = undefined;
10
+ position = Math.floor(position / 2);
11
+ level++;
12
+ }
13
+ frontier[level] = node;
14
+ let root;
15
+ for (const left of frontier)
16
+ if (left)
17
+ root = root ? merkleRoot([left, root]) : left;
18
+ return { v: 1, type: "certify.scheme-append", log: owner, index, entry: jcsHash(entry), size: index + 1, root: `sha256:${Buffer.from(root).toString("hex")}` };
19
+ });
20
+ }
@@ -0,0 +1,25 @@
1
+ export interface SchemeTextFile {
2
+ path: string;
3
+ text: string;
4
+ }
5
+ export interface SchemeTextBundle {
6
+ v: 1;
7
+ type: "certify.scheme-text";
8
+ scheme: string;
9
+ files: SchemeTextFile[];
10
+ }
11
+ export declare const SCHEME_TEXT_MAX_BYTES = 4194304;
12
+ /** LF normalization is explicit; Unicode content is retained byte for byte otherwise. */
13
+ export declare function buildSchemeTextBundle(input: {
14
+ scheme: string;
15
+ files: readonly SchemeTextFile[];
16
+ }): SchemeTextBundle;
17
+ /** The released file is these canonical JSON bytes; a digest alone is not board approval. */
18
+ export declare function schemeTextBundleHash(doc: unknown): string;
19
+ export declare function verifySchemeTextBundle(doc: unknown, expected: {
20
+ scheme: string;
21
+ sha256: string;
22
+ }): {
23
+ ok: boolean;
24
+ reasons: string[];
25
+ };