@rubric-protocol/decision-verify 1.0.1

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/src/verify.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Assemble a decision-verify result from a stored bundle (tasks/P4.md).
3
+ *
4
+ * Scope (be honest about what this proves): this checks the bundle's INTERNAL
5
+ * consistency — the decision still hashes to its committed decisionHash, the
6
+ * recomputed leaf is the one the proof carries, the proof folds to the root the
7
+ * bundle claims, and the (caller-supplied) signature verifies. It does NOT
8
+ * contact HCS to confirm the root was actually anchored, nor does it establish
9
+ * that the signing key is trusted. `consistencyVerified` reflects only the
10
+ * former; on-chain anchoring is the job of the (out-of-scope) attestation service.
11
+ */
12
+ import { DAR_VERSION, decisionHashOf, leafHash } from "@rubric-protocol/attest-decision";
13
+ import { verifyMerkleProof } from "./merkle.js";
14
+ import type { SignatureVerifier, VerifiableBundle, VerificationResult, VerifyStatus } from "./ports.js";
15
+
16
+ const LEAF_TYPES: ReadonlySet<string> = new Set(["decision", "schema-change", "checkpoint"]);
17
+ const KNOWN = parseVersion(DAR_VERSION)!; // { major: 0, minor: 1 }
18
+
19
+ function parseVersion(v: string): { major: number; minor: number } | null {
20
+ const m = /^DAR\/(\d+)\.(\d+)$/.exec(v);
21
+ return m ? { major: Number(m[1]), minor: Number(m[2]) } : null;
22
+ }
23
+
24
+ /** Spec §5.1 version gate: reject unknown major / bad leafType; needs-upgrade for a newer minor. */
25
+ function versionStatus(v: string, leafType: string): VerifyStatus {
26
+ const parsed = parseVersion(v);
27
+ if (!parsed || parsed.major !== KNOWN.major) return "rejected"; // unknown major/format
28
+ if (!LEAF_TYPES.has(leafType)) return "rejected";
29
+ if (parsed.minor > KNOWN.minor) return "needs-upgrade"; // MUST NOT strip-and-rehash a newer minor
30
+ return "ok";
31
+ }
32
+
33
+ export function assembleVerification(
34
+ bundle: VerifiableBundle,
35
+ verifySignature: SignatureVerifier,
36
+ ): VerificationResult {
37
+ const { dar, merkleProof, anchorRef } = bundle;
38
+ const status = versionStatus(dar.v, dar.leafType);
39
+ const sig = verifySignature(bundle.signature, anchorRef.root);
40
+
41
+ const base = {
42
+ decisionId: dar.decisionId,
43
+ dar,
44
+ merkleProof,
45
+ anchorRef,
46
+ signature: { verified: sig.verified, keyRef: sig.keyRef },
47
+ };
48
+
49
+ // Do not claim (or compute) leaf verification for a version we can't fully
50
+ // understand — spec §5.1 forbids strip-and-rehash of a newer minor.
51
+ if (status !== "ok") {
52
+ return { ...base, drift: false, status, consistencyVerified: false };
53
+ }
54
+
55
+ const decisionHashOk = decisionHashOf(dar.schemaHash, dar.inputHash, dar.outputHash) === dar.decisionHash;
56
+ const leaf = leafHash(dar);
57
+ const leafMatches = leaf === merkleProof.leaf;
58
+ const proofFolds = verifyMerkleProof(leaf, merkleProof.steps, merkleProof.root);
59
+ const rootMatchesAnchor = merkleProof.root === anchorRef.root;
60
+ const drift = !decisionHashOk || !leafMatches || !proofFolds || !rootMatchesAnchor;
61
+
62
+ return { ...base, drift, status, consistencyVerified: !drift && sig.verified };
63
+ }