Files
miner/crates/engine-gpu/src/mining.wgsl
T
Nikolaus HegerandClaude Fable 5 d55b970ab6 Optimize GPU Poseidon2 mining 4.1x on Apple Silicon (u64 shader, midstate, lazy squeeze)
Three stacked optimizations, each verified bit-exact against the CPU
reference (qp-poseidon-core) by the dual-shader component suite and a
new GPU/CPU parity example:

- mining_u64.wgsl: native 64-bit Goldilocks arithmetic with plonky2-style
  lazy reduction, used automatically when the adapter supports
  SHADER_INT64 (all Apple Silicon, NVIDIA, modern AMD). mining.wgsl
  remains the 32-bit fallback. (+50%)
- Midstate precompute: the first two of five sponge permutations absorb
  the header and high nonce half, both constant per batch; they are now
  computed once on the CPU (pow_core::mining_midstate) and resumed on
  the GPU. Batches are clamped so nonces never carry past 2^256. (+77%)
- Lazy second squeeze: the first squeeze yields the most significant 256
  bits of the hash, which decide hash-vs-target unless exactly equal to
  the target's high half, so the common reject path skips the final
  permutation. (+56%)

Apple M4 throughput: 2.81 -> 11.4 MH/s at the default 1M batch
(criterion large_range_1m/gpu: 355.8ms -> 86.3ms, -75.7%).

Also:
- Fix criterion GPU bench crash: thread-local worker resources are now
  tagged with an engine id so multiple GpuEngines per process never mix
  devices.
- Fix stale end-to-end test harness (6-binding layout) and run the full
  component suite against both shader variants.
- New examples: hashrate, gpu_cpu_parity (25 jobs incl. 2^256-boundary
  crossing), gpu_features.
- Add Apple M5 family GPU tiers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 23:40:12 +08:00

924 lines
33 KiB
WebGPU Shading Language

// Quantus Mining Shader - WGSL Compute Shader for GPU Mining
// Implements Poseidon2 hash function over Goldilocks field
// Goldilocks field constants: p = 2^64 - 2^32 + 1 = 18446744069414584321
// Since WGSL doesn't support 64-bit literals, we work with 32-bit chunks
// p = 0xFFFFFFFF00000001 = [0x00000001, 0xFFFFFFFF] in little-endian u32 pairs
const GOLDILOCKS_PRIME_LOW: u32 = 1u; // Low 32 bits
const GOLDILOCKS_PRIME_HIGH: u32 = 4294967295u; // High 32 bits (2^32 - 1)
// Poseidon2 constants
const WIDTH: u32 = 12u;
const RATE: u32 = 8u;
const EXTERNAL_ROUNDS: u32 = 4u;
const INTERNAL_ROUNDS: u32 = 22u;
// Individual constants for testing
const INTERNAL_CONST_0_LOW: u32 = 2018170979u;
const INTERNAL_CONST_0_HIGH: u32 = 2549578122u;
const INTERNAL_CONST_1_LOW: u32 = 794875120u;
const INTERNAL_CONST_1_HIGH: u32 = 3520249608u;
// Real Poseidon2 constants extracted from qp-poseidon-constants
// Internal round constants (22 values)
const INTERNAL_CONSTANTS: array<array<u32, 2>, 22> = array<array<u32, 2>, 22>(
array<u32, 2>(2018170979u, 2549578122u),
array<u32, 2>(794875120u, 3520249608u),
array<u32, 2>(2677723654u, 1772320679u),
array<u32, 2>(2743438884u, 2849007878u),
array<u32, 2>(518907317u, 693269760u),
array<u32, 2>(293328710u, 1484055617u),
array<u32, 2>(2834138828u, 2315799483u),
array<u32, 2>(1558078501u, 1039128420u),
array<u32, 2>(2266808631u, 966316006u),
array<u32, 2>(3393728842u, 1045622667u),
array<u32, 2>(2245828300u, 2521440415u),
array<u32, 2>(751064958u, 1070374632u),
array<u32, 2>(3490278765u, 2390340773u),
array<u32, 2>(3526960470u, 2224174634u),
array<u32, 2>(639988950u, 4000511088u),
array<u32, 2>(1839350858u, 504240201u),
array<u32, 2>(559852230u, 255489215u),
array<u32, 2>(2713771731u, 453385078u),
array<u32, 2>(1745082278u, 422331096u),
array<u32, 2>(2544763488u, 4141129721u),
array<u32, 2>(2700752774u, 1052996327u),
array<u32, 2>(4063512019u, 1429786100u)
);
// Initial external round constants (4 rounds x 12 elements)
const INITIAL_EXTERNAL_CONSTANTS: array<array<array<u32, 2>, 12>, 4> = array<array<array<u32, 2>, 12>, 4>(
array<array<u32, 2>, 12>(
array<u32, 2>(2539329031u, 3221415792u),
array<u32, 2>(4262746426u, 3164936845u),
array<u32, 2>(3883202553u, 1922272763u),
array<u32, 2>(3761386668u, 3841130025u),
array<u32, 2>(1411081289u, 3588274735u),
array<u32, 2>(4090250945u, 3962812520u),
array<u32, 2>(1100826458u, 1215155029u),
array<u32, 2>(1489773809u, 1813820067u),
array<u32, 2>(2585015995u, 3824356688u),
array<u32, 2>(2378857513u, 3651555078u),
array<u32, 2>(2864423342u, 3852156759u),
array<u32, 2>(1531416540u, 708695120u)
),
array<array<u32, 2>, 12>(
array<u32, 2>(1987505445u, 2913073372u),
array<u32, 2>(1426707734u, 655469195u),
array<u32, 2>(3385403543u, 1256631504u),
array<u32, 2>(1381422714u, 1458257259u),
array<u32, 2>(2453402910u, 528129365u),
array<u32, 2>(964329320u, 905986685u),
array<u32, 2>(1534247888u, 3842469367u),
array<u32, 2>(744525997u, 4241857185u),
array<u32, 2>(1756723870u, 3448331916u),
array<u32, 2>(3610291774u, 1105166073u),
array<u32, 2>(2596181885u, 3997051784u),
array<u32, 2>(3199845381u, 3533420525u)
),
array<array<u32, 2>, 12>(
array<u32, 2>(4127777666u, 2103183598u),
array<u32, 2>(2867888172u, 2309916828u),
array<u32, 2>(1831532055u, 3009056407u),
array<u32, 2>(2947502451u, 3675530062u),
array<u32, 2>(3565886616u, 2029012066u),
array<u32, 2>(3833391242u, 642945968u),
array<u32, 2>(1773785903u, 2577032347u),
array<u32, 2>(1770914259u, 1689297286u),
array<u32, 2>(3752758200u, 3993707216u),
array<u32, 2>(3389302766u, 1339375184u),
array<u32, 2>(2180141127u, 1466089441u),
array<u32, 2>(3199591357u, 4111832034u)
),
array<array<u32, 2>, 12>(
array<u32, 2>(1625498743u, 509487959u),
array<u32, 2>(4188712685u, 1646551713u),
array<u32, 2>(3451003566u, 2854767422u),
array<u32, 2>(1412166652u, 1674110767u),
array<u32, 2>(3410212320u, 1000704202u),
array<u32, 2>(3381743837u, 602777331u),
array<u32, 2>(3131873882u, 2866003620u),
array<u32, 2>(2610174026u, 3923414377u),
array<u32, 2>(3644719692u, 3450945356u),
array<u32, 2>(1458984419u, 2418851081u),
array<u32, 2>(3344519983u, 1531855103u),
array<u32, 2>(2721413879u, 3732495392u)
)
);
// Terminal external round constants (4 rounds x 12 elements)
const TERMINAL_EXTERNAL_CONSTANTS: array<array<array<u32, 2>, 12>, 4> = array<array<array<u32, 2>, 12>, 4>(
array<array<u32, 2>, 12>(
array<u32, 2>(3773451374u, 2071119161u),
array<u32, 2>(3805190518u, 340095962u),
array<u32, 2>(2402679944u, 2149591222u),
array<u32, 2>(743434178u, 1832305922u),
array<u32, 2>(2847530739u, 2718290175u),
array<u32, 2>(514243119u, 4142392203u),
array<u32, 2>(3844443492u, 888639642u),
array<u32, 2>(2008645578u, 2957397405u),
array<u32, 2>(3732799654u, 1692252629u),
array<u32, 2>(664231319u, 248567644u),
array<u32, 2>(287781771u, 482031345u),
array<u32, 2>(3486561978u, 1718871301u)
),
array<array<u32, 2>, 12>(
array<u32, 2>(814165505u, 1616875560u),
array<u32, 2>(2123759183u, 1070984082u),
array<u32, 2>(2722916813u, 3893372341u),
array<u32, 2>(3726899022u, 4157656693u),
array<u32, 2>(2824360073u, 4086907574u),
array<u32, 2>(4155973110u, 1837140488u),
array<u32, 2>(2297731723u, 4169165669u),
array<u32, 2>(707924090u, 1474243980u),
array<u32, 2>(1298483757u, 384287239u),
array<u32, 2>(4243798069u, 557703745u),
array<u32, 2>(1510569718u, 2968696976u),
array<u32, 2>(3174388759u, 3638808363u)
),
array<array<u32, 2>, 12>(
array<u32, 2>(2044277519u, 3835193622u),
array<u32, 2>(2743212840u, 1983595986u),
array<u32, 2>(3943309069u, 151568229u),
array<u32, 2>(424355060u, 1989274413u),
array<u32, 2>(867046322u, 239293714u),
array<u32, 2>(4230997871u, 2479068123u),
array<u32, 2>(1565052394u, 2566260552u),
array<u32, 2>(815274432u, 3822673712u),
array<u32, 2>(1051683535u, 519405993u),
array<u32, 2>(2687564964u, 186958263u),
array<u32, 2>(1450226471u, 1648586942u),
array<u32, 2>(1511122054u, 1595811937u)
),
array<array<u32, 2>, 12>(
array<u32, 2>(2426274859u, 4261676319u),
array<u32, 2>(1207777283u, 1918363057u),
array<u32, 2>(3090099832u, 3870167883u),
array<u32, 2>(4101522705u, 1460796764u),
array<u32, 2>(201900220u, 4164567654u),
array<u32, 2>(2587682901u, 752404845u),
array<u32, 2>(2967564913u, 2100296475u),
array<u32, 2>(3404347409u, 2242778408u),
array<u32, 2>(3350048952u, 1386431957u),
array<u32, 2>(4093308564u, 1347177553u),
array<u32, 2>(2633812729u, 3169012324u),
array<u32, 2>(1727753673u, 3768793234u)
)
);
// Helper function to create GoldilocksField from constant array
fn gf_from_const(val: array<u32, 2>) -> GoldilocksField {
return gf_from_u64_parts(val[0], val[1]);
}
// MDS matrix constants for width 12 - circulant matrix first row
// MDS matrix diagonal for width 12 Goldilocks (from p3_goldilocks constants)
// Each element is stored as [low32, high32] pairs
const MDS_MATRIX_DIAG_12: array<array<u32, 2>, 12> = array<array<u32, 2>, 12>(
array<u32, 2>(0x23ba9300u, 0xc3b6c08eu), // 0xc3b6c08e23ba9300u
array<u32, 2>(0x4a324fb6u, 0xd84b5de9u), // 0xd84b5de94a324fb6u
array<u32, 2>(0x5b35b84fu, 0x0d0c371cu), // 0x0d0c371c5b35b84fu
array<u32, 2>(0xe7188037u, 0x7964f570u), // 0x7964f570e7188037u
array<u32, 2>(0xd996604bu, 0x5daf18bbu), // 0x5daf18bbd996604bu
array<u32, 2>(0xb9595257u, 0x6743bc47u), // 0x6743bc47b9595257u
array<u32, 2>(0x2c59bb70u, 0x5528b936u), // 0x5528b9362c59bb70u
array<u32, 2>(0x7127b68bu, 0xac45e25bu), // 0xac45e25b7127b68bu
array<u32, 2>(0xfbb606b5u, 0xa2077d7du), // 0xa2077d7dfbb606b5u
array<u32, 2>(0xaee378aeu, 0xf3faac6fu), // 0xf3faac6faee378aeu
array<u32, 2>(0x1545e883u, 0x0c6388b5u), // 0x0c6388b51545e883u
array<u32, 2>(0x44917b60u, 0xd27dbb69u) // 0xd27dbb6944917b60u
);
// Storage buffers
@group(0) @binding(0) var<storage, read_write> results: array<atomic<u32>>;
// Sponge state after absorbing header + high nonce half (12 felts as LE u32 pairs),
// precomputed on the host per batch. See pow_core::mining_midstate.
@group(0) @binding(1) var<storage, read> midstate: array<u32, 24>;
@group(0) @binding(2) var<storage, read> start_nonce: array<u32, 16>; // 64 bytes
@group(0) @binding(3) var<storage, read> difficulty_target: array<u32, 16>; // 64 bytes (U512 target)
@group(0) @binding(4) var<storage, read> dispatch_config: array<u32, 3>; // [total_threads, nonces_per_thread, total_nonces]
// Goldilocks field element represented as [limb0, limb1]
// where the value is limb0 + limb1*2^32
struct GoldilocksField {
limb0: u32,
limb1: u32,
}
// Field modulus P = 2^64 - 2^32 + 1 = 0xFFFFFFFF00000001
// In u32 limbs: P = [1, 0xFFFFFFFF]
const P_LIMB0: u32 = 1u;
const P_LIMB1: u32 = 0xFFFFFFFFu;
// EPSILON = 2^32 - 1 = 0x00000000FFFFFFFF
// In u32 limbs: EPSILON = [0xFFFFFFFF, 0]
const EPSILON_LIMB0: u32 = 0xFFFFFFFFu;
const EPSILON_LIMB1: u32 = 0u;
// Helper to create field elements
fn gf_from_limbs(l0: u32, l1: u32) -> GoldilocksField {
return GoldilocksField(l0, l1);
}
fn gf_zero() -> GoldilocksField {
return gf_from_limbs(0u, 0u);
}
fn gf_one() -> GoldilocksField {
return gf_from_limbs(1u, 0u);
}
fn gf_from_u32(val: u32) -> GoldilocksField {
return GoldilocksField(val, 0u);
}
// Convert a 64-bit value (as two u32s) to GoldilocksField
fn gf_from_u64_parts(low: u32, high: u32) -> GoldilocksField {
var result = GoldilocksField(low, high);
// Reduce if >= P
if (gf_compare(result, gf_from_limbs(P_LIMB0, P_LIMB1)) != 2u) {
result = gf_sub(result, gf_from_limbs(P_LIMB0, P_LIMB1));
}
return result;
}
// Addition with carry for u32 values
// Returns vec2<u32>(sum, carry)
fn u32_add_with_carry(a: u32, b: u32, carry_in: u32) -> vec2<u32> {
let sum1 = a + b;
let c1 = select(0u, 1u, sum1 < a);
let sum2 = sum1 + carry_in;
let c2 = select(0u, 1u, sum2 < sum1);
return vec2<u32>(sum2, c1 + c2);
}
// Subtraction with borrow for u32 values
// Returns vec2<u32>(diff, borrow)
fn u32_sub_with_borrow(a: u32, b: u32, borrow_in: u32) -> vec2<u32> {
let diff1 = a - b;
let b1 = select(0u, 1u, diff1 > a); // if a - b > a, then underflow happened (since b > 0)
// Wait, if a < b, a - b wraps around to a large number.
// e.g. 2 - 3 = 0xFFFFFFFF. 0xFFFFFFFF > 2. Correct.
let diff2 = diff1 - borrow_in;
let b2 = select(0u, 1u, diff2 > diff1);
return vec2<u32>(diff2, b1 + b2);
}
// Multiply two u32 values to get a u64 result (as vec2<u32>)
fn u32_mul_to_u64(a: u32, b: u32) -> vec2<u32> {
let a_lo = a & 0xFFFFu;
let a_hi = a >> 16u;
let b_lo = b & 0xFFFFu;
let b_hi = b >> 16u;
let p0 = a_lo * b_lo;
let p1 = a_hi * b_lo;
let p2 = a_lo * b_hi;
let p3 = a_hi * b_hi;
let sum_mid_part = p1 + p2;
let carry_mid = select(0u, 1u, sum_mid_part < p1);
let term_mid_lo = sum_mid_part << 16u;
let term_mid_hi = (sum_mid_part >> 16u) | (carry_mid << 16u);
let res_lo = p0 + term_mid_lo;
let carry_lo = select(0u, 1u, res_lo < p0);
let res_hi = p3 + term_mid_hi + carry_lo;
return vec2<u32>(res_lo, res_hi);
}
// Compare two GoldilocksField values
// Returns: 0 if a == b, 1 if a > b, 2 if a < b
fn gf_compare(a: GoldilocksField, b: GoldilocksField) -> u32 {
// Compare from most significant limb to least
if (a.limb1 != b.limb1) {
return select(2u, 1u, a.limb1 > b.limb1);
}
if (a.limb0 != b.limb0) {
return select(2u, 1u, a.limb0 > b.limb0);
}
return 0u; // Equal
}
// Goldilocks field addition
fn gf_add(a: GoldilocksField, b: GoldilocksField) -> GoldilocksField {
// Add limb by limb with carry propagation
let add0 = u32_add_with_carry(a.limb0, b.limb0, 0u);
let add1 = u32_add_with_carry(a.limb1, b.limb1, add0.y);
var result = GoldilocksField(add0.x, add1.x);
// Handle overflow: if carry out, we've computed a + b = result + 2^64
// In Goldilocks: 2^64 ≡ 2^32 - 1 (mod P)
if (add1.y != 0u) {
// Add EPSILON = 0xFFFFFFFF00000000 (Wait, EPSILON is 2^32 - 1)
// EPSILON = 0x00000000FFFFFFFF
// EPSILON_LIMB0 = 0xFFFFFFFF, EPSILON_LIMB1 = 0
let eps_add0 = u32_add_with_carry(result.limb0, EPSILON_LIMB0, 0u);
let eps_add1 = u32_add_with_carry(result.limb1, EPSILON_LIMB1, eps_add0.y);
result = GoldilocksField(eps_add0.x, eps_add1.x);
// If adding EPSILON caused another overflow, add EPSILON again
if (eps_add1.y != 0u) {
let eps2_add0 = u32_add_with_carry(result.limb0, EPSILON_LIMB0, 0u);
let eps2_add1 = u32_add_with_carry(result.limb1, EPSILON_LIMB1, eps2_add0.y);
result = GoldilocksField(eps2_add0.x, eps2_add1.x);
}
}
// Final reduction if result >= P
let p = gf_from_limbs(P_LIMB0, P_LIMB1);
if (gf_compare(result, p) != 2u) { // if result >= P
result = gf_sub_no_underflow(result, p);
}
return result;
}
// Helper for subtraction without underflow (assumes a >= b)
fn gf_sub_no_underflow(a: GoldilocksField, b: GoldilocksField) -> GoldilocksField {
let sub0 = u32_sub_with_borrow(a.limb0, b.limb0, 0u);
let sub1 = u32_sub_with_borrow(a.limb1, b.limb1, sub0.y);
return GoldilocksField(sub0.x, sub1.x);
}
// Goldilocks field subtraction
fn gf_sub(a: GoldilocksField, b: GoldilocksField) -> GoldilocksField {
// If a >= b, do direct subtraction
if (gf_compare(a, b) != 2u) {
return gf_sub_no_underflow(a, b);
}
// Otherwise: a < b, so compute a - b + P
let p = gf_from_limbs(P_LIMB0, P_LIMB1);
let a_plus_p = gf_add_no_reduction(a, p);
return gf_sub_no_underflow(a_plus_p, b);
}
// Addition without final modular reduction (used internally)
fn gf_add_no_reduction(a: GoldilocksField, b: GoldilocksField) -> GoldilocksField {
let add0 = u32_add_with_carry(a.limb0, b.limb0, 0u);
let add1 = u32_add_with_carry(a.limb1, b.limb1, add0.y);
return GoldilocksField(add0.x, add1.x);
}
// Simplified multiplication that handles carries more carefully
fn gf_mul_unreduced(a: GoldilocksField, b: GoldilocksField) -> array<u32, 4> {
// p0 = a0 * b0 (64 bits)
let p0 = u32_mul_to_u64(a.limb0, b.limb0);
// p1 = a0 * b1 (64 bits)
let p1 = u32_mul_to_u64(a.limb0, b.limb1);
// p2 = a1 * b0 (64 bits)
let p2 = u32_mul_to_u64(a.limb1, b.limb0);
// p3 = a1 * b1 (64 bits)
let p3 = u32_mul_to_u64(a.limb1, b.limb1);
// result[0] = p0.x
// result[1] = p0.y + p1.x + p2.x + carry
// result[2] = p1.y + p2.y + p3.x + carry
// result[3] = p3.y + carry
var r0 = p0.x;
let sum1 = u32_add_with_carry(p0.y, p1.x, 0u);
let sum2 = u32_add_with_carry(sum1.x, p2.x, 0u);
var r1 = sum2.x;
var c1 = sum1.y + sum2.y; // carry to next limb
let sum3 = u32_add_with_carry(p1.y, p2.y, c1);
let sum4 = u32_add_with_carry(sum3.x, p3.x, 0u);
var r2 = sum4.x;
var c2 = sum3.y + sum4.y;
let sum5 = u32_add_with_carry(p3.y, 0u, c2);
var r3 = sum5.x;
return array<u32, 4>(r0, r1, r2, r3);
}
// Reduce a 4-limb number modulo the Goldilocks prime
fn gf_reduce_4limb(limbs: array<u32, 4>) -> GoldilocksField {
// x_lo = limbs[0], limbs[1]
let x_lo = gf_from_limbs(limbs[0], limbs[1]);
// x_hi = limbs[2], limbs[3]
// x_hi_hi = limbs[3] (upper 32 bits of x_hi)
let x_hi_hi = gf_from_limbs(limbs[3], 0u);
// x_hi_lo = limbs[2] (lower 32 bits of x_hi)
let x_hi_lo = gf_from_limbs(limbs[2], 0u);
// Step 1: t0 = x_lo - x_hi_hi (with underflow detection)
var t0: GoldilocksField;
var underflow = false;
let sub0 = u32_sub_with_borrow(x_lo.limb0, x_hi_hi.limb0, 0u);
let sub1 = u32_sub_with_borrow(x_lo.limb1, x_hi_hi.limb1, sub0.y);
t0 = GoldilocksField(sub0.x, sub1.x);
if (sub1.y != 0u) {
underflow = true;
}
// Step 2: if underflow { t0 -= NEG_ORDER; }
if (underflow) {
let eps_sub0 = u32_sub_with_borrow(t0.limb0, EPSILON_LIMB0, 0u);
let eps_sub1 = u32_sub_with_borrow(t0.limb1, EPSILON_LIMB1, eps_sub0.y);
t0 = GoldilocksField(eps_sub0.x, eps_sub1.x);
}
// Step 3: t1 = x_hi_lo * NEG_ORDER
// NEG_ORDER = 2^32 - 1
// x_hi_lo * (2^32 - 1) = (x_hi_lo << 32) - x_hi_lo
// x_hi_lo is [limbs[2], 0]
// x_hi_lo << 32 is [0, limbs[2]]
let shifted = GoldilocksField(0u, x_hi_lo.limb0);
let t1_sub0 = u32_sub_with_borrow(shifted.limb0, x_hi_lo.limb0, 0u);
let t1_sub1 = u32_sub_with_borrow(shifted.limb1, x_hi_lo.limb1, t1_sub0.y);
let t1 = GoldilocksField(t1_sub0.x, t1_sub1.x);
// Step 4: result = t0 + t1 (with overflow handling like CPU)
let add0 = u32_add_with_carry(t0.limb0, t1.limb0, 0u);
let add1 = u32_add_with_carry(t0.limb1, t1.limb1, add0.y);
var result = GoldilocksField(add0.x, add1.x);
// If overflow, add NEG_ORDER
if (add1.y != 0u) {
let eps_add0 = u32_add_with_carry(result.limb0, EPSILON_LIMB0, 0u);
let eps_add1 = u32_add_with_carry(result.limb1, EPSILON_LIMB1, eps_add0.y);
result = GoldilocksField(eps_add0.x, eps_add1.x);
}
return result;
}
// Main Goldilocks field multiplication
fn gf_mul(a: GoldilocksField, b: GoldilocksField) -> GoldilocksField {
// Handle special cases
if (a.limb0 == 0u && a.limb1 == 0u) { return gf_zero(); }
if (b.limb0 == 0u && b.limb1 == 0u) { return gf_zero(); }
if (a.limb0 == 1u && a.limb1 == 0u) { return b; }
if (b.limb0 == 1u && b.limb1 == 0u) { return a; }
// General case: multiply and reduce
let unreduced = gf_mul_unreduced(a, b);
return gf_reduce_4limb(unreduced);
}
// S-box: x^7 in Goldilocks field (efficient approach)
fn sbox(x: GoldilocksField) -> GoldilocksField {
let x2 = gf_mul(x, x);
let x4 = gf_mul(x2, x2);
let x6 = gf_mul(x4, x2);
return gf_mul(x6, x);
}
// External linear layer for width 12 using correct 4x4 MDS matrix
// Standard MDSMat4: [[2, 3, 1, 1], [1, 2, 3, 1], [1, 1, 2, 3], [3, 1, 1, 2]]
fn external_linear_layer(state: ptr<function, array<GoldilocksField, 12>>) {
// First apply the 4x4 MDS matrix to each consecutive 4 elements
for (var chunk = 0u; chunk < 3u; chunk++) {
let offset = chunk * 4u;
var x: array<GoldilocksField, 4>;
// Copy chunk
for (var i = 0u; i < 4u; i++) {
x[i] = (*state)[offset + i];
}
// Optimized 4x4 MDS application using only additions and doubles,
// matching apply_external_linear_layer_to_chunk in the CPU implementation.
let t01 = gf_add(x[0], x[1]);
let t23 = gf_add(x[2], x[3]);
let t0123 = gf_add(t01, t23);
let t01123 = gf_add(t0123, x[1]);
let t01233 = gf_add(t0123, x[3]);
let two_x0 = gf_add(x[0], x[0]);
let two_x2 = gf_add(x[2], x[2]);
// The order of updates matches the reference algorithm:
// x[3] = t01233 + 2*x[0]
// x[1] = t01123 + 2*x[2]
// x[0] = t01123 + t01
// x[2] = t01233 + t23
let new_3 = gf_add(t01233, two_x0);
let new_1 = gf_add(t01123, two_x2);
let new_0 = gf_add(t01123, t01);
let new_2 = gf_add(t01233, t23);
// Copy back
(*state)[offset + 0u] = new_0;
(*state)[offset + 1u] = new_1;
(*state)[offset + 2u] = new_2;
(*state)[offset + 3u] = new_3;
}
// Now apply the circulant matrix part
// Precompute the four sums of every four elements
var sums: array<GoldilocksField, 4>;
for (var k = 0u; k < 4u; k++) {
sums[k] = gf_zero();
for (var j = k; j < 12u; j += 4u) {
sums[k] = gf_add(sums[k], (*state)[j]);
}
}
// Add the appropriate sum to each element
for (var i = 0u; i < 12u; i++) {
(*state)[i] = gf_add((*state)[i], sums[i % 4u]);
}
}
// Internal linear layer using diagonal matrix for width 12
fn internal_linear_layer(state: ptr<function, array<GoldilocksField, 12>>) {
var result: array<GoldilocksField, 12>;
// Sum all elements
var sum = gf_zero();
for (var i = 0u; i < 12u; i++) {
sum = gf_add(sum, (*state)[i]);
}
// Apply diagonal matrix: result[i] = state[i] * diag[i] + sum
for (var i = 0u; i < 12u; i++) {
let diag_val = gf_from_u64_parts(
MDS_MATRIX_DIAG_12[i][0], // low 32 bits
MDS_MATRIX_DIAG_12[i][1] // high 32 bits
);
let scaled = gf_mul((*state)[i], diag_val);
result[i] = gf_add(scaled, sum);
}
// Copy result back to state
for (var i = 0u; i < 12u; i++) {
(*state)[i] = result[i];
}
}
// Fixed Poseidon2 permutation with proper structure
fn poseidon2_permute(state: ptr<function, array<GoldilocksField, 12>>) {
// Initial MDS permutation (required by p3-poseidon2 spec)
external_linear_layer(state);
// Initial external rounds (4 rounds)
for (var round = 0u; round < 4u; round++) {
// Add round constants
for (var i = 0u; i < 12u; i++) {
(*state)[i] = gf_add((*state)[i], gf_from_const(INITIAL_EXTERNAL_CONSTANTS[round][i]));
}
// S-box on all elements
for (var i = 0u; i < 12u; i++) {
(*state)[i] = sbox((*state)[i]);
}
// External linear layer (4x4 MDS matrix)
external_linear_layer(state);
}
// Internal rounds (22 rounds)
for (var round = 0u; round < 22u; round++) {
// Add round constant to first element only
(*state)[0] = gf_add((*state)[0], gf_from_const(INTERNAL_CONSTANTS[round]));
// S-box on first element only
(*state)[0] = sbox((*state)[0]);
// Internal linear layer (diagonal matrix)
internal_linear_layer(state);
}
// Terminal external rounds (4 rounds)
for (var round = 0u; round < 4u; round++) {
// Add round constants
for (var i = 0u; i < 12u; i++) {
(*state)[i] = gf_add((*state)[i], gf_from_const(TERMINAL_EXTERNAL_CONSTANTS[round][i]));
}
// S-box on all elements
for (var i = 0u; i < 12u; i++) {
(*state)[i] = sbox((*state)[i]);
}
// External linear layer (4x4 MDS matrix)
external_linear_layer(state);
}
}
// Convert bytes to Goldilocks field elements matching reference implementation
// Reference uses 4 bytes per field element with injective padding, creating 25 field elements from 96 bytes
fn bytes_to_field_elements(input: array<u32, 24>) -> array<GoldilocksField, 25> {
var felts: array<GoldilocksField, 25>;
// Convert u32 array to bytes (96 bytes total)
var bytes: array<u32, 96>; // Using u32s to store bytes for easier processing
for (var i = 0u; i < 24u; i++) {
let val = input[i];
bytes[i * 4u + 0u] = val & 0xFFu; // byte 0
bytes[i * 4u + 1u] = (val >> 8u) & 0xFFu; // byte 1
bytes[i * 4u + 2u] = (val >> 16u) & 0xFFu; // byte 2
bytes[i * 4u + 3u] = (val >> 24u) & 0xFFu; // byte 3
}
// Apply injective padding: add 1 byte, then pad with zeros to 4-byte alignment
var padded_len = 96u + 1u; // 96 bytes + 1 marker byte = 97
let padding_needed = (4u - (padded_len % 4u)) % 4u;
padded_len = padded_len + padding_needed; // Should be 100 bytes (25 u32s worth)
// Create padded byte array
var padded_bytes: array<u32, 100>;
for (var i = 0u; i < 96u; i++) {
padded_bytes[i] = bytes[i];
}
padded_bytes[96] = 1u; // End marker
for (var i = 97u; i < 100u; i++) {
padded_bytes[i] = 0u; // Padding zeros
}
// Convert every 4 bytes to one field element (25 field elements total)
for (var i = 0u; i < 25u; i++) {
let byte_idx = i * 4u;
// Create u32 from 4 bytes in little-endian order
let val = padded_bytes[byte_idx] |
(padded_bytes[byte_idx + 1u] << 8u) |
(padded_bytes[byte_idx + 2u] << 16u) |
(padded_bytes[byte_idx + 3u] << 24u);
felts[i] = gf_from_u32(val);
}
return felts;
}
// Convert field elements back to bytes
fn field_elements_to_bytes(felts: array<GoldilocksField, 4>) -> array<u32, 8> {
var result: array<u32, 8>;
for (var i = 0u; i < 4u; i++) {
result[i * 2u] = felts[i].limb0;
result[i * 2u + 1u] = felts[i].limb1;
}
return result;
}
// Fixed Poseidon2 hash function with proper sponge construction
fn poseidon2_hash_squeeze_twice(input: array<u32, 24>) -> array<u32, 16> {
var state: array<GoldilocksField, 12>;
// Initialize state to zero
for (var i = 0u; i < 12u; i++) {
state[i] = gf_zero();
}
// Convert input to field elements (25 total: 24 from input + 1 terminator)
let input_felts = bytes_to_field_elements(input);
// Sponge construction matching CPU reference exactly:
// CPU processes field elements using push_to_buf() which absorbs in chunks of RATE=8
// Process first 24 elements (3 complete chunks of 8)
for (var chunk = 0u; chunk < 3u; chunk++) {
// Absorb 8 elements for this chunk
for (var i = 0u; i < 8u; i++) {
let felt_idx = chunk * 8u + i;
state[i] = gf_add(state[i], input_felts[felt_idx]);
}
// Permute after each complete chunk
poseidon2_permute(&state);
}
// Process remaining 1 element (partial chunk)
// Now we have element 24 (the terminator = 1) remaining
state[0] = gf_add(state[0], input_felts[24u]); // Add the terminator (value 1)
// Add sponge padding marker (ONE) to the next position
state[1] = gf_add(state[1], gf_one());
// Final permutation
poseidon2_permute(&state);
// First squeeze - get first 4 field elements (POSEIDON2_OUTPUT=4 -> 32 bytes)
let first_output = field_elements_to_bytes(array<GoldilocksField, 4>(
state[0], state[1], state[2], state[3]
));
// Second squeeze
poseidon2_permute(&state);
let second_output = field_elements_to_bytes(array<GoldilocksField, 4>(
state[0], state[1], state[2], state[3]
));
// Combine both squeezes into 64-byte output
var result: array<u32, 16>;
for (var i = 0u; i < 8u; i++) {
result[i] = first_output[i];
result[i + 8u] = second_output[i];
}
return result;
}
// Poseidon2 hash function for 64-byte input (used in double hash)
fn poseidon2_hash_squeeze_twice_64(input: array<u32, 16>) -> array<u32, 16> {
var state: array<GoldilocksField, 12>;
// Initialize state to zero
for (var i = 0u; i < 12u; i++) {
state[i] = gf_zero();
}
// Convert input to field elements
// 64 bytes = 16 u32s = 16 felts
// Padding adds 1 byte + 3 zeros = 4 bytes = 1 felt (value 1)
// Total 17 felts
// Process first 16 elements (4 complete chunks of 4)
for (var chunk = 0u; chunk < 4u; chunk++) {
// Absorb 4 elements for this chunk
for (var i = 0u; i < 4u; i++) {
let felt_idx = chunk * 4u + i;
state[i] = gf_add(state[i], gf_from_u32(input[felt_idx]));
}
// Permute after each complete chunk
poseidon2_permute(&state);
}
// Handle the last felt (padding marker = 1) and sponge padding
// The last felt is 1 (from bytes 1, 0, 0, 0)
state[0] = gf_add(state[0], gf_one());
// Add sponge padding marker (ONE) to the next position
state[1] = gf_add(state[1], gf_one());
// Final permutation
poseidon2_permute(&state);
// First squeeze
let first_output = field_elements_to_bytes(array<GoldilocksField, 4>(
state[0], state[1], state[2], state[3]
));
// Second squeeze
poseidon2_permute(&state);
let second_output = field_elements_to_bytes(array<GoldilocksField, 4>(
state[0], state[1], state[2], state[3]
));
// Combine both squeezes into 64-byte output
var result: array<u32, 16>;
for (var i = 0u; i < 8u; i++) {
result[i] = first_output[i];
result[i + 8u] = second_output[i];
}
return result;
}
// Single Poseidon2 hash with double squeeze (512-bit output)
fn hash_squeeze_twice(input: array<u32, 24>) -> array<u32, 16> {
return poseidon2_hash_squeeze_twice(input);
}
fn bswap32(v: u32) -> u32 {
return ((v & 0xFFu) << 24u) | ((v & 0xFF00u) << 8u) | ((v >> 8u) & 0xFF00u) | (v >> 24u);
}
// Main mining kernel
@compute @workgroup_size(256)
fn mining_main(@builtin(global_invocation_id) global_id: vec3<u32>) {
// If solution already found, exit early
if (atomicLoad(&results[0]) != 0u) {
return;
}
let thread_id = global_id.x;
// Read dispatch configuration from buffer
let total_threads = dispatch_config[0]; // Total logical threads in this dispatch
let nonces_per_thread = dispatch_config[1]; // Nonces processed by each thread
let total_nonces = dispatch_config[2]; // Total logical nonces this dispatch should cover
// Guard against threads beyond configured total_threads
if (thread_id >= total_threads) {
return;
}
// Base logical index for this thread
let base_index = thread_id * nonces_per_thread;
// Work coarsening: each thread processes a contiguous block of nonces
for (var j = 0u; j < nonces_per_thread; j = j + 1u) {
let logical_index = base_index + j;
if (logical_index >= total_nonces) {
break;
}
// Check if solution already found (early exit for entire dispatch)
if (atomicLoad(&results[0]) != 0u) {
return;
}
// current_nonce = start_nonce + logical_index. The host guarantees a batch
// never carries into the high nonce half (limbs 8..15).
var current_nonce: array<u32, 16>;
var carry: u32 = 0u;
let val0 = start_nonce[0];
let sum0 = val0 + logical_index;
current_nonce[0] = sum0;
carry = select(0u, 1u, sum0 < val0);
for (var i = 1u; i < 8u; i++) {
let val = start_nonce[i];
let sum = val + carry;
current_nonce[i] = sum;
carry = select(0u, 1u, sum < val);
}
for (var i = 8u; i < 16u; i++) {
current_nonce[i] = start_nonce[i];
}
// Resume the sponge from the precomputed midstate: absorb the low nonce
// half (as Big Endian words), pad, squeeze twice (3 permutations instead of 5).
var state: array<GoldilocksField, 12>;
for (var i = 0u; i < 12u; i++) {
state[i] = gf_from_limbs(midstate[2u * i], midstate[2u * i + 1u]);
}
for (var i = 0u; i < 8u; i++) {
let val = current_nonce[7u - i];
let rev = ((val & 0xFFu) << 24u) |
((val & 0xFF00u) << 8u) |
((val & 0xFF0000u) >> 8u) |
((val & 0xFF000000u) >> 24u);
state[i] = gf_add(state[i], gf_from_u32(rev));
}
poseidon2_permute(&state);
state[0] = gf_add(state[0], gf_one());
state[1] = gf_add(state[1], gf_one());
poseidon2_permute(&state);
// First squeeze yields the most significant 256 bits of the hash, which
// decide hash-vs-target on their own unless they exactly equal the
// target's high half. Only candidates pay for the second squeeze.
let first_output = field_elements_to_bytes(array<GoldilocksField, 4>(
state[0], state[1], state[2], state[3]
));
var hash_le: array<u32, 16>;
for (var i = 0u; i < 8u; i++) {
hash_le[15u - i] = bswap32(first_output[i]);
}
var cmp = 0u;
for (var i = 0u; i < 8u; i++) {
let h = hash_le[15u - i];
let t = difficulty_target[15u - i];
if (h != t) {
cmp = select(2u, 1u, h > t);
break;
}
}
if (cmp == 1u) {
continue;
}
poseidon2_permute(&state);
let second_output = field_elements_to_bytes(array<GoldilocksField, 4>(
state[0], state[1], state[2], state[3]
));
for (var i = 0u; i < 8u; i++) {
hash_le[i] = bswap32(second_output[7u - i]);
}
var below = cmp == 2u;
if (!below) {
for (var i = 0u; i < 8u; i++) {
let h = hash_le[7u - i];
let t = difficulty_target[7u - i];
if (h != t) {
below = h < t;
break;
}
}
}
if (below) {
// Try to claim the solution
if (atomicExchange(&results[0], 1u) == 0u) {
// results layout: [0]=found, [1..16]=nonce, [17..32]=hash
for (var i = 0u; i < 16u; i++) {
atomicStore(&results[1u + i], current_nonce[i]);
atomicStore(&results[17u + i], hash_le[i]);
}
}
return; // Exit loop after finding solution
}
}
}