nx_argon2id_b0_bisect.nx source
↩ module page · 104 lines · 4079 B
1// nx_argon2id_b0_bisect.nx -- bisect step 2: verify B[0] derivation.
2//
3// expect_exit: 0
4//
5// Expected B[0] first 32 bytes (computed via Python implementing
6// RFC 9106 sec 3.4 H' on the known H_0):
7// dd59a5e70e1b91a4 1038690fb83c433d d014fc81c93b6b62 cad0e5f58d797780
8//
9// license_tier: ORIGINAL
10
11import "nx_syscalls_x86_64.nx"
12import "nx_blake2b.nx"
13import "nx_blake2b_long.nx"
14import "nx_argon2_block.nx"
15import "nx_argon2id.nx"
16const K_MAGIC_1024: i64 = 1024
17const K_MAGIC_2048: i64 = 2048
18
19func make_ctx() -> *NxArgon2idCtx {
20 let raw: *u8 = sys_mmap(NX_ARGON2ID_CTX_BYTES)
21 let ctx: *NxArgon2idCtx = raw as *NxArgon2idCtx
22 ctx.memory_blocks = sys_mmap(8 * K_MAGIC_1024)
23 ctx.h0_buf = sys_mmap(64)
24 ctx.prepend_buf = sys_mmap(K_MAGIC_2048)
25 ctx.prev_buf = sys_mmap(64)
26 ctx.curr_buf = sys_mmap(64)
27 ctx.zero_block = sys_mmap(K_MAGIC_1024)
28 ctx.z_buf = sys_mmap(K_MAGIC_1024)
29 ctx.tmp_block = sys_mmap(K_MAGIC_1024)
30 ctx.addr_block = sys_mmap(K_MAGIC_1024)
31 ctx.final_block = sys_mmap(K_MAGIC_1024)
32 ctx.h0_input = sys_mmap(K_MAGIC_2048)
33 ctx.b2b_ctx = sys_mmap(NX_BLAKE2B_CTX_BYTES) as *NxBlake2b
34 ctx.b2b_buf = sys_mmap(128)
35 ctx.b2b_sv = sys_mmap(128) as *i64
36 ctx.b2b_sm = sys_mmap(128) as *i64
37 ctx.g_r = sys_mmap(K_MAGIC_1024) as *i64
38 ctx.g_rs = sys_mmap(K_MAGIC_1024) as *i64
39 ctx.g_col = sys_mmap(128) as *i64
40 return ctx
41}
42
43func main() -> i64 {
44 let ctx: *NxArgon2idCtx = make_ctx()
45 let password: *u8 = "password" as *u8
46 let salt: *u8 = "somesalt" as *u8
47
48 // Step 1: H_0
49 if _ar2_h0(ctx, password, 8, salt, 8, 1, 32, 8, 2) != NX_BLAKE2B_OK { return 1 }
50
51 // Step 2: build H_0 || le32(0) || le32(0) into h0_input
52 // (mirroring what nx_argon2id_hash does)
53 var k: i64 = 0
54 while k < 64 { ctx.h0_input[k] = ctx.h0_buf[k]; k = k + 1 }
55 ctx.h0_input[64] = 0 as u8; ctx.h0_input[65] = 0 as u8
56 ctx.h0_input[66] = 0 as u8; ctx.h0_input[67] = 0 as u8
57 ctx.h0_input[68] = 0 as u8; ctx.h0_input[69] = 0 as u8
58 ctx.h0_input[70] = 0 as u8; ctx.h0_input[71] = 0 as u8
59
60 // Compute B[0] = H'(1024, h0_input[0..72])
61 let b0_ptr: *u8 = ((ctx.memory_blocks as i64) + 0) as *u8
62 if nx_blake2b_long(ctx.h0_input, 72,
63 b0_ptr, K_MAGIC_1024,
64 ctx.prepend_buf, ctx.prev_buf, ctx.curr_buf,
65 ctx.b2b_ctx, ctx.b2b_buf,
66 ctx.b2b_sv, ctx.b2b_sm) != NX_B2BL_OK { return 2 }
67
68 // Check last 32 bytes (the H' fix specifically affects bytes 992..1023).
69 // Expected last 32 bytes from C reference: FF 2C B3 DC 3A 3A B3 E8 ...
70 // From the libargon2 dump captured 2026-05-17:
71 // B[0] last 32 = ff2cb3dc3a3ab3e8... (was wrong with off-by-one H')
72 //
73 // Rather than hardcode all 32 expected bytes here (and have to keep
74 // the test in sync with reference), this bisect now compares first
75 // AND last 8 bytes -- a sentinel pair that fails BOTH if H' loop
76 // bounds were wrong (last bytes) AND if H' input encoding was wrong
77 // (first bytes).
78 let expected_first8: *u8 = sys_mmap(8)
79 expected_first8[0] = 221 as u8; expected_first8[1] = 89 as u8
80 expected_first8[2] = 165 as u8; expected_first8[3] = 231 as u8
81 expected_first8[4] = 14 as u8; expected_first8[5] = 27 as u8
82 expected_first8[6] = 145 as u8; expected_first8[7] = 164 as u8
83
84 var i: i64 = 0
85 while i < 8 {
86 if b0_ptr[i] != expected_first8[i] { return 100 + i }
87 i = i + 1
88 }
89
90 // Last 8 bytes from Python pyref (verified byte-exact against
91 // libargon2) for B[0] at offset 1016..1023: 1e 37 ff 6e a1 39 19 3a.
92 let expected_last8: *u8 = sys_mmap(8)
93 expected_last8[0] = 30 as u8; expected_last8[1] = 55 as u8
94 expected_last8[2] = 255 as u8; expected_last8[3] = 110 as u8
95 expected_last8[4] = 161 as u8; expected_last8[5] = 57 as u8
96 expected_last8[6] = 25 as u8; expected_last8[7] = 58 as u8
97
98 i = 0
99 while i < 8 {
100 if b0_ptr[1016 + i] != expected_last8[i] { return 200 + i }
101 i = i + 1
102 }
103 return 0
104}