@mikeargento/bitgraph-player 0.2.0 → 0.3.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/README.md +1 -1
- package/SPEC.md +91 -5
- package/dist/__tests__/sig-v2.test.d.ts +2 -0
- package/dist/__tests__/sig-v2.test.d.ts.map +1 -0
- package/dist/__tests__/sig-v2.test.js +266 -0
- package/dist/__tests__/sig-v2.test.js.map +1 -0
- package/dist/evaluate.d.ts +8 -21
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +93 -2
- package/dist/evaluate.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/play.d.ts +12 -2
- package/dist/play.d.ts.map +1 -1
- package/dist/play.js +39 -4
- package/dist/play.js.map +1 -1
- package/dist/rule.d.ts +0 -13
- package/dist/rule.d.ts.map +1 -1
- package/dist/rule.js +84 -8
- package/dist/rule.js.map +1 -1
- package/dist/sig.d.ts +38 -0
- package/dist/sig.d.ts.map +1 -0
- package/dist/sig.js +154 -0
- package/dist/sig.js.map +1 -0
- package/dist/types.d.ts +21 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/verdict.d.ts +1 -1
- package/dist/verdict.d.ts.map +1 -1
- package/dist/verdict.js +17 -2
- package/dist/verdict.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/sig-v2.test.ts +315 -0
- package/src/evaluate.ts +107 -2
- package/src/index.ts +7 -3
- package/src/play.ts +43 -5
- package/src/rule.ts +93 -9
- package/src/sig.ts +170 -0
- package/src/types.ts +21 -4
- package/src/verdict.ts +18 -2
package/dist/types.d.ts
CHANGED
|
@@ -108,9 +108,19 @@ export type Claim = {
|
|
|
108
108
|
any: Claim[];
|
|
109
109
|
} | {
|
|
110
110
|
not: Claim;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Format 2 only: a valid bitgraph-sig/1 by the named trusted key over
|
|
114
|
+
* the role's digest is present in the supplied evidence. TRUE or
|
|
115
|
+
* UNDETERMINED, never FALSE — "this key never signed these bytes" is a
|
|
116
|
+
* negative over an open world no bundle can close.
|
|
117
|
+
*/
|
|
118
|
+
| {
|
|
119
|
+
signedBy: [string, string];
|
|
111
120
|
};
|
|
121
|
+
export type RuleFormat = "bitgraph-player/1" | "bitgraph-player/2";
|
|
112
122
|
export interface Rule {
|
|
113
|
-
rule:
|
|
123
|
+
rule: RuleFormat;
|
|
114
124
|
id: string;
|
|
115
125
|
cast: Record<string, CastEntry>;
|
|
116
126
|
/** The only defined value. Absence is only ever asserted within the declared cast. */
|
|
@@ -122,6 +132,13 @@ export interface Rule {
|
|
|
122
132
|
requires: {
|
|
123
133
|
ordering: EvidenceTier;
|
|
124
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* Format 2 only. Named keys the rule author trusts. The name-to-key
|
|
137
|
+
* binding is DECLARED — surfaced in the verdict, never derived. What
|
|
138
|
+
* becomes derived under format 2 is only the math: a signature by the
|
|
139
|
+
* named key material verifies over the role's digest.
|
|
140
|
+
*/
|
|
141
|
+
trustedKeys?: Record<string, import("./sig.js").TrustedKey>;
|
|
125
142
|
claim: Claim;
|
|
126
143
|
/**
|
|
127
144
|
* A label and nothing else. No field in this format is capable of
|
|
@@ -168,13 +185,14 @@ export interface DerivedStep {
|
|
|
168
185
|
}
|
|
169
186
|
/** One assertion Player took on somebody's word. */
|
|
170
187
|
export interface DeclaredEntry {
|
|
171
|
-
assertion: "signedBy" | "means" | "closed-world" | "pinned-occurrence";
|
|
188
|
+
assertion: "signedBy" | "means" | "closed-world" | "pinned-occurrence" | "trusted-key";
|
|
172
189
|
role?: string;
|
|
173
190
|
verifiedHere: false;
|
|
174
191
|
[k: string]: unknown;
|
|
175
192
|
}
|
|
176
193
|
export interface Verdict {
|
|
177
|
-
|
|
194
|
+
/** Tracks the rule format: a /1 rule yields a /1 verdict, byte-identical to prior releases. */
|
|
195
|
+
verdict: "bitgraph-player-verdict/1" | "bitgraph-player-verdict/2";
|
|
178
196
|
result: ThreeValued;
|
|
179
197
|
rule: {
|
|
180
198
|
id: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAMjE,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,cAAc,CAAC;AAM5D;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;AAE5F;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,sBAAsB,CAAC;AAElE,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,CASzD;AAED,mDAAmD;AACnD,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAE3E;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,QAAQ,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC;IACpD,4DAA4D;IAC5D,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,4FAA4F;IAC5F,mBAAmB,EAAE,OAAO,CAAC;IAC7B,gGAAgG;IAChG,MAAM,EAAE,OAAO,CAAC;IAChB,0FAA0F;IAC1F,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;;GAGG;AACH,MAAM,MAAM,OAAO,GACf;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GACrB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,EAAE,CAAC,EAAE,OAAO,CAAC;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,KAAK,GACb;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC5B;IAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC3B;IAAE,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACrC;IAAE,GAAG,EAAE,KAAK,EAAE,CAAA;CAAE,GAChB;IAAE,GAAG,EAAE,KAAK,EAAE,CAAA;CAAE,GAChB;IAAE,GAAG,EAAE,KAAK,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/verdict.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ import type { Resolution, Rule, Verdict } from "./types.js";
|
|
|
26
26
|
* A unit test asserts this equals package.json's version, so the
|
|
27
27
|
* constant cannot drift silently across releases.
|
|
28
28
|
*/
|
|
29
|
-
export declare const PLAYER_VERSION = "0.
|
|
29
|
+
export declare const PLAYER_VERSION = "0.3.0";
|
|
30
30
|
/** The player package's own version. */
|
|
31
31
|
export declare function playerVersion(): string;
|
|
32
32
|
export declare function buildVerdict(rule: Rule, ruleSha256Hex: string, resolutions: Map<string, Resolution>, evaluation: Evaluation, audit: AuditResult): Verdict;
|
package/dist/verdict.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verdict.d.ts","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAiB,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC,wCAAwC;AACxC,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAeD,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,WAAW,GACjB,OAAO,
|
|
1
|
+
{"version":3,"file":"verdict.d.ts","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,KAAK,EAAiB,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE3E;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC,wCAAwC;AACxC,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAeD,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,WAAW,GACjB,OAAO,CA6FT;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAkBzD"}
|
package/dist/verdict.js
CHANGED
|
@@ -9,7 +9,7 @@ import { normalizeDigest } from "./rule.js";
|
|
|
9
9
|
* A unit test asserts this equals package.json's version, so the
|
|
10
10
|
* constant cannot drift silently across releases.
|
|
11
11
|
*/
|
|
12
|
-
export const PLAYER_VERSION = "0.
|
|
12
|
+
export const PLAYER_VERSION = "0.3.0";
|
|
13
13
|
/** The player package's own version. */
|
|
14
14
|
export function playerVersion() {
|
|
15
15
|
return PLAYER_VERSION;
|
|
@@ -81,6 +81,21 @@ export function buildVerdict(rule, ruleSha256Hex, resolutions, evaluation, audit
|
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
// Format 2: the name-to-key bindings are declared trust. The signature
|
|
85
|
+
// MATH is derived (it appears in `derived` steps); that key K IS the
|
|
86
|
+
// named party is taken on the rule author's word, exactly like a cast
|
|
87
|
+
// digest's meaning.
|
|
88
|
+
if (rule.trustedKeys !== undefined) {
|
|
89
|
+
for (const [keyName, key] of Object.entries(rule.trustedKeys)) {
|
|
90
|
+
declared.push({
|
|
91
|
+
assertion: "trusted-key",
|
|
92
|
+
verifiedHere: false,
|
|
93
|
+
keyName,
|
|
94
|
+
alg: key.alg,
|
|
95
|
+
publicKey: key.publicKey,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
84
99
|
declared.push({
|
|
85
100
|
assertion: "closed-world",
|
|
86
101
|
verifiedHere: false,
|
|
@@ -89,7 +104,7 @@ export function buildVerdict(rule, ruleSha256Hex, resolutions, evaluation, audit
|
|
|
89
104
|
claim: "Absence is asserted only among the declared cast. Nothing in BitGraph establishes that the declared cast is complete; that completeness is the rule author's assertion.",
|
|
90
105
|
});
|
|
91
106
|
const verdict = {
|
|
92
|
-
verdict: "bitgraph-player-verdict/1",
|
|
107
|
+
verdict: rule.rule === "bitgraph-player/2" ? "bitgraph-player-verdict/2" : "bitgraph-player-verdict/1",
|
|
93
108
|
result: evaluation.result,
|
|
94
109
|
rule: { id: rule.id, sha256: ruleSha256Hex },
|
|
95
110
|
cast,
|
package/dist/verdict.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAqBrF,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC,wCAAwC;AACxC,MAAM,UAAU,aAAa;IAC3B,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,SAAS,eAAe,CAAC,GAAe;IACtC,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,UAAU;YACb,OAAO,UAAU,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,iBAAiB,CAAC;QAC9D,KAAK,WAAW;YACd,OAAO,WAAW,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAU,EACV,aAAqB,EACrB,WAAoC,EACpC,UAAsB,EACtB,KAAkB;IAElB,qEAAqE;IACrE,6CAA6C;IAC7C,MAAM,IAAI,GAAoB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAoB,CAAC;IACrE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAe,CAAC;QAChD,MAAM,OAAO,GAA4B;YACvC,SAAS,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,CAAW;YAClD,UAAU,EAAE,eAAe,CAAC,GAAG,CAAC;SACjC,CAAC;QACF,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC5B,OAAO,CAAC,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC;YACxC,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;YACzE,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;YACpC,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC;YACzE,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS;gBAAE,OAAO,CAAC,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC;QACvF,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;IACvB,CAAC;IAED,qEAAqE;IACrE,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC9B,QAAQ,CAAC,IAAI,CAAC;gBACZ,SAAS,EAAE,OAAO;gBAClB,IAAI;gBACJ,YAAY,EAAE,KAAK;gBACnB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,eAAe,CAAC,KAAK,CAAC,MAAM,CAAW;aACnD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,IAAI,KAAK,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC;gBACZ,SAAS,EAAE,mBAAmB;gBAC9B,IAAI;gBACJ,YAAY,EAAE,KAAK;gBACnB,EAAE,EAAE,KAAK,CAAC,EAAE;aACb,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC;gBACZ,SAAS,EAAE,UAAU;gBACrB,IAAI;gBACJ,YAAY,EAAE,KAAK;gBACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC;QACZ,SAAS,EAAE,cAAc;QACzB,YAAY,EAAE,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM;QACvC,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ;QAChD,KAAK,EACH,yKAAyK;KAC5K,CAAC,CAAC;IAEH,MAAM,OAAO,GAAY;QACvB,OAAO,
|
|
1
|
+
{"version":3,"file":"verdict.js","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikeargento/bitgraph-player",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Deterministic evaluation of causal rules over BitGraph proof bundles: a pure function from (rule, verified evidence) to a reproducible verdict.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026 Mike Argento. Licensed under the MIT License. See LICENSE.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Format 2: trustedKeys + signedBy.
|
|
5
|
+
*
|
|
6
|
+
* The invariants under test:
|
|
7
|
+
* - signedBy is TRUE or UNDETERMINED, never FALSE, so not(signedBy)
|
|
8
|
+
* is permanently UNDETERMINED: no open-world negative escapes.
|
|
9
|
+
* - Signature math is derived; the name-to-key binding is declared.
|
|
10
|
+
* - A /1 rule parses and evaluates exactly as before (verdict/1), and
|
|
11
|
+
* /2-only syntax in a /1 rule is a parse error.
|
|
12
|
+
* - Broken signature files are noise, never evidence and never errors.
|
|
13
|
+
*
|
|
14
|
+
* All evidence is synthetic and in-memory. No ledger writes, ever.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { strict as assert } from "node:assert";
|
|
18
|
+
import { createHash, generateKeyPairSync, sign as cryptoSign } from "node:crypto";
|
|
19
|
+
import { test } from "node:test";
|
|
20
|
+
import { evaluate } from "../evaluate.js";
|
|
21
|
+
import { playAudit, claimUsesSignatures } from "../play.js";
|
|
22
|
+
import { parseRule, RuleError } from "../rule.js";
|
|
23
|
+
import { parseSigFile, sigMessage } from "../sig.js";
|
|
24
|
+
import { resolveCast } from "../cast.js";
|
|
25
|
+
import { digestFor, makeAudit } from "./fixtures.js";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Key + signature helpers (in-memory, per-run keys; verification is the
|
|
29
|
+
// deterministic part under test, not key generation)
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
function ed25519Pair(): { publicKeyB64: string; privateKey: import("node:crypto").KeyObject } {
|
|
33
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
34
|
+
const spki = publicKey.export({ format: "der", type: "spki" }) as Buffer;
|
|
35
|
+
// Raw key = last 32 bytes of the SPKI encoding.
|
|
36
|
+
return { publicKeyB64: spki.subarray(spki.length - 32).toString("base64"), privateKey };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function es256Pair(): { publicKeyB64: string; privateKey: import("node:crypto").KeyObject } {
|
|
40
|
+
const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "P-256" });
|
|
41
|
+
const spki = publicKey.export({ format: "der", type: "spki" }) as Buffer;
|
|
42
|
+
return { publicKeyB64: spki.toString("base64"), privateKey };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hexOfB64(digestB64: string): string {
|
|
46
|
+
return Buffer.from(digestB64, "base64").toString("hex");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function makeSigBytes(
|
|
50
|
+
alg: "ed25519" | "es256",
|
|
51
|
+
publicKeyB64: string,
|
|
52
|
+
privateKey: import("node:crypto").KeyObject,
|
|
53
|
+
overHex: string
|
|
54
|
+
): Uint8Array {
|
|
55
|
+
const message = sigMessage(overHex);
|
|
56
|
+
const signature =
|
|
57
|
+
alg === "ed25519"
|
|
58
|
+
? cryptoSign(null, message, privateKey)
|
|
59
|
+
: cryptoSign("sha256", message, privateKey);
|
|
60
|
+
return Buffer.from(
|
|
61
|
+
JSON.stringify({
|
|
62
|
+
sig: "bitgraph-sig/1",
|
|
63
|
+
over: `sha256:${overHex}`,
|
|
64
|
+
alg,
|
|
65
|
+
publicKey: publicKeyB64,
|
|
66
|
+
signature: signature.toString("base64"),
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function evidenceOf(...items: Uint8Array[]): Map<string, Uint8Array> {
|
|
72
|
+
const map = new Map<string, Uint8Array>();
|
|
73
|
+
for (const bytes of items) {
|
|
74
|
+
map.set(createHash("sha256").update(bytes).digest("hex"), bytes);
|
|
75
|
+
}
|
|
76
|
+
return map;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const WORK = digestFor("work");
|
|
80
|
+
|
|
81
|
+
function v2Rule(publicKeyB64: string, alg: "ed25519" | "es256", claim: unknown): string {
|
|
82
|
+
return JSON.stringify({
|
|
83
|
+
rule: "bitgraph-player/2",
|
|
84
|
+
id: "test-v2",
|
|
85
|
+
cast: { work: { digest: WORK } },
|
|
86
|
+
world: "closed",
|
|
87
|
+
requires: { ordering: "assumption-dependent" },
|
|
88
|
+
trustedKeys: { mike: { alg, publicKey: publicKeyB64 } },
|
|
89
|
+
claim,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function auditWithWork() {
|
|
94
|
+
return makeAudit({
|
|
95
|
+
proofs: [{ name: "w", digestB64: WORK, epochId: "e1", counter: "1" }],
|
|
96
|
+
partitions: [{ epochId: "e1", members: ["w"] }],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Parsing
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
test("signedBy in a /1 rule is a parse error", () => {
|
|
105
|
+
const text = JSON.stringify({
|
|
106
|
+
rule: "bitgraph-player/1",
|
|
107
|
+
id: "r",
|
|
108
|
+
cast: { work: { digest: WORK } },
|
|
109
|
+
world: "closed",
|
|
110
|
+
requires: { ordering: "hash-linked" },
|
|
111
|
+
claim: { signedBy: ["work", "mike"] },
|
|
112
|
+
});
|
|
113
|
+
assert.throws(() => parseRule(text), RuleError);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("trustedKeys in a /1 rule is a parse error", () => {
|
|
117
|
+
const text = JSON.stringify({
|
|
118
|
+
rule: "bitgraph-player/1",
|
|
119
|
+
id: "r",
|
|
120
|
+
cast: { work: { digest: WORK } },
|
|
121
|
+
world: "closed",
|
|
122
|
+
requires: { ordering: "hash-linked" },
|
|
123
|
+
trustedKeys: { mike: { alg: "ed25519", publicKey: "A".repeat(43) + "=" } },
|
|
124
|
+
claim: { exists: "work" },
|
|
125
|
+
});
|
|
126
|
+
assert.throws(() => parseRule(text), RuleError);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("signedBy referencing an undeclared trusted key is a parse error", () => {
|
|
130
|
+
const { publicKeyB64 } = ed25519Pair();
|
|
131
|
+
const text = v2Rule(publicKeyB64, "ed25519", { signedBy: ["work", "ghost"] });
|
|
132
|
+
assert.throws(() => parseRule(text), /does not declare/);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("trustedKeys entries are strictly validated", () => {
|
|
136
|
+
for (const bad of [
|
|
137
|
+
{ alg: "rsa", publicKey: "x" },
|
|
138
|
+
{ alg: "ed25519" },
|
|
139
|
+
{ publicKey: "x" },
|
|
140
|
+
{ alg: "ed25519", publicKey: "x", extra: 1 },
|
|
141
|
+
]) {
|
|
142
|
+
const text = JSON.stringify({
|
|
143
|
+
rule: "bitgraph-player/2",
|
|
144
|
+
id: "r",
|
|
145
|
+
cast: { work: { digest: WORK } },
|
|
146
|
+
world: "closed",
|
|
147
|
+
requires: { ordering: "hash-linked" },
|
|
148
|
+
trustedKeys: { mike: bad },
|
|
149
|
+
claim: { exists: "work" },
|
|
150
|
+
});
|
|
151
|
+
assert.throws(() => parseRule(text), RuleError, JSON.stringify(bad));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("pure-integer trusted key names are rejected", () => {
|
|
156
|
+
const text = JSON.stringify({
|
|
157
|
+
rule: "bitgraph-player/2",
|
|
158
|
+
id: "r",
|
|
159
|
+
cast: { work: { digest: WORK } },
|
|
160
|
+
world: "closed",
|
|
161
|
+
requires: { ordering: "hash-linked" },
|
|
162
|
+
trustedKeys: { "42": { alg: "ed25519", publicKey: "A".repeat(43) + "=" } },
|
|
163
|
+
claim: { exists: "work" },
|
|
164
|
+
});
|
|
165
|
+
assert.throws(() => parseRule(text), /non-digit/);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("claimUsesSignatures walks the tree", () => {
|
|
169
|
+
assert.equal(claimUsesSignatures({ exists: "x" }), false);
|
|
170
|
+
assert.equal(
|
|
171
|
+
claimUsesSignatures({ all: [{ exists: "x" }, { not: { signedBy: ["x", "k"] } }] }),
|
|
172
|
+
true
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Signature file parsing
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
test("parseSigFile accepts a well-formed file and rejects noise", () => {
|
|
181
|
+
const { publicKeyB64, privateKey } = ed25519Pair();
|
|
182
|
+
const good = makeSigBytes("ed25519", publicKeyB64, privateKey, hexOfB64(WORK));
|
|
183
|
+
assert.notEqual(parseSigFile(good), undefined);
|
|
184
|
+
assert.equal(parseSigFile(Buffer.from("not json")), undefined);
|
|
185
|
+
assert.equal(parseSigFile(Buffer.from(JSON.stringify({ sig: "other/1" }))), undefined);
|
|
186
|
+
assert.equal(
|
|
187
|
+
parseSigFile(Buffer.from(JSON.stringify({ sig: "bitgraph-sig/1", over: "x", alg: "rsa" }))),
|
|
188
|
+
undefined
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Evaluation: ed25519 and es256
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
for (const alg of ["ed25519", "es256"] as const) {
|
|
197
|
+
test(`signedBy(${alg}): valid signature in evidence evaluates TRUE with derived math`, () => {
|
|
198
|
+
const pair = alg === "ed25519" ? ed25519Pair() : es256Pair();
|
|
199
|
+
const rule = parseRule(v2Rule(pair.publicKeyB64, alg, { signedBy: ["work", "mike"] }));
|
|
200
|
+
const audit = auditWithWork();
|
|
201
|
+
const evidence = evidenceOf(makeSigBytes(alg, pair.publicKeyB64, pair.privateKey, hexOfB64(WORK)));
|
|
202
|
+
const result = playAudit(rule, "0".repeat(64), audit, evidence);
|
|
203
|
+
assert.equal(result.verdict.result, "TRUE");
|
|
204
|
+
assert.equal(result.verdict.verdict, "bitgraph-player-verdict/2");
|
|
205
|
+
const step = result.verdict.derived.find((s) => s.claim.startsWith("signedBy"));
|
|
206
|
+
assert.ok(step !== undefined);
|
|
207
|
+
assert.equal((step as NonNullable<typeof step>).result, "TRUE");
|
|
208
|
+
// The binding is declared, alongside the math being derived.
|
|
209
|
+
const trusted = result.verdict.declared.find((d) => d.assertion === "trusted-key");
|
|
210
|
+
assert.ok(trusted !== undefined);
|
|
211
|
+
assert.equal((trusted as NonNullable<typeof trusted>)["keyName"], "mike");
|
|
212
|
+
assert.equal((trusted as NonNullable<typeof trusted>).verifiedHere, false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test(`signedBy(${alg}): tampered signature is noise, evaluates UNDETERMINED`, () => {
|
|
216
|
+
const pair = alg === "ed25519" ? ed25519Pair() : es256Pair();
|
|
217
|
+
const rule = parseRule(v2Rule(pair.publicKeyB64, alg, { signedBy: ["work", "mike"] }));
|
|
218
|
+
const bytes = makeSigBytes(alg, pair.publicKeyB64, pair.privateKey, hexOfB64(WORK));
|
|
219
|
+
const parsed = JSON.parse(Buffer.from(bytes).toString("utf8")) as Record<string, string>;
|
|
220
|
+
const sigBytes = Buffer.from(parsed["signature"] as string, "base64");
|
|
221
|
+
sigBytes[0] = (sigBytes[0] as number) ^ 0xff;
|
|
222
|
+
parsed["signature"] = sigBytes.toString("base64");
|
|
223
|
+
const tampered = Buffer.from(JSON.stringify(parsed));
|
|
224
|
+
const result = playAudit(rule, "0".repeat(64), auditWithWork(), evidenceOf(tampered));
|
|
225
|
+
assert.equal(result.verdict.result, "UNDETERMINED");
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
test("a non-P-256 key declared as es256 is undecodable key material: UNDETERMINED, never a verify", () => {
|
|
230
|
+
const { publicKey, privateKey } = generateKeyPairSync("ec", { namedCurve: "secp384r1" });
|
|
231
|
+
const spki = (publicKey.export({ format: "der", type: "spki" }) as Buffer).toString("base64");
|
|
232
|
+
const rule = parseRule(v2Rule(spki, "es256", { signedBy: ["work", "mike"] }));
|
|
233
|
+
const evidence = evidenceOf(makeSigBytes("es256", spki, privateKey, hexOfB64(WORK)));
|
|
234
|
+
const result = playAudit(rule, "0".repeat(64), auditWithWork(), evidence);
|
|
235
|
+
assert.equal(result.verdict.result, "UNDETERMINED");
|
|
236
|
+
const step = result.verdict.derived.find((s) => s.claim.startsWith("signedBy"));
|
|
237
|
+
assert.ok(step !== undefined);
|
|
238
|
+
assert.match(String((step as NonNullable<typeof step>).because["reason"]), /not decodable/);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("signedBy with empty evidence is UNDETERMINED, and not(signedBy) stays UNDETERMINED", () => {
|
|
242
|
+
const { publicKeyB64 } = ed25519Pair();
|
|
243
|
+
const rule = parseRule(v2Rule(publicKeyB64, "ed25519", { not: { signedBy: ["work", "mike"] } }));
|
|
244
|
+
const result = playAudit(rule, "0".repeat(64), auditWithWork());
|
|
245
|
+
// Never FALSE and never TRUE: the open world stays open.
|
|
246
|
+
assert.equal(result.verdict.result, "UNDETERMINED");
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("signedBy by the wrong key is UNDETERMINED, never FALSE", () => {
|
|
250
|
+
const right = ed25519Pair();
|
|
251
|
+
const wrong = ed25519Pair();
|
|
252
|
+
const rule = parseRule(v2Rule(right.publicKeyB64, "ed25519", { signedBy: ["work", "mike"] }));
|
|
253
|
+
const evidence = evidenceOf(
|
|
254
|
+
makeSigBytes("ed25519", wrong.publicKeyB64, wrong.privateKey, hexOfB64(WORK))
|
|
255
|
+
);
|
|
256
|
+
const result = playAudit(rule, "0".repeat(64), auditWithWork(), evidence);
|
|
257
|
+
assert.equal(result.verdict.result, "UNDETERMINED");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("signedBy over a different digest does not satisfy the role's claim", () => {
|
|
261
|
+
const pair = ed25519Pair();
|
|
262
|
+
const rule = parseRule(v2Rule(pair.publicKeyB64, "ed25519", { signedBy: ["work", "mike"] }));
|
|
263
|
+
const other = digestFor("other");
|
|
264
|
+
const evidence = evidenceOf(
|
|
265
|
+
makeSigBytes("ed25519", pair.publicKeyB64, pair.privateKey, hexOfB64(other))
|
|
266
|
+
);
|
|
267
|
+
const result = playAudit(rule, "0".repeat(64), auditWithWork(), evidence);
|
|
268
|
+
assert.equal(result.verdict.result, "UNDETERMINED");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("signedBy evaluates against the declared digest even when the role has no recording", () => {
|
|
272
|
+
const pair = ed25519Pair();
|
|
273
|
+
const text = JSON.stringify({
|
|
274
|
+
rule: "bitgraph-player/2",
|
|
275
|
+
id: "r",
|
|
276
|
+
cast: { work: { digest: WORK, optional: true } },
|
|
277
|
+
world: "closed",
|
|
278
|
+
requires: { ordering: "hash-linked" },
|
|
279
|
+
trustedKeys: { mike: { alg: "ed25519", publicKey: pair.publicKeyB64 } },
|
|
280
|
+
claim: { signedBy: ["work", "mike"] },
|
|
281
|
+
});
|
|
282
|
+
const rule = parseRule(text);
|
|
283
|
+
const audit = makeAudit({ proofs: [], partitions: [] });
|
|
284
|
+
const evidence = evidenceOf(
|
|
285
|
+
makeSigBytes("ed25519", pair.publicKeyB64, pair.privateKey, hexOfB64(WORK))
|
|
286
|
+
);
|
|
287
|
+
const resolutions = resolveCast(rule.cast, audit);
|
|
288
|
+
const evaluation = evaluate(rule, resolutions, audit, evidence);
|
|
289
|
+
assert.equal(evaluation.result, "TRUE");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("a /1 rule still yields a /1 verdict and ignores supplied evidence", () => {
|
|
293
|
+
const text = JSON.stringify({
|
|
294
|
+
rule: "bitgraph-player/1",
|
|
295
|
+
id: "r",
|
|
296
|
+
cast: { work: { digest: WORK } },
|
|
297
|
+
world: "closed",
|
|
298
|
+
requires: { ordering: "assumption-dependent" },
|
|
299
|
+
claim: { exists: "work" },
|
|
300
|
+
});
|
|
301
|
+
const rule = parseRule(text);
|
|
302
|
+
const result = playAudit(rule, "0".repeat(64), auditWithWork(), evidenceOf(Buffer.from("junk")));
|
|
303
|
+
assert.equal(result.verdict.verdict, "bitgraph-player-verdict/1");
|
|
304
|
+
assert.equal(result.verdict.result, "TRUE");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("multiple candidates: the winner is deterministic and reported by content hash", () => {
|
|
308
|
+
const pair = ed25519Pair();
|
|
309
|
+
const rule = parseRule(v2Rule(pair.publicKeyB64, "ed25519", { signedBy: ["work", "mike"] }));
|
|
310
|
+
const sig = makeSigBytes("ed25519", pair.publicKeyB64, pair.privateKey, hexOfB64(WORK));
|
|
311
|
+
const junk = Buffer.from("{}");
|
|
312
|
+
const a = playAudit(rule, "0".repeat(64), auditWithWork(), evidenceOf(sig, junk));
|
|
313
|
+
const b = playAudit(rule, "0".repeat(64), auditWithWork(), evidenceOf(junk, sig));
|
|
314
|
+
assert.equal(a.bytes, b.bytes);
|
|
315
|
+
});
|
package/src/evaluate.ts
CHANGED
|
@@ -21,9 +21,12 @@
|
|
|
21
21
|
* that evidence in either direction.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import type { KeyObject } from "node:crypto";
|
|
24
25
|
import type { AuditResult, ObservedProof } from "@mikeargento/bitgraph-audit";
|
|
25
26
|
import { kleeneAll, kleeneAny, kleeneNot } from "./logic.js";
|
|
26
27
|
import { compare } from "./order.js";
|
|
28
|
+
import { decodeDigestBytes, normalizeDigest } from "./rule.js";
|
|
29
|
+
import { keyObjectFor, parseSigFile, verifySigFile } from "./sig.js";
|
|
27
30
|
import { basisTier, meetsFloor } from "./types.js";
|
|
28
31
|
import type {
|
|
29
32
|
Claim,
|
|
@@ -41,12 +44,22 @@ export interface Evaluation {
|
|
|
41
44
|
weakestEvidence?: EvidenceTier;
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Signature evidence for format-2 signedBy claims: candidate file bytes
|
|
49
|
+
* keyed by lowercase hex SHA-256. Collected by the caller (play() streams
|
|
50
|
+
* size-capped matched artifacts from the bundle); evaluate() itself stays
|
|
51
|
+
* a pure function over its arguments.
|
|
52
|
+
*/
|
|
53
|
+
export type SigEvidence = ReadonlyMap<string, Uint8Array>;
|
|
54
|
+
|
|
44
55
|
interface Ctx {
|
|
45
56
|
rule: Rule;
|
|
46
57
|
resolutions: Map<string, Resolution>;
|
|
47
58
|
audit: AuditResult;
|
|
48
59
|
steps: DerivedStep[];
|
|
49
60
|
usedTiers: Set<EvidenceTier>;
|
|
61
|
+
sigEvidence: SigEvidence;
|
|
62
|
+
keyObjects: Map<string, KeyObject | undefined>;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
function positionOf(proof: ObservedProof): Record<string, unknown> {
|
|
@@ -198,8 +211,91 @@ function evalPrecedes(ctx: Ctx, claim: string, x: string, y: string): ThreeValue
|
|
|
198
211
|
});
|
|
199
212
|
}
|
|
200
213
|
|
|
214
|
+
/**
|
|
215
|
+
* signedBy(role, keyName): TRUE when a valid bitgraph-sig/1 by the named
|
|
216
|
+
* trusted key over the role's digest is present in the supplied evidence;
|
|
217
|
+
* UNDETERMINED otherwise, NEVER FALSE. Absence of a signature from a
|
|
218
|
+
* bundle proves nothing about the world, and `not(signedBy(...))` is
|
|
219
|
+
* therefore permanently UNDETERMINED by the Kleene table.
|
|
220
|
+
*
|
|
221
|
+
* The claim is about BITS, not occurrences: it evaluates against the
|
|
222
|
+
* declared role digest whether or not the role resolved to a recording.
|
|
223
|
+
* Only an undeclared role is UNDETERMINED for want of a digest.
|
|
224
|
+
*/
|
|
225
|
+
function evalSignedBy(ctx: Ctx, role: string, keyName: string): ThreeValued {
|
|
226
|
+
const claim = `signedBy(${role}, ${keyName})`;
|
|
227
|
+
const entry = ctx.rule.cast[role];
|
|
228
|
+
if (entry === undefined) {
|
|
229
|
+
return record(ctx, {
|
|
230
|
+
claim,
|
|
231
|
+
result: "UNDETERMINED",
|
|
232
|
+
because: { reason: `role "${role}" is not declared in the cast` },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// parseRule guarantees the key name resolves; guard for direct API use.
|
|
236
|
+
const key = ctx.rule.trustedKeys?.[keyName];
|
|
237
|
+
if (key === undefined) {
|
|
238
|
+
return record(ctx, {
|
|
239
|
+
claim,
|
|
240
|
+
result: "UNDETERMINED",
|
|
241
|
+
because: { reason: `trusted key "${keyName}" is not declared` },
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (!ctx.keyObjects.has(keyName)) ctx.keyObjects.set(keyName, keyObjectFor(key));
|
|
245
|
+
const keyObject = ctx.keyObjects.get(keyName);
|
|
246
|
+
if (keyObject === undefined) {
|
|
247
|
+
return record(ctx, {
|
|
248
|
+
claim,
|
|
249
|
+
result: "UNDETERMINED",
|
|
250
|
+
because: {
|
|
251
|
+
reason: `trusted key "${keyName}" is not decodable as ${key.alg} key material; no signature can verify against it`,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const digestB64 = normalizeDigest(entry.digest) as string;
|
|
257
|
+
const targetHex = (decodeDigestBytes(digestB64) as Buffer).toString("hex");
|
|
258
|
+
|
|
259
|
+
// Deterministic scan: candidates in ascending content-hash order.
|
|
260
|
+
const candidates = [...ctx.sigEvidence.keys()].sort();
|
|
261
|
+
let parsedCount = 0;
|
|
262
|
+
for (const sha256Hex of candidates) {
|
|
263
|
+
const bytes = ctx.sigEvidence.get(sha256Hex) as Uint8Array;
|
|
264
|
+
const sig = parseSigFile(bytes);
|
|
265
|
+
if (sig === undefined) continue;
|
|
266
|
+
parsedCount += 1;
|
|
267
|
+
if (verifySigFile(sig, key, keyObject, targetHex, decodeDigestBytes)) {
|
|
268
|
+
return record(ctx, {
|
|
269
|
+
claim,
|
|
270
|
+
result: "TRUE",
|
|
271
|
+
because: {
|
|
272
|
+
signatureSha256Hex: sha256Hex,
|
|
273
|
+
alg: key.alg,
|
|
274
|
+
keyName,
|
|
275
|
+
over: `sha256:${targetHex}`,
|
|
276
|
+
note: "signature math verified here; that this key belongs to the named party is declared, never derived",
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return record(ctx, {
|
|
282
|
+
claim,
|
|
283
|
+
result: "UNDETERMINED",
|
|
284
|
+
because: {
|
|
285
|
+
reason:
|
|
286
|
+
`no valid bitgraph-sig/1 by trusted key "${keyName}" over the role digest is present in the ` +
|
|
287
|
+
`supplied evidence (${ctx.sigEvidence.size} candidate file(s), ${parsedCount} parsed as signatures); ` +
|
|
288
|
+
`absence of a signature from a bundle proves nothing`,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
201
293
|
function evalClaim(ctx: Ctx, claim: Claim): ThreeValued {
|
|
202
294
|
if ("exists" in claim) return evalExists(ctx, claim.exists);
|
|
295
|
+
if ("signedBy" in claim) {
|
|
296
|
+
const [role, keyName] = claim.signedBy;
|
|
297
|
+
return evalSignedBy(ctx, role, keyName);
|
|
298
|
+
}
|
|
203
299
|
if ("before" in claim) {
|
|
204
300
|
const [x, y] = claim.before;
|
|
205
301
|
return evalPrecedes(ctx, `before(${x}, ${y})`, x, y);
|
|
@@ -224,9 +320,18 @@ function evalClaim(ctx: Ctx, claim: Claim): ThreeValued {
|
|
|
224
320
|
export function evaluate(
|
|
225
321
|
rule: Rule,
|
|
226
322
|
resolutions: Map<string, Resolution>,
|
|
227
|
-
audit: AuditResult
|
|
323
|
+
audit: AuditResult,
|
|
324
|
+
sigEvidence: SigEvidence = new Map()
|
|
228
325
|
): Evaluation {
|
|
229
|
-
const ctx: Ctx = {
|
|
326
|
+
const ctx: Ctx = {
|
|
327
|
+
rule,
|
|
328
|
+
resolutions,
|
|
329
|
+
audit,
|
|
330
|
+
steps: [],
|
|
331
|
+
usedTiers: new Set(),
|
|
332
|
+
sigEvidence,
|
|
333
|
+
keyObjects: new Map(),
|
|
334
|
+
};
|
|
230
335
|
const result = evalClaim(ctx, rule.claim);
|
|
231
336
|
const evaluation: Evaluation = { result, steps: ctx.steps };
|
|
232
337
|
if (ctx.usedTiers.has("assumption-dependent")) {
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Deterministic evaluation of causal rules over BitGraph proof bundles.
|
|
7
7
|
*
|
|
8
|
-
* BitGraph records. Player
|
|
8
|
+
* BitGraph records. Player evaluates.
|
|
9
9
|
*
|
|
10
10
|
* Player is a pure function over `runAudit()`'s output: same rule, same
|
|
11
11
|
* bundle, same verdict, on anyone's machine, with no network access at
|
|
@@ -24,14 +24,18 @@ export type {
|
|
|
24
24
|
OrderResult,
|
|
25
25
|
Resolution,
|
|
26
26
|
Rule,
|
|
27
|
+
RuleFormat,
|
|
27
28
|
ThreeValued,
|
|
28
29
|
Verdict,
|
|
29
30
|
} from "./types.js";
|
|
30
31
|
export { basisTier, meetsFloor } from "./types.js";
|
|
31
32
|
|
|
32
|
-
export { parseRule, normalizeDigest, RuleError } from "./rule.js";
|
|
33
|
-
export { play, playAudit, PlayError } from "./play.js";
|
|
33
|
+
export { parseRule, normalizeDigest, decodeDigestBytes, RuleError } from "./rule.js";
|
|
34
|
+
export { play, playAudit, claimUsesSignatures, SIG_EVIDENCE_MAX_BYTES, PlayError } from "./play.js";
|
|
34
35
|
export type { PlayResult, PlayStage } from "./play.js";
|
|
36
|
+
export { parseSigFile, verifySigFile, keyObjectFor, sigMessage } from "./sig.js";
|
|
37
|
+
export type { SigAlg, SigFile, TrustedKey } from "./sig.js";
|
|
38
|
+
export type { SigEvidence } from "./evaluate.js";
|
|
35
39
|
export { scaffoldRule, roleNameForFile, ORDERING_PLACEHOLDER } from "./init.js";
|
|
36
40
|
export type { ScaffoldEntry } from "./init.js";
|
|
37
41
|
export { resolveCast, resolveRole } from "./cast.js";
|