code wiki / _hdl_build / nx_hr_pow.nx
nx_hr_pow.nx source
↩ module page · 76 lines · 5353 B
1// nx_hr_pow.nx -- NISHI HR, the PROOF-OF-WORK challenge (the "cost" limb of behavioral bot defense). Privacy-
2// preserving: NO IP/PII -- a visitor just has to PAY a small compute cost the server can cheaply verify, which is
3// negligible for one human but ruinous for a bot hammering many requests. STATELESS + tamper-proof: the challenge
4// is HMAC-bound to a server secret (so the server verifies it issued it without storing it) and carries an EXPIRY
5// (so it can't be replayed forever). Sovereign crypto: composes sha256_digest. The client must find a `solution`
6// integer s.t. sha256("<nonce>/<solution>") has >= `difficulty` leading zero BITS.
7// challenge wire form: "<nonce>:<difficulty>:<expiry>:<mac-hex>" (mac = sha256(secret || "<nonce>:<diff>:<exp>"))
8// license_tier: ORIGINAL
9import "nx_sha256.nx" // sha256_digest(bytes,n,out32)
10import "nx_syscalls.nx"
11
12func pow_puts(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ dst[o+i]=s[i]; i=i+1 } return o+i }
13func pow_putn(dst: *u8, o: i64, v: i64) -> i64 { if v==0 { dst[o]=48 as u8; return o+1 } var m: i64=v; if m<0{m=0-m} let t:*u8=sys_mmap(24); var k:i64=0; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var j:i64=0; while j<k{dst[o+j]=t[k-1-j];j=j+1} return o+k }
14func pow_hex(inp: *u8, n: i64, out: *u8) -> i64 { let hx: *u8="0123456789abcdef" as *u8; var i: i64=0; while i<n { out[i*2]=hx[((inp[i] as i64)>>4)&15]; out[i*2+1]=hx[(inp[i] as i64)&15]; i=i+1 } return n*2 }
15func pow_atoi(buf: *u8, s: i64, e: i64) -> i64 { var v: i64=0; var i: i64=s; while i<e { let c: i64=buf[i] as i64; if c>=48 { if c<=57 { v=v*10+(c-48); i=i+1 } else {i=e} } else {i=e} } return v }
16func pow_colon(buf: *u8, start: i64, n: i64) -> i64 { var i: i64=start; while i<n { if buf[i]==(58 as u8) { return i } i=i+1 } return n }
17func pow_eqrange(a: *u8, as0: i64, ae: i64, b: *u8, bs: i64, be: i64) -> i64 { if (ae-as0)!=(be-bs) { return 0 } var k: i64=0; while k<(ae-as0) { if a[as0+k]!=b[bs+k] { return 0 } k=k+1 } return 1 }
18
19// count leading zero BITS of the 32-byte hash.
20func pow_lead_zero_bits(h: *u8) -> i64 {
21 var bits: i64 = 0; var i: i64 = 0
22 while i < 32 {
23 let b: i64 = h[i] as i64
24 if b == 0 { bits = bits + 8; i = i + 1 } else {
25 var mask: i64 = 128; var c: i64 = 0; var go: i64 = 1
26 while go == 1 { if mask <= 0 { go = 0 } else { if (b & mask) == 0 { c = c + 1; mask = mask / 2 } else { go = 0 } } }
27 bits = bits + c; i = 32
28 }
29 }
30 return bits
31}
32// the MAC over a payload: hex(sha256(secret || payload)). writes to out_hex (64 chars), returns len.
33func pow_mac(secret: *u8, slen: i64, payload: *u8, plen: i64, out_hex: *u8) -> i64 {
34 let buf: *u8 = sys_mmap(slen + plen + 16)
35 var i: i64 = 0; while i < slen { buf[i] = secret[i]; i = i + 1 }
36 var j: i64 = 0; while j < plen { buf[slen+j] = payload[j]; j = j + 1 }
37 let h: *u8 = sys_mmap(32); sha256_digest(buf, slen + plen, h)
38 return pow_hex(h, 32, out_hex)
39}
40// does sha256("<nonce>/<solution>") have >= difficulty leading zero bits?
41func pow_solution_ok(nonce: *u8, noncelen: i64, solution: i64, difficulty: i64) -> i64 {
42 let buf: *u8 = sys_mmap(noncelen + 40)
43 var i: i64 = 0; while i < noncelen { buf[i] = nonce[i]; i = i + 1 }
44 var o: i64 = noncelen; buf[o] = 47 as u8; o = o + 1 // '/'
45 o = pow_putn(buf, o, solution)
46 let h: *u8 = sys_mmap(32); sha256_digest(buf, o, h)
47 if pow_lead_zero_bits(h) >= difficulty { return 1 }
48 return 0
49}
50// ISSUE a challenge "<nonce>:<diff>:<exp>:<mac>" into out (NUL-term); returns len.
51func pow_make(secret: *u8, slen: i64, nonce: i64, difficulty: i64, expiry_ts: i64, out: *u8) -> i64 {
52 var o: i64 = pow_putn(out, 0, nonce); out[o]=58 as u8; o=o+1
53 o = pow_putn(out, o, difficulty); out[o]=58 as u8; o=o+1
54 o = pow_putn(out, o, expiry_ts)
55 let mac: *u8 = sys_mmap(80); pow_mac(secret, slen, out, o, mac) // mac over "<nonce>:<diff>:<exp>"
56 out[o]=58 as u8; o=o+1
57 var k: i64=0; while mac[k]!=(0 as u8) { if k<64 { out[o]=mac[k]; o=o+1; k=k+1 } else { k=64 } } if k==0 { var z: i64=0; while z<64 { out[o]=mac[z]; o=o+1; z=z+1 } }
58 out[o]=0 as u8; return o
59}
60// VERIFY: challenge well-formed + OUR mac (not forged) + not expired + solution meets difficulty. 1=pass, 0=reject.
61func pow_verify(chal: *u8, chlen: i64, solution: i64, secret: *u8, slen: i64, now_ts: i64) -> i64 {
62 let c1: i64 = pow_colon(chal, 0, chlen) // end of nonce
63 if c1 >= chlen { return 0 }
64 let c2: i64 = pow_colon(chal, c1+1, chlen) // end of difficulty
65 if c2 >= chlen { return 0 }
66 let c3: i64 = pow_colon(chal, c2+1, chlen) // end of expiry; mac follows
67 if c3 >= chlen { return 0 }
68 let difficulty: i64 = pow_atoi(chal, c1+1, c2)
69 let expiry: i64 = pow_atoi(chal, c2+1, c3)
70 // recompute the mac over payload = chal[0..c3) and compare to chal[c3+1..chlen)
71 let mymac: *u8 = sys_mmap(80); pow_mac(secret, slen, chal, c3, mymac)
72 var mymaclen: i64 = 0; while mymac[mymaclen]!=(0 as u8) { mymaclen = mymaclen + 1 }
73 if pow_eqrange(mymac, 0, mymaclen, chal, c3+1, chlen) == 0 { return 0 } // forged / tampered
74 if now_ts > expiry { return 0 } // expired
75 return pow_solution_ok(chal, c1, solution, difficulty) // nonce = chal[0..c1)
76}