nx_argon2_block_test.nx source
↩ module page · 127 lines · 4235 B
1// nx_argon2_block_test.nx -- algebraic-property smoke for G(X, Y).
2//
3// expect_exit: 0
4//
5// Tests:
6// 1. G(0, 0) = 0 -- all-zero in, all-zero out (R = 0 XOR 0 = 0;
7// GB(0,0,0,0) = (0,0,0,0); final XOR yields 0).
8// 2. G(X, X) = 0 -- self-XOR makes R = 0; same as test 1.
9// 3. G(X, Y) symmetric: G(X, Y) == G(Y, X). R = X XOR Y is
10// symmetric, P operates on R, final XOR R is symmetric.
11// 4. G(X, Y) deterministic: two runs yield same output.
12// 5. G(X, Y) sensitive to a single-bit flip in X (avalanche
13// check): flipping one bit of X must change at least 256
14// bits of output (4x SHA-256-style avalanche; Argon2's G
15// empirically has very strong avalanche due to multiplications).
16//
17// license_tier: ORIGINAL
18
19import "nx_syscalls_x86_64.nx"
20import "nx_argon2_block.nx"
21
22func bytes_eq(a: *u8, b: *u8, n: i64) -> i64 {
23 var i: i64 = 0
24 while i < n {
25 if a[i] != b[i] { return 0 }
26 i = i + 1
27 }
28 return 1
29}
30
31func bits_differ(a: *u8, b: *u8, n: i64) -> i64 {
32 var diff: i64 = 0
33 var i: i64 = 0
34 while i < n {
35 let x: i64 = (a[i] as i64) ^ (b[i] as i64)
36 // popcount of x (0..255).
37 var bit: i64 = 0
38 while bit < 8 {
39 if ((x >> bit) & 1) == 1 { diff = diff + 1 }
40 bit = bit + 1
41 }
42 i = i + 1
43 }
44 return diff
45}
46
47func main() -> i64 {
48 let zero1: *u8 = sys_mmap(1024)
49 let zero2: *u8 = sys_mmap(1024)
50 // mmap returns zeroed memory under Linux MAP_ANONYMOUS, so
51 // zero1 + zero2 are already 1024 zero bytes.
52
53 let out1: *u8 = sys_mmap(1024)
54 let out2: *u8 = sys_mmap(1024)
55 let out3: *u8 = sys_mmap(1024)
56 let r_scratch: *i64 = sys_mmap(1024) as *i64
57 let rs_scratch: *i64 = sys_mmap(1024) as *i64
58 let col_tmp: *i64 = sys_mmap(128) as *i64
59
60 // ---- Test 1: G(0, 0) = 0 ----
61 if nx_argon2_g(zero1, zero2, out1,
62 r_scratch, rs_scratch, col_tmp) != NX_A2B_OK { return 1 }
63 var i: i64 = 0
64 while i < 1024 {
65 if out1[i] != (0 as u8) { return 2 }
66 i = i + 1
67 }
68
69 // ---- Build a non-trivial X (pattern: i mod 251) ----
70 let x: *u8 = sys_mmap(1024)
71 let y: *u8 = sys_mmap(1024)
72 i = 0
73 while i < 1024 {
74 x[i] = (i % 251) as u8
75 y[i] = ((i * 7 + 13) % 251) as u8
76 i = i + 1
77 }
78
79 // ---- Test 2: G(X, X) = 0 ----
80 if nx_argon2_g(x, x, out1,
81 r_scratch, rs_scratch, col_tmp) != NX_A2B_OK { return 10 }
82 i = 0
83 while i < 1024 {
84 if out1[i] != (0 as u8) { return 11 }
85 i = i + 1
86 }
87
88 // ---- Test 3: G(X, Y) symmetric ----
89 if nx_argon2_g(x, y, out1,
90 r_scratch, rs_scratch, col_tmp) != NX_A2B_OK { return 20 }
91 if nx_argon2_g(y, x, out2,
92 r_scratch, rs_scratch, col_tmp) != NX_A2B_OK { return 21 }
93 if bytes_eq(out1, out2, 1024) != 1 { return 22 }
94
95 // ---- Test 4: determinism ----
96 if nx_argon2_g(x, y, out3,
97 r_scratch, rs_scratch, col_tmp) != NX_A2B_OK { return 30 }
98 if bytes_eq(out1, out3, 1024) != 1 { return 31 }
99
100 // ---- Test 5: avalanche -- flip one bit of x, expect many bits change ----
101 let x_flipped: *u8 = sys_mmap(1024)
102 i = 0
103 while i < 1024 {
104 x_flipped[i] = x[i]
105 i = i + 1
106 }
107 x_flipped[0] = (x_flipped[0] as i64 ^ 1) as u8
108 if nx_argon2_g(x_flipped, y, out2,
109 r_scratch, rs_scratch, col_tmp) != NX_A2B_OK { return 40 }
110 let diff: i64 = bits_differ(out1, out2, 1024)
111 // Argon2's G has very strong avalanche due to multiplications.
112 // Expect roughly 50% of bits to flip = ~4096 bits. Require >= 1024
113 // (12.5%) as a conservative lower bound that catches obvious
114 // implementation breakage but doesn't false-fail on statistical
115 // variance.
116 if diff < 1024 { return 41 }
117
118 // ---- BAD_ARG ----
119 if nx_argon2_g(0 as *u8, y, out1, r_scratch, rs_scratch, col_tmp)
120 != NX_A2B_BAD_ARG { return 50 }
121 if nx_argon2_g(x, 0 as *u8, out1, r_scratch, rs_scratch, col_tmp)
122 != NX_A2B_BAD_ARG { return 51 }
123 if nx_argon2_g(x, y, 0 as *u8, r_scratch, rs_scratch, col_tmp)
124 != NX_A2B_BAD_ARG { return 52 }
125
126 return 0
127}