Skip to content

Instantly share code, notes, and snippets.

@bczhc
Created February 10, 2026 06:13
Show Gist options
  • Select an option

  • Save bczhc/c3ca95891f8b5acda3527a0f0373ac4e to your computer and use it in GitHub Desktop.

Select an option

Save bczhc/c3ca95891f8b5acda3527a0f0373ac4e to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>WebGPU SHA256 Miner Simulator</title>
<style>
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #121212; color: #e0e0e0; padding: 20px; }
.container { max-width: 1000px; margin: 0 auto; background: #1e1e1e; padding: 30px; border-radius: 12px; border: 1px solid #333; }
h2 { color: #00f2ff; margin-top: 0; font-weight: 300; letter-spacing: 1px; }
.controls { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 25px; }
.control-group { display: flex; flex-direction: column; }
label { margin-bottom: 6px; font-size: 0.85rem; color: #888; text-transform: uppercase; }
input { padding: 10px; background: #2a2a2a; border: 1px solid #444; color: #fff; border-radius: 6px; outline: none; transition: border 0.3s; }
input:focus { border-color: #00f2ff; }
.full-width { grid-column: span 2; }
button {
grid-column: span 2; padding: 15px; background: #00f2ff; border: none; color: #000;
font-weight: bold; border-radius: 6px; cursor: pointer; font-size: 1rem; margin-top: 10px;
}
button:hover { background: #00d1db; }
button:disabled { background: #444; color: #888; cursor: not-allowed; }
#log {
background: #080808; color: #00ff41; padding: 20px; border-radius: 8px;
height: 400px; overflow-y: auto; font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.95rem; font-weight: 600; line-height: 1.5; border: 1px solid #222; white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="container">
<h2>GPU SHA256 MINER SIMULATOR</h2>
<div class="controls">
<div class="control-group"><label>Workgroup Size</label><input type="number" id="workgroup_size" value="256"></div>
<div class="control-group"><label>Dispatch X</label><input type="number" id="dispatch_x" value="2048"></div>
<div class="control-group"><label>Iterations per thread</label><input type="number" id="iterations" value="64"></div>
<div class="control-group"><label>Difficulty (bits)</label><input type="number" id="difficulty" value="24"></div>
<div class="control-group full-width"><label>Start Hex Data (32 bytes)</label><input type="text" id="start_hex" placeholder="0000000000000000000000000000000000000000000000000000000000000000"></div>
<button id="start_btn">START MINING</button>
</div>
<div id="log">READY...</div>
</div>
<script>
// --- JS 原生 SHA256 实现 (用于最终验证输出) ---
async function jsSha256(uint8Array) {
const hashBuffer = await crypto.subtle.digest('SHA-256', uint8Array);
return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
}
const logEl = document.getElementById('log');
function log(msg, color) {
const span = document.createElement('div');
if(color) span.style.color = color;
span.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
logEl.appendChild(span);
logEl.scrollTop = logEl.scrollHeight;
}
// 严格对应 Rust 的 add_big_int
function addBigInt(data, n) {
let carry = BigInt(n);
for (let i = 0; i < data.length; i++) {
if (carry === 0n) break;
let sum = BigInt(data[i]) + (carry & 0xFFFFFFFFn);
data[i] = Number(sum & 0xFFn);
carry = sum >> 8n;
}
}
function generateCheckDifficultyWgsl(difficultyBits) {
let conditions = [];
const fullBytes = Math.floor(difficultyBits / 8);
for (let i = 0; i < fullBytes; i++) conditions.push(`buf[${i}] == 0u`);
const remainingBits = difficultyBits % 8;
if (remainingBits > 0) {
const shift = 8 - remainingBits;
conditions.push(`(buf[${fullBytes}] >> ${shift}u) == 0u`);
}
return `fn check_difficulty(buf: ptr<function, array<u32, 32>>) -> bool { return ${conditions.length === 0 ? "true" : conditions.join(" && ")}; }`;
}
async function start() {
if (!navigator.gpu) { log("ERROR: WebGPU NOT SUPPORTED", "#ff4444"); return; }
const args = {
workgroupSize: parseInt(document.getElementById('workgroup_size').value),
dispatchX: parseInt(document.getElementById('dispatch_x').value),
iterations: parseInt(document.getElementById('iterations').value),
difficulty: parseInt(document.getElementById('difficulty').value),
startHex: document.getElementById('start_hex').value.trim()
};
const adapter = await navigator.gpu.requestAdapter({
featureLevel: 'compatibility',
});
const device = await adapter.requestDevice();
let shaderText;
try {
// const resp = await fetch('./sha256-miner.wgsl');
// shaderText = await resp.text();
shaderText = SHADER_TEXT;
} catch (e) {
log("ERROR: ./sha256-miner.wgsl not found!", "#ff4444");
return;
}
const shaderLines = shaderText.split('\n');
shaderLines.shift(); // 移除第一行占位
const finalShaderSource = generateCheckDifficultyWgsl(args.difficulty) + "\n" + shaderLines.join('\n');
const shaderModule = device.createShaderModule({ code: finalShaderSource });
const pipeline = await device.createComputePipelineAsync({
layout: 'auto',
compute: {
module: shaderModule,
entryPoint: 'main',
constants: {
WORKGROUP_SIZE: args.workgroupSize,
ITERATIONS_PER_THREAD: args.iterations,
RUNS_PER_DISPATCH: args.dispatchX * args.workgroupSize,
DIFFICULTY_BITS: args.difficulty
}
}
});
const inputBuffer = device.createBuffer({ size: 128, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
const resultBuffer = device.createBuffer({ size: 128, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC });
const mapReadBuffer = device.createBuffer({ size: 128, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [{ binding: 0, resource: { buffer: inputBuffer } }, { binding: 1, resource: { buffer: resultBuffer } }]
});
let inputData = new Uint8Array(32);
if (args.startHex) {
const hexMatch = args.startHex.match(/[0-9a-fA-F]{2}/g) || [];
hexMatch.forEach((byte, i) => { if(i < 32) inputData[i] = parseInt(byte, 16); });
}
let counter = 0;
let hashes = 0n;
const runsPerDispatch = args.dispatchX * args.workgroupSize;
const startTime = performance.now();
document.getElementById('start_btn').disabled = true;
while (true) {
const elapsedSec = (performance.now() - startTime) / 1000;
const hashrate = elapsedSec > 0 ? (hashes / BigInt(Math.max(1, Math.floor(elapsedSec)))) : 0n;
const hexInput = Array.from(inputData).map(b => b.toString(16).padStart(2, '0')).join('');
log(`DISPATCH: ${counter} | HASHES: ${hashes.toLocaleString()} | RATE: ${hashrate.toLocaleString()} H/s`);
// 写入 Buffer (u32 视角)
const u32View = new Uint32Array(32);
for(let i=0; i<32; i++) u32View[i] = inputData[i];
device.queue.writeBuffer(inputBuffer, 0, u32View);
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(args.dispatchX, 1, 1);
pass.end();
encoder.copyBufferToBuffer(resultBuffer, 0, mapReadBuffer, 0, 128);
device.queue.submit([encoder.finish()]);
const hashesComputed = BigInt(runsPerDispatch) * BigInt(args.iterations);
hashes += hashesComputed;
await mapReadBuffer.mapAsync(GPUMapMode.READ);
const resultRaw = new Uint32Array(mapReadBuffer.getMappedRange());
let foundIndex = -1;
for (let i = 0; i < 32; i++) { if (resultRaw[i] !== 0) { foundIndex = i; break; } }
if (foundIndex !== -1) {
const foundInputU32 = new Uint32Array(resultRaw);
mapReadBuffer.unmap();
// 转换回 Uint8 以便 JS SHA256 验证
const foundInputU8 = new Uint8Array(32);
for(let i=0; i<32; i++) foundInputU8[i] = foundInputU32[i];
const foundHex = Array.from(foundInputU8).map(b => b.toString(16).padStart(2, '0')).join('');
const verifiedHash = await jsSha256(foundInputU8);
log(`\n🎉 MATCH FOUND!`, "#00ff41");
log(` > Input (Hex): ${foundHex}`, "#ffffff");
log(` > Hash (SHA256): ${verifiedHash}`, "#00f2ff");
log(` > Time: ${((performance.now() - startTime)/1000).toFixed(4)}s`, "#888");
document.getElementById('start_btn').disabled = false;
break;
}
mapReadBuffer.unmap();
addBigInt(inputData, Number(hashesComputed));
counter++;
await new Promise(r => requestAnimationFrame(r));
}
}
document.getElementById('start_btn').onclick = start;
const SHADER_TEXT = `fn check_difficulty(buf: ptr<function, array<u32, SHA256_BLOCK_SIZE>>) -> bool { /* stub */; return true; }
override WORKGROUP_SIZE = 0u;
override ITERATIONS_PER_THREAD = 0u;
override RUNS_PER_DISPATCH = 0u;
override DIFFICULTY_BITS = 0u;
struct SHA256_CTX {
data : array<u32, 64>,
datalen : u32,
bitlen : array<u32, 2>,
state : array<u32, 8>,
info : u32,
};
@group(0) @binding(0) var<storage, read> start : array<u32>;
@group(0) @binding(1) var<storage, read_write> result : array<u32>;
const SHA256_BLOCK_SIZE = 32;
const INPUT_SIZE = 32;
const k = array<u32, 64> (
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
);
fn ROTLEFT(a : u32, b : u32) -> u32{return (((a) << (b)) | ((a) >> (32-(b))));}
fn ROTRIGHT(a : u32, b : u32) -> u32{return (((a) >> (b)) | ((a) << (32-(b))));}
fn CH(x : u32, y : u32, z : u32) -> u32{return (((x) & (y)) ^ (~(x) & (z)));}
fn MAJ(x : u32, y : u32, z : u32) -> u32{return (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)));}
fn EP0(x : u32) -> u32{return (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22));}
fn EP1(x : u32) -> u32{return (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25));}
fn SIG0(x : u32) -> u32{return (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3));}
fn SIG1(x : u32) -> u32{return (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10));}
fn sha256_transform(ctx : ptr<function, SHA256_CTX>)
{
var a : u32;
var b : u32;
var c : u32;
var d : u32;
var e : u32;
var f : u32;
var g : u32;
var h : u32;
var i : u32 = 0;
var j : u32 = 0;
var t1 : u32;
var t2 : u32;
var m : array<u32, 64> ;
while(i < 16) {
m[i] = ((*ctx).data[j] << 24) | ((*ctx).data[j + 1] << 16) | ((*ctx).data[j + 2] << 8) | ((*ctx).data[j + 3]);
i++;
j += 4;
}
while(i < 64) {
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
i++;
}
a = (*ctx).state[0];
b = (*ctx).state[1];
c = (*ctx).state[2];
d = (*ctx).state[3];
e = (*ctx).state[4];
f = (*ctx).state[5];
g = (*ctx).state[6];
h = (*ctx).state[7];
i = 0;
for (; i < 64; i++) {
t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
t2 = EP0(a) + MAJ(a,b,c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
(*ctx).state[0] += a;
(*ctx).state[1] += b;
(*ctx).state[2] += c;
(*ctx).state[3] += d;
(*ctx).state[4] += e;
(*ctx).state[5] += f;
(*ctx).state[6] += g;
(*ctx).state[7] += h;
}
fn sha256_update(ctx : ptr<function, SHA256_CTX>, input_data: ptr<function, array<u32, INPUT_SIZE>>, len : u32)
{
for (var i :u32 = 0; i < len; i++) {
(*ctx).data[(*ctx).datalen] = input_data[i];
(*ctx).datalen++;
if ((*ctx).datalen == 64) {
sha256_transform(ctx);
if ((*ctx).bitlen[0] > 0xffffffff - (512)){
(*ctx).bitlen[1]++;
}
(*ctx).bitlen[0] += 512;
(*ctx).datalen = 0;
}
}
}
fn sha256_final(ctx : ptr<function, SHA256_CTX>, hash: ptr<function, array<u32, SHA256_BLOCK_SIZE>> )
{
var i : u32 = (*ctx).datalen;
if ((*ctx).datalen < 56) {
(*ctx).data[i] = 0x80;
i++;
while (i < 56){
(*ctx).data[i] = 0x00;
i++;
}
}
else {
(*ctx).data[i] = 0x80;
i++;
while (i < 64){
(*ctx).data[i] = 0x00;
i++;
}
sha256_transform(ctx);
for (var i = 0; i < 56 ; i++) {
(*ctx).data[i] = 0;
}
}
if ((*ctx).bitlen[0] > 0xffffffff - (*ctx).datalen * 8) {
(*ctx).bitlen[1]++;
}
(*ctx).bitlen[0] += (*ctx).datalen * 8;
(*ctx).data[63] = (*ctx).bitlen[0];
(*ctx).data[62] = (*ctx).bitlen[0] >> 8;
(*ctx).data[61] = (*ctx).bitlen[0] >> 16;
(*ctx).data[60] = (*ctx).bitlen[0] >> 24;
(*ctx).data[59] = (*ctx).bitlen[1];
(*ctx).data[58] = (*ctx).bitlen[1] >> 8;
(*ctx).data[57] = (*ctx).bitlen[1] >> 16;
(*ctx).data[56] = (*ctx).bitlen[1] >> 24;
sha256_transform(ctx);
for (i = 0; i < 4; i++) {
(*hash)[i] = ((*ctx).state[0] >> (24 - i * 8)) & 0x000000ff;
(*hash)[i + 4] = ((*ctx).state[1] >> (24 - i * 8)) & 0x000000ff;
(*hash)[i + 8] = ((*ctx).state[2] >> (24 - i * 8)) & 0x000000ff;
(*hash)[i + 12] = ((*ctx).state[3] >> (24 - i * 8)) & 0x000000ff;
(*hash)[i + 16] = ((*ctx).state[4] >> (24 - i * 8)) & 0x000000ff;
(*hash)[i + 20] = ((*ctx).state[5] >> (24 - i * 8)) & 0x000000ff;
(*hash)[i + 24] = ((*ctx).state[6] >> (24 - i * 8)) & 0x000000ff;
(*hash)[i + 28] = ((*ctx).state[7] >> (24 - i * 8)) & 0x000000ff;
}
}
fn sha256_init(ctx : ptr<function, SHA256_CTX>) {
// CTX INIT
(*ctx).datalen = 0;
(*ctx).bitlen[0] = 0;
(*ctx).bitlen[1] = 0;
(*ctx).state[0] = 0x6a09e667;
(*ctx).state[1] = 0xbb67ae85;
(*ctx).state[2] = 0x3c6ef372;
(*ctx).state[3] = 0xa54ff53a;
(*ctx).state[4] = 0x510e527f;
(*ctx).state[5] = 0x9b05688c;
(*ctx).state[6] = 0x1f83d9ab;
(*ctx).state[7] = 0x5be0cd19;
}
fn set_local_input_with_offset(p_data: ptr<function, array<u32, SHA256_BLOCK_SIZE>>, n: u32) {
var carry = n;
for (var i = 0u; i < 32u; i++) {
let sum = start[i] + carry;
(*p_data)[i] = sum & 255u;
carry = sum >> 8u;
}
}
@compute @workgroup_size(WORKGROUP_SIZE)
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
let _r = result[0];
for (var i = 0u; i < ITERATIONS_PER_THREAD; i += 1) {
let addition = i * RUNS_PER_DISPATCH + global_id.x;
var this_input: array<u32, SHA256_BLOCK_SIZE>;
set_local_input_with_offset(&this_input, addition);
var ctx : SHA256_CTX;
sha256_init(&ctx);
var buf : array<u32, SHA256_BLOCK_SIZE>;
sha256_update(&ctx, &this_input, INPUT_SIZE);
sha256_final(&ctx, &buf);
if check_difficulty(&buf) {
for (var j = 0u; j < 32; j += 1) {
// result[j] = buf[j];
result[j] = this_input[j];
}
}
}
}
`;
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment