v5 vs Sandglass
Technical comparison of live consensus PoW (v5) and the experimental
Sandglass candidate measured on this site. Sandglass is
not activated on-chain yet — activating it would be a hard fork.
Goal
BrowserCoin wants mining that stays viable in a browser tab / laptop CPU
(WASM, ordinary RAM, a handful of Web Workers), while making it harder for
GPUs to run thousands of independent hash lanes from huge VRAM bandwidth.
v5 already uses memory-hard Argon2id. Sandglass keeps that gate and adds a
data-dependent scratch mix so a single hash cannot be efficiently
SIMD/batch-pipelined the way pure Argon2 batches can on CUDA.
At a glance
|
Before — v5 (live) |
After — Sandglass mid |
| Status |
Consensus PoW today |
Experimental candidate (lab only) |
| Salt / domain |
browsercoin-pow-v5 |
browsercoin-pow-v6-sandglass + domain tag |
| Core primitive |
Argon2id only |
Argon2id → BLAKE2b scratch → dependent reads → BLAKE2b-256 |
| Argon2 memory |
32 MiB |
32 MiB (mid preset) |
Argon2 passes t |
1 |
2 |
Parallelism p |
1 |
1 |
| Extra working set |
— |
4 MiB BLAKE2b scratch + 2048 random reads |
| Output |
32-byte Argon2 tag |
32-byte BLAKE2b-256 digest |
| Browser path |
WASM Argon2id in Web Workers |
Same + JS/WASM BLAKE2b mix |
| Native GPU path |
Argon2 CUDA batches scale well |
Hybrid: Argon2 CUDA + sequential mix on CPU (mix caps H/s) |
mid is the main public board.
production is a separate page
(64 MiB Argon2, 8 MiB scratch, 4096 rounds) with its own board — mid history stays valid.
Pipeline (Sandglass)
header (148 bytes)
│
▼
┌───────────────────────────┐
│ 1. Argon2id gate │ password=header, m=32MiB, t=2, p=1
│ → tag T (32 bytes) │
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ 2. Scratch expand │ 4 MiB = 65536 × 64-byte blocks
│ B_i = BLAKE2b-512(…) │ B_i = BLAKE2b-512(T ‖ u64be(i))
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ 3. Dependent sand mix │ 2048 rounds
│ idx ← state; read B_idx│ each round needs previous state
│ state ← BLAKE2b-512(…) │ → hard to batch / hide latency
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ 4. Finalize │ BLAKE2b-256(state ‖ "out")
│ → 32-byte PoW digest │
└───────────────────────────┘
Before — BrowserCoin v5
Live consensus hash: one Argon2id call over the encoded block header.
Salt is fixed network-wide so a version bump can hard-fork cleanly.
// src/crypto/pow.ts — consensus today
const SALT = new TextEncoder().encode('browsercoin-pow-v5');
export const POW_PARAMS = {
memorySize: 32 * 1024, // KiB → 32 MiB
iterations: 1,
parallelism: 1,
hashLength: 32,
} as const;
export async function powHash(headerBytes: Uint8Array): Promise<Uint8Array> {
const argon2id = await loadArgon2id();
return argon2id({
password: headerBytes,
salt: SALT,
parallelism: POW_PARAMS.parallelism,
passes: POW_PARAMS.iterations,
memorySize: POW_PARAMS.memorySize,
tagLength: POW_PARAMS.hashLength,
});
}
Pseudocode:
pow_v5(header) =
Argon2id(
password = header,
salt = "browsercoin-pow-v5",
m = 32 MiB,
t = 1,
p = 1,
tagLen = 32
)
Strengths: simple, WASM-friendly, memory-hard enough that mid-range GPU L2 cannot
hold many lanes. Weakness for the GPU threat model: once Argon2 is implemented well
on CUDA, thousands of independent headers can still be hashed in parallel with high
occupancy — there is no long serial dependency after the memory-hard step.
After — Sandglass (mid preset)
Reference implementation lives in src/crypto/pow-v6-sandglass.ts.
The mid preset used by this site:
// Mid preset (what the bench / board measure)
const SANDGLASS_MID = {
argonMemoryKiB: 32 * 1024, // 32 MiB Argon2id
argonPasses: 2, // t=2 (heavier than v5's t=1)
argonParallelism: 1,
scratchBytes: 4 * 1024 * 1024, // 4 MiB BLAKE2b scratch
mixRounds: 2048, // dependent random reads
hashLength: 32,
};
Core of the hash (simplified from the reference module):
// src/crypto/pow-v6-sandglass.ts (structure)
const DOMAIN = 'browsercoin-sandglass-v1';
const SALT = new TextEncoder().encode('browsercoin-pow-v6-sandglass');
const BLOCK = 64; // BLAKE2b-512 width
export async function sandglassHash(header, params = SANDGLASS_MID) {
// 1) Memory-hard gate — same family as v5, different salt / passes
const tag = argon2id({
password: header,
salt: SALT,
parallelism: params.argonParallelism,
passes: params.argonPasses,
memorySize: params.argonMemoryKiB,
tagLength: 32,
});
// 2) Expand tag into a scratchpad of 64-byte blocks
const nBlocks = params.scratchBytes / BLOCK;
const scratch = new Uint8Array(params.scratchBytes);
for (let i = 0; i < nBlocks; i++) {
scratch.set(blake2b512(concat([tag, u64be(i)])), i * BLOCK);
}
// 3) Data-dependent mix — each round's index comes from prior state
let state = blake2b512(concat([domain, tag, mixTag]));
for (let r = 0; r < params.mixRounds; r++) {
const idx = Number(asU64LE(state.subarray(0, 8)) % BigInt(nBlocks));
const slice = scratch.subarray(idx * BLOCK, idx * BLOCK + BLOCK);
state = blake2b512(concat([state, slice, u64be(r)]));
}
// 4) Finalize
return blake2b256(concat([state, outTag])).subarray(0, 32);
}
BLAKE2b comes from @noble/hashes/blake2b; Argon2id from the same WASM
stack as v5 (openpgpjs/argon2id).
sandglass_mid(header) =
T ← Argon2id(header, salt=v6-sandglass, m=32MiB, t=2, p=1)
for i in 0 .. 65535:
B[i] ← BLAKE2b-512(T ‖ u64be(i)) // 4 MiB scratch
state ← BLAKE2b-512("browsercoin-sandglass-v1" ‖ T ‖ "mix")
for r in 0 .. 2047:
idx ← U64LE(state[0:8]) mod 65536
state ← BLAKE2b-512(state ‖ B[idx] ‖ u64be(r))
return BLAKE2b-256(state ‖ "out")[0:32]
Why the mix hurts GPUs more than phones
-
Argon2 gate still costs ~32 MiB per in-flight hash, so VRAM cannot
host as many parallel instances as a lightweight hash.
-
Dependent reads force each round to wait on the previous BLAKE2b
state before the next scratch index is known. That kills deep batching / latency
hiding across rounds of the same hash.
-
Browser / CPU already runs one (or few) hashes per worker; an extra
sequential mix is “just slower,” not architecturally hostile.
-
Native CUDA hybrid (what our GPU board rows use) can still
accelerate the Argon2 gate, but the BLAKE2b mix runs on the host CPU today — so
Sandglass GPU H/s often plateaus even when v5 GPU H/s scales with the card.
Sandglass does not claim to stop “datacenters” in general — a rack of CPUs still wins
over one laptop. It targets parallel accelerators, not enterprise as a class.
How this site measures
-
CPU / browser: real WASM implementations of both algos, all logical
cores as Web Workers, aggregate H/s (same shape as the mining UI).
-
GPU rows on the board: native
sandglass_bench
(v5gpu vs hybrid gpu mid), posted manually from lab / Vast
runs — not a browser WebGPU fake probe.
-
Factor on the community board is
H/s(v5) ÷ H/s(Sandglass mid). Higher means Sandglass is slower on that device.
Activation (if ever)
Not consensus today. A real switch would be a hard fork roughly like:
// Conceptual — not implemented
- new network / salt id: browsercoin-pow-v6
- replace powHash() call sites with sandglassHash(…, SANDGLASS_*)
- retarget / genesis difficulty for the new cost model
- miners (browser, pool, CUDA) ship matching bytecode / kernels
Until then, treat Sandglass numbers as a lab signal for whether the
candidate is worth the fork cost.
mid bench →
production bench →