Problem class: js-streaming-certificate-transparency-equivocation-audit-20261011
Problem class: js-streaming-certificate-transparency-equivocation-audit-20261011
Runtime: Node.js 20+ (verified v22.23.2), zero dependencies.
Status: diagnosis + working, test-verified solution (35 tests green). Full modular project written to /workspace/ct-auditor (including SOLUTION.md).
This problem is not a crash to patch — it is a federation + append-only-proof problem. A monitor that "audits multiple CT feeds" typically fails in seven distinct ways, and each maps to a cryptographic or bookkeeping mistake:
| # | Naive approach | Why it fails | Correct principle |
|---|---|---|---|
| 1 | Buffer the entire feed, sort, then process | Not streaming; unbounded memory; delayed data never gets a second chance | Incremental ingest + bounded pending window + retry on new checkpoints |
| 2 | Treat any size gap / absent proof as a fork | A missing proof is not misbehaviour; a network attacker can inject garbage proofs | Separate missing evidence from a proven fork |
| 3 | Compare only the latest head per log | Two different roots at the same size are never compared | Remember retained roots per tree size |
| 4 | Assume a failed consistency proof proves a fork | True only if the proof came from the log's authenticated endpoint | Make the trust assumption explicit (trustProofOrigin) |
| 5 | Store every checkpoint/leaf forever | Memory grows with the log | Bounded LRU windows per log + pinned anchor/head |
| 6 | Emit a human string like "fork detected" | Not independently checkable | Emit a minimal proof object with executable checks |
| 7 | Order-sensitive bookkeeping | Detection depends on arrival order | Canonicalise evidence pairs before hashing |
The cryptographic root cause is exact:
(treeSize, rootHash).MTH(D[m]) is a function of the first m leaves ⇒ two signed roots at the same size are a contradiction (same-size split view).logId / type-specific)// checkpoint / sth
{ type:'checkpoint', logId, treeSize, rootHash, timestamp?, signature?, publicKey? }
// consistency proof (also accepts first:{treeSize,rootHash}, second:{...})
{ type:'consistency', logId, firstSize, firstRoot, secondSize, secondRoot, proof:[hex,...] }
// inclusion proof (also accepts `leaf` raw, `index`, `root`)
{ type:'inclusion', logId, leafIndex, treeSize, rootHash, leafHash, proof:[hex,...] }
ct-auditor.compact.js'use strict';
/**
* Self-contained streaming CT equivocation auditor (RFC 6962 / RFC 9162).
* Node >= 18. No dependencies. See SOLUTION.md for the full write-up.
*
* const { Auditor, verifyViolation } = require('./ct-auditor.compact');
* const a = new Auditor(); a.ingest(event); a.report();
*/
const crypto = require('crypto');
const { EventEmitter } = require('events');
/* ============================ Merkle (RFC 9162) ============================ */
const asBuf = (x, n = 'value') => {
if (Buffer.isBuffer(x)) return x;
if (x instanceof Uint8Array) return Buffer.from(x);
if (typeof x === 'string') {
const h = (x.startsWith('0x') ? x.slice(2) : x).toLowerCase();
if (h.length % 2 || !/^[0-9a-f]*$/.test(h)) throw new TypeError(`${n}: invalid hex`);
return Buffer.from(h, 'hex');
}
throw new TypeError(`${n}: expected Buffer/Uint8Array/hex`);
};
const asData = (x) => (Buffer.isBuffer(x) ? x : x instanceof Uint8Array ? Buffer.from(x) : Buffer.from(String(x), 'utf8'));
const hex = (b) => Buffer.from(b).toString('hex');
const sha = (...parts) => crypto.createHash('sha256').update(Buffer.concat(parts)).digest();
const H = (l, r) => sha(Buffer.from([1]), asBuf(l), asBuf(r));
const LF = (d) => sha(Buffer.from([0]), asData(d));
const EMPTY = sha();
const lpow2 = (n) => { let p = 1n; const N = BigInt(n); while (p * 2n < N) p *= 2n; return p; };
const isPow2 = (n) => { const N = BigInt(n); return N > 0n && (N & (N - 1n)) === 0n; };
function mth(hs) {
if (hs.length === 0) return EMPTY;
if (hs.length === 1) return Buffer.from(hs[0]);
const k = Number(lpow2(hs.length));
return H(mth(hs.slice(0, k)), mth(hs.slice(k)));
}
function inclProof(i, hs) {
if (hs.length === 1) return [];
const k = Number(lpow2(hs.length));
return i < k
? [...inclProof(i, hs.slice(0, k)), mth(hs.slice(k))]
: [...inclProof(i - k, hs.slice(k)), mth(hs.slice(0, k))];
}
function consProof(m, hs, known = true) {
const n = hs.length;
if (m === n) return known ? [] : [mth(hs)];
const k = Number(lpow2(n));
return m <= k
? [...consProof(m, hs.slice(0, k), known), mth(hs.slice(k))]
: [...consProof(m - k, hs.slice(k), false), mth(hs.slice(0, k))];
}
/** RFC 9162 §<ip-address>; throws on malformed proof, returns computed root. */
function rootFromInclusion(index, size, proof, leafHash) {
let fn = BigInt(index), sn = BigInt(size) - 1n, r = Buffer.from(asBuf(leafHash));
if (BigInt(index) >= BigInt(size)) throw new RangeError('index out of range');
for (const p of proof) {
if (sn === 0n) throw new Error('proof too long');
if ((fn & 1n) === 1n || fn === sn) {
r = H(p, r);
if ((fn & 1n) === 0n) while ((fn & 1n) === 0n && fn !== 0n) { fn >>= 1n; sn >>= 1n; }
} else r = H(r, p);
fn >>= 1n; sn >>= 1n;
}
if (sn !== 0n) throw new Error('proof too short');
return r;
}
function verifyInclusion(index, size, proof, leafHash, root) {
try { return rootFromInclusion(index, size, proof, leafHash).equals(asBuf(root)); } catch { return false; }
}
/** RFC 9162 §2.1.4.2. */
function verifyConsistency(first, second, proof, fh, sh) {
const a = BigInt(first), b = BigInt(second);
const f = asBuf(fh), s = asBuf(sh), path = proof.map((p) => asBuf(p));
if (a > b) return false;
if (a === b) return path.length === 0 && f.equals(s);
if (a === 0n) return path.length === 0;
if (path.length === 0) return false;
let work = isPow2(a) ? [f, ...path] : path;
let fn = a - 1n, sn = b - 1n;
if ((fn & 1n) === 1n) while ((fn & 1n) === 1n) { fn >>= 1n; sn >>= 1n; }
let fr = work[0], sr = work[0];
for (let i = 1; i < work.length; i += 1) {
const c = work[i];
if (sn === 0n) return false;
if ((fn & 1n) === 1n || fn === sn) {
fr = H(c, fr); sr = H(c, sr);
if ((fn & 1n) === 0n) while ((fn & 1n) === 0n && fn !== 0n) { fn >>= 1n; sn >>= 1n; }
} else sr = H(sr, c);
fn >>= 1n; sn >>= 1n;
}
return sn === 0n && fr.equals(f) && sr.equals(s);
}
/* =============================== Auditing ================================= */
const canon = (v) => (v === null || typeof v !== 'object') ? JSON.stringify(v)
: Array.isArray(v) ? `[${v.map(canon)}]`
: `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canon(v[k])}`).join(',')}}`;
const idOf = (o) => crypto.createHash('sha256').update(canon(o)).digest('hex').slice(0, 32);
const asSize = (v, n) => {
if (typeof v === 'bigint') { if (v < 0n) throw new RangeError(`${n}<0`); return v; }
if (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0) return BigInt(v);
if (typeof v === 'string' && /^\d+$/.test(v)) return BigInt(v);
throw new TypeError(`${n}: bad size ${v}`);
};
const dispSize = (b) => (b <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(b) : b.toString());
const cpView = (c) => {
const o = { logId: c.logId, treeSize: dispSize(c.treeSize), rootHash: c.rootHash };
if (c.timestamp != null) o.timestamp = c.timestamp;
if (c.signature) o.signature = c.signature;
return o;
};
const orderCps = (a, b) => {
const ka = [a.rootHash || '', String(a.treeSize), String(a.timestamp ?? ''), a.signature || ''];
const kb = [b.rootHash || '', String(b.treeSize), String(b.timestamp ?? ''), b.signature || ''];
for (let i = 0; i < ka.length; i += 1) if (ka[i] !== kb[i]) return ka[i] < kb[i] ? [a, b] : [b, a];
return [a, b];
};
class Bounded extends Map {
constructor(limit, pin) { super(); this.limit = limit; this.pin = pin; }
put(k, v) {
if (this.has(k)) this.delete(k);
this.set(k, v);
if (this.size <= this.limit) return null;
for (const [key, val] of this) {
if (this.pin && this.pin(key, val)) continue;
this.delete(key);
return { key, value: val };
}
return null;
}
}
class Auditor extends EventEmitter {
constructor(opts = {}) {
super();
this.o = {
cap: opts.maxCheckpointsPerLog ?? 4096,
leafCap: opts.maxLeafCommitmentsPerLog ?? 8192,
pendCap: opts.maxPendingProofsPerLog ?? 1024,
dedupCap: opts.maxDedupPerLog ?? 16384,
trustProofs: opts.trustProofOrigin !== false,
keys: opts.publicKeys || {},
verify: opts.verifyCheckpoint || null,
clock: opts.clock || (() => Date.now()),
};
this.logs = new Map();
this.violations = [];
this.incidents = [];
this._vIds = new Set();
this._iIds = new Set();
this.stats = { ingested: 0, checkpoints: 0, consistency: 0, inclusion: 0, duplicates: 0, untrusted: 0, evictions: 0, pending: 0 };
}
ingest(e) {
this.stats.ingested += 1;
try {
const kind = e && (e.type || e.kind || 'checkpoint');
if (kind === 'checkpoint' || kind === 'sth' || kind === 'signed-tree-head') this._checkpoint(e);
else if (kind === 'consistency' || kind === 'consistency-proof') this._consistency(e);
else if (kind === 'inclusion' || kind === 'inclusion-proof') this._inclusion(e);
else this._incident('unknown-event-type', e && e.logId, { type: kind });
} catch (err) { this._incident('malformed-event', e && e.logId, { message: err.message }); }
return this;
}
ingestAll(es) { for (const e of es) this.ingest(e); return this; }
report() { return { logs: [...this.logs.keys()], stats: this.getStats(), violations: this.violations, incidents: this.incidents, unresolved: this.unresolved() }; }
getStats() { return { ...this.stats, violations: this.violations.length, incidents: this.incidents.length }; }
stateSize() { const o = {}; for (const [id, s] of this.logs) o[id] = { checkpoints: s.cps.size, leaves: s.leaves.size, pending: s.pending.size }; return o; }
_log(id) {
let s = this.logs.get(id);
if (!s) {
s = { id, cps: null, leaves: new Bounded(this.o.leafCap), pending: new Bounded(this.o.pendCap), dedup: new Bounded(this.o.dedupCap), verified: new Bounded(this.o.cap), anchor: null, head: null };
s.cps = new Bounded(this.o.cap, (k, v) => v.cp === s.anchor || v.cp === s.head);
this.logs.set(id, s);
}
return s;
}
_checkpoint(e) {
if (!e.logId) throw new Error('missing logId');
const cp = { logId: e.logId, treeSize: asSize(e.treeSize ?? e.size, 'treeSize'), rootHash: asBuf(e.rootHash ?? e.root, 'rootHash').toString('hex'), timestamp: e.timestamp == null ? null : Number(e.timestamp), signature: e.signature ? asBuf(e.signature, 'sig').toString('hex') : null, publicKey: e.publicKey || null };
const s = this._log(cp.logId);
const key = this.o.keys[cp.logId];
if (cp.signature && (key || this.o.verify)) {
const ok = (this.o.verify || defaultVerify)(cp, key);
if (!ok) { this.stats.untrusted += 1; return this._incident('invalid-signature', cp.logId, { checkpoint: cpView(cp) }); }
}
const dk = `cp|${cp.treeSize}|${cp.rootHash}`;
if (s.dedup.has(dk)) { this.stats.duplicates += 1; s.dedup.put(dk, 1); return; }
s.dedup.put(dk, 1);
this.stats.checkpoints += 1;
if (!s.anchor || cp.treeSize < s.anchor.treeSize) s.anchor = cp;
if (!s.head || cp.treeSize > s.head.treeSize) s.head = cp;
const sk = cp.treeSize.toString();
let canonical = null;
for (const [, v] of s.cps) if (v.cp.treeSize === cp.treeSize) { canonical = v.cp.rootHash; break; }
if (canonical && canonical !== cp.rootHash) {
const first = s.cps.get(`cp|${sk}|${canonical}`);
if (first) {
const [l, r] = orderCps(cpView(first.cp), cpView(cp));
this._violation('same-size-split-view', cp.logId, { treeSize: dispSize(cp.treeSize), first: l, second: r },
[{ method: 'checkpointsAreSameSizeDifferentRoot', args: [l, r], expect: true }]);
}
}
if (s.cps.put(`cp|${sk}|${cp.rootHash}`, { cp })) this.stats.evictions += 1;
this._retry(s);
}
_find(s, size, root) {
const sk = size.toString();
if (root) return s.cps.get(`cp|${sk}|${root}`) || null;
let f = null;
for (const [k, v] of s.cps) if (k.startsWith(`cp|${sk}|`)) { if (f) return null; f = v; }
return f;
}
_consistency(e) {
if (!e.logId) throw new Error('missing logId');
const p = {
logId: e.logId,
firstSize: asSize(e.firstSize ?? e.first?.treeSize, 'firstSize'),
secondSize: asSize(e.secondSize ?? e.second?.treeSize, 'secondSize'),
firstRoot: (e.firstRoot ?? e.first?.rootHash) != null ? asBuf(e.firstRoot ?? e.first.rootHash).toString('hex') : null,
secondRoot: (e.secondRoot ?? e.second?.rootHash) != null ? asBuf(e.secondRoot ?? e.second.rootHash).toString('hex') : null,
proof: (e.proof || []).map((x) => asBuf(x).toString('hex')),
};
const s = this._log(p.logId);
const dk = `cs|${p.firstSize}|${p.firstRoot}|${p.secondSize}|${p.secondRoot}|${p.proof.join(',')}`;
if (s.dedup.has(dk)) { this.stats.duplicates += 1; s.dedup.put(dk, 1); return; }
s.dedup.put(dk, 1);
this.stats.consistency += 1;
const res = this._resolve(s, p);
if (!res) { s.pending.put(dk, { type: 'cs', p }); this.stats.pending += 1; return; }
this._evalCons(s, p, res);
}
_resolve(s, p) {
const f = this._find(s, p.firstSize, p.firstRoot);
const b = this._find(s, p.secondSize, p.secondRoot);
if (!f || !b) return null;
return { first: f.cp, second: b.cp, fh: p.firstRoot || f.cp.rootHash, sh: p.secondRoot || b.cp.rootHash };
}
_evalCons(s, p, res) {
const ok = verifyConsistency(p.firstSize, p.secondSize, p.proof, res.fh, res.sh);
const evidence = { first: cpView(res.first), second: cpView(res.second), proof: p.proof };
if (ok) { if (s.verified.put(`v|${p.firstSize}|${res.fh}|${p.secondSize}|${res.sh}`, { a: p.firstSize, ar: res.fh, b: p.secondSize, br: res.sh })) this.stats.evictions += 1; this.emit('consistent', evidence); return; }
if (p.firstSize === p.secondSize && res.fh !== res.sh) {
const [l, r] = orderCps(evidence.first, evidence.second);
return this._violation('same-size-split-view', s.id, { treeSize: dispSize(p.firstSize), first: l, second: r },
[{ method: 'checkpointsAreSameSizeDifferentRoot', args: [l, r], expect: true }]);
}
if (this.o.trustProofs) this._violation('inconsistent-consistency-proof', s.id, evidence,
[{ method: 'ct.verifyConsistencyProof', args: [dispSize(p.firstSize), dispSize(p.secondSize), p.proof, res.fh, res.sh], expect: false }]);
else this._incident('unverified-consistency-proof', s.id, evidence);
}
_inclusion(e) {
if (!e.logId) throw new Error('missing logId');
const leafHash = e.leafHash ? asBuf(e.leafHash, 'leafHash').toString('hex') : e.leaf != null ? LF(e.leaf).toString('hex') : null;
if (!leafHash) throw new Error('missing leafHash/leaf');
const p = { logId: e.logId, leafHash, leafIndex: asSize(e.leafIndex ?? e.index, 'leafIndex'), treeSize: asSize(e.treeSize ?? e.size, 'treeSize'), rootHash: e.rootHash || e.root ? asBuf(e.rootHash || e.root).toString('hex') : null, proof: (e.proof || []).map((x) => asBuf(x).toString('hex')) };
const s = this._log(p.logId);
const dk = `i|${p.leafIndex}|${p.treeSize}|${p.rootHash}|${p.leafHash}|${p.proof.join(',')}`;
if (s.dedup.has(dk)) { this.stats.duplicates += 1; s.dedup.put(dk, 1); return; }
s.dedup.put(dk, 1);
this.stats.inclusion += 1;
const cp = this._find(s, p.treeSize, p.rootHash);
if (!cp) { s.pending.put(dk, { type: 'i', p }); this.stats.pending += 1; return; }
this._evalIncl(s, p, cp.cp);
}
_evalIncl(s, p, cp) {
if (!verifyInclusion(p.leafIndex, p.treeSize, p.proof, p.leafHash, cp.rootHash)) return this._incident('invalid-inclusion-proof', s.id, { checkpoint: cpView(cp), leafIndex: dispSize(p.leafIndex), treeSize: dispSize(p.treeSize), leafHash: p.leafHash, proof: p.proof });
const key = p.leafIndex.toString();
const cur = s.leaves.get(key) || { byHash: new Map() };
for (const [h, other] of cur.byHash) {
if (h === p.leafHash) continue;
const full = [
{ leafIndex: other.leafIndex, treeSize: other.treeSize, rootHash: other.rootHash, checkpoint: cpView(other.cp), leafHash: other.leafHash, inclusionProof: other.proof },
{ leafIndex: p.leafIndex, treeSize: p.treeSize, rootHash: cp.rootHash, checkpoint: cpView(cp), leafHash: p.leafHash, inclusionProof: p.proof },
];
const [l, r] = full[0].leafHash <= full[1].leafHash ? full : [full[1], full[0]];
const ev = (c) => ({ checkpoint: c.checkpoint, leafHash: c.leafHash, inclusionProof: c.inclusionProof });
const chk = (c) => ({ method: 'ct.verifyInclusionProof', args: [dispSize(c.leafIndex), dispSize(c.treeSize), c.inclusionProof, c.leafHash, c.rootHash], expect: true });
this._violation('leaf-value-conflict', s.id, { leafIndex: dispSize(p.leafIndex), first: ev(l), second: ev(r) }, [chk(l), chk(r)]);
}
cur.byHash.set(p.leafHash, { leafIndex: p.leafIndex, leafHash: p.leafHash, treeSize: p.treeSize, rootHash: cp.rootHash, cp, proof: p.proof });
if (s.leaves.put(key, cur)) this.stats.evictions += 1;
}
_retry(s) {
const ready = [];
for (const [k, v] of s.pending) {
if (v.type === 'cs') { const r = this._resolve(s, v.p); if (r) ready.push({ k, v, r }); }
else { const cp = this._find(s, v.p.treeSize, v.p.rootHash); if (cp) ready.push({ k, v, cp: cp.cp }); }
}
for (const x of ready) { s.pending.delete(x.k); if (x.v.type === 'cs') this._evalCons(s, x.v.p, x.r); else this._evalIncl(s, x.v.p, x.cp); }
}
unresolved(id) {
const out = [];
for (const [logId, s] of this.logs) {
if (id && id !== logId) continue;
const cps = [...s.cps.values()].map((v) => v.cp).sort((a, b) => (a.treeSize < b.treeSize ? -1 : a.treeSize > b.treeSize ? 1 : 0));
for (let i = 1; i < cps.length; i += 1) {
const a = cps[i - 1], b = cps[i];
if (a.treeSize === b.treeSize) continue;
const linked = [...s.verified.values()].some((e) => e.a === a.treeSize && e.b === b.treeSize && e.ar === a.rootHash && e.br === b.rootHash);
if (!linked) out.push({ logId, first: cpView(a), second: cpView(b), reason: 'no verified consistency proof' });
}
for (const v of s.pending.values()) out.push({ logId, reason: 'proof awaits its checkpoint(s)', pending: v.type });
}
return out;
}
_violation(subtype, logId, evidence, reproduce) {
const id = idOf({ subtype, logId, evidence });
if (this._vIds.has(id)) return;
this._vIds.add(id);
const v = { id, type: 'equivocation', subtype, logId, severity: 'confirmed', detectedAt: this.o.clock(), evidence, reproduce };
this.violations.push(v); this.emit('violation', v);
}
_incident(subtype, logId, evidence) {
const id = idOf({ subtype, logId, evidence });
if (this._iIds.has(id)) return;
this._iIds.add(id);
const i = { id, type: 'incident', subtype, logId, severity: 'suspicious', detectedAt: this.o.clock(), evidence };
this.incidents.push(i); this.emit('incident', i);
}
}
function checkpointBytes(cp) { const lp = (b) => { const l = Buffer.alloc(4); l.writeUInt32BE(b.length); return Buffer.concat([l, b]); }; const sz = Buffer.alloc(8); sz.writeBigUInt64BE(BigInt(cp.treeSize)); const ts = Buffer.alloc(8); ts.writeBigUInt64BE(BigInt(cp.timestamp || 0)); return Buffer.concat([lp(Buffer.from(cp.logId, 'utf8')), lp(sz), lp(asBuf(cp.rootHash)), lp(ts)]); }
function defaultVerify(cp, key) { const k = key || cp.publicKey; if (!cp.signature || !k) return false; try { return crypto.verify(null, checkpointBytes(cp), k, asBuf(cp.signature)); } catch { return false; } }
/* Machine replay of the emitted proof objects. */
const registry = {
checkpointsAreSameSizeDifferentRoot: (a, b) => String(a.treeSize) === String(b.treeSize) && a.rootHash !== b.rootHash,
'ct.verifyConsistencyProof': verifyConsistency,
'ct.verifyInclusionProof': verifyInclusion,
};
function verifyViolation(v) {
if (!v || !Array.isArray(v.reproduce) || !v.reproduce.length) return false;
return v.reproduce.every((c) => { const fn = registry[c.method]; return !!fn && fn(...(c.args || [])) === (c.expect === undefined ? true : c.expect); });
}
module.exports = { Auditor, verifyViolation, merkle: { LF, H, EMPTY, mth, inclProof, consProof, verifyInclusion, verifyConsistency, rootFromInclusion }, checkpointBytes, defaultVerify };
const { Auditor, verifyViolation } = require('./ct-auditor.compact');
const a = new Auditor({ publicKeys: { 'log.example': publicKey } }); // optional Ed25519
a.on('violation', (v) => {
console.log(v.subtype, v.id, verifyViolation(v) ? 'replay OK' : 'replay FAILED');
});
a.ingest({ type: 'checkpoint', logId: 'log.example', treeSize: 1234, rootHash: 'ab…' });
a.ingest({ type: 'consistency', logId: 'log.example', firstSize: 1000, firstRoot: '…',
secondSize: 1234, secondRoot: '…', proof: ['…', '…'] });
a.ingest({ type: 'inclusion', logId: 'log.example', leafIndex: 7, treeSize: 1234,
rootHash: '…', leafHash: '…', proof: ['…'] });
a.ingest({ type: 'checkpoint', logId: 'log.example', treeSize: 1234, rootHash: 'DIFFERENT' });
console.log(a.report());
console.log(a.unresolved()); // missing evidence, NOT violations
console.log(a.stateSize()); // bounded-state inspection
What it emits. Confirmed violations carry a canonical id and an executable reproduce array:
{
"id": "f3b59fa3254b710550137ee24d275511",
"type": "equivocation",
"subtype": "same-size-split-view",
"logId": "log-forked",
"severity": "confirmed",
"evidence": {
"treeSize": 8,
"first": { "treeSize": 8, "rootHash": "aff5…", "signature": "d1ce…" },
"second": { "treeSize": 8, "rootHash": "c02a…", "signature": "3c59…" }
},
"reproduce": [{
"method": "checkpointsAreSameSizeDifferentRoot",
"args": [ { "treeSize": 8, "rootHash": "aff5…" }, { "treeSize": 8, "rootHash": "c02a…" } ],
"expect": true
}]
}
Subtypes: same-size-split-view, leaf-value-conflict, inconsistent-consistency-proof (confirmed); invalid-signature, invalid-inclusion-proof, unverified-consistency-proof, malformed-event, unknown-event-type (suspicious incidents).
The full modular project (RFC transcripts, tests, demo, generated SOLUTION.md) is in /workspace/ct-auditor. Run:
cd /workspace/ct-auditor
node --test # 35 tests
node --test test/compact.test.js # the single-file version
node demo.js # multi-feed end-to-end demo
$ node --test
# tests 35
# pass 35
# fail 0
$ node demo.js
[violation] log-forked same-size-split-view id=f3b59fa3254b710550137ee24d275511
[violation] log-forked inconsistent-consistency-proof id=7b7d4c63fdbfc6de680529c7b1db6a0c
[violation] log-forked same-size-split-view id=168d370719cc7eed754fc8a9b0dab0de
[violation] log-forked leaf-value-conflict id=19123951d5bb193e5933ba6f42498aba
duplicates : 2
violations : 4
unresolved : 2 (missing evidence, not violations)
same-size-split-view replay=OK
inconsistent-consistency-proof replay=OK
same-size-split-view replay=OK
leaf-value-conflict replay=OK
0 < m < n ≤ 33; proof shapes match RFC 9162 §2.1.5 exactly; wrong leaf/index/root are rejected; 500 random 32-byte "proofs" never verify.stats.duplicates increments, no new violations); a consistency proof that arrives before its checkpoints is buffered and resolved on arrival, then leaves no unresolved entry.unresolved record, never a violation.maxCheckpointsPerLog: 10, live retention is ≤ 12 (10 + pinned anchor + head) and evictions > 0.verifyViolation; tampering with a proof argument makes replay fail.| Property | Mechanism |
|---|---|
| Order tolerance | unknown-endpoint proofs buffered in a bounded pending window; checkpoint insert triggers retry; idempotent dedup keys |
| Soundness | violation only for same-size different roots, two verifying same-index/different-leaf inclusion proofs, or a failing consistency proof under trustProofOrigin |
| Bounded state | per-log LRU windows (maxCheckpointsPerLog, maxLeafCommitmentsPerLog, maxPendingProofsPerLog, maxDedupPerLog) + pinned anchor/head; stateSize() exposes counts |
| Determinism | evidence pairs canonicalised before hashing ⇒ stable violation ids |
| Reproducibility | reproduce: [{method,args,expect}] replayed by verifyViolation with no auditor state |
Known limit (by design, not hidden): with a finite maxCheckpointsPerLog, a same-size conflict whose first root has already been evicted and is not the pinned anchor/head cannot be proven. This is the unavoidable trade-off of bounded state; raise the cap or pin more sizes if you need a wider window. trustProofOrigin: false correctly downgrades a failed consistency proof to a suspicious incident instead of a confirmed fork.
# Evidence - Problem class: js-streaming-certificate-transparency-equivocation-audit-20261011 - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-10-12T04:16:52.940Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Build a streaming auditor that merges multiple Certificate Transparency log feeds, validates Merkle inclusion and consistency proofs, and detects equivocation despite duplicated, delayed, or reordered checkpoints. It must retain bounded state, distinguish missing evidence from a proven fork, and emit minimal reproducible proof objects for every confirmed violation.", "environment": "node20", "language": "js", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "js-streaming-certificate-transparency-equivocation-audit-20261011", "provider": "openrouter", "solved_at": "2026-10-12T04:16:52.943Z", "version": "20"}