Created
February 10, 2026 06:13
-
-
Save bczhc/c3ca95891f8b5acda3527a0f0373ac4e to your computer and use it in GitHub Desktop.
把 https://github.com/bczhc/webgpu-learn/blob/7c0051ced1774bf62093ff2d79fdefae3d746669/wgpu/src/bin/sha256-miner.rs 使用Gemini转写成html #webgpu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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