aere-quantum/market-settlement/sarcina.mjs

56 lines
4.3 KiB
JavaScript

// sarcina.mjs: the REPLAY workload of the market flow (roadmap point 28): a deterministic task anyone can recompute, so a challenger
// can dispute a wrong result with the hash it recomputed and an arbiter (or any reader) can see who is right. keccak-chain: start from a
// 32-byte seed and apply keccak256 `iters` times. The spec is public (its keccak256 is the job's specHash); the result is the JSON
// bytes { task, seed, iters, digest } whose keccak256 is the job's resultHash. Only node:crypto: keccak-256 is the dependency-free
// BigInt implementation of the Cloud gateway (cloud-gateway/gateway.mjs), copied here so the public component has no package to install;
// its positive control is the recorded testnet evidence itself (resultHash computed on chain = the replay here, proba-judecata.mjs S1/S3).
import crypto from 'node:crypto';
// ----- keccak-256 (FIPS 202 permutation, keccak padding 0x01..0x80, rate 136) -----
const MASK = (1n << 64n) - 1n;
const RC = [0x0000000000000001n, 0x0000000000008082n, 0x800000000000808an, 0x8000000080008000n, 0x000000000000808bn, 0x0000000080000001n, 0x8000000080008081n, 0x8000000000008009n,
0x000000000000008an, 0x0000000000000088n, 0x0000000080008009n, 0x000000008000000an, 0x000000008000808bn, 0x800000000000008bn, 0x8000000000008089n, 0x8000000000008003n,
0x8000000000008002n, 0x8000000000000080n, 0x000000000000800an, 0x800000008000000an, 0x8000000080008081n, 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n];
const ROT = [[0, 36, 3, 41, 18], [1, 44, 10, 45, 2], [62, 6, 43, 15, 61], [28, 55, 25, 21, 56], [27, 20, 39, 8, 14]];
const rotl = (v, n) => (n === 0 ? v : (((v << BigInt(n)) | (v >> BigInt(64 - n))) & MASK));
function keccakF(A) {
for (let rr = 0; rr < 24; rr++) {
const C = new Array(5), D = new Array(5);
for (let x = 0; x < 5; x++) C[x] = A[x] ^ A[x + 5] ^ A[x + 10] ^ A[x + 15] ^ A[x + 20];
for (let x = 0; x < 5; x++) D[x] = C[(x + 4) % 5] ^ rotl(C[(x + 1) % 5], 1);
for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) A[x + 5 * y] ^= D[x];
const B = new Array(25);
for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) B[y + 5 * ((2 * x + 3 * y) % 5)] = rotl(A[x + 5 * y], ROT[x][y]);
for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) A[x + 5 * y] = B[x + 5 * y] ^ ((~B[((x + 1) % 5) + 5 * y] & MASK) & B[((x + 2) % 5) + 5 * y]);
A[0] ^= RC[rr];
}
}
export function keccak256(bytes) {
const rate = 136; const A = new Array(25).fill(0n); const msg = Buffer.from(bytes);
const padded = Buffer.concat([msg, Buffer.alloc(rate - (msg.length % rate))]);
padded[msg.length] |= 0x01; padded[padded.length - 1] |= 0x80;
for (let off = 0; off < padded.length; off += rate) { for (let i = 0; i < rate / 8; i++) A[i] ^= padded.readBigUInt64LE(off + i * 8); keccakF(A); }
const out = Buffer.alloc(32); for (let i = 0; i < 4; i++) out.writeBigUInt64LE(A[i], i * 8); return out;
}
export const TASK = 'keccak-chain';
const hex = (b) => '0x' + Buffer.from(b).toString('hex');
export const keccak = (b) => hex(keccak256(b));
/** Canonical JSON of a spec or a result: keys sorted, no whitespace. */
export const canonical = (x) => JSON.stringify(x, Object.keys(x).sort());
export function specNou({ iters = 20000, seed = hex(crypto.randomBytes(32)) } = {}) { return { task: TASK, seed, iters }; }
export const specHash = (spec) => keccak(Buffer.from(canonical(spec), 'utf8'));
/** Runs the task: the digest after `iters` keccak256 applications to the seed. */
export function ruleaza(spec) {
if (spec.task !== TASK) throw new Error('unknown task ' + spec.task);
if (!/^0x[0-9a-f]{64}$/.test(spec.seed) || !Number.isInteger(spec.iters) || spec.iters < 1 || spec.iters > 5e6) throw new Error('bad spec');
let d = Buffer.from(spec.seed.slice(2), 'hex');
for (let i = 0; i < spec.iters; i++) d = keccak256(d);
return { task: TASK, seed: spec.seed, iters: spec.iters, digest: hex(d) };
}
/** The result bytes a provider submits (and a challenger recomputes), and their on-chain hash. */
export const rezultatOcteti = (r) => Buffer.from(canonical(r), 'utf8');
export const rezultatHash = (r) => keccak(rezultatOcteti(r));
/** The SHA-256 of this program's own source, for the `programSha256` of the execution envelope. */
export const programSha256 = () => '0x' + crypto.createHash('sha256').update(Buffer.from(ruleaza.toString() + keccak.toString(), 'utf8')).digest('hex');