Below is a toy block. Pick a difficulty — how many leading zeros the hash must have — then press Mine the block. The computer counts up through nonces, hashing each one, until it finds a hash that clears the bar.
<p class="hint">Mining is a lottery. Fix the block data, then search for a <b>nonce</b> (a counter) so the block's hash starts with enough leading zeros. Verifying a winning nonce is one hash; <b>finding</b> one means trying on average 16^difficulty of them.</p>
<label class="row">Block data
<input id="data" type="text" value="KIPU block #1" />
</label>
<label class="row">Difficulty (leading zeros)
<input id="diff" type="range" min="1" max="5" value="2" />
<span id="diffv" class="pill">2</span>
</label>
<div class="btns">
<button id="mine" type="button">Mine the block</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="status" id="status">Pick a difficulty, then mine.</div>
<div class="hash" id="hash"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.row { display: flex; align-items: center; gap: .6rem; font-size: .9rem; font-weight: 600;
color: #1d3557; margin: .5rem 0; flex-wrap: wrap; }
input[type=text] { font: 500 14px ui-monospace, monospace; padding: .4rem .6rem;
border: 1px solid #cdd9e3; border-radius: 8px; flex: 1; min-width: 140px; }
input[type=range] { flex: 1; min-width: 120px; }
.pill { display: inline-flex; min-width: 1.6em; justify-content: center; background: #e8eef3;
color: #1d3557; border: 1px solid #cdd9e3; border-radius: 6px; padding: .1rem .4rem; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin: .7rem 0 .4rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .55; cursor: default; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.work { color: #b5651d; }
.hash { font: 600 14px ui-monospace, monospace; word-break: break-all; margin-top: .3rem;
background: #f3f5f8; border: 1px solid #e1e7ee; border-radius: 8px; padding: .6rem; min-height: 1.2em; }
.hash b { color: #0a7d33; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// A toy proof-of-work. Real Bitcoin uses double SHA-256; here we use a small deterministic hash so it runs in the sandbox with no imports. The IDEA is identical: a hash is easy to compute and easy to verify, but to make it start with D leading zero hex digits you must keep trying nonces — on average 16^D attempts. Raising the difficulty by one multiplies the expected work by 16.
// 32-bit FNV-1a style mix, rendered as 8 hex digits. Avalanche-like: tiny input changes scatter the output, so the only way to a "lucky" hash is to search.
function toyHash(str){
let h = 0x811c9dc5 >>> 0;
for(let i=0;i<str.length;i++){
h ^= str.charCodeAt(i);
h = Math.imul(h, 0x01000193) >>> 0;
}
// extra scramble so leading digits look random
h ^= h >>> 15; h = Math.imul(h, 0x2c1b3c6d) >>> 0;
h ^= h >>> 12; h = Math.imul(h, 0x297a2d39) >>> 0;
h ^= h >>> 15;
return (h >>> 0).toString(16).padStart(8,'0');
}
const $ = id => document.getElementById(id);
function setStatus(t,cls){ const s=$('status'); s.textContent=t; s.className='status '+cls; }
$('diff').addEventListener('input', ()=>{ $('diffv').textContent=$('diff').value; });
function mine(){
const data = $('data').value;
const diff = +$('diff').value;
const target = '0'.repeat(diff);
setStatus('Mining… searching for a winning nonce.','work');
$('mine').disabled = true; $('hash').innerHTML='';
// Defer so the "Mining…" message paints before the loop runs.
setTimeout(()=>{
let nonce = 0, hash = '';
const CAP = 8000000; // safety bound
while(nonce < CAP){
hash = toyHash(data + '|' + nonce);
if(hash.startsWith(target)) break;
nonce++;
}
$('mine').disabled = false;
if(hash.startsWith(target)){
$('hash').innerHTML = '<b>'+hash.slice(0,diff)+'</b>'+hash.slice(diff);
setStatus(fmt('Mined! nonce=%nonce% found after %nonce%%v% tries (expected ~%pow%).', { nonce: nonce, v: 1, pow: Math.pow(16,diff) }),'ok');
console.log('nonce',nonce,'hash',hash,'tries',nonce+1,'expected',Math.pow(16,diff));
} else {
setStatus('No nonce under the cap — raise the cap or lower difficulty.','work');
}
}, 30);
}
$('mine').addEventListener('click', mine);
$('reset').addEventListener('click', ()=>{
$('data').value='KIPU block #1'; $('diff').value=2; $('diffv').textContent='2';
$('hash').innerHTML=''; setStatus('Pick a difficulty, then mine.','');
});
<p class="hint">Mining is a lottery. Fix the block data, then search for a <b>nonce</b> (a counter) so the block's hash starts with enough leading zeros. Verifying a winning nonce is one hash; <b>finding</b> one means trying on average 16^difficulty of them.</p>
<label class="row">Block data
<input id="data" type="text" value="KIPU block #1" />
</label>
<label class="row">Difficulty (leading zeros)
<input id="diff" type="range" min="1" max="5" value="2" />
<span id="diffv" class="pill">2</span>
</label>
<div class="btns">
<button id="mine" type="button">Mine the block</button>
<button id="reset" type="button" class="ghost">Reset</button>
</div>
<div class="status" id="status">Pick a difficulty, then mine.</div>
<div class="hash" id="hash"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.row { display: flex; align-items: center; gap: .6rem; font-size: .9rem; font-weight: 600;
color: #1d3557; margin: .5rem 0; flex-wrap: wrap; }
input[type=text] { font: 500 14px ui-monospace, monospace; padding: .4rem .6rem;
border: 1px solid #cdd9e3; border-radius: 8px; flex: 1; min-width: 140px; }
input[type=range] { flex: 1; min-width: 120px; }
.pill { display: inline-flex; min-width: 1.6em; justify-content: center; background: #e8eef3;
color: #1d3557; border: 1px solid #cdd9e3; border-radius: 6px; padding: .1rem .4rem; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; margin: .7rem 0 .4rem; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .55; cursor: default; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.work { color: #b5651d; }
.hash { font: 600 14px ui-monospace, monospace; word-break: break-all; margin-top: .3rem;
background: #f3f5f8; border: 1px solid #e1e7ee; border-radius: 8px; padding: .6rem; min-height: 1.2em; }
.hash b { color: #0a7d33; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// A toy proof-of-work. Real Bitcoin uses double SHA-256; here we use a small deterministic hash so it runs in the sandbox with no imports. The IDEA is identical: a hash is easy to compute and easy to verify, but to make it start with D leading zero hex digits you must keep trying nonces — on average 16^D attempts. Raising the difficulty by one multiplies the expected work by 16.
// 32-bit FNV-1a style mix, rendered as 8 hex digits. Avalanche-like: tiny input changes scatter the output, so the only way to a "lucky" hash is to search.
function toyHash(str){
let h = 0x811c9dc5 >>> 0;
for(let i=0;i<str.length;i++){
h ^= str.charCodeAt(i);
h = Math.imul(h, 0x01000193) >>> 0;
}
// extra scramble so leading digits look random
h ^= h >>> 15; h = Math.imul(h, 0x2c1b3c6d) >>> 0;
h ^= h >>> 12; h = Math.imul(h, 0x297a2d39) >>> 0;
h ^= h >>> 15;
return (h >>> 0).toString(16).padStart(8,'0');
}
const $ = id => document.getElementById(id);
function setStatus(t,cls){ const s=$('status'); s.textContent=t; s.className='status '+cls; }
$('diff').addEventListener('input', ()=>{ $('diffv').textContent=$('diff').value; });
function mine(){
const data = $('data').value;
const diff = +$('diff').value;
const target = '0'.repeat(diff);
setStatus('Mining… searching for a winning nonce.','work');
$('mine').disabled = true; $('hash').innerHTML='';
// Defer so the "Mining…" message paints before the loop runs.
setTimeout(()=>{
let nonce = 0, hash = '';
const CAP = 8000000; // safety bound
while(nonce < CAP){
hash = toyHash(data + '|' + nonce);
if(hash.startsWith(target)) break;
nonce++;
}
$('mine').disabled = false;
if(hash.startsWith(target)){
$('hash').innerHTML = '<b>'+hash.slice(0,diff)+'</b>'+hash.slice(diff);
setStatus(fmt('Mined! nonce=%nonce% found after %nonce%%v% tries (expected ~%pow%).', { nonce: nonce, v: 1, pow: Math.pow(16,diff) }),'ok');
console.log('nonce',nonce,'hash',hash,'tries',nonce+1,'expected',Math.pow(16,diff));
} else {
setStatus('No nonce under the cap — raise the cap or lower difficulty.','work');
}
}, 30);
}
$('mine').addEventListener('click', mine);
$('reset').addEventListener('click', ()=>{
$('data').value='KIPU block #1'; $('diff').value=2; $('diffv').textContent='2';
$('hash').innerHTML=''; setStatus('Pick a difficulty, then mine.','');
});
Watch the asymmetry. Checking the winning nonce is one hash: you can confirm it in an instant. Finding it took the machine many tries — and each extra zero of difficulty multiplies the expected work by 16, because a hex hash starts with a zero only one time in sixteen. Real Bitcoin tunes this knob so that, no matter how much hardware joins, a block still takes about ten minutes.
Comments
Loading comments...