@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
262 lines (261 loc) • 9.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReceiptVerifier = exports.MerkleLogVerifier = void 0;
exports.createReceiptVerifier = createReceiptVerifier;
const crypto_1 = require("crypto");
/**
* Merkle log verifier implementation
*/
class MerkleLogVerifier {
/**
* Verify a receipt's inclusion proof against a log root
*/
static async verifyInclusion(receipt, logRoot) {
try {
// Validate receipt format
if (!receipt.inclusionProof || !Array.isArray(receipt.inclusionProof)) {
return {
valid: false,
error: "Invalid inclusion proof format",
};
}
if (receipt.logIndex < 0 || receipt.logIndex >= logRoot.size) {
return {
valid: false,
error: `Log index ${receipt.logIndex} out of bounds for log size ${logRoot.size}`,
};
}
// Compute leaf hash from receipt content
const leafHash = await this.computeLeafHash(receipt);
// Verify inclusion proof
const computedRoot = await this.computeRootFromProof(leafHash, receipt.logIndex, receipt.inclusionProof);
const isValid = computedRoot === logRoot.root;
return {
valid: isValid,
error: isValid ? undefined : "Inclusion proof verification failed",
logRoot,
};
}
catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : "Verification failed",
};
}
}
/**
* Compute leaf hash from receipt content
*/
static async computeLeafHash(receipt) {
// Create canonical representation of receipt for hashing
const canonical = {
ref: receipt.ref,
contentHash: receipt.contentHash,
action: receipt.action,
ts: receipt.ts,
};
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(canonical));
const hashBuffer = await crypto_1.webcrypto.subtle.digest("SHA-256", data);
const hashArray = new Uint8Array(hashBuffer);
return Array.from(hashArray)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
/**
* Compute root hash from inclusion proof
*/
static async computeRootFromProof(leafHash, leafIndex, proof) {
let currentHash = leafHash;
let currentIndex = leafIndex;
for (const proofHash of proof) {
// Determine if current node is left or right child
const isLeftChild = currentIndex % 2 === 0;
if (isLeftChild) {
// Current node is left child, proof hash is right sibling
currentHash = await this.hashPair(currentHash, proofHash);
}
else {
// Current node is right child, proof hash is left sibling
currentHash = await this.hashPair(proofHash, currentHash);
}
// Move up to parent level
currentIndex = Math.floor(currentIndex / 2);
}
return currentHash;
}
/**
* Hash a pair of nodes
*/
static async hashPair(left, right) {
const encoder = new TextEncoder();
const data = encoder.encode(left + right);
const hashBuffer = await crypto_1.webcrypto.subtle.digest("SHA-256", data);
const hashArray = new Uint8Array(hashBuffer);
return Array.from(hashArray)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
/**
* Verify consistency between two log roots
*/
static async verifyConsistency(oldRoot, newRoot, consistencyProof) {
try {
if (oldRoot.size > newRoot.size) {
return {
valid: false,
error: "Old log size cannot be greater than new log size",
};
}
if (oldRoot.size === newRoot.size) {
// Same size, roots should be identical
return {
valid: oldRoot.root === newRoot.root,
error: oldRoot.root === newRoot.root
? undefined
: "Roots don't match for same size logs",
};
}
// TODO: Implement full consistency proof verification
// This is a simplified version for demonstration
const isValid = consistencyProof.length > 0 && oldRoot.timestamp <= newRoot.timestamp;
return {
valid: isValid,
error: isValid ? undefined : "Consistency proof verification failed",
logRoot: newRoot,
};
}
catch (error) {
return {
valid: false,
error: error instanceof Error
? error.message
: "Consistency verification failed",
};
}
}
}
exports.MerkleLogVerifier = MerkleLogVerifier;
/**
* Receipt verifier with policy controls
*/
class ReceiptVerifier {
options;
constructor(options = {}) {
this.options = options;
}
/**
* Verify a receipt against KTA log
*/
async verify(receipt) {
try {
// Skip verification if disabled
if (!this.options.enabled) {
return {
valid: true,
error: "Receipt verification disabled",
};
}
// Fetch current log root from KTA
const logRoot = await this.fetchLogRoot();
if (!logRoot) {
return {
valid: false,
error: "Failed to fetch log root from KTA",
};
}
// Verify inclusion proof
return await MerkleLogVerifier.verifyInclusion(receipt, logRoot);
}
catch (error) {
return {
valid: false,
error: error instanceof Error
? error.message
: "Receipt verification failed",
};
}
}
/**
* Batch verify multiple receipts
*/
async verifyBatch(receipts) {
const results = [];
// Fetch log root once for all receipts
const logRoot = this.options.enabled ? await this.fetchLogRoot() : null;
for (const receipt of receipts) {
if (!this.options.enabled) {
results.push({
valid: true,
error: "Receipt verification disabled",
});
continue;
}
if (!logRoot) {
results.push({
valid: false,
error: "Failed to fetch log root from KTA",
});
continue;
}
try {
const result = await MerkleLogVerifier.verifyInclusion(receipt, logRoot);
results.push(result);
}
catch (error) {
results.push({
valid: false,
error: error instanceof Error
? error.message
: "Receipt verification failed",
});
}
}
return results;
}
/**
* Fetch current log root from KTA
*/
async fetchLogRoot() {
try {
// TODO: Replace with actual KTA API call
// This is a placeholder implementation
const ktaUrl = this.options.ktaBaseUrl || "https://knowthat.ai";
const response = await fetch(`${ktaUrl}/api/log/root`);
if (!response.ok) {
throw new Error(`KTA API error: ${response.status}`);
}
const data = await response.json();
return {
root: data.root,
size: data.size,
timestamp: data.timestamp,
};
}
catch (error) {
// In case of network error, return mock data for testing
if (this.options.allowMockData) {
return {
root: Array.from({ length: 64 }, () => Math.floor(Math.random() * 16).toString(16)).join(""),
size: 10000,
timestamp: Math.floor(Date.now() / 1000),
};
}
console.warn("Failed to fetch log root from KTA:", error);
return null;
}
}
}
exports.ReceiptVerifier = ReceiptVerifier;
/**
* Create receipt verifier instance
*/
function createReceiptVerifier(options) {
return new ReceiptVerifier({
enabled: true,
ktaBaseUrl: "https://knowthat.ai",
allowMockData: false,
cacheLogRootTtl: 300, // 5 minutes
...options,
});
}