code wiki / _hdl_build / nx_pow_challenge.nx
nx_pow_challenge.nx source
↩ module page · 73 lines · 2864 B
1// nx_pow_challenge.nx -- proof-of-work client puzzle for the access wall's L7 DDoS shedding (hashcash;
2// Cloudflare "under attack"). Under high load/anomaly the server issues a fresh CHALLENGE + a difficulty D;
3// the client must find a SOLUTION such that sha256(challenge || solution) has >= D leading zero bits. The
4// server VERIFIES in O(1) (one sha256) but the client pays O(2^D) expected work -- the asymmetry that makes a
5// flood expensive for the attacker and a one-time cost for a legit client. Reuses the KAT-verified nx_sha256
6// (no rolled crypto). license_tier: ORIGINAL
7import "nx_sha256.nx"
8import "nx_syscalls.nx"
9
10// count leading zero BITS of a 32-byte hash.
11func pow_lzb(h: *u8) -> i64 {
12 var bits: i64 = 0
13 var i: i64 = 0
14 var go: i64 = 1
15 while go == 1 {
16 if i >= 32 { go = 0 }
17 else {
18 let byte: i64 = h[i] as i64
19 if byte == 0 { bits = bits + 8; i = i + 1 }
20 else {
21 var mask: i64 = 128
22 var inner: i64 = 1
23 while inner == 1 {
24 if mask == 0 { inner = 0 }
25 else {
26 if (byte & mask) == 0 { bits = bits + 1; mask = mask / 2 }
27 else { inner = 0 }
28 }
29 }
30 go = 0
31 }
32 }
33 }
34 return bits
35}
36
37// unsigned decimal -> string in out; returns length.
38func pow_u2s(v: i64, out: *u8) -> i64 {
39 if v == 0 { out[0] = 48 as u8; return 1 }
40 let t: *u8 = sys_mmap(28)
41 var m: i64 = v
42 var k: i64 = 0
43 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
44 var i: i64 = 0
45 while i < k { out[i] = t[k-1-i]; i = i + 1 }
46 return k
47}
48
49// VERIFY (server, O(1)): does sha256(challenge || solution) have >= difficulty leading zero bits? 1/0.
50func pow_verify(challenge: *u8, clen: i64, solution: *u8, slen: i64, difficulty: i64) -> i64 {
51 let buf: *u8 = sys_mmap(clen + slen + 16)
52 var i: i64 = 0
53 while i < clen { buf[i] = challenge[i]; i = i + 1 }
54 var j: i64 = 0
55 while j < slen { buf[clen + j] = solution[j]; j = j + 1 }
56 let h: *u8 = sys_mmap(32)
57 sha256_digest(buf, clen + slen, h)
58 if pow_lzb(h) >= difficulty { return 1 }
59 return 0
60}
61
62// SOLVE (client, O(2^D) expected): brute-force a numeric solution whose decimal string satisfies the puzzle.
63// writes the solution into sol_out, returns its length, or -1 if not found within max_tries (the server NEVER
64// runs this -- the cost asymmetry is the defense).
65func pow_solve(challenge: *u8, clen: i64, difficulty: i64, sol_out: *u8, max_tries: i64) -> i64 {
66 var nce: i64 = 0
67 while nce < max_tries {
68 let slen: i64 = pow_u2s(nce, sol_out)
69 if pow_verify(challenge, clen, sol_out, slen, difficulty) == 1 { return slen }
70 nce = nce + 1
71 }
72 return 0 - 1
73}