@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,78 @@
1
+ // Preflight JSON before recursive canonicalization or crypto: fixed aggregate limits, no serialization copies.
2
+ export function withinVerificationBudget(value, limits = {}) {
3
+ const maxBytes = limits.maxBytes ?? 67_108_864, maxNodes = limits.maxNodes ?? 1_500_000, maxDepth = limits.maxDepth ?? 32;
4
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 67_108_864 || !Number.isSafeInteger(maxNodes) || maxNodes < 1 || maxNodes > 1_500_000 || !Number.isSafeInteger(maxDepth) || maxDepth < 0 || maxDepth > 32)
5
+ return false;
6
+ const stack = [{ value, depth: 0 }];
7
+ const active = new WeakSet();
8
+ let bytes = 0, nodes = 0;
9
+ const literal = (s) => {
10
+ if (s.length > maxBytes - bytes || !s.isWellFormed())
11
+ return false;
12
+ bytes += Buffer.byteLength(s, "utf8") + 2;
13
+ // Most evidence strings need no JSON escapes; scan those in native code once.
14
+ if (/[\u0000-\u001f"\\]/.test(s))
15
+ for (let i = 0; i < s.length; i++) {
16
+ const c = s.charCodeAt(i);
17
+ if (c === 34 || c === 92 || c === 8 || c === 9 || c === 10 || c === 12 || c === 13)
18
+ bytes++;
19
+ else if (c < 32)
20
+ bytes += 5;
21
+ }
22
+ return bytes <= maxBytes;
23
+ };
24
+ try {
25
+ while (stack.length) {
26
+ const item = stack.pop(), v = item.value;
27
+ if (item.exit) {
28
+ active.delete(v);
29
+ continue;
30
+ }
31
+ if (++nodes > maxNodes || item.depth > maxDepth)
32
+ return false;
33
+ if (typeof v === "string") {
34
+ if (!literal(v))
35
+ return false;
36
+ }
37
+ else if (v === null || typeof v === "boolean")
38
+ bytes += 5;
39
+ else if (typeof v === "number") {
40
+ if (!Number.isFinite(v) || Math.abs(v) > Number.MAX_SAFE_INTEGER)
41
+ return false;
42
+ bytes += 32;
43
+ }
44
+ else if (typeof v === "object") {
45
+ if (active.has(v) || (!Array.isArray(v) && Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null))
46
+ return false;
47
+ const names = Array.isArray(v) ? null : Object.keys(v);
48
+ const size = names?.length ?? v.length;
49
+ if (size + nodes + stack.length > maxNodes)
50
+ return false;
51
+ bytes += 2 + size;
52
+ active.add(v);
53
+ stack.push({ value: v, depth: item.depth, exit: true });
54
+ if (names)
55
+ for (const name of names) {
56
+ if (!literal(name))
57
+ return false;
58
+ const descriptor = Object.getOwnPropertyDescriptor(v, name);
59
+ if (!("value" in descriptor))
60
+ return false;
61
+ bytes++;
62
+ stack.push({ value: descriptor.value, depth: item.depth + 1 });
63
+ }
64
+ else
65
+ for (let i = 0; i < size; i++)
66
+ stack.push({ value: v[i], depth: item.depth + 1 });
67
+ }
68
+ else
69
+ return false;
70
+ if (bytes > maxBytes)
71
+ return false;
72
+ }
73
+ return true;
74
+ }
75
+ catch {
76
+ return false;
77
+ }
78
+ }
@@ -0,0 +1,32 @@
1
+ import type { Credential } from "@zanii/credentials";
2
+ import type { SchemeLogsInput } from "./scheme-log.ts";
3
+ export interface IssuerPolicy {
4
+ v: 1;
5
+ type: "certify.issuer-policy";
6
+ domain: string;
7
+ dids: string[];
8
+ status_dids: string[];
9
+ require_pq: boolean;
10
+ require_pq_from: string | null;
11
+ pq_bindings: string[];
12
+ }
13
+ export interface ReleaseVerificationPolicy {
14
+ owner: string;
15
+ boards: readonly unknown[];
16
+ releases: readonly unknown[];
17
+ withdrawals?: readonly unknown[];
18
+ rejectWithdrawn?: boolean;
19
+ profileSha256?: string;
20
+ schemeSha256?: string;
21
+ schemeLog?: SchemeLogsInput;
22
+ }
23
+ export declare function isIssuerPolicy(value: unknown): value is IssuerPolicy;
24
+ export declare function issuerPolicyCheck(cert: Credential, document: unknown, policy: unknown, opts?: {
25
+ allowNoPq?: boolean;
26
+ status?: unknown;
27
+ revocationList?: unknown;
28
+ }): {
29
+ reasons: string[];
30
+ requirePq: boolean;
31
+ };
32
+ export declare function releasePolicyReasons(cert: Credential, policy: ReleaseVerificationPolicy): string[];
@@ -0,0 +1,85 @@
1
+ // Optional relying-party policies. Downloaded trust metadata cannot replace these local pins.
2
+ import { publicKeyFromDid } from "@zanii/core";
3
+ import { bindingHash } from "@zanii/pq";
4
+ import { requireRelease } from "./board.js";
5
+ import { pqCommitment, pqPolicyOf } from "./pq.js";
6
+ import { isIso, isObj, isSha } from "./shared.js";
7
+ import { signingIdentityReasons } from "./signing-identity.js";
8
+ const did = (v) => typeof v === "string" && v.length <= 69 && publicKeyFromDid(v) !== null;
9
+ const hash = (v) => isSha(v) && v.length === 71;
10
+ const unique = (v, min, valid) => Array.isArray(v) && v.length >= min && v.length <= 64 && Array.from(v).every(valid) && new Set(v).size === v.length;
11
+ const equalSet = (a, b, valid) => unique(a, 0, valid) && a.length === b.length && a.every((x) => b.includes(x));
12
+ const boundedBinding = (b) => isObj(b) && Object.keys(b).length === 8 && b.v === 1 && b.type === "pq.binding" && b.alg === "ML-DSA-65"
13
+ && did(b.did) && isIso(b.ts) && ["pq_pub", "sig_ed", "sig_pq"].every((key) => typeof b[key] === "string" && b[key].length <= 7000);
14
+ export function isIssuerPolicy(value) {
15
+ if (!isObj(value) || Object.keys(value).length !== 8 || !["v", "type", "domain", "dids", "status_dids", "require_pq", "require_pq_from", "pq_bindings"].every((k) => Object.hasOwn(value, k)))
16
+ return false;
17
+ return value.v === 1 && value.type === "certify.issuer-policy" && typeof value.domain === "string" && value.domain.length <= 253
18
+ && /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/.test(value.domain) && value.domain.trim() === value.domain
19
+ && unique(value.dids, 1, did) && unique(value.status_dids, 1, did) && value.status_dids.every((d) => value.dids.includes(d))
20
+ && typeof value.require_pq === "boolean" && (value.require_pq_from === null || isIso(value.require_pq_from))
21
+ && (value.require_pq || value.require_pq_from === null) && unique(value.pq_bindings, value.require_pq ? 1 : 0, hash);
22
+ }
23
+ export function issuerPolicyCheck(cert, document, policy, opts = {}) {
24
+ const reasons = [], fail = (s) => reasons.push(`issuer policy: ${s}`);
25
+ if (!isIssuerPolicy(policy))
26
+ return { reasons: ["issuer policy: invalid pinned policy (v1)"], requirePq: true };
27
+ const requirePq = pqPolicyOf(policy, false, String(cert?.issued_at)) === "required";
28
+ if (cert?.issuer_domain !== policy.domain || !policy.dids.includes(cert?.issuer))
29
+ fail("certificate issuer is not pinned");
30
+ if (!isObj(document))
31
+ fail("issuer document is required");
32
+ else {
33
+ if (document.owner !== undefined)
34
+ reasons.push(...signingIdentityReasons(document.owner, document.dids ?? [], document.status_dids ?? []));
35
+ if (document.v !== 1 || document.domain !== policy.domain)
36
+ fail("issuer document domain does not match the pin");
37
+ if (!equalSet(document.dids, policy.dids, did) || !equalSet(document.status_dids, policy.status_dids, did))
38
+ fail("issuer keys changed or are missing; review and update the local policy for a rotation");
39
+ if (document.require_pq !== policy.require_pq || (document.require_pq_from ?? null) !== policy.require_pq_from)
40
+ fail("post-quantum requirement or cutover is missing or changed");
41
+ const bindings = document.pq_bindings;
42
+ if (!Array.isArray(bindings) || bindings.length > 64 || !Array.from(bindings).every(boundedBinding))
43
+ fail("post-quantum binding metadata is required");
44
+ else
45
+ try {
46
+ if (!equalSet(bindings.map((b) => bindingHash(b)), policy.pq_bindings, hash))
47
+ fail("post-quantum bindings do not match the local pins");
48
+ }
49
+ catch {
50
+ fail("post-quantum binding metadata is invalid");
51
+ }
52
+ }
53
+ if (opts.allowNoPq)
54
+ fail("post-quantum opt-out is incompatible with a pinned policy");
55
+ if (requirePq && !policy.pq_bindings.includes(pqCommitment(cert) ?? ""))
56
+ fail("certificate post-quantum key is not pinned");
57
+ if (requirePq && opts.status !== undefined && !policy.pq_bindings.includes(pqCommitment(opts.status) ?? ""))
58
+ fail("status post-quantum key is not pinned");
59
+ if (isObj(opts.status) && !policy.status_dids.includes(String(opts.status.issuer)))
60
+ fail("status signer is not pinned");
61
+ if (isObj(opts.revocationList) && !policy.status_dids.includes(String(opts.revocationList.issuer)))
62
+ fail("revocation list signer is not pinned");
63
+ return { reasons, requirePq };
64
+ }
65
+ export function releasePolicyReasons(cert, policy) {
66
+ if (!isObj(policy) || !did(policy.owner) || !Array.isArray(policy.boards) || policy.boards.length > 10_000
67
+ || !Array.isArray(policy.releases) || policy.releases.length > 10_000 || !isIso(cert?.issued_at)
68
+ || Object.keys(policy).some((k) => !["owner", "boards", "releases", "withdrawals", "rejectWithdrawn", "profileSha256", "schemeSha256", "schemeLog"].includes(k))
69
+ || (policy.rejectWithdrawn !== undefined && typeof policy.rejectWithdrawn !== "boolean")
70
+ || (policy.schemeLog !== undefined && !isObj(policy.schemeLog))
71
+ || (policy.withdrawals !== undefined && (!Array.isArray(policy.withdrawals) || policy.withdrawals.length > 10_000))
72
+ || (policy.rejectWithdrawn === true && policy.withdrawals === undefined)
73
+ || (policy.profileSha256 !== undefined && !hash(policy.profileSha256)) || (policy.schemeSha256 !== undefined && !hash(policy.schemeSha256)))
74
+ return ["release policy: invalid policy or missing trusted logs"];
75
+ const claim = isObj(cert.claim) ? cert.claim : {};
76
+ const reasons = [];
77
+ for (const [kind, ref, digest] of [["profile", claim.profile, policy.profileSha256], ["scheme", claim.scheme, policy.schemeSha256]]) {
78
+ const checked = requireRelease(policy.releases, policy.boards, { kind, ref: String(ref) }, cert.issued_at, {
79
+ owner: policy.owner, ...(digest ? { sha256: digest } : {}), ...(policy.rejectWithdrawn ? { withdrawals: policy.withdrawals } : {}),
80
+ ...(policy.schemeLog ? { schemeLog: policy.schemeLog } : {}),
81
+ });
82
+ reasons.push(...checked.reasons.map((r) => `release policy: ${r}`));
83
+ }
84
+ return [...new Set(reasons)];
85
+ }
@@ -0,0 +1,19 @@
1
+ import { type SignedTreeHead } from "@zanii/core";
2
+ import { type Signer } from "./signer.ts";
3
+ import { type StatusDocument, type StatusWitness } from "./status.ts";
4
+ /** Section 1: the documents form one log: each signed by one issuer, at its index, naming its predecessor. */
5
+ export declare function verifyStatusLog(docs: readonly unknown[]): {
6
+ ok: boolean;
7
+ reasons: string[];
8
+ };
9
+ /** Section 2: the issuer's signed tree head over the whole log (leaf i = document i). */
10
+ export declare function statusCheckpoint(docs: readonly StatusDocument[], ts: string, signer: Signer): Promise<SignedTreeHead>;
11
+ /** Section 2: the inclusion proof of document `index` in the checkpoint over `docs`, hex. */
12
+ export declare const statusInclusionProof: (docs: readonly StatusDocument[], index: number) => string[];
13
+ /** Section 2: the proof that the log over `docs` extends its first `fromSize` documents, hex. */
14
+ export declare const statusConsistencyProof: (docs: readonly StatusDocument[], fromSize: number) => string[];
15
+ /** Section 3: the reasons a status document is not in a checkpoint co-signed by `k` trusted witnesses. */
16
+ export declare function witnessReasons(doc: Record<string, unknown>, w: StatusWitness): {
17
+ reasons: string[];
18
+ witnesses: string[];
19
+ };
@@ -0,0 +1,80 @@
1
+ // spec/witness.md: the status log. Every status document names the previous one (`seq`, `prev`);
2
+ // the issuer checkpoints the log as a Merkle tree head, and partner witnesses (@zanii/witness)
3
+ // check it is append-only and co-sign it.
4
+ import { canonicalBytes, consistencyProof, createSTHAsync, inclusionProof, jcsHash, leafHash, merkleRoot, verifyInclusion, verifySignedObject, verifySTH, } from "@zanii/core";
5
+ import { verifyQuorum } from "@zanii/witness";
6
+ import { isInt, isIso, isObj, isSha, ms } from "./shared.js";
7
+ import { signerDid } from "./signer.js";
8
+ import { verifyStatus } from "./status.js";
9
+ const HEX32 = /^[0-9a-f]{64}$/;
10
+ const hex = (b) => Buffer.from(b).toString("hex");
11
+ const bytes = (h) => new Uint8Array(Buffer.from(h, "hex"));
12
+ const leaves = (docs) => docs.map((d) => leafHash(canonicalBytes(d)));
13
+ /** Section 1: the documents form one log: each signed by one issuer, at its index, naming its predecessor. */
14
+ export function verifyStatusLog(docs) {
15
+ const reasons = [];
16
+ if (!Array.isArray(docs) || docs.length === 0)
17
+ return { ok: false, reasons: ["the status log is empty"] };
18
+ docs.forEach((d, i) => {
19
+ const prev = i > 0 ? docs[i - 1] : null;
20
+ if (!isObj(d) || (d.v !== 1 && d.v !== 2) || d.type !== "certify.status" || (d.v === 2 ? !verifyStatus(d, { issuer: String(d.issuer), now: String(d.issued_at) }).ok : !verifySignedObject(d)) || d.signer !== d.issuer) {
21
+ reasons.push(`document ${i} is not a signed status document`);
22
+ return;
23
+ }
24
+ if (i > 0 && (!isObj(prev) || prev.issuer !== d.issuer))
25
+ reasons.push(`document ${i} is from a different issuer`);
26
+ if (d.seq !== i)
27
+ reasons.push(`document ${i} is not at position ${i}`);
28
+ if (d.prev !== (i > 0 && isObj(prev) ? jcsHash(prev) : null))
29
+ reasons.push(`document ${i} does not name the previous document`);
30
+ if (i > 0 && isObj(prev) && !(ms(d.issued_at) >= ms(prev.issued_at)))
31
+ reasons.push(`document ${i} is older than the previous document`);
32
+ });
33
+ return { ok: reasons.length === 0, reasons };
34
+ }
35
+ /** Section 2: the issuer's signed tree head over the whole log (leaf i = document i). */
36
+ export async function statusCheckpoint(docs, ts, signer) {
37
+ const check = verifyStatusLog(docs);
38
+ if (!check.ok)
39
+ throw new Error(check.reasons[0]);
40
+ if (docs[0].issuer !== signerDid(signer))
41
+ throw new Error("signer is not the status log issuer");
42
+ if (!isIso(ts) || ms(ts) < ms(docs.at(-1).issued_at))
43
+ throw new Error("ts must be an ISO UTC timestamp, not before the last document");
44
+ const root = `sha256:${hex(merkleRoot(leaves(docs)))}`;
45
+ return createSTHAsync({ logId: signerDid(signer), size: docs.length, root, ts }, signer.sign);
46
+ }
47
+ /** Section 2: the inclusion proof of document `index` in the checkpoint over `docs`, hex. */
48
+ export const statusInclusionProof = (docs, index) => inclusionProof(index, leaves(docs)).map(hex);
49
+ /** Section 2: the proof that the log over `docs` extends its first `fromSize` documents, hex. */
50
+ export const statusConsistencyProof = (docs, fromSize) => consistencyProof(fromSize, leaves(docs)).map(hex);
51
+ const cosigShape = (c) => isObj(c) && typeof c.witnessed_log_id === "string" && isObj(c.cosignature) && typeof c.cosignature.log_id === "string";
52
+ /** Section 3: the reasons a status document is not in a checkpoint co-signed by `k` trusted witnesses. */
53
+ export function witnessReasons(doc, w) {
54
+ const reasons = [];
55
+ const cp = isObj(w?.checkpoint) ? w.checkpoint : null;
56
+ if (!isInt(doc.seq, 0, Number.MAX_SAFE_INTEGER))
57
+ reasons.push("status document is not in a status log");
58
+ if (!cp || cp.log_id !== doc.issuer || !verifySTH(cp)) {
59
+ reasons.push("status checkpoint is not signed by the issuer");
60
+ return { reasons, witnesses: [] };
61
+ }
62
+ const proof = Array.isArray(w.proof) ? w.proof : [];
63
+ const included = isInt(doc.seq, 0, Number.MAX_SAFE_INTEGER) &&
64
+ doc.seq < cp.size &&
65
+ isSha(cp.root) &&
66
+ proof.every((h) => typeof h === "string" && HEX32.test(h)) &&
67
+ verifyInclusion(leafHash(canonicalBytes(doc)), doc.seq, cp.size, proof.map(bytes), bytes(cp.root.slice(7)));
68
+ if (!included)
69
+ reasons.push("status document is not in the checkpoint");
70
+ if (!isInt(w.k, 1, 1000)) {
71
+ reasons.push("witness policy k must be an integer from 1");
72
+ return { reasons, witnesses: [] };
73
+ }
74
+ const trusted = Array.isArray(w.trusted) ? w.trusted : [];
75
+ const cosigs = Array.isArray(w.cosignatures) ? w.cosignatures.filter(cosigShape) : [];
76
+ const q = verifyQuorum(cp, cosigs, { trusted, k: w.k });
77
+ if (!q.ok)
78
+ reasons.push(`status checkpoint has ${q.witnesses.length} of the ${w.k} trusted witness co-signatures required`);
79
+ return { reasons, witnesses: q.witnesses };
80
+ }
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "@zanii/certify",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.6.0",
4
+ "description": "Build and verify Zanii Certify certificates for AI agents: fingerprints, signed certificates, status documents, test suites and the certify command.",
5
+ "license": "Apache-2.0",
6
+ "homepage": "https://zanii.agency",
7
+ "author": "Zanii <info@zanii.agency>",
8
+ "type": "module",
9
+ "engines": {
10
+ "node": ">=24"
11
+ },
12
+ "bin": {
13
+ "certify": "./src/cli.ts"
14
+ },
15
+ "exports": {
16
+ ".": "./src/index.ts"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "bin": {
27
+ "certify": "./dist/cli.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.build.json",
37
+ "test": "node --test \"src/**/*.test.ts\"",
38
+ "prepublishOnly": "pnpm build"
39
+ },
40
+ "dependencies": {
41
+ "@scure/base": "1.2.6",
42
+ "@zanii/attest": "0.1.3",
43
+ "@zanii/blackbox": "0.23.0",
44
+ "@zanii/core": "^0.5.6",
45
+ "@zanii/credentials": "^0.1.2",
46
+ "@zanii/insurance": "0.1.3",
47
+ "@zanii/kyb": "0.1.2",
48
+ "@zanii/pq": "0.2.2",
49
+ "@zanii/redact": "0.1.3",
50
+ "@zanii/succession": "0.1.2",
51
+ "@zanii/vc": "0.1.2",
52
+ "@zanii/witness": "0.1.5"
53
+ },
54
+ "devDependencies": {
55
+ "@zanii/swarm": "0.2.3"
56
+ },
57
+ "keywords": [
58
+ "ai-agents",
59
+ "certification",
60
+ "verifiable-credentials",
61
+ "ai-assurance",
62
+ "ed25519",
63
+ "post-quantum",
64
+ "arabic",
65
+ "zanii"
66
+ ]
67
+ }