@kyaki/witness 0.1.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/dist/accountability.d.ts +43 -0
- package/dist/accountability.d.ts.map +1 -0
- package/dist/accountability.js +64 -0
- package/dist/accountability.js.map +1 -0
- package/dist/adapters/monitor-http.d.ts +31 -0
- package/dist/adapters/monitor-http.d.ts.map +1 -0
- package/dist/adapters/monitor-http.js +59 -0
- package/dist/adapters/monitor-http.js.map +1 -0
- package/dist/adapters/witness-http.d.ts +62 -0
- package/dist/adapters/witness-http.d.ts.map +1 -0
- package/dist/adapters/witness-http.js +212 -0
- package/dist/adapters/witness-http.js.map +1 -0
- package/dist/durable-witness.d.ts +101 -0
- package/dist/durable-witness.d.ts.map +1 -0
- package/dist/durable-witness.js +179 -0
- package/dist/durable-witness.js.map +1 -0
- package/dist/fan-out.d.ts +20 -0
- package/dist/fan-out.d.ts.map +1 -0
- package/dist/fan-out.js +30 -0
- package/dist/fan-out.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/monitor.d.ts +83 -0
- package/dist/monitor.d.ts.map +1 -0
- package/dist/monitor.js +133 -0
- package/dist/monitor.js.map +1 -0
- package/dist/pollination.d.ts +109 -0
- package/dist/pollination.d.ts.map +1 -0
- package/dist/pollination.js +170 -0
- package/dist/pollination.js.map +1 -0
- package/dist/ssrf.d.ts +20 -0
- package/dist/ssrf.d.ts.map +1 -0
- package/dist/ssrf.js +205 -0
- package/dist/ssrf.js.map +1 -0
- package/package.json +33 -0
- package/src/accountability.ts +72 -0
- package/src/adapters/monitor-http.ts +69 -0
- package/src/adapters/witness-http.ts +235 -0
- package/src/durable-witness.ts +190 -0
- package/src/fan-out.ts +34 -0
- package/src/index.ts +28 -0
- package/src/monitor.ts +159 -0
- package/src/pollination.ts +229 -0
- package/src/ssrf.ts +190 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* accountability.ts — witness accountability (Phase 7). Makes witness COLLUSION
|
|
3
|
+
* ACCOUNTABLE (never prevented): when co-signatures by the SAME witness over the SAME
|
|
4
|
+
* (logId, treeSize) with DIFFERENT roots are CO-LOCATED at one observer, it forms a
|
|
5
|
+
* non-repudiable `WitnessEquivocationProof` naming that witness.
|
|
6
|
+
*
|
|
7
|
+
* THE CO-LOCATION PRECONDITION (the honest boundary — same class as the operator monitor):
|
|
8
|
+
* a proof forms only if BOTH divergent co-signatures reach this corpus. A witness co-signs
|
|
9
|
+
* honestly via its durable store (which never co-signs two roots at one size), so the two
|
|
10
|
+
* divergent co-signatures come from a COMPROMISED key / faulty store and are observed by
|
|
11
|
+
* DIFFERENT relying parties (e.g. two victims that pollinated the same witness at the same
|
|
12
|
+
* size and got different roots) or via gossip. If a colluder never lets its two
|
|
13
|
+
* observations meet at one extractor, NO proof forms. Absence of a proof proves nothing.
|
|
14
|
+
* Quorum intersection (k > n/2) guarantees a successful two-quorum split SHARES a
|
|
15
|
+
* double-signer — but extraction still requires both quorums' co-signatures to co-locate.
|
|
16
|
+
*
|
|
17
|
+
* SPENDS NEVER DEPEND ON THIS — it is the same liveness-only, opportunistic aggregation as
|
|
18
|
+
* the operator equivocation monitor. The sole spend-time safety control stays
|
|
19
|
+
* verifyWitnessedTreeHead at quorum >= 2 fail-closed.
|
|
20
|
+
*/
|
|
21
|
+
import { type WitnessConflictStore, type WitnessCosigStore, type WitnessCosignature, type WitnessEquivocationProof } from '@kyaki/core';
|
|
22
|
+
export interface WitnessAccountabilityConfig {
|
|
23
|
+
/** Durable corpus of observed witness co-signatures (keyed so two divergent roots coexist). */
|
|
24
|
+
observed: WitnessCosigStore;
|
|
25
|
+
/** Durable store of detected proofs (named, faulty witnesses). */
|
|
26
|
+
conflicts: WitnessConflictStore;
|
|
27
|
+
}
|
|
28
|
+
export declare class WitnessAccountabilityMonitor {
|
|
29
|
+
private readonly cfg;
|
|
30
|
+
constructor(cfg: WitnessAccountabilityConfig);
|
|
31
|
+
/**
|
|
32
|
+
* Ingest one observed witness co-signature (the store verifies it before recording) and,
|
|
33
|
+
* if it now completes a same-witness / same-size / different-root pair, form + persist the
|
|
34
|
+
* proof. Returns the proof iff one is available for that witness (newly formed or already
|
|
35
|
+
* recorded). Idempotent — re-observing the same co-signature never duplicates or errors.
|
|
36
|
+
*/
|
|
37
|
+
observe(cosig: WitnessCosignature): Promise<WitnessEquivocationProof | undefined>;
|
|
38
|
+
/** Re-scan every observed witness and persist any equivocation proof. Returns the proofs held. */
|
|
39
|
+
sweep(): Promise<WitnessEquivocationProof[]>;
|
|
40
|
+
/** A witness's proof, re-detected from the corpus (and persisted) or read back from store. */
|
|
41
|
+
private detectFor;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=accountability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accountability.d.ts","sourceRoot":"","sources":["../src/accountability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAEL,KAAK,oBAAoB,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAC1G,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,2BAA2B;IAC1C,+FAA+F;IAC/F,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,kEAAkE;IAClE,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,qBAAa,4BAA4B;IAC3B,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,2BAA2B;IAE7D;;;;;OAKG;IACG,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC;IASvF,kGAAkG;IAC5F,KAAK,IAAI,OAAO,CAAC,wBAAwB,EAAE,CAAC;IASlD,8FAA8F;YAChF,SAAS;CAUxB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* accountability.ts — witness accountability (Phase 7). Makes witness COLLUSION
|
|
3
|
+
* ACCOUNTABLE (never prevented): when co-signatures by the SAME witness over the SAME
|
|
4
|
+
* (logId, treeSize) with DIFFERENT roots are CO-LOCATED at one observer, it forms a
|
|
5
|
+
* non-repudiable `WitnessEquivocationProof` naming that witness.
|
|
6
|
+
*
|
|
7
|
+
* THE CO-LOCATION PRECONDITION (the honest boundary — same class as the operator monitor):
|
|
8
|
+
* a proof forms only if BOTH divergent co-signatures reach this corpus. A witness co-signs
|
|
9
|
+
* honestly via its durable store (which never co-signs two roots at one size), so the two
|
|
10
|
+
* divergent co-signatures come from a COMPROMISED key / faulty store and are observed by
|
|
11
|
+
* DIFFERENT relying parties (e.g. two victims that pollinated the same witness at the same
|
|
12
|
+
* size and got different roots) or via gossip. If a colluder never lets its two
|
|
13
|
+
* observations meet at one extractor, NO proof forms. Absence of a proof proves nothing.
|
|
14
|
+
* Quorum intersection (k > n/2) guarantees a successful two-quorum split SHARES a
|
|
15
|
+
* double-signer — but extraction still requires both quorums' co-signatures to co-locate.
|
|
16
|
+
*
|
|
17
|
+
* SPENDS NEVER DEPEND ON THIS — it is the same liveness-only, opportunistic aggregation as
|
|
18
|
+
* the operator equivocation monitor. The sole spend-time safety control stays
|
|
19
|
+
* verifyWitnessedTreeHead at quorum >= 2 fail-closed.
|
|
20
|
+
*/
|
|
21
|
+
import { detectWitnessEquivocation, verifyWitnessCosignature, verifyWitnessEquivocation, } from '@kyaki/core';
|
|
22
|
+
export class WitnessAccountabilityMonitor {
|
|
23
|
+
cfg;
|
|
24
|
+
constructor(cfg) {
|
|
25
|
+
this.cfg = cfg;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Ingest one observed witness co-signature (the store verifies it before recording) and,
|
|
29
|
+
* if it now completes a same-witness / same-size / different-root pair, form + persist the
|
|
30
|
+
* proof. Returns the proof iff one is available for that witness (newly formed or already
|
|
31
|
+
* recorded). Idempotent — re-observing the same co-signature never duplicates or errors.
|
|
32
|
+
*/
|
|
33
|
+
async observe(cosig) {
|
|
34
|
+
// Verify-before-record at the monitor TOO (defense in depth — a non-authentic co-signature
|
|
35
|
+
// must never reach the corpus, where it could PK-squat and shadow a genuine root). The store
|
|
36
|
+
// also re-verifies, so neither layer can be the single point that lets junk in.
|
|
37
|
+
if (!cosig || !verifyWitnessCosignature(cosig))
|
|
38
|
+
return undefined;
|
|
39
|
+
await this.cfg.observed.record(cosig);
|
|
40
|
+
return this.detectFor(cosig.witnessId);
|
|
41
|
+
}
|
|
42
|
+
/** Re-scan every observed witness and persist any equivocation proof. Returns the proofs held. */
|
|
43
|
+
async sweep() {
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const w of await this.cfg.observed.witnesses()) {
|
|
46
|
+
const p = await this.detectFor(w);
|
|
47
|
+
if (p)
|
|
48
|
+
out.push(p);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
/** A witness's proof, re-detected from the corpus (and persisted) or read back from store. */
|
|
53
|
+
async detectFor(witnessId) {
|
|
54
|
+
const cosigs = await this.cfg.observed.byWitness(witnessId);
|
|
55
|
+
const proof = detectWitnessEquivocation(cosigs, witnessId);
|
|
56
|
+
if (proof && verifyWitnessEquivocation(proof, witnessId)) {
|
|
57
|
+
await this.cfg.conflicts.record(proof); // monotone, idempotent
|
|
58
|
+
return proof;
|
|
59
|
+
}
|
|
60
|
+
// No fresh pair (or only one root seen so far) ⇒ surface any already-recorded proof.
|
|
61
|
+
return this.cfg.conflicts.forWitness(witnessId);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=accountability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accountability.js","sourceRoot":"","sources":["../src/accountability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EACL,yBAAyB,EAAE,wBAAwB,EAAE,yBAAyB,GAE/E,MAAM,aAAa,CAAC;AASrB,MAAM,OAAO,4BAA4B;IACV;IAA7B,YAA6B,GAAgC;QAAhC,QAAG,GAAH,GAAG,CAA6B;IAAG,CAAC;IAEjE;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,KAAyB;QACrC,2FAA2F;QAC3F,6FAA6F;QAC7F,gFAAgF;QAChF,IAAI,CAAC,KAAK,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACjE,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,kGAAkG;IAClG,KAAK,CAAC,KAAK;QACT,MAAM,GAAG,GAA+B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;YACpD,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAClC,IAAI,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,8FAA8F;IACtF,KAAK,CAAC,SAAS,CAAC,SAAiB;QACvC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,yBAAyB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC3D,IAAI,KAAK,IAAI,yBAAyB,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAG,uBAAuB;YACjE,OAAO,KAAK,CAAC;QACf,CAAC;QACD,qFAAqF;QACrF,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAClD,CAAC;CACF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/monitor-http.ts — the WitnessMonitor's HTTP edge.
|
|
3
|
+
*
|
|
4
|
+
* One read route, `GET /conflict`, serving the durable EquivocationProof (the klaxon):
|
|
5
|
+
* - 200 { version, proof } — a detected, self-verifying proof. The CONSUMER MUST
|
|
6
|
+
* re-verify it with verifyEquivocation(proof, PINNED_OPERATOR_DID); this endpoint is
|
|
7
|
+
* an UNTRUSTED aggregator — a 200 status is not evidence, the proof bytes are.
|
|
8
|
+
* - 204 — no conflict OBSERVED by this monitor (NOT "no fork
|
|
9
|
+
* exists" — absence proves nothing; detection is liveness-bounded).
|
|
10
|
+
* - 500 { error } — a monitor-internal fault.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors witness-http hardening: a thrown store/DB fault becomes a WRITTEN response,
|
|
13
|
+
* never a hung socket; the served body is a single fixed-shape proof, not unbounded data.
|
|
14
|
+
*/
|
|
15
|
+
import { type Server } from 'node:http';
|
|
16
|
+
import type { ConflictStore } from '@kyaki/core';
|
|
17
|
+
/** Bumped if the wire shape of the /conflict body changes (consumer compatibility). */
|
|
18
|
+
export declare const CONFLICT_WIRE_VERSION = 1;
|
|
19
|
+
export interface MonitorHttpRequest {
|
|
20
|
+
method: string;
|
|
21
|
+
path: string;
|
|
22
|
+
}
|
|
23
|
+
export interface MonitorHttpResponse {
|
|
24
|
+
status: number;
|
|
25
|
+
body: unknown;
|
|
26
|
+
}
|
|
27
|
+
export type MonitorHttpHandler = (req: MonitorHttpRequest) => Promise<MonitorHttpResponse>;
|
|
28
|
+
export declare function createMonitorHttpHandler(conflicts: ConflictStore): MonitorHttpHandler;
|
|
29
|
+
/** Bind the pure handler to node:http. No request body is read (read-only routes). */
|
|
30
|
+
export declare function createMonitorServer(conflicts: ConflictStore): Server;
|
|
31
|
+
//# sourceMappingURL=monitor-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor-http.d.ts","sourceRoot":"","sources":["../../src/adapters/monitor-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAoC,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,uFAAuF;AACvF,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AACD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AACD,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAE3F,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,aAAa,GAAG,kBAAkB,CAarF;AAED,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM,CAsBpE"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/monitor-http.ts — the WitnessMonitor's HTTP edge.
|
|
3
|
+
*
|
|
4
|
+
* One read route, `GET /conflict`, serving the durable EquivocationProof (the klaxon):
|
|
5
|
+
* - 200 { version, proof } — a detected, self-verifying proof. The CONSUMER MUST
|
|
6
|
+
* re-verify it with verifyEquivocation(proof, PINNED_OPERATOR_DID); this endpoint is
|
|
7
|
+
* an UNTRUSTED aggregator — a 200 status is not evidence, the proof bytes are.
|
|
8
|
+
* - 204 — no conflict OBSERVED by this monitor (NOT "no fork
|
|
9
|
+
* exists" — absence proves nothing; detection is liveness-bounded).
|
|
10
|
+
* - 500 { error } — a monitor-internal fault.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors witness-http hardening: a thrown store/DB fault becomes a WRITTEN response,
|
|
13
|
+
* never a hung socket; the served body is a single fixed-shape proof, not unbounded data.
|
|
14
|
+
*/
|
|
15
|
+
import { createServer as nodeCreateServer } from 'node:http';
|
|
16
|
+
/** Bumped if the wire shape of the /conflict body changes (consumer compatibility). */
|
|
17
|
+
export const CONFLICT_WIRE_VERSION = 1;
|
|
18
|
+
export function createMonitorHttpHandler(conflicts) {
|
|
19
|
+
return async (req) => {
|
|
20
|
+
if (req.method === 'GET' && req.path === '/conflict') {
|
|
21
|
+
try {
|
|
22
|
+
const proof = await conflicts.latest();
|
|
23
|
+
if (!proof)
|
|
24
|
+
return { status: 204, body: undefined };
|
|
25
|
+
return { status: 200, body: { version: CONFLICT_WIRE_VERSION, proof } };
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { status: 500, body: { error: 'INTERNAL' } };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { status: 404, body: { error: 'NOT_FOUND' } };
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Bind the pure handler to node:http. No request body is read (read-only routes). */
|
|
35
|
+
export function createMonitorServer(conflicts) {
|
|
36
|
+
const handle = createMonitorHttpHandler(conflicts);
|
|
37
|
+
return nodeCreateServer((req, res) => {
|
|
38
|
+
req.on('error', () => { });
|
|
39
|
+
res.on('error', () => { });
|
|
40
|
+
void (async () => {
|
|
41
|
+
const path = (req.url ?? '/').split('?')[0] ?? '/';
|
|
42
|
+
try {
|
|
43
|
+
const result = await handle({ method: req.method ?? 'GET', path });
|
|
44
|
+
if (result.status === 204) {
|
|
45
|
+
res.writeHead(204);
|
|
46
|
+
res.end();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
res.writeHead(result.status, { 'Content-Type': 'application/json' });
|
|
50
|
+
res.end(JSON.stringify(result.body));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
54
|
+
res.end(JSON.stringify({ error: 'INTERNAL' }));
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=monitor-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor-http.js","sourceRoot":"","sources":["../../src/adapters/monitor-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,YAAY,IAAI,gBAAgB,EAAe,MAAM,WAAW,CAAC;AAG1E,uFAAuF;AACvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAYvC,MAAM,UAAU,wBAAwB,CAAC,SAAwB;IAC/D,OAAO,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACrD,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,CAAC;gBACvC,IAAI,CAAC,KAAK;oBAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;gBACpD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,EAAE,CAAC;YAC1E,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;YACtD,CAAC;QACH,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC;IACvD,CAAC,CAAC;AACJ,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,mBAAmB,CAAC,SAAwB;IAC1D,MAAM,MAAM,GAAG,wBAAwB,CAAC,SAAS,CAAC,CAAC;IACnD,OAAO,gBAAgB,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACnC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC1B,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC1B,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;YACnD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACnE,IAAI,MAAM,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;oBAC1B,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACnB,GAAG,CAAC,GAAG,EAAE,CAAC;oBACV,OAAO;gBACT,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBACrE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;YACjD,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;IACP,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/witness-http.ts — the witness's HTTP edge (a transport, not authority).
|
|
3
|
+
*
|
|
4
|
+
* A pure `(req) => Promise<res>` handler plus a node:http binding, mirroring
|
|
5
|
+
* @kyaki/api's adapter — but HARDENED per the design review, because a witness is a
|
|
6
|
+
* public-facing party an operator does not control:
|
|
7
|
+
* - a request-body BYTE CAP in the binding (413) so an unbounded POST can't
|
|
8
|
+
* exhaust heap before parsing;
|
|
9
|
+
* - a consistency-proof LENGTH CAP in the handler (400) — a real proof is
|
|
10
|
+
* O(log n) (≤ 64 nodes covers 2^64 entries), so a longer one is junk/abuse;
|
|
11
|
+
* - a try/catch around dispatch so a thrown WITNESS_* error becomes a WRITTEN
|
|
12
|
+
* response with the right status, never a hung socket / TCP reset;
|
|
13
|
+
* - an error TAXONOMY that keeps the smoking gun loud: an operator-attributable
|
|
14
|
+
* CONFLICT (equivocation / non-monotonic / non-consistent fork) is 409, an
|
|
15
|
+
* input fault (bad signature / missing or mis-shaped proof / wrong log) is 400,
|
|
16
|
+
* and only an unexpected internal fault is 500.
|
|
17
|
+
*
|
|
18
|
+
* NOTE (documented out-of-scope, per review): /cosign is unauthenticated. Any
|
|
19
|
+
* co-signature a replayer obtains is honest (the witness only ever endorses the
|
|
20
|
+
* operator's own signed heads), but advancing the anchor and the write path should
|
|
21
|
+
* be gated to the operator in production (private network / mTLS / a signed request
|
|
22
|
+
* envelope). The byte cap blunts the worst abuse; rate-limiting is a deployment concern.
|
|
23
|
+
*/
|
|
24
|
+
import { type Server } from 'node:http';
|
|
25
|
+
import type { ConsistencyProof, SignedTreeHead, WitnessCosignature, WitnessHead } from '@kyaki/core';
|
|
26
|
+
/** The witness surface the adapter needs (satisfied by DurableWitness). */
|
|
27
|
+
export interface WitnessLike {
|
|
28
|
+
cosign(sth: SignedTreeHead, consistencyProof?: ConsistencyProof): Promise<WitnessCosignature>;
|
|
29
|
+
head(): Promise<WitnessHead | undefined>;
|
|
30
|
+
/** The operator-signed STH endorsed at `treeSize` (latest if omitted) — the gossip
|
|
31
|
+
* source a WitnessMonitor pulls via `GET /sth`. */
|
|
32
|
+
signedHead(treeSize?: number): Promise<SignedTreeHead | undefined>;
|
|
33
|
+
/** This witness's endorsement (its endorsed STH + its own co-signature) at `treeSize`
|
|
34
|
+
* (latest if omitted) — what a relying party pulls via `GET /cosig` to POLLINATE the
|
|
35
|
+
* head it was served (confirm a quorum of trusted witnesses co-signed the exact root). */
|
|
36
|
+
endorsement(treeSize?: number): Promise<{
|
|
37
|
+
sth: SignedTreeHead;
|
|
38
|
+
cosignature: WitnessCosignature;
|
|
39
|
+
} | undefined>;
|
|
40
|
+
}
|
|
41
|
+
export interface HttpRequest {
|
|
42
|
+
method: string;
|
|
43
|
+
path: string;
|
|
44
|
+
query?: Record<string, string | undefined>;
|
|
45
|
+
body?: unknown;
|
|
46
|
+
}
|
|
47
|
+
export interface HttpResponse {
|
|
48
|
+
status: number;
|
|
49
|
+
body: unknown;
|
|
50
|
+
}
|
|
51
|
+
export type HttpHandler = (req: HttpRequest) => Promise<HttpResponse>;
|
|
52
|
+
/**
|
|
53
|
+
* Routes:
|
|
54
|
+
* POST /cosign { sth, consistencyProof? } → 200 WitnessCosignature
|
|
55
|
+
* · 400 input fault · 409 operator conflict
|
|
56
|
+
* · 400 PROOF_TOO_LARGE
|
|
57
|
+
* GET /head → 200 { head: WitnessHead | null }
|
|
58
|
+
*/
|
|
59
|
+
export declare function createWitnessHttpHandler(witness: WitnessLike): HttpHandler;
|
|
60
|
+
/** Bind the pure handler to node:http, enforcing the request-body byte cap. */
|
|
61
|
+
export declare function createWitnessServer(witness: WitnessLike): Server;
|
|
62
|
+
//# sourceMappingURL=witness-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"witness-http.d.ts","sourceRoot":"","sources":["../../src/adapters/witness-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAoC,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAErG,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC9F,IAAI,IAAI,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IACzC;wDACoD;IACpD,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACnE;;+FAE2F;IAC3F,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,cAAc,CAAC;QAAC,WAAW,EAAE,kBAAkB,CAAA;KAAE,GAAG,SAAS,CAAC,CAAC;CAC/G;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAiCtE;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CA6E1E;AAED,+EAA+E;AAC/E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CA+DhE"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/witness-http.ts — the witness's HTTP edge (a transport, not authority).
|
|
3
|
+
*
|
|
4
|
+
* A pure `(req) => Promise<res>` handler plus a node:http binding, mirroring
|
|
5
|
+
* @kyaki/api's adapter — but HARDENED per the design review, because a witness is a
|
|
6
|
+
* public-facing party an operator does not control:
|
|
7
|
+
* - a request-body BYTE CAP in the binding (413) so an unbounded POST can't
|
|
8
|
+
* exhaust heap before parsing;
|
|
9
|
+
* - a consistency-proof LENGTH CAP in the handler (400) — a real proof is
|
|
10
|
+
* O(log n) (≤ 64 nodes covers 2^64 entries), so a longer one is junk/abuse;
|
|
11
|
+
* - a try/catch around dispatch so a thrown WITNESS_* error becomes a WRITTEN
|
|
12
|
+
* response with the right status, never a hung socket / TCP reset;
|
|
13
|
+
* - an error TAXONOMY that keeps the smoking gun loud: an operator-attributable
|
|
14
|
+
* CONFLICT (equivocation / non-monotonic / non-consistent fork) is 409, an
|
|
15
|
+
* input fault (bad signature / missing or mis-shaped proof / wrong log) is 400,
|
|
16
|
+
* and only an unexpected internal fault is 500.
|
|
17
|
+
*
|
|
18
|
+
* NOTE (documented out-of-scope, per review): /cosign is unauthenticated. Any
|
|
19
|
+
* co-signature a replayer obtains is honest (the witness only ever endorses the
|
|
20
|
+
* operator's own signed heads), but advancing the anchor and the write path should
|
|
21
|
+
* be gated to the operator in production (private network / mTLS / a signed request
|
|
22
|
+
* envelope). The byte cap blunts the worst abuse; rate-limiting is a deployment concern.
|
|
23
|
+
*/
|
|
24
|
+
import { createServer as nodeCreateServer } from 'node:http';
|
|
25
|
+
/** A real Merkle consistency proof is O(log n); anything longer is junk or abuse. */
|
|
26
|
+
const MAX_PROOF_NODES = 64;
|
|
27
|
+
/** Honest STH + proof is well under 1 KiB; cap the raw body far below heap-risk. */
|
|
28
|
+
const MAX_BODY_BYTES = 64 * 1024;
|
|
29
|
+
/** Operator-attributable conflicts → 409 (the alarm); input faults → 400. */
|
|
30
|
+
const OPERATOR_CONFLICTS = new Set([
|
|
31
|
+
'WITNESS_NON_MONOTONIC', 'WITNESS_EQUIVOCATION', 'WITNESS_CONSISTENCY_INVALID',
|
|
32
|
+
'WITNESS_STORE_NON_MONOTONIC', 'WITNESS_STORE_EQUIVOCATION',
|
|
33
|
+
]);
|
|
34
|
+
const INPUT_FAULTS = new Set([
|
|
35
|
+
'WITNESS_STH_SIGNATURE_INVALID', 'WITNESS_CONSISTENCY_REQUIRED',
|
|
36
|
+
'WITNESS_CONSISTENCY_SIZE_MISMATCH', 'WITNESS_WRONG_LOG',
|
|
37
|
+
]);
|
|
38
|
+
/** The machine code is the message up to the first ':' or space. */
|
|
39
|
+
function codeOf(err) {
|
|
40
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
+
return msg.split(/[:\s]/)[0] ?? 'INTERNAL';
|
|
42
|
+
}
|
|
43
|
+
function statusForCode(code) {
|
|
44
|
+
if (INPUT_FAULTS.has(code))
|
|
45
|
+
return 400;
|
|
46
|
+
if (OPERATOR_CONFLICTS.has(code))
|
|
47
|
+
return 409;
|
|
48
|
+
// Any OTHER integrity-prefixed code (e.g. a store backstop like WITNESS_STORE_LOG_MISMATCH,
|
|
49
|
+
// or a future WITNESS_*/COSIG_* fault) is operator-attributable — surface it LOUD as a 409,
|
|
50
|
+
// never let it fall through to 500 where the client would misread it as transport/liveness.
|
|
51
|
+
if (code.startsWith('WITNESS_') || code.startsWith('COSIG_'))
|
|
52
|
+
return 409;
|
|
53
|
+
return 500;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Routes:
|
|
57
|
+
* POST /cosign { sth, consistencyProof? } → 200 WitnessCosignature
|
|
58
|
+
* · 400 input fault · 409 operator conflict
|
|
59
|
+
* · 400 PROOF_TOO_LARGE
|
|
60
|
+
* GET /head → 200 { head: WitnessHead | null }
|
|
61
|
+
*/
|
|
62
|
+
export function createWitnessHttpHandler(witness) {
|
|
63
|
+
return async (req) => {
|
|
64
|
+
const { method, path } = req;
|
|
65
|
+
if (method === 'POST' && path === '/cosign') {
|
|
66
|
+
const body = (req.body ?? {});
|
|
67
|
+
if (!body.sth) {
|
|
68
|
+
return { status: 400, body: { error: 'MISSING_STH' } };
|
|
69
|
+
}
|
|
70
|
+
if (body.consistencyProof && Array.isArray(body.consistencyProof.proof)
|
|
71
|
+
&& body.consistencyProof.proof.length > MAX_PROOF_NODES) {
|
|
72
|
+
return { status: 400, body: { error: 'PROOF_TOO_LARGE', max: MAX_PROOF_NODES } };
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const cosignature = await witness.cosign(body.sth, body.consistencyProof);
|
|
76
|
+
return { status: 200, body: cosignature };
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const code = codeOf(err);
|
|
80
|
+
const status = statusForCode(code);
|
|
81
|
+
// A 500 is an unexpected internal fault — surface a generic body, never a hang.
|
|
82
|
+
return { status, body: { error: status === 500 ? 'INTERNAL' : code } };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (method === 'GET' && path === '/head') {
|
|
86
|
+
try {
|
|
87
|
+
const head = await witness.head();
|
|
88
|
+
return { status: 200, body: { head: head ?? null } };
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Keep the "no thrown error escapes the handler" invariant uniform across routes —
|
|
92
|
+
// a store/DB fault in head() becomes a written 500, not an unhandled rejection for
|
|
93
|
+
// an in-process caller composing the pure handler without the node:http binding.
|
|
94
|
+
return { status: 500, body: { error: 'INTERNAL' } };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (method === 'GET' && path === '/sth') {
|
|
98
|
+
// The gossip read a WitnessMonitor pulls. `?size=` selects a specific endorsed
|
|
99
|
+
// size (for cross-witness same-size comparison); omitted ⇒ the latest endorsed STH.
|
|
100
|
+
const raw = req.query?.['size'];
|
|
101
|
+
let treeSize;
|
|
102
|
+
if (raw !== undefined && raw !== '') {
|
|
103
|
+
treeSize = Number(raw);
|
|
104
|
+
if (!Number.isInteger(treeSize) || treeSize < 0) {
|
|
105
|
+
return { status: 400, body: { error: 'BAD_SIZE' } };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const sth = await witness.signedHead(treeSize);
|
|
110
|
+
return { status: 200, body: { sth: sth ?? null } };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return { status: 500, body: { error: 'INTERNAL' } };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (method === 'GET' && path === '/cosig') {
|
|
117
|
+
// The relying-party POLLINATION read: this witness's endorsement (STH + its own
|
|
118
|
+
// co-signature) at `?size=`. A victim pulls this DIRECTLY from each trusted witness
|
|
119
|
+
// to confirm the head it was served — so the operator cannot withhold/cherry-pick.
|
|
120
|
+
const raw = req.query?.['size'];
|
|
121
|
+
let treeSize;
|
|
122
|
+
if (raw !== undefined && raw !== '') {
|
|
123
|
+
treeSize = Number(raw);
|
|
124
|
+
if (!Number.isInteger(treeSize) || treeSize < 0) {
|
|
125
|
+
return { status: 400, body: { error: 'BAD_SIZE' } };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const e = await witness.endorsement(treeSize);
|
|
130
|
+
return { status: 200, body: { sth: e?.sth ?? null, cosignature: e?.cosignature ?? null } };
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return { status: 500, body: { error: 'INTERNAL' } };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { status: 404, body: { error: 'NOT_FOUND' } };
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Bind the pure handler to node:http, enforcing the request-body byte cap. */
|
|
140
|
+
export function createWitnessServer(witness) {
|
|
141
|
+
const handle = createWitnessHttpHandler(witness);
|
|
142
|
+
return nodeCreateServer((req, res) => {
|
|
143
|
+
// A public-facing endpoint an operator does not control: a client that resets the
|
|
144
|
+
// connection mid-body makes the request stream emit 'error', which Node re-throws as
|
|
145
|
+
// an uncaught exception (crashing the witness) if unhandled. Swallow it on both streams.
|
|
146
|
+
req.on('error', () => { });
|
|
147
|
+
res.on('error', () => { });
|
|
148
|
+
const chunks = [];
|
|
149
|
+
let total = 0;
|
|
150
|
+
let aborted = false;
|
|
151
|
+
const fail = (status, error) => {
|
|
152
|
+
// Send the response and close the connection; the `aborted` flag stops further
|
|
153
|
+
// buffering. We do NOT req.destroy() here — destroying the socket before the body
|
|
154
|
+
// flushes can RST the connection and lose the response (e.g. the 413).
|
|
155
|
+
aborted = true;
|
|
156
|
+
res.writeHead(status, { 'Content-Type': 'application/json', 'Connection': 'close' });
|
|
157
|
+
res.end(JSON.stringify({ error }));
|
|
158
|
+
};
|
|
159
|
+
req.on('data', (c) => {
|
|
160
|
+
if (aborted)
|
|
161
|
+
return;
|
|
162
|
+
total += c.length;
|
|
163
|
+
if (total > MAX_BODY_BYTES) {
|
|
164
|
+
fail(413, 'PAYLOAD_TOO_LARGE');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
chunks.push(c);
|
|
168
|
+
});
|
|
169
|
+
req.on('end', () => {
|
|
170
|
+
if (aborted)
|
|
171
|
+
return;
|
|
172
|
+
const fail500 = () => {
|
|
173
|
+
try {
|
|
174
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
175
|
+
res.end(JSON.stringify({ error: 'INTERNAL' }));
|
|
176
|
+
}
|
|
177
|
+
catch { /* already responded */ }
|
|
178
|
+
};
|
|
179
|
+
void (async () => {
|
|
180
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
181
|
+
let body;
|
|
182
|
+
try {
|
|
183
|
+
body = raw ? JSON.parse(raw) : undefined;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
187
|
+
res.end(JSON.stringify({ error: 'INVALID_JSON' }));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Parse path + query WITHOUT new URL(): a raw target like `//` makes new URL() THROW
|
|
191
|
+
// ('Invalid URL'), which — before the dispatch try — would escape this voided async
|
|
192
|
+
// IIFE as an unhandled rejection, hanging the socket (and crashing under the default
|
|
193
|
+
// --unhandled-rejections=throw). split + URLSearchParams never throw on a bad target.
|
|
194
|
+
const [rawPath, qs] = (req.url ?? '/').split('?');
|
|
195
|
+
const query = {};
|
|
196
|
+
for (const [k, v] of new URLSearchParams(qs ?? ''))
|
|
197
|
+
query[k] = v;
|
|
198
|
+
try {
|
|
199
|
+
const result = await handle({ method: req.method ?? 'GET', path: rawPath || '/', query, body });
|
|
200
|
+
res.writeHead(result.status, { 'Content-Type': 'application/json' });
|
|
201
|
+
res.end(JSON.stringify(result.body));
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// The pure handler maps every expected rejection itself; anything escaping
|
|
205
|
+
// here is a genuine internal fault — still write a body, never hang.
|
|
206
|
+
fail500();
|
|
207
|
+
}
|
|
208
|
+
})().catch(fail500); // belt-and-suspenders: no throw can leave the socket hung
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=witness-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"witness-http.js","sourceRoot":"","sources":["../../src/adapters/witness-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* durable-witness.ts — the DURABLE, independent witness party.
|
|
3
|
+
*
|
|
4
|
+
* `@kyaki/core`'s `Witness` is the verification ENGINE, but its remembered head is
|
|
5
|
+
* in-process: it forgets on restart, so it provides no cross-restart rollback
|
|
6
|
+
* defense and cannot be horizontally scaled. `DurableWitness` closes that — it is
|
|
7
|
+
* the leg of "anchored AND externally witnessed" the moat rests on.
|
|
8
|
+
*
|
|
9
|
+
* The cross-party guarantee lives in TWO places working together:
|
|
10
|
+
* 1. PERSIST-BEFORE-EMIT — a co-signature is returned only AFTER the head it
|
|
11
|
+
* endorses is durably committed. Releasing a co-sig before recording it would
|
|
12
|
+
* let the witness forget, after a crash, that it had endorsed head N and later
|
|
13
|
+
* co-sign a forked N′ — exactly the equivocation it exists to prevent.
|
|
14
|
+
* 2. VERIFY-UNDER-LOCK against the DURABLE head — the append-only consistency
|
|
15
|
+
* check (which the store cannot perform; it has no Merkle state) runs inside
|
|
16
|
+
* the store's per-log lock against the head read FROM STORAGE, never an
|
|
17
|
+
* in-memory cache. So two instances sharing one store can never co-sign a fork
|
|
18
|
+
* from stale local state. In-process requests are additionally serialized per
|
|
19
|
+
* log by a KeyedMutex so two concurrent /cosign calls cannot interleave across
|
|
20
|
+
* the store await.
|
|
21
|
+
*
|
|
22
|
+
* What this does NOT defend (honest boundary, enforced at the relying party by
|
|
23
|
+
* `verifyWitnessedTreeHead`): a single witness does not defeat a general cross-party
|
|
24
|
+
* split-view, nor a compromised/colluding witness key — those need quorum ≥ 2 under
|
|
25
|
+
* disjoint control and, ultimately, witness-to-witness gossip (a documented follow-up).
|
|
26
|
+
*/
|
|
27
|
+
import { type ConsistencyProof, type EndorsedHead, type Identity, type SignedTreeHead, type WitnessCosignature, type WitnessHead, type WitnessStateStore } from '@kyaki/core';
|
|
28
|
+
export declare class DurableWitness {
|
|
29
|
+
private readonly keys;
|
|
30
|
+
private readonly logId;
|
|
31
|
+
private readonly store;
|
|
32
|
+
private readonly mutex;
|
|
33
|
+
private constructor();
|
|
34
|
+
/**
|
|
35
|
+
* Build a witness pinned to one operator log. The durable store is the SOLE
|
|
36
|
+
* authority for the witness's last head — it is read under lock on every cosign,
|
|
37
|
+
* so there is no in-memory baseline to go stale. `create()` is the only
|
|
38
|
+
* constructor so a caller cannot bypass the store with a hand-seeded head.
|
|
39
|
+
*/
|
|
40
|
+
static create(keys: Identity, logId: string, store: WitnessStateStore): Promise<DurableWitness>;
|
|
41
|
+
get id(): string;
|
|
42
|
+
/** The witness's durable last-co-signed head. The operator builds its next
|
|
43
|
+
* consistency proof from THIS (reading storage, never a cache) so an honest
|
|
44
|
+
* extension is never spuriously rejected for a size mismatch. */
|
|
45
|
+
head(): Promise<WitnessHead | undefined>;
|
|
46
|
+
/**
|
|
47
|
+
* Co-sign `sth` iff it is an append-only extension of the witness's durable head.
|
|
48
|
+
* Throws a named WITNESS_* error on rejection (the caller maps it to a status):
|
|
49
|
+
* - WITNESS_WRONG_LOG — sth is for a log this witness does not watch;
|
|
50
|
+
* - WITNESS_STH_SIGNATURE_INVALID / _CONSISTENCY_REQUIRED / _SIZE_MISMATCH
|
|
51
|
+
* — input faults (the proof is missing/wrong shape);
|
|
52
|
+
* - WITNESS_NON_MONOTONIC / _EQUIVOCATION / _CONSISTENCY_INVALID
|
|
53
|
+
* — operator-attributable conflicts (a fork attempt).
|
|
54
|
+
*/
|
|
55
|
+
cosign(sth: SignedTreeHead, consistencyProof?: ConsistencyProof, now?: Date): Promise<WitnessCosignature>;
|
|
56
|
+
/** The operator-signed STH this witness endorsed at `treeSize` (its latest endorsed
|
|
57
|
+
* STH if omitted), read straight from the durable store — the gossip source a
|
|
58
|
+
* WitnessMonitor pulls to detect cross-witness equivocation. */
|
|
59
|
+
signedHead(treeSize?: number): Promise<SignedTreeHead | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* This witness's OWN endorsement at `treeSize` (its latest if omitted): the
|
|
62
|
+
* operator-signed STH it endorsed AND its co-signature over it. The co-signature is
|
|
63
|
+
* re-derived deterministically from the retained STH — its body is a pure function of
|
|
64
|
+
* (witnessId, logId, treeSize, rootHash), so this needs no extra stored state and is
|
|
65
|
+
* byte-identical to the one emitted at cosign time.
|
|
66
|
+
*
|
|
67
|
+
* This is what relying-party POLLINATION pulls DIRECTLY from each trusted witness: the
|
|
68
|
+
* victim asks the witnesses themselves "did you co-sign this exact root at this size?",
|
|
69
|
+
* so the operator cannot withhold or cherry-pick which witnesses endorsed which root,
|
|
70
|
+
* and the witness-signed co-signature cannot be forged by a man-in-the-middle.
|
|
71
|
+
* undefined if this witness retained no endorsement at that size.
|
|
72
|
+
*/
|
|
73
|
+
endorsement(treeSize?: number): Promise<{
|
|
74
|
+
sth: SignedTreeHead;
|
|
75
|
+
cosignature: WitnessCosignature;
|
|
76
|
+
} | undefined>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* An in-memory `WitnessStateStore` for single-process tests and demos. It serializes
|
|
80
|
+
* `cosign` via a promise chain (mirroring the durable per-log lock) and applies the
|
|
81
|
+
* same monotonic + equivocation backstop the durable store does. It is NOT durable —
|
|
82
|
+
* it forgets on restart, so the cross-restart guarantee requires a durable store
|
|
83
|
+
* (`@kyaki/postgres` `PgWitnessStore`). Use it to exercise the witness LOGIC.
|
|
84
|
+
*/
|
|
85
|
+
export declare class MemoryWitnessStore implements WitnessStateStore {
|
|
86
|
+
private current;
|
|
87
|
+
private cosignature;
|
|
88
|
+
/** Append-only operator-signed STHs keyed by tree size (FIRST-write-wins, mirroring
|
|
89
|
+
* the durable kya_endorsed_sth ON CONFLICT DO NOTHING). Retained so a past size
|
|
90
|
+
* stays serveable for gossip after the witness advances beyond it. */
|
|
91
|
+
private readonly endorsed;
|
|
92
|
+
private chain;
|
|
93
|
+
load(): Promise<WitnessHead | undefined>;
|
|
94
|
+
/** The co-signature co-committed with the current head (for parity with the
|
|
95
|
+
* durable store, which persists both in one transaction). */
|
|
96
|
+
latestCosignature(): Promise<WitnessCosignature | undefined>;
|
|
97
|
+
/** The endorsed operator STH at `treeSize` (latest if omitted). undefined if none. */
|
|
98
|
+
signedHead(treeSize?: number): Promise<SignedTreeHead | undefined>;
|
|
99
|
+
cosign(decide: (prior: WitnessHead | undefined) => EndorsedHead): Promise<WitnessCosignature>;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=durable-witness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-witness.d.ts","sourceRoot":"","sources":["../src/durable-witness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAEL,KAAK,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAC5E,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAClE,MAAM,aAAa,CAAC;AAErB,qBAAa,cAAc;IAIvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK;IALxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAE1C,OAAO;IAMP;;;;;OAKG;WACU,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC;IAIrG,IAAI,EAAE,IAAI,MAAM,CAEf;IAED;;sEAEkE;IAC5D,IAAI,IAAI,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAI9C;;;;;;;;OAQG;IACG,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAwB/G;;qEAEiE;IAC3D,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAIxE;;;;;;;;;;;;OAYG;IACG,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,cAAc,CAAC;QAAC,WAAW,EAAE,kBAAkB,CAAA;KAAE,GAAG,SAAS,CAAC;CAKpH;AAED;;;;;;GAMG;AACH,qBAAa,kBAAmB,YAAW,iBAAiB;IAC1D,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,WAAW,CAAiC;IACpD;;2EAEuE;IACvE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqC;IAC9D,OAAO,CAAC,KAAK,CAAuC;IAE9C,IAAI,IAAI,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAI9C;kEAC8D;IACxD,iBAAiB,IAAI,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAIlE,sFAAsF;IAChF,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAOxE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,KAAK,YAAY,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAiC9F"}
|