code wiki / (root) / nx_argon2_block.nx

nx_argon2_block.nx source

↩ module page · 258 lines · 8937 B

1// nx_argon2_block.nx -- RFC 9106 sec 3.5/3.6 Argon2 G compression 2// + P permutation. 3// 4// The third and final brick in the Argon2id arc (before the 5// orchestrator that ties indexing + memory matrix together). G is 6// what gives Argon2 its memory hardness: it's intentionally slow on 7// GPU/ASIC because it requires 1024-byte block-at-a-time mixing 8// that can't be unrolled or pipelined deeply. 9// 10// ===== G compression function (RFC 9106 sec 3.5) ================= 11// 12// G(X, Y) -> Z where X, Y, Z are each 1024-byte blocks. 13// 14// 1. R = X XOR Y (1024 bytes) 15// 2. Save R as R_save for the final XOR. 16// 3. View R as 8x8 matrix of u128 registers (each u128 = 2 u64). 17// Equivalently: 8 rows of 16 u64 words (a row = 128 bytes). 18// 4. Row-wise: apply P to each of 8 rows. 19// 5. Column-wise: apply P to each of 8 "columns" (gathered slice 20// of 16 u64 from rows i = 0..7 at columns 2j, 2j+1). 21// 6. Output Z = R XOR R_save. 22// 23// ===== P permutation (RFC 9106 sec 3.6) ========================== 24// 25// P operates on 16 u64 words v_0..v_15 in-place. Same call pattern 26// as BLAKE2b's round but using GB (modified G with 2*L(a)*L(b) 27// instead of message words): 28// 29// GB(v_0, v_4, v_8, v_12) 30// GB(v_1, v_5, v_9, v_13) 31// GB(v_2, v_6, v_10, v_14) 32// GB(v_3, v_7, v_11, v_15) 33// GB(v_0, v_5, v_10, v_15) 34// GB(v_1, v_6, v_11, v_12) 35// GB(v_2, v_7, v_8, v_13) 36// GB(v_3, v_4, v_9, v_14) 37// 38// ===== GB function (RFC 9106 sec 3.6) ============================ 39// 40// GB(a, b, c, d): 41// a = (a + b + 2*L(a)*L(b)) mod 2^64 42// d = rotr64(d XOR a, 32) 43// c = (c + d + 2*L(c)*L(d)) mod 2^64 44// b = rotr64(b XOR c, 24) 45// a = (a + b + 2*L(a)*L(b)) mod 2^64 46// d = rotr64(d XOR a, 16) 47// c = (c + d + 2*L(c)*L(d)) mod 2^64 48// b = rotr64(b XOR c, 63) 49// 50// where L(x) = x mod 2^32 (lower 32 bits of x). The MULTIPLY 51// is critical -- it's what makes G expensive on hardware that 52// has weak 32-bit-integer multiplication (older GPUs). Modern 53// GPUs DO have fast int-mul but the memory-traffic pattern (a 54// different 1024-byte block per G call) is what blunts them. 55// 56// ===== Caller-owns-memory pattern ================================ 57// 58// Caller supplies: 59// - r: 128 i64 array = 1024 bytes for R working space 60// - rsave: 128 i64 array = 1024 bytes for R_save 61// - col_tmp: 16 i64 array = column gather scratch 62// 63// license_tier: ORIGINAL 64 65// nx_safety_envelope: 66// intended_use: Argon2 G compression / P permutation 67// sil_target: SIL2 68// evidence: [g_x_x_returns_zero_block, 69// g_symmetric_x_y_equals_y_x, 70// determinism_two_runs_same_output, 71// regression_capture_known_input] 72// verdict: NOT_YET_EVALUATED (gating: cross-validate 73// against argon2_ref C output on a real Argon2id 74// end-to-end run, next session) 75 76import "nx_blake2b.nx" // for shared rotr64 / load/store helpers 77const NX_MAGIC_4294967295: i64 = 4294967295 78 79// ===== Verdict ==================================================== 80 81const NX_A2B_OK: i64 = 0 82const NX_A2B_BAD_ARG: i64 = 1 83const NX_A2B_VERDICT_N: i64 = 2 84 85func nx_a2b_verdict_name(v: i64) -> *u8 { 86 if v == NX_A2B_OK { return "OK" } 87 if v == NX_A2B_BAD_ARG { return "BAD_ARG" } 88 return "UNKNOWN" 89} 90 91const NX_A2B_BLOCK_BYTES: i64 = 1024 92const NX_A2B_BLOCK_WORDS: i64 = 128 // 1024 / 8 93 94// ===== Local 64-bit helpers (avoid name-clashing across modules) ==== 95 96func _a2b_shr64(x: i64, n: i64) -> i64 { 97 let nn: i64 = n & 63 98 if nn == 0 { return x } 99 let mask_lo: i64 = (1 << (64 - nn)) - 1 100 return (x >> nn) & mask_lo 101} 102 103func _a2b_rotr64(x: i64, n: i64) -> i64 { 104 let nn: i64 = n & 63 105 if nn == 0 { return x } 106 let lo: i64 = _a2b_shr64(x, nn) 107 let hi: i64 = x << (64 - nn) 108 return lo | hi 109} 110 111func _a2b_load_u64_le(buf: *u8, off: i64) -> i64 { 112 let b0: i64 = (buf[off + 0] as i64) & 255 113 let b1: i64 = (buf[off + 1] as i64) & 255 114 let b2: i64 = (buf[off + 2] as i64) & 255 115 let b3: i64 = (buf[off + 3] as i64) & 255 116 let b4: i64 = (buf[off + 4] as i64) & 255 117 let b5: i64 = (buf[off + 5] as i64) & 255 118 let b6: i64 = (buf[off + 6] as i64) & 255 119 let b7: i64 = (buf[off + 7] as i64) & 255 120 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 121 | (b4 << 32) | (b5 << 40) | (b6 << 48) | (b7 << 56) 122} 123 124func _a2b_store_u64_le(buf: *u8, off: i64, v: i64) -> i64 { 125 buf[off + 0] = (v & 255) as u8 126 buf[off + 1] = (_a2b_shr64(v, 8) & 255) as u8 127 buf[off + 2] = (_a2b_shr64(v, 16) & 255) as u8 128 buf[off + 3] = (_a2b_shr64(v, 24) & 255) as u8 129 buf[off + 4] = (_a2b_shr64(v, 32) & 255) as u8 130 buf[off + 5] = (_a2b_shr64(v, 40) & 255) as u8 131 buf[off + 6] = (_a2b_shr64(v, 48) & 255) as u8 132 buf[off + 7] = (_a2b_shr64(v, 56) & 255) as u8 133 return 0 134} 135 136// ===== GB function ================================================= 137// 138// Operates in-place on v[ia], v[ib], v[ic], v[id]. 139 140func _a2b_gb(v: *i64, ia: i64, ib: i64, ic: i64, id: i64) -> i64 { 141 let mask32: i64 = NX_MAGIC_4294967295 // 0xFFFFFFFF 142 143 // Step 1: a = a + b + 2*L(a)*L(b) 144 let la1: i64 = v[ia] & mask32 145 let lb1: i64 = v[ib] & mask32 146 let prod1: i64 = la1 * lb1 147 v[ia] = v[ia] + v[ib] + (prod1 << 1) 148 // d = rotr(d XOR a, 32) 149 v[id] = _a2b_rotr64(v[id] ^ v[ia], 32) 150 // Step 2: c = c + d + 2*L(c)*L(d) 151 let lc2: i64 = v[ic] & mask32 152 let ld2: i64 = v[id] & mask32 153 let prod2: i64 = lc2 * ld2 154 v[ic] = v[ic] + v[id] + (prod2 << 1) 155 // b = rotr(b XOR c, 24) 156 v[ib] = _a2b_rotr64(v[ib] ^ v[ic], 24) 157 // Step 3: a = a + b + 2*L(a)*L(b) 158 let la3: i64 = v[ia] & mask32 159 let lb3: i64 = v[ib] & mask32 160 let prod3: i64 = la3 * lb3 161 v[ia] = v[ia] + v[ib] + (prod3 << 1) 162 // d = rotr(d XOR a, 16) 163 v[id] = _a2b_rotr64(v[id] ^ v[ia], 16) 164 // Step 4: c = c + d + 2*L(c)*L(d) 165 let lc4: i64 = v[ic] & mask32 166 let ld4: i64 = v[id] & mask32 167 let prod4: i64 = lc4 * ld4 168 v[ic] = v[ic] + v[id] + (prod4 << 1) 169 // b = rotr(b XOR c, 63) 170 v[ib] = _a2b_rotr64(v[ib] ^ v[ic], 63) 171 return 0 172} 173 174// ===== P permutation on 16-word slice starting at base ============ 175 176func _a2b_p(v: *i64, base: i64) -> i64 { 177 // Column step. 178 _a2b_gb(v, base + 0, base + 4, base + 8, base + 12) 179 _a2b_gb(v, base + 1, base + 5, base + 9, base + 13) 180 _a2b_gb(v, base + 2, base + 6, base + 10, base + 14) 181 _a2b_gb(v, base + 3, base + 7, base + 11, base + 15) 182 // Diagonal step. 183 _a2b_gb(v, base + 0, base + 5, base + 10, base + 15) 184 _a2b_gb(v, base + 1, base + 6, base + 11, base + 12) 185 _a2b_gb(v, base + 2, base + 7, base + 8, base + 13) 186 _a2b_gb(v, base + 3, base + 4, base + 9, base + 14) 187 return 0 188} 189 190// ===== G compression function ===================================== 191// 192// nx_argon2_g(x, y, out, r, rsave, col_tmp) -> verdict 193// 194// All buffer pointers are 1024-byte blocks unless noted: 195// x, y, out: *u8, 1024 bytes each 196// r, rsave: *i64, 128 entries each (1024 bytes each) 197// col_tmp: *i64, 16 entries (column gather scratch) 198 199func nx_argon2_g(x: *u8, y: *u8, out: *u8, 200 r: *i64, rsave: *i64, col_tmp: *i64) -> i64 { 201 if x == (0 as *u8) { return NX_A2B_BAD_ARG } 202 if y == (0 as *u8) { return NX_A2B_BAD_ARG } 203 if out == (0 as *u8) { return NX_A2B_BAD_ARG } 204 205 // 1. Load R = X XOR Y as 128 i64 LE words. 206 var i: i64 = 0 207 while i < NX_A2B_BLOCK_WORDS { 208 let xi: i64 = _a2b_load_u64_le(x, i * 8) 209 let yi: i64 = _a2b_load_u64_le(y, i * 8) 210 r[i] = xi ^ yi 211 i = i + 1 212 } 213 214 // 2. Save R for final XOR. 215 i = 0 216 while i < NX_A2B_BLOCK_WORDS { 217 rsave[i] = r[i] 218 i = i + 1 219 } 220 221 // 3. Row-wise P (8 rows of 16 u64). 222 var row: i64 = 0 223 while row < 8 { 224 _a2b_p(r, row * 16) 225 row = row + 1 226 } 227 228 // 4. Column-wise P. 229 // Column j (0..7) consists of u64 indices [2j, 2j+1, 16+2j, 16+2j+1, ..., 112+2j, 112+2j+1]. 230 // Gather, run P, scatter back. 231 var col: i64 = 0 232 while col < 8 { 233 var rr: i64 = 0 234 while rr < 8 { 235 col_tmp[rr * 2 + 0] = r[rr * 16 + col * 2 + 0] 236 col_tmp[rr * 2 + 1] = r[rr * 16 + col * 2 + 1] 237 rr = rr + 1 238 } 239 _a2b_p(col_tmp, 0) 240 rr = 0 241 while rr < 8 { 242 r[rr * 16 + col * 2 + 0] = col_tmp[rr * 2 + 0] 243 r[rr * 16 + col * 2 + 1] = col_tmp[rr * 2 + 1] 244 rr = rr + 1 245 } 246 col = col + 1 247 } 248 249 // 5. Z = R XOR R_save, write LE to out. 250 i = 0 251 while i < NX_A2B_BLOCK_WORDS { 252 let z: i64 = r[i] ^ rsave[i] 253 _a2b_store_u64_le(out, i * 8, z) 254 i = i + 1 255 } 256 257 return NX_A2B_OK 258}