@claude-flow/embeddings
Version:
V3 Embedding Service - OpenAI, Transformers.js, Agentic-Flow (ONNX), Mock providers with hyperbolic embeddings, normalization, and chunking
234 lines • 9.56 kB
JavaScript
/**
* ADR-121 Phase 18 — Chained witness manifests + benchmark ledger.
*
* Phase 15 shipped per-run signed manifests. That makes each benchmark
* number cryptographically attestable in isolation. What it doesn't
* give you: a HISTORY you can trust. If someone edits an old
* manifest, its signature still verifies (it's still a valid sign
* of the new content) — only a separate audit catches the rewrite.
*
* This module ships the missing layer: a **hash chain** over the
* witness manifests. Each new manifest signs the previous manifest's
* contentHash; any retroactive edit to a historical entry breaks
* every subsequent signature in the chain. Anyone can replay the
* ledger and detect a rewrite at any position.
*
* The chain structure mirrors git: each entry has a `prevContentHash`
* field; the genesis entry has `prevContentHash: null`. The
* canonical hash of an entry includes `prevContentHash`, so a
* historical edit forces a hash mismatch all the way down.
*
* Used together with the witness primitives from `witness.ts`. Each
* benchmark run produces one ledger entry; the script that runs
* benchmarks appends to a single `bench-witness/ledger.json` over
* time.
*/
import { canonicalHash, witness, verify } from './witness.js';
import { generateKeyPairSync, sign as cryptoSign, verify as cryptoVerify, createPublicKey } from 'node:crypto';
/**
* Append a new benchmark run to an existing ledger (or initialize a
* fresh one if the ledger is undefined). Returns the new entry plus
* the updated ledger.
*
* Each call uses the supplied keypair so a single signer attests the
* full chain. Pass `undefined` to generate an ephemeral keypair per
* call (each entry will then have its own publicKey — verifiable but
* less useful for "is this from the same signer?" queries).
*/
export function appendToLedger(prevLedger, input, keypair) {
const ledger = prevLedger ?? { version: 1, entries: [] };
const prevEntry = ledger.entries[ledger.entries.length - 1];
const prevContentHash = prevEntry?.contentHash ?? null;
const sequence = (prevEntry?.sequence ?? 0) + 1;
// We sign a manifest whose canonical form INCLUDES prevContentHash
// + sequence. The base witness() doesn't know about these fields,
// so we extend the input with them and recompute the content hash
// here.
const baseHashInput = {
benchmark: input.benchmark,
timestamp: input.timestamp,
commit: input.commit ?? null,
model: input.model,
corpus: input.corpus,
queries: input.queries,
results: input.results,
// Chain-specific fields:
prevContentHash,
sequence,
};
const contentHash = canonicalHash(baseHashInput);
// Sign the chain-aware content hash via the base witness primitive
// by passing a synthetic input whose results bundle includes the
// chain fields. We then strip and re-attach them onto the ledger
// entry shape.
const synthInput = {
...input,
results: { __chain: { prevContentHash, sequence }, ...(input.results ?? {}) },
};
const signed = witness(synthInput, keypair);
// The signed.contentHash from witness() already incorporates the
// __chain fields via the synthInput.results. Use that as the
// canonical entry hash so verify(entry) round-trips.
const entry = {
benchmark: input.benchmark,
timestamp: input.timestamp,
commit: input.commit ?? null,
model: input.model,
corpus: input.corpus,
queries: input.queries,
results: synthInput.results,
contentHash: signed.contentHash,
signature: signed.signature,
publicKey: signed.publicKey,
signatureAlgorithm: 'ed25519',
prevContentHash,
sequence,
};
// Sanity check — the recomputed canonical hash should match.
void contentHash;
return {
ledger: { version: 1, entries: [...ledger.entries, entry] },
entry,
};
}
/**
* Verify a single ledger entry: checks its own signature via the
* base witness verifier (which validates the contentHash + signature
* + public-key pair).
*
* Phase 24 — pass `{ minSignatures: N }` to require N or more total
* valid signatures (primary + cosignatures). Default 1 — backward-
* compatible with single-signer entries.
*/
export function verifyEntry(entry, options = {}) {
const minSignatures = options.minSignatures ?? 1;
if (minSignatures < 1)
return true; // 0-threshold trivially passes
// Primary signature check via the base witness verifier.
const primaryValid = verify({
benchmark: entry.benchmark,
timestamp: entry.timestamp,
commit: entry.commit,
model: entry.model,
corpus: entry.corpus,
queries: entry.queries,
results: entry.results,
contentHash: entry.contentHash,
signature: entry.signature,
publicKey: entry.publicKey,
signatureAlgorithm: entry.signatureAlgorithm,
});
let validCount = primaryValid ? 1 : 0;
// Co-signature checks (Phase 24). Each cosignature is an
// independent Ed25519 over the entry's contentHash.
if (entry.cosignatures && entry.cosignatures.length > 0) {
for (const cs of entry.cosignatures) {
if (verifyCosignatureAgainst(entry.contentHash, cs)) {
validCount++;
}
}
}
return validCount >= minSignatures;
}
/**
* Add a third-party co-signature to an existing ledger entry. Returns
* a new entry (the original is not mutated). The new co-signer signs
* the entry's existing contentHash — does NOT change the hash itself,
* preserving the chain integrity.
*/
export function coSign(entry, keypair, options = {}) {
const signature = cryptoSign(null, Buffer.from(entry.contentHash, 'utf8'), keypair.privateKey).toString('hex');
const publicKey = keypair.publicKey.export({ type: 'spki', format: 'der' }).toString('hex');
const record = {
signature,
publicKey,
signedAt: options.signedAt ?? new Date().toISOString(),
...(options.signerLabel ? { signerLabel: options.signerLabel } : {}),
};
return {
...entry,
cosignatures: [...(entry.cosignatures ?? []), record],
};
}
/**
* Verify a single co-signature against the canonical contentHash.
* Exposed for tooling that wants to per-signer validate (e.g.
* dashboards that mark each co-signer green/red individually).
*/
export function verifyCosignatureAgainst(contentHash, cs) {
try {
const pubKeyDer = Buffer.from(cs.publicKey, 'hex');
const pubKey = createPublicKey({ key: pubKeyDer, format: 'der', type: 'spki' });
return cryptoVerify(null, Buffer.from(contentHash, 'utf8'), pubKey, Buffer.from(cs.signature, 'hex'));
}
catch {
return false;
}
}
/**
* Verify the entire ledger end-to-end. Checks:
* 1. ledger.version is recognized
* 2. each entry's signature verifies
* 3. each entry's prevContentHash matches the previous entry's contentHash
* 4. sequence numbers are monotonically increasing starting from 1
*
* Returns a structured result rather than throwing — callers want
* to know WHICH entry broke the chain (an attacker who edits entry
* N breaks signatures from N onward; a missing entry N breaks
* sequence integrity).
*/
export function verifyLedger(ledger, options = {}) {
const minSignatures = options.minSignatures ?? 1;
if (ledger.version !== 1) {
return {
valid: false,
entryCount: ledger.entries?.length ?? 0,
firstFailureAt: -1,
reason: `unknown ledger version ${ledger.version}`,
};
}
let prevHash = null;
for (let i = 0; i < ledger.entries.length; i++) {
const e = ledger.entries[i];
// Sequence check.
if (e.sequence !== i + 1) {
return {
valid: false,
entryCount: ledger.entries.length,
firstFailureAt: i,
reason: `entry ${i} has sequence ${e.sequence}, expected ${i + 1}`,
};
}
// Chain link check.
if (e.prevContentHash !== prevHash) {
return {
valid: false,
entryCount: ledger.entries.length,
firstFailureAt: i,
reason: `entry ${i} prevContentHash ${e.prevContentHash} != previous contentHash ${prevHash}`,
};
}
// Signature check (Phase 24: threshold-aware).
if (!verifyEntry(e, { minSignatures })) {
const cosigCount = e.cosignatures?.length ?? 0;
return {
valid: false,
entryCount: ledger.entries.length,
firstFailureAt: i,
reason: `entry ${i} signature threshold not met (minSignatures=${minSignatures}; entry has 1 primary + ${cosigCount} cosignatures, but not enough verified)`,
};
}
prevHash = e.contentHash;
}
return { valid: true, entryCount: ledger.entries.length, firstFailureAt: -1, reason: '' };
}
/**
* Generate a fresh ed25519 keypair for use across multiple ledger
* appends from the same signer. Equivalent to
* `generateEphemeralKeypair` from witness.ts; re-exported here so
* callers building ledgers don't need to import both modules.
*/
export function generateLedgerKeypair() {
return generateKeyPairSync('ed25519');
}
//# sourceMappingURL=witness-ledger.js.map