<p class="hint">A zk-STARK encodes a computation as a low-degree polynomial. The prover evaluates it at random points; the verifier checks a few and runs a low-degree test. A cheating prover cannot fake this without the polynomial shooting to a high degree.</p>
<div class="controls">
<button id="btn-honest" type="button">Honest prover</button>
<button id="btn-cheat" type="button" class="ghost">Cheating prover</button>
<button id="btn-verify" type="button" class="ghost">Verify</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<canvas id="chart" width="560" height="240"></canvas>
<div id="status" class="status"></div>
<div id="details" class="details"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; background: #fff; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .75rem; line-height: 1.45; }
.controls { display: flex; gap: .45rem; flex-wrap: wrap; margin-bottom: .65rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
canvas { display: block; width: 100%; max-width: 560px; height: auto;
border: 1px solid #dce3ea; border-radius: 8px; background: #f8fafc; }
.status { font-size: .97rem; font-weight: 600; min-height: 1.5em;
margin: .55rem 0 .3rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.details { font-size: .82rem; color: #555; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// โโ Finite-field arithmetic mod a small prime โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// We work in GF(p) with p = 97 for clarity. A real STARK uses a ~256-bit prime.
const P = 97;
const modP = x => ((x % P) + P) % P;
const addF = (a, b) => modP(a + b);
const mulF = (a, b) => modP(a * b);
// Evaluate a polynomial (array of coefficients, lowest degree first) at x in GF(P).
function evalPolyGF(coeffs, x) {
let result = 0, xp = 1;
for (const c of coeffs) { result = addF(result, mulF(c, xp)); xp = mulF(xp, x); }
return result;
}
// Evaluate polynomial over real numbers (for display only).
function evalPolyReal(coeffs, x) {
let result = 0, xp = 1;
for (const c of coeffs) { result += c * xp; xp *= x; }
return result;
}
// Degree of a polynomial (ignoring leading zeros).
function degree(coeffs) {
let d = coeffs.length - 1;
while (d > 0 && coeffs[d] === 0) d--;
return d;
}
function modpow(base, exp, m) {
let result = 1; base %= m;
while (exp > 0) {
if (exp & 1) result = result * base % m;
base = base * base % m; exp >>= 1;
}
return result;
}
const modInv = (a, p) => modpow(a, p - 2, p);
// Lagrange interpolation in GF(P): recover polynomial coefficients from (xs, ys).
function lagrangeCoeffs(xs, ys) {
const n = xs.length;
const result = new Array(n).fill(0);
for (let i = 0; i < n; i++) {
let basis = [1], denom = 1;
for (let j = 0; j < n; j++) {
if (j === i) continue;
const factor = [modP(-xs[j]), 1]; // (x - xs[j])
const next = new Array(basis.length + 1).fill(0);
for (let a = 0; a < basis.length; a++) {
next[a] = addF(next[a], mulF(basis[a], factor[0]));
next[a+1] = addF(next[a+1], mulF(basis[a], factor[1]));
}
basis = next;
denom = mulF(denom, modP(xs[i] - xs[j]));
}
const scale = mulF(ys[i], modInv(denom, P));
for (let k = 0; k < basis.length; k++) {
result[k] = addF(result[k], mulF(basis[k], scale));
}
}
return result;
}
// โโ Polynomial scenario โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Honest: f(x) = xยณ + 2xยฒ + 3x + 5 (degree 3)
// Real values: f(1)=11, f(2)=27, f(3)=59, f(4)=113
// GF(97) values: f(1)=11, f(2)=27, f(3)=59, f(4)=16 (113 mod 97 = 16)
//
// Cheating: prover claims f(2)=60 instead of 27 (tampered output).
// Real display for cheat: (1,11),(2,60),(3,59),(4,113)
// GF(97) cheat evals: [11, 60, 59, 16]
const HONEST_COEFFS = [5, 3, 2, 1]; // 5 + 3x + 2xยฒ + xยณ
const EVAL_POINTS = [1, 2, 3, 4];
// Real (non-modular) honest values for canvas display
const HONEST_REAL = EVAL_POINTS.map(x => evalPolyReal(HONEST_COEFFS, x)); // [11,27,59,113]
// GF(97) honest evaluations for cryptographic operations
const HONEST_GF = EVAL_POINTS.map(x => evalPolyGF(HONEST_COEFFS, x)); // [11,27,59,16]
// Cheat: tamper f(2) from 27 to 60
const CHEAT_REAL = [11, 60, 59, 113];
const CHEAT_GF = [11, 60, 59, 16];
// โโ Canvas drawing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
// Domain x in [0.5, 4.5], range y in [0, 130]
const toCanX = x => 45 + (x - 0.5) / 4.0 * (W - 55);
const toCanY = y => H - 22 - (y / 130) * (H - 32);
function drawAxes() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = '#f8fafc'; ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = '#dce3ea'; ctx.lineWidth = 1;
for (let y = 0; y <= 130; y += 20) {
const cy = toCanY(y);
ctx.beginPath(); ctx.moveTo(45, cy); ctx.lineTo(W - 8, cy); ctx.stroke();
ctx.fillStyle = '#aaa'; ctx.font = '10px system-ui';
ctx.fillText(y, 2, cy + 4);
}
for (let x = 1; x <= 4; x++) {
const cx = toCanX(x);
ctx.beginPath(); ctx.strokeStyle = '#dce3ea'; ctx.moveTo(cx, 10); ctx.lineTo(cx, H - 22); ctx.stroke();
ctx.fillStyle = '#aaa'; ctx.font = '10px system-ui';
ctx.fillText('x=' + x, cx - 10, H - 8);
}
ctx.strokeStyle = '#999'; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(45, H - 22); ctx.lineTo(W - 8, H - 22); ctx.stroke();
ctx.beginPath(); ctx.moveTo(45, H - 22); ctx.lineTo(45, 10); ctx.stroke();
}
function drawCurveFn(fn, color, dashed) {
ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = 2.2;
ctx.setLineDash(dashed ? [5, 3] : []);
let first = true;
for (let xf = 0.5; xf <= 4.5; xf += 0.04) {
const v = fn(xf);
const cx = toCanX(xf), cy = toCanY(v);
if (cy < 0 || cy > H + 20) { first = true; continue; }
if (first) { ctx.moveTo(cx, cy); first = false; } else ctx.lineTo(cx, cy);
}
ctx.stroke(); ctx.setLineDash([]);
}
function drawPoints(xs, ys, color, showLabel) {
for (let i = 0; i < xs.length; i++) {
const cx = toCanX(xs[i]), cy = toCanY(ys[i]);
ctx.beginPath(); ctx.arc(cx, cy, 5.5, 0, Math.PI * 2);
ctx.fillStyle = color; ctx.fill();
ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
if (showLabel) {
ctx.fillStyle = color; ctx.font = 'bold 11px system-ui';
const label = '(' + xs[i] + ', ' + ys[i] + ')';
const lx = cx + (xs[i] <= 2 ? 8 : -ctx.measureText(label).width - 8);
ctx.fillText(label, lx, cy - 6);
}
}
}
// Real-number Lagrange for display curve
function lagrangeRealFn(xs, ys) {
const n = xs.length;
return xval => {
let sum = 0;
for (let i = 0; i < n; i++) {
let L = 1;
for (let j = 0; j < n; j++) {
if (j !== i) L *= (xval - xs[j]) / (xs[i] - xs[j]);
}
sum += ys[i] * L;
}
return sum;
};
}
// โโ State โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let currentMode = null;
function reset() {
currentMode = null;
drawAxes();
setStatus('', '');
document.getElementById('details').textContent = '';
document.getElementById('btn-verify').disabled = true;
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t; s.className = 'status ' + (cls || '');
}
// โโ Honest prover โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('btn-honest').addEventListener('click', () => {
currentMode = 'honest';
drawAxes();
drawCurveFn(x => evalPolyReal(HONEST_COEFFS, x), '#1d88e5', false);
drawPoints(EVAL_POINTS, HONEST_REAL, '#1d88e5', true);
setStatus('Honest prover: degree-3 polynomial committed at 4 points.', 'info');
document.getElementById('details').textContent =
'f(x) = xยณ + 2xยฒ + 3x + 5 โ a degree-3 polynomial (low degree). The prover evaluates it at x = 1, 2, 3, 4 and sends those values to the verifier. Press Verify to run the low-degree spot-check.';
document.getElementById('btn-verify').disabled = false;
});
// โโ Cheating prover โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('btn-cheat').addEventListener('click', () => {
currentMode = 'cheat';
drawAxes();
// Faint honest curve
drawCurveFn(x => evalPolyReal(HONEST_COEFFS, x), '#aac4e0', true);
// Cheat curve via real Lagrange through tampered points
const cheatFn = lagrangeRealFn(EVAL_POINTS, CHEAT_REAL);
drawCurveFn(cheatFn, '#e63946', false);
drawPoints(EVAL_POINTS, CHEAT_REAL, '#e63946', true);
setStatus('Cheating prover: tampered evaluation at x=2 (27 โ 60).', 'bad');
document.getElementById('details').textContent =
'The cheating prover changed f(2) from 27 to 60. The unique degree-3 polynomial through these tampered points differs from the honest one everywhere else. Press Verify to see if a spot-check at a fresh point (x=5) catches the lie.';
document.getElementById('btn-verify').disabled = false;
});
// โโ Verify (low-degree spot-check) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('btn-verify').addEventListener('click', () => {
if (!currentMode) return;
// Recover polynomial in GF(97) from committed evaluations
const gfEvals = currentMode === 'honest' ? HONEST_GF : CHEAT_GF;
const coeffs = lagrangeCoeffs(EVAL_POINTS, gfEvals);
const deg = degree(coeffs);
// Spot-check at x=5 (not in the original evaluation set)
const predicted = evalPolyGF(coeffs, 5);
const trueHonest = evalPolyGF(HONEST_COEFFS, 5); // f(5) = 195 mod 97 = 1
let verdict, cls, detail;
if (currentMode === 'honest') {
verdict = fmt('โ Degree test: degree = %deg% โค 3. Spot-check at x=5: %predicted% = correct (%trueHonest%). Proof accepted!', { deg: deg, predicted: predicted, trueHonest: trueHonest });
cls = 'ok';
detail = fmt('The proverโs polynomial has degree 3 (low). The spot-check at x=5 matches the honest computation (f(5) = 195 mod 97 = 1). In a real FRI protocol dozens of such checks are done with cryptographic randomness โ each check the prover cannot predict in advance.%predicted%%trueHonest%', { predicted: predicted, trueHonest: trueHonest });
} else {
verdict = fmt('โ Spot-check at x=5: prover predicted %predicted%, honest value is %trueHonest%. Proof REJECTED!', { predicted: predicted, trueHonest: trueHonest });
cls = 'bad';
detail = fmt('The cheating prover tampered with f(2). The recovered polynomial gives a different answer at x=5 (%predicted% โ %trueHonest%). A single random spot-check exposes the fraud. In a real STARK, the prover cannot know which points the verifier will query โ making any tampering detectable with overwhelming probability.', { predicted: predicted, trueHonest: trueHonest });
}
setStatus(verdict, cls);
document.getElementById('details').textContent = detail;
});
document.getElementById('btn-reset').addEventListener('click', reset);
// Init
reset();
<p class="hint">A zk-STARK encodes a computation as a low-degree polynomial. The prover evaluates it at random points; the verifier checks a few and runs a low-degree test. A cheating prover cannot fake this without the polynomial shooting to a high degree.</p>
<div class="controls">
<button id="btn-honest" type="button">Honest prover</button>
<button id="btn-cheat" type="button" class="ghost">Cheating prover</button>
<button id="btn-verify" type="button" class="ghost">Verify</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<canvas id="chart" width="560" height="240"></canvas>
<div id="status" class="status"></div>
<div id="details" class="details"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; background: #fff; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .75rem; line-height: 1.45; }
.controls { display: flex; gap: .45rem; flex-wrap: wrap; margin-bottom: .65rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .85rem;
border: 1px solid #1d3557; background: #1d3557; color: #fff;
border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
canvas { display: block; width: 100%; max-width: 560px; height: auto;
border: 1px solid #dce3ea; border-radius: 8px; background: #f8fafc; }
.status { font-size: .97rem; font-weight: 600; min-height: 1.5em;
margin: .55rem 0 .3rem; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.info { color: #1d3557; }
.details { font-size: .82rem; color: #555; line-height: 1.5; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// โโ Finite-field arithmetic mod a small prime โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// We work in GF(p) with p = 97 for clarity. A real STARK uses a ~256-bit prime.
const P = 97;
const modP = x => ((x % P) + P) % P;
const addF = (a, b) => modP(a + b);
const mulF = (a, b) => modP(a * b);
// Evaluate a polynomial (array of coefficients, lowest degree first) at x in GF(P).
function evalPolyGF(coeffs, x) {
let result = 0, xp = 1;
for (const c of coeffs) { result = addF(result, mulF(c, xp)); xp = mulF(xp, x); }
return result;
}
// Evaluate polynomial over real numbers (for display only).
function evalPolyReal(coeffs, x) {
let result = 0, xp = 1;
for (const c of coeffs) { result += c * xp; xp *= x; }
return result;
}
// Degree of a polynomial (ignoring leading zeros).
function degree(coeffs) {
let d = coeffs.length - 1;
while (d > 0 && coeffs[d] === 0) d--;
return d;
}
function modpow(base, exp, m) {
let result = 1; base %= m;
while (exp > 0) {
if (exp & 1) result = result * base % m;
base = base * base % m; exp >>= 1;
}
return result;
}
const modInv = (a, p) => modpow(a, p - 2, p);
// Lagrange interpolation in GF(P): recover polynomial coefficients from (xs, ys).
function lagrangeCoeffs(xs, ys) {
const n = xs.length;
const result = new Array(n).fill(0);
for (let i = 0; i < n; i++) {
let basis = [1], denom = 1;
for (let j = 0; j < n; j++) {
if (j === i) continue;
const factor = [modP(-xs[j]), 1]; // (x - xs[j])
const next = new Array(basis.length + 1).fill(0);
for (let a = 0; a < basis.length; a++) {
next[a] = addF(next[a], mulF(basis[a], factor[0]));
next[a+1] = addF(next[a+1], mulF(basis[a], factor[1]));
}
basis = next;
denom = mulF(denom, modP(xs[i] - xs[j]));
}
const scale = mulF(ys[i], modInv(denom, P));
for (let k = 0; k < basis.length; k++) {
result[k] = addF(result[k], mulF(basis[k], scale));
}
}
return result;
}
// โโ Polynomial scenario โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Honest: f(x) = xยณ + 2xยฒ + 3x + 5 (degree 3)
// Real values: f(1)=11, f(2)=27, f(3)=59, f(4)=113
// GF(97) values: f(1)=11, f(2)=27, f(3)=59, f(4)=16 (113 mod 97 = 16)
//
// Cheating: prover claims f(2)=60 instead of 27 (tampered output).
// Real display for cheat: (1,11),(2,60),(3,59),(4,113)
// GF(97) cheat evals: [11, 60, 59, 16]
const HONEST_COEFFS = [5, 3, 2, 1]; // 5 + 3x + 2xยฒ + xยณ
const EVAL_POINTS = [1, 2, 3, 4];
// Real (non-modular) honest values for canvas display
const HONEST_REAL = EVAL_POINTS.map(x => evalPolyReal(HONEST_COEFFS, x)); // [11,27,59,113]
// GF(97) honest evaluations for cryptographic operations
const HONEST_GF = EVAL_POINTS.map(x => evalPolyGF(HONEST_COEFFS, x)); // [11,27,59,16]
// Cheat: tamper f(2) from 27 to 60
const CHEAT_REAL = [11, 60, 59, 113];
const CHEAT_GF = [11, 60, 59, 16];
// โโ Canvas drawing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
// Domain x in [0.5, 4.5], range y in [0, 130]
const toCanX = x => 45 + (x - 0.5) / 4.0 * (W - 55);
const toCanY = y => H - 22 - (y / 130) * (H - 32);
function drawAxes() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = '#f8fafc'; ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = '#dce3ea'; ctx.lineWidth = 1;
for (let y = 0; y <= 130; y += 20) {
const cy = toCanY(y);
ctx.beginPath(); ctx.moveTo(45, cy); ctx.lineTo(W - 8, cy); ctx.stroke();
ctx.fillStyle = '#aaa'; ctx.font = '10px system-ui';
ctx.fillText(y, 2, cy + 4);
}
for (let x = 1; x <= 4; x++) {
const cx = toCanX(x);
ctx.beginPath(); ctx.strokeStyle = '#dce3ea'; ctx.moveTo(cx, 10); ctx.lineTo(cx, H - 22); ctx.stroke();
ctx.fillStyle = '#aaa'; ctx.font = '10px system-ui';
ctx.fillText('x=' + x, cx - 10, H - 8);
}
ctx.strokeStyle = '#999'; ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.moveTo(45, H - 22); ctx.lineTo(W - 8, H - 22); ctx.stroke();
ctx.beginPath(); ctx.moveTo(45, H - 22); ctx.lineTo(45, 10); ctx.stroke();
}
function drawCurveFn(fn, color, dashed) {
ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = 2.2;
ctx.setLineDash(dashed ? [5, 3] : []);
let first = true;
for (let xf = 0.5; xf <= 4.5; xf += 0.04) {
const v = fn(xf);
const cx = toCanX(xf), cy = toCanY(v);
if (cy < 0 || cy > H + 20) { first = true; continue; }
if (first) { ctx.moveTo(cx, cy); first = false; } else ctx.lineTo(cx, cy);
}
ctx.stroke(); ctx.setLineDash([]);
}
function drawPoints(xs, ys, color, showLabel) {
for (let i = 0; i < xs.length; i++) {
const cx = toCanX(xs[i]), cy = toCanY(ys[i]);
ctx.beginPath(); ctx.arc(cx, cy, 5.5, 0, Math.PI * 2);
ctx.fillStyle = color; ctx.fill();
ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.5; ctx.stroke();
if (showLabel) {
ctx.fillStyle = color; ctx.font = 'bold 11px system-ui';
const label = '(' + xs[i] + ', ' + ys[i] + ')';
const lx = cx + (xs[i] <= 2 ? 8 : -ctx.measureText(label).width - 8);
ctx.fillText(label, lx, cy - 6);
}
}
}
// Real-number Lagrange for display curve
function lagrangeRealFn(xs, ys) {
const n = xs.length;
return xval => {
let sum = 0;
for (let i = 0; i < n; i++) {
let L = 1;
for (let j = 0; j < n; j++) {
if (j !== i) L *= (xval - xs[j]) / (xs[i] - xs[j]);
}
sum += ys[i] * L;
}
return sum;
};
}
// โโ State โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let currentMode = null;
function reset() {
currentMode = null;
drawAxes();
setStatus('', '');
document.getElementById('details').textContent = '';
document.getElementById('btn-verify').disabled = true;
}
function setStatus(t, cls) {
const s = document.getElementById('status');
s.textContent = t; s.className = 'status ' + (cls || '');
}
// โโ Honest prover โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('btn-honest').addEventListener('click', () => {
currentMode = 'honest';
drawAxes();
drawCurveFn(x => evalPolyReal(HONEST_COEFFS, x), '#1d88e5', false);
drawPoints(EVAL_POINTS, HONEST_REAL, '#1d88e5', true);
setStatus('Honest prover: degree-3 polynomial committed at 4 points.', 'info');
document.getElementById('details').textContent =
'f(x) = xยณ + 2xยฒ + 3x + 5 โ a degree-3 polynomial (low degree). The prover evaluates it at x = 1, 2, 3, 4 and sends those values to the verifier. Press Verify to run the low-degree spot-check.';
document.getElementById('btn-verify').disabled = false;
});
// โโ Cheating prover โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('btn-cheat').addEventListener('click', () => {
currentMode = 'cheat';
drawAxes();
// Faint honest curve
drawCurveFn(x => evalPolyReal(HONEST_COEFFS, x), '#aac4e0', true);
// Cheat curve via real Lagrange through tampered points
const cheatFn = lagrangeRealFn(EVAL_POINTS, CHEAT_REAL);
drawCurveFn(cheatFn, '#e63946', false);
drawPoints(EVAL_POINTS, CHEAT_REAL, '#e63946', true);
setStatus('Cheating prover: tampered evaluation at x=2 (27 โ 60).', 'bad');
document.getElementById('details').textContent =
'The cheating prover changed f(2) from 27 to 60. The unique degree-3 polynomial through these tampered points differs from the honest one everywhere else. Press Verify to see if a spot-check at a fresh point (x=5) catches the lie.';
document.getElementById('btn-verify').disabled = false;
});
// โโ Verify (low-degree spot-check) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
document.getElementById('btn-verify').addEventListener('click', () => {
if (!currentMode) return;
// Recover polynomial in GF(97) from committed evaluations
const gfEvals = currentMode === 'honest' ? HONEST_GF : CHEAT_GF;
const coeffs = lagrangeCoeffs(EVAL_POINTS, gfEvals);
const deg = degree(coeffs);
// Spot-check at x=5 (not in the original evaluation set)
const predicted = evalPolyGF(coeffs, 5);
const trueHonest = evalPolyGF(HONEST_COEFFS, 5); // f(5) = 195 mod 97 = 1
let verdict, cls, detail;
if (currentMode === 'honest') {
verdict = fmt('โ Degree test: degree = %deg% โค 3. Spot-check at x=5: %predicted% = correct (%trueHonest%). Proof accepted!', { deg: deg, predicted: predicted, trueHonest: trueHonest });
cls = 'ok';
detail = fmt('The proverโs polynomial has degree 3 (low). The spot-check at x=5 matches the honest computation (f(5) = 195 mod 97 = 1). In a real FRI protocol dozens of such checks are done with cryptographic randomness โ each check the prover cannot predict in advance.%predicted%%trueHonest%', { predicted: predicted, trueHonest: trueHonest });
} else {
verdict = fmt('โ Spot-check at x=5: prover predicted %predicted%, honest value is %trueHonest%. Proof REJECTED!', { predicted: predicted, trueHonest: trueHonest });
cls = 'bad';
detail = fmt('The cheating prover tampered with f(2). The recovered polynomial gives a different answer at x=5 (%predicted% โ %trueHonest%). A single random spot-check exposes the fraud. In a real STARK, the prover cannot know which points the verifier will query โ making any tampering detectable with overwhelming probability.', { predicted: predicted, trueHonest: trueHonest });
}
setStatus(verdict, cls);
document.getElementById('details').textContent = detail;
});
document.getElementById('btn-reset').addEventListener('click', reset);
// Init
reset();
Comments
Loading comments...