| |
| |
| |
|
|
|
|
| class WORMReceiptChain {
|
| constructor() {
|
| this.receipts = [];
|
| this.chain = [];
|
| this.previousHash = this.genesisHash();
|
| }
|
|
|
| |
| |
|
|
| genesisHash() {
|
| return '0'.repeat(128);
|
| }
|
|
|
| |
| |
|
|
| canonicalForm(receiptData) {
|
| const {
|
| sequenceNumber,
|
| agentId,
|
| capabilityId,
|
| instructionHash,
|
| action,
|
| inputHash,
|
| outputHash,
|
| keyVersion,
|
| signature,
|
| status,
|
| } = receiptData;
|
|
|
|
|
| return [
|
| `seq:${sequenceNumber}`,
|
| `agent:${agentId}`,
|
| `cap:${capabilityId}`,
|
| `instr:${instructionHash}`,
|
| `action:${action}`,
|
| `input:${inputHash}`,
|
| `output:${outputHash}`,
|
| `keyver:${keyVersion}`,
|
| `sig:${signature}`,
|
| `status:${status}`,
|
| ].join('|');
|
| }
|
|
|
| |
| |
|
|
| async computeSHA512(data) {
|
| const encoder = new TextEncoder();
|
| const dataBuffer = encoder.encode(data);
|
| const hashBuffer = await crypto.subtle.digest('SHA-512', dataBuffer);
|
| const hashArray = Array.from(new Uint8Array(hashBuffer));
|
| return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
| }
|
|
|
| |
| |
|
|
| async createReceipt(receiptData) {
|
| const sequenceNumber = this.receipts.length + 1;
|
| const canonical = this.canonicalForm({
|
| sequenceNumber,
|
| ...receiptData,
|
| });
|
|
|
| const receiptHash = await this.computeSHA512(canonical);
|
|
|
| const receipt = {
|
| sequenceNumber,
|
| receiptId: `rcpt-${sequenceNumber.toString().padStart(10, '0')}`,
|
| receiptHash,
|
| previousHash: this.previousHash,
|
| canonical,
|
| timestamp: Math.floor(Date.now() / 1000),
|
| ...receiptData,
|
| };
|
|
|
| this.receipts.push(receipt);
|
| this.previousHash = receiptHash;
|
| this.chain.push({
|
| sequence: sequenceNumber,
|
| hash: receiptHash,
|
| previousHash: receipt.previousHash,
|
| });
|
|
|
| return receipt;
|
| }
|
|
|
| |
| |
|
|
| verifyChainIntegrity() {
|
| for (let i = 0; i < this.receipts.length; i++) {
|
| const receipt = this.receipts[i];
|
|
|
|
|
| const canonical = this.canonicalForm({
|
| sequenceNumber: receipt.sequenceNumber,
|
| agentId: receipt.agentId,
|
| capabilityId: receipt.capabilityId,
|
| instructionHash: receipt.instructionHash,
|
| action: receipt.action,
|
| inputHash: receipt.inputHash,
|
| outputHash: receipt.outputHash,
|
| keyVersion: receipt.keyVersion,
|
| signature: receipt.signature,
|
| status: receipt.status,
|
| });
|
|
|
| if (canonical !== receipt.canonical) {
|
| return {
|
| valid: false,
|
| error: `Receipt ${i}: canonical form mismatch`,
|
| receipt: receipt,
|
| };
|
| }
|
|
|
|
|
| if (i > 0) {
|
| const prevReceipt = this.receipts[i - 1];
|
| if (receipt.previousHash !== prevReceipt.receiptHash) {
|
| return {
|
| valid: false,
|
| error: `Receipt ${i}: previous hash mismatch`,
|
| receipt: receipt,
|
| };
|
| }
|
| } else {
|
|
|
| if (receipt.previousHash !== this.genesisHash()) {
|
| return {
|
| valid: false,
|
| error: `Receipt 0: should link to genesis hash`,
|
| receipt: receipt,
|
| };
|
| }
|
| }
|
| }
|
|
|
| return { valid: true, receiptsVerified: this.receipts.length };
|
| }
|
|
|
| |
| |
|
|
| exportReceipt(index) {
|
| if (index < 0 || index >= this.receipts.length) {
|
| throw new Error('Receipt index out of bounds');
|
| }
|
|
|
| const receipt = this.receipts[index];
|
| return {
|
| receiptId: receipt.receiptId,
|
| sequenceNumber: receipt.sequenceNumber,
|
| receiptHash: receipt.receiptHash,
|
| previousHash: receipt.previousHash,
|
| agentId: receipt.agentId,
|
| capabilityId: receipt.capabilityId,
|
| action: receipt.action,
|
| status: receipt.status,
|
| canonical: receipt.canonical,
|
| timestamp: receipt.timestamp,
|
| };
|
| }
|
|
|
| |
| |
|
|
| exportChain() {
|
| return {
|
| totalReceipts: this.receipts.length,
|
| genesisHash: this.genesisHash(),
|
| receipts: this.receipts.map((r, i) => this.exportReceipt(i)),
|
| chainValid: this.verifyChainIntegrity().valid,
|
| };
|
| }
|
|
|
| |
| |
|
|
| getReceipt(sequenceNumber) {
|
| return this.receipts.find(r => r.sequenceNumber === sequenceNumber);
|
| }
|
|
|
| |
| |
|
|
| getLastReceipt() {
|
| return this.receipts[this.receipts.length - 1];
|
| }
|
|
|
| |
| |
|
|
| clear() {
|
| this.receipts = [];
|
| this.chain = [];
|
| this.previousHash = this.genesisHash();
|
| }
|
|
|
| |
| |
|
|
| getStats() {
|
| return {
|
| totalReceipts: this.receipts.length,
|
| chainValid: this.verifyChainIntegrity().valid,
|
| genesisHash: this.genesisHash(),
|
| currentHeadHash: this.previousHash,
|
| receipts: this.receipts.map(r => ({
|
| sequence: r.sequenceNumber,
|
| receiptId: r.receiptId,
|
| hash: r.receiptHash,
|
| })),
|
| };
|
| }
|
| }
|
|
|
|
|
| if (typeof module !== 'undefined' && module.exports) {
|
| module.exports = WORMReceiptChain;
|
| }
|
|
|