#!/usr/bin/env node 'use strict'; // Compute9 Evidence v0.1: offline simulation evidence only. Node.js >=22. const crypto = require('node:crypto'); const fs = require('node:fs'); const VERSION = '0.1.0'; const SCHEMA = 'compute9.evidence.v0.1'; const ZERO = '0'.repeat(64); const MAX_BYTES = 2 * 1024 * 1024; const MAX_EVENTS = 1024; const HEX = /^[a-f0-9]{64}$/; const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/; function fail(code) { throw new Error(code); } function object(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) && [Object.prototype, null].includes(Object.getPrototypeOf(value)); } function keys(value, expected) { if (!object(value)) fail('invalid_object'); const found = Object.keys(value).sort(); if (found.length !== expected.length || found.some((k, i) => k !== [...expected].sort()[i])) fail('unexpected_or_missing_fields'); } // Restricted canonical-json-v1: sorted object keys, safe integers and JSON values. // This is not a general RFC 8785/JCS implementation. function canonical(value) { if (value === null || typeof value === 'boolean' || typeof value === 'string') return JSON.stringify(value); if (typeof value === 'number') { if (!Number.isSafeInteger(value) || Object.is(value, -0)) fail('unsafe_number'); return String(value); } if (Array.isArray(value)) return '[' + value.map(canonical).join(',') + ']'; if (!object(value)) fail('unsupported_json_value'); return '{' + Object.keys(value).sort().map(k => JSON.stringify(k) + ':' + canonical(value[k])).join(',') + '}'; } function hashRecord(record) { return crypto.createHash('sha256').update('GPU9D/Compute9 record v0.1\n' + canonical(record)).digest('hex'); } function checkpointMessage(bundle) { return Buffer.from('GPU9D/Compute9 checkpoint v0.1\n' + canonical({ schema_version: bundle.schema_version, test_mode: bundle.test_mode, run_id: bundle.run_id, event_count: bundle.checkpoint.event_count, head_hash: bundle.checkpoint.head_hash })); } function publicKey(pem) { if (typeof pem !== 'string' || pem.length > 512 || !pem.startsWith('-----BEGIN PUBLIC KEY-----\n')) fail('invalid_public_key'); let key; try { key = crypto.createPublicKey(pem); } catch { fail('invalid_public_key'); } if (key.asymmetricKeyType !== 'ed25519') fail('unsupported_key_type'); return key; } function nonnegative(value) { return Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0); } function validateRecord(record, runId, sequence, previousHash, previousTime, state) { keys(record, ['run_id', 'sequence', 'occurred_at', 'previous_hash', 'kind', 'test_mode', 'metrics']); if (record.run_id !== runId || record.sequence !== sequence || record.previous_hash !== previousHash) fail('broken_chain'); if (record.test_mode !== 'simulation') fail('unsupported_test_mode'); if (typeof record.occurred_at !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(record.occurred_at)) fail('invalid_timestamp'); const time = Date.parse(record.occurred_at); if (!Number.isFinite(time) || new Date(time).toISOString() !== record.occurred_at || time < previousTime) fail('invalid_timestamp'); const next = { initial: ['test_started'], test_started: ['test_completed', 'test_interrupted', 'test_failed'], test_interrupted: ['test_resumed', 'test_failed'], test_resumed: ['test_completed', 'test_interrupted', 'test_failed'], test_completed: [], test_failed: [] }; if (!next[state].includes(record.kind)) fail('invalid_event_transition'); keys(record.metrics, ['duration_ms', 'operation_count']); if (!nonnegative(record.metrics.duration_ms) || !nonnegative(record.metrics.operation_count)) fail('invalid_metrics'); return time; } function verifyBundle(bundle, options = {}) { keys(bundle, ['schema_version', 'test_mode', 'run_id', 'public_key_pem', 'events', 'checkpoint']); if (bundle.schema_version !== SCHEMA || bundle.test_mode !== 'simulation') fail('unsupported_schema_or_mode'); if (typeof bundle.run_id !== 'string' || !UUID.test(bundle.run_id)) fail('invalid_run_id'); if (!Array.isArray(bundle.events) || !bundle.events.length || bundle.events.length > MAX_EVENTS) fail('invalid_event_count'); keys(bundle.checkpoint, ['event_count', 'head_hash', 'signature_algorithm', 'signature']); if (bundle.checkpoint.event_count !== bundle.events.length || !HEX.test(bundle.checkpoint.head_hash)) fail('checkpoint_mismatch'); if (bundle.checkpoint.signature_algorithm !== 'ed25519' || typeof bundle.checkpoint.signature !== 'string' || !/^[A-Za-z0-9+/]{86}==$/.test(bundle.checkpoint.signature)) fail('invalid_signature_encoding'); let previous = ZERO, time = -Infinity, state = 'initial'; for (const [i, event] of bundle.events.entries()) { keys(event, ['record', 'hash']); time = validateRecord(event.record, bundle.run_id, i, previous, time, state); const computed = hashRecord(event.record); if (event.hash !== computed) fail('record_hash_mismatch'); previous = computed; state = event.record.kind; } if (previous !== bundle.checkpoint.head_hash) fail('checkpoint_mismatch'); const key = publicKey(bundle.public_key_pem); if (!crypto.verify(null, checkpointMessage(bundle), key, Buffer.from(bundle.checkpoint.signature, 'base64'))) fail('invalid_checkpoint_signature'); let pinned = false; if (options.trustedKey !== undefined) { const expected = publicKey(options.trustedKey).export({type:'spki', format:'der'}); const actual = key.export({type:'spki', format:'der'}); if (expected.length !== actual.length || !crypto.timingSafeEqual(expected, actual)) fail('trusted_key_mismatch'); pinned = true; } if ((options.expectedHead === undefined) !== (options.expectedCount === undefined)) fail('expected_checkpoint_requires_head_and_count'); const anchored = options.expectedHead !== undefined; if (anchored && (!HEX.test(options.expectedHead) || !nonnegative(options.expectedCount) || options.expectedHead !== previous || options.expectedCount !== bundle.events.length)) fail('expected_checkpoint_mismatch'); return { tool_version: VERSION, schema_version: SCHEMA, integrity_valid: true, checkpoint_signature_valid: true, trust_status: pinned ? 'key_matches_explicit_pin' : 'untrusted_embedded_key', expected_checkpoint_matches: anchored ? true : null, history_completeness: anchored ? 'matches_supplied_checkpoint_only' : 'bundle_only_not_full_history', run_id: bundle.run_id, event_count: bundle.events.length, head_hash: previous, final_state: state, test_mode: 'simulation', gpu_test_performed: false, performance_verified: false, provider_identity_verified: false, evidence_grade_assigned: false }; } function makeDemo(keyPair = crypto.generateKeyPairSync('ed25519')) { const runId = crypto.randomUUID(), now = new Date().toISOString(); let previous = ZERO; const events = ['test_started', 'test_interrupted', 'test_resumed', 'test_completed'].map((kind, sequence) => { const record = {run_id:runId, sequence, occurred_at:now, previous_hash:previous, kind, test_mode:'simulation', metrics:{duration_ms:0, operation_count:0}}; previous = hashRecord(record); return {record, hash:previous}; }); const bundle = {schema_version:SCHEMA, test_mode:'simulation', run_id:runId, public_key_pem:keyPair.publicKey.export({type:'spki', format:'pem'}), events, checkpoint:{event_count:events.length, head_hash:previous, signature_algorithm:'ed25519', signature:''}}; bundle.checkpoint.signature = crypto.sign(null, checkpointMessage(bundle), keyPair.privateKey).toString('base64'); verifyBundle(bundle); return bundle; } function readLimited(file, limit = MAX_BYTES) { const info = fs.statSync(file); if (!info.isFile() || info.size > limit) fail('input_size_limit'); const data = fs.readFileSync(file); if (data.length > limit) fail('input_size_limit'); return data.toString('utf8'); } function main(argv) { const [command, ...rest] = argv; if (command === '--version') { if(rest.length) fail('invalid_arguments'); console.log(VERSION); return; } if (!command || command === '--help' || command === 'help') { console.log('Compute9 Evidence v' + VERSION + ' — offline simulation only\n' + 'node compute9-evidence.cjs demo > demo.json\n' + 'node compute9-evidence.cjs verify --file demo.json [--trusted-key-file public.pem] [--expected-head SHA256 --expected-count N]\n' + 'No GPU workload, telemetry upload, private-key export, or external connection is performed.\n' + 'A self-signed bundle does not prove provider identity, performance, or full history.'); return; } if (command === 'demo') { if(rest.length) fail('invalid_arguments'); console.log(JSON.stringify(makeDemo(), null, 2)); return; } if (command !== 'verify' || rest.length % 2) fail('invalid_arguments'); const args = {}; for (let i = 0; i < rest.length; i += 2) { const name = rest[i], value = rest[i + 1]; if (!['--file','--trusted-key-file','--expected-head','--expected-count'].includes(name) || Object.hasOwn(args, name) || !value || value.startsWith('--')) fail('invalid_arguments'); args[name] = value; } if (!args['--file']) fail('file_required'); const options = {}; if (args['--trusted-key-file']) options.trustedKey = readLimited(args['--trusted-key-file'], 512); if (args['--expected-head']) options.expectedHead = args['--expected-head']; if (args['--expected-count'] !== undefined) { if (!/^(0|[1-9][0-9]*)$/.test(args['--expected-count'])) fail('invalid_expected_count'); options.expectedCount = Number(args['--expected-count']); } console.log(JSON.stringify(verifyBundle(JSON.parse(readLimited(args['--file'])), options), null, 2)); } if (require.main === module) { try { main(process.argv.slice(2)); } catch (error) { const code = /^[a-z_]+$/.test(error.message) ? error.message : 'invalid_or_unreadable_input'; console.error(JSON.stringify({valid:false, error:code})); process.exitCode = 1; } } module.exports = {VERSION, SCHEMA, ZERO, canonical, hashRecord, checkpointMessage, makeDemo, verifyBundle, main};