diff --git a/ui/src/lib/PowVerifyOverlay.svelte b/ui/src/lib/PowVerifyOverlay.svelte new file mode 100644 index 0000000..ccecc97 --- /dev/null +++ b/ui/src/lib/PowVerifyOverlay.svelte @@ -0,0 +1,1433 @@ + + + + + + + diff --git a/ui/src/lib/SharePowPage.svelte b/ui/src/lib/SharePowPage.svelte index c65c632..4e4001e 100644 --- a/ui/src/lib/SharePowPage.svelte +++ b/ui/src/lib/SharePowPage.svelte @@ -1,8 +1,10 @@ @@ -698,6 +295,20 @@ share hash. No trust in this dashboard required.

+
+ This is what turns a claim into proof. + A screenshot of a big difficulty number proves nothing — anyone can edit one, and + no one can tell from a picture whether the work behind it was ever done. These 80 bytes + can't be faked that way. They are the exact preimage of the share hash, so anyone can + hash them and see for themselves both that the hash is genuine and that finding it took + the work claimed. The header also commits to the + previous block, fixing when the work happened, and + through the merkle root to the coinbase + transaction, fixing who it pays — so the same proof can't be lifted from another + miner's share or replayed from a different block. Publish this, and the claim stands on + its own. +
+
{#each fields as f (f.key)}{f.raw}{/each} @@ -712,190 +323,17 @@ type="button" class="verify-btn" class:ok={allOk} - disabled={animating} - onclick={startVerify} + onclick={() => (showOverlay = true)} > - {animating ? "Verifying…" : verified ? (allOk ? "Verified ✓" : "Verification failed") : "Verify in this browser"} + {verified ? (allOk ? "Verified \u2713 — watch again" : "Verification failed — replay") : "Verify in this browser"} - replays the full verification — merkle proof, padding, every SHA-256 stage - — from the real computation, locally + takes over the screen and replays the whole verification — merkle proof, + padding, every SHA-256 round — from the real computation, locally
- {#if animating && step} -
-
- - - -
- - {#if chapter} -
- {chapter.n}/{chapter.of} - {chapter.title} -
- - {/if} - - {#if step.kind === "chapter"} -
{step.ch.title}
- {:else if step.kind === "merkle-init"} -
SHA-256d(coinbase transaction) → its txid:
-
{step.txid}
-
internal byte order, as used while hashing
- {:else if step.kind === "merkle-fold"} -
- Fold level {step.i}/{step.total} — concatenate with branch hash, SHA-256d again: -
-
- current - {step.left} - branch {step.i} - {step.right} - → new - {step.result} -
-
- each branch hash summarizes every other transaction in its subtree -
- {:else if step.kind === "merkle-done"} -
Merkle root reproduced:
-
{step.root}
-
- {step.matches - ? "= the merkle root committed in the header ✓" - : "≠ the header's merkle root!"} -
- {:else if step.kind === "padding"} -
- 80 header bytes + 0x80 marker + 39 zero bytes + - 640-bit length = 128 bytes: -
-
-
-
message block 1 · 512 bits
-
- {#each padBlock1 as s, i (i)}{s.hex}{/each} -
-
-
-
message block 2 · 512 bits
-
- {#each padBlock2 as s, i (i)}{s.hex}{/each} -
-
-
-
- note the merkle root straddling the block - boundary — SHA-256 processes 64 bytes at a time regardless of field layout -
- {:else if step.kind === "init-h"} -
Eight 32-bit registers start from the SHA-256 constants:
-
- {#each INIT_H as v, i (i)} -
- H{i} - {v} -
- {/each} -
-
- the fractional parts of the square roots of the first eight primes — "nothing up - my sleeve" numbers -
- {:else if step.kind === "schedule"} -
- Block {step.block}/{step.blocks} becomes sixteen 32-bit words w0…w15: -
-
- {#each step.words as w, i (i)} -
w{i}{w}
- {/each} -
-
- rounds 17–64 consume words expanded from these (σ0/σ1 mixing) -
- {:else if step.kind === "round"} -
-
-
-
- block {step.block}/{step.blocks} · compression round - {String(step.round).padStart(2, "0")}/64 · - W[t]={step.wt} K[t]={step.kt} -
-
- {#each step.regs as r, i (i)} -
- {"abcdefgh"[i]} - {r} -
- {/each} -
-
- T1 = h + Σ1(e) + Ch(e,f,g) + K[t] + W[t]  ·  T2 = Σ0(a) + Maj(a,b,c) -  →  a = T1+T2, e = d+T1, rest shift down -
- {:else if step.kind === "add-h"} -
- 64 rounds done — the result is added into the running state - (Davies–Meyer), chaining block {step.block} forward: -
-
- {#each step.state as v, i (i)} -
- H{i} - {v} -
- {/each} -
- {:else if step.kind === "digest"} -
{step.label}:
-
{step.hex}
- {:else if step.kind === "reverse"} -
{digest2Hex}
- -
- {#each finalHashChars as ch}{ch.c}{/each} -
- {:else if step.kind === "target"} -
-
- your hash - - {#each finalHashChars as ch}{ch.c}{/each} - -
-
- share target - {shareTargetHex} -
-
- network target - {netTargetHex} -
-
-
- hash < share target — that's what made it a - {formatDifficulty(pow.sdiff)} share. The network target is another - {targetGapX >= 1000 ? formatDifficulty(targetGapX) : targetGapX.toFixed(1)}× - lower: beat it, and this header would have been block #{pow.height.toLocaleString()}. -
- {/if} -
- {/if} - - {#if verified && !animating} + {#if verified}
Computed block header hash
@@ -904,12 +342,12 @@
{finalHash === pow.hash ? "= the recorded share hash — proof of work is genuine" - : "≠ the recorded share hash!"} + : "\u2260 the recorded share hash!"}
    {#each checks as c (c.name)}
  • - {c.ok ? "✓" : "✗"} + {c.ok ? "\u2713" : "\u2717"} {c.name} {c.detail} @@ -917,7 +355,6 @@
  • {/each}
-
{/if} @@ -997,6 +434,15 @@ {/if} +{#if showOverlay && pow} + (showOverlay = false)} + oncomplete={onVerifyComplete} + /> +{/if} +