nx_opaque_core.nx source
↩ module page · 408 lines · 18814 B
1// nx_opaque_core.nx -- V-MODAUTH-2a: OPAQUE (RFC 9807) shared primitives.
2//
3// OPAQUE = the asymmetric PAKE the modern-auth charter names as the auth
4// crown jewel: the server NEVER sees the password and stores NOTHING that
5// permits an offline dictionary attack without ALSO stealing the server's
6// long-term oprf_seed (and even then must pay the memory-hard KSF per guess).
7//
8// This module is the primitive floor the rest of V-MODAUTH-2 composes:
9// nx_opq_hash_to_scalar RFC 9497 §4.4 HashToScalar (P-256 group order n)
10// nx_opq_derive_keypair RFC 9497 §3.2 DeriveKeyPair (seed -> sk, pk)
11// nx_opq_dh RFC 9807 §6.4.1.2 DiffieHellman (P-256, compressed out)
12// nx_opq_expand_label RFC 9807 §6.4.2.1 Expand-Label (TLS 1.3 style, "OPAQUE-" prefix)
13// nx_opq_derive_secret RFC 9807 §6.4.2.1 Derive-Secret
14// nx_opq_stretch RFC 9807 KSF hook: mode 0 = Identity (RFC KAT), mode 1 = argon2id
15// nx_opq_randomized_password RFC 9807 §5.2.3 Extract("", oprf_output || stretched)
16// nx_opq_ct_eq constant-time tag compare
17//
18// COMPOSES (avoid duplicate primitives):
19// hub/nx_voprf + hub/nx_voprf_finalize RFC 9497 OPRF (Blind/BlindEvaluate/Finalize) -- VERIFIED CAPREG299
20// hub/nx_h2c_p256 expand_message_xmd (RFC 9380)
21// nx_p256_modn / nx_p256_point / nx_p256_scalar_mul group arithmetic
22// nx_hkdf / nx_hmac Extract / Expand / MAC
23// nx_argon2id memory-hard KSF (RFC 9106) -- vault KDF v2 lineage
24//
25// COMPOSED BY:
26// hub/nx_opaque_envelope.nx §4 Store/Recover
27// hub/nx_opaque_3dh.nx §6.4 AKE key schedule
28// hub/nx_opaque_pake.nx §5 + §6 top-level registration/login
29//
30// SPEC REFERENCES:
31// RFC 9807 (OPAQUE) §4, §5, §6; test vectors Appendix C.1.5/C.1.6 (P256-SHA256)
32// RFC 9497 (OPRF) §3.2 DeriveKeyPair, §4.4 HashToScalar for P256-SHA256
33// RFC 8446 §7.1 HKDF-Expand-Label encoding (repurposed with "OPAQUE-" prefix)
34// vectors staged: knowledge/specs/2026-06-10-rfc9807-opaque-p256-vectors.ref
35// license_tier: ORIGINAL
36
37import "nx_syscalls.nx"
38import "nx_u256.nx"
39import "nx_p256_field.nx"
40import "nx_p256_field_inv.nx"
41import "nx_p256_modn.nx"
42import "nx_p256_point.nx"
43import "nx_p256_point_add.nx"
44import "nx_p256_scalar_mul.nx"
45import "nx_csprng.nx"
46import "sha256.nx"
47import "nx_hmac.nx"
48import "nx_hkdf.nx"
49import "nx_argon2id.nx"
50import "hub/nx_h2c_p256.nx"
51import "hub/nx_voprf.nx"
52import "hub/nx_voprf_finalize.nx"
53
54// ===== Sealed verdict surface (codes 1440-1459) =================================================
55const NX_OPQ_OK: i64 = 0
56const NX_OPQ_BAD_INPUT: i64 = 1440
57const NX_OPQ_BUF_OVERFLOW: i64 = 1441
58const NX_OPQ_DERIVE_FAILED: i64 = 1442 // DeriveKeyPair exhausted 255 counters (negligible)
59const NX_OPQ_DH_FAILED: i64 = 1443 // invalid peer point / infinity result
60const NX_OPQ_ENVELOPE_AUTH_FAIL: i64 = 1444 // Recover: auth_tag mismatch (wrong password)
61const NX_OPQ_SERVER_AUTH_FAIL: i64 = 1445 // client: KE2 server_mac mismatch
62const NX_OPQ_CLIENT_AUTH_FAIL: i64 = 1446 // server: KE3 client_mac mismatch
63const NX_OPQ_OPRF_FAILED: i64 = 1447
64const NX_OPQ_KSF_FAILED: i64 = 1448
65const NX_OPQ_CSPRNG_FAILED: i64 = 1449
66
67// ===== Suite parameters: OPAQUE-3DH over OPRF(P-256, SHA-256) per RFC 9807 §7 ====================
68const NX_OPQ_NH: i64 = 32 // hash output
69const NX_OPQ_NPK: i64 = 33 // compressed P-256 public key
70const NX_OPQ_NSK: i64 = 32 // scalar private key
71const NX_OPQ_NM: i64 = 32 // MAC output
72const NX_OPQ_NX: i64 = 32 // AKE secret length
73const NX_OPQ_NOK: i64 = 32 // OPRF private key
74const NX_OPQ_NOE: i64 = 33 // serialized OPRF group element
75const NX_OPQ_NN: i64 = 32 // nonce length
76const NX_OPQ_NSEED: i64 = 32 // keypair derivation seed length
77
78const NX_OPQ_MAX_ID_LEN: i64 = 128 // client/server identity cap (charter realm cap)
79const NX_OPQ_MAX_PW_LEN: i64 = 256 // matches NX_MAUTH_MAX_PASSPHRASE_LEN
80const NX_OPQ_MAX_CONTEXT_LEN: i64 = 64
81
82// KSF modes for nx_opq_stretch:
83const NX_OPQ_KSF_IDENTITY: i64 = 0 // RFC test-vector configuration (KAT only; NEVER production)
84const NX_OPQ_KSF_ARGON2ID: i64 = 1 // production: memory-hard per charter + vault KDF v2 lineage
85
86// argon2id KSF salt: fixed all-zero 16 bytes. Deterministic by REQUIREMENT (the same
87// password must re-derive the same envelope keys at every login); per-user uniqueness
88// already comes from the per-credential OPRF key, so a fixed KSF salt adds no risk
89// (RFC 9807 §7 lists scrypt(S=zeroes(16)) the same way).
90const NX_OPQ_KSF_SALT_BYTES: i64 = 16
91// argon2id per-call scratch sizes (nx_opq_stretch): named ONCE so map and release cannot disagree.
92const NX_OPQ_KIB: i64 = 1024
93const NX_OPQ_A2_SMALL: i64 = 64 // h0 / prev / curr blake2b digests
94const NX_OPQ_A2_PREPEND: i64 = 1100 // H0 prepend scratch
95const NX_OPQ_A2_BLOCK: i64 = 1024 // one argon2 block (and the g_r/g_rs/h0_input/addr/tmp/final/zero blocks)
96const NX_OPQ_A2_B2B: i64 = 128 // blake2b buf/sv/sm + g_col
97// zeroing helper for secret-bearing scratch (a plain byte loop; nothing here is timing-sensitive)
98func _opq_wipe(p: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { p[i] = 0 as u8; i = i + 1 } return 0 }
99
100// ===== constant-time compare =================================================
101
102func nx_opq_ct_eq(a: *u8, b: *u8, n: i64) -> i64 {
103 var diff: i64 = 0
104 var i: i64 = 0
105 while i < n {
106 diff = diff | ((a[i] as i64) ^ (b[i] as i64))
107 i = i + 1
108 }
109 if diff == 0 { return 1 }
110 return 0
111}
112
113// ===== HashToScalar per RFC 9497 §4.4 (P256-SHA256 suite) =================================================
114//
115// uniform = expand_message_xmd_sha256(msg, DST, 48); out = OS2IP(uniform) mod n.
116// Reduction mirrors _h2c_reduce_48be_mod_p but over the GROUP ORDER n:
117// value = high16 * 2^256 + low32; out = (high16 * Rn + low) mod n, Rn = 2^256 mod n.
118
119func _opq_load_Rn(out: *i64) -> i64 {
120 // Rn = 2^256 - n (n > 2^255 so 2^256 mod n = 2^256 - n)
121 // = 0x00000000 FFFFFFFF 00000000 00000000 43190552 58E8617B 0C46353D 039CDAAF (BE words)
122 out[0] = 0x039CDAAF as i64
123 out[1] = 0x0C46353D as i64
124 out[2] = 0x58E8617B as i64
125 out[3] = 0x43190552 as i64
126 out[4] = 0x00000000 as i64
127 out[5] = 0x00000000 as i64
128 out[6] = 0xFFFFFFFF as i64
129 out[7] = 0x00000000 as i64
130 return NX_OPQ_OK
131}
132
133func _opq_reduce_48be_mod_n(bytes_48: *u8, out_scalar: *i64) -> i64 {
134 let high_u256: *i64 = u256_alloc()
135 let low_u256: *i64 = u256_alloc()
136 let Rn: *i64 = u256_alloc()
137 let n: *i64 = u256_alloc()
138
139 let high_be_32: *u8 = sys_mmap(32)
140 var i: i64 = 0
141 while i < 16 { high_be_32[i] = 0 as u8; i = i + 1 }
142 var j: i64 = 0
143 while j < 16 { high_be_32[16 + j] = bytes_48[j]; j = j + 1 }
144 u256_load_be(high_u256, high_be_32)
145 u256_load_be(low_u256, (bytes_48 as i64 + 16) as *u8)
146
147 p256_modn_load_n(n)
148 if u256_cmp(low_u256, n) >= 0 {
149 let tmp: *i64 = u256_alloc()
150 u256_sub_with_borrow(tmp, low_u256, n)
151 u256_copy(low_u256, tmp)
152 }
153
154 _opq_load_Rn(Rn)
155 let prod: *i64 = u256_alloc()
156 p256_modn_mul(prod, high_u256, Rn)
157 p256_modn_add(out_scalar, prod, low_u256)
158 return NX_OPQ_OK
159}
160
161// msg -> scalar in [0, n-1] as 32-byte BE. dst is caller-built ("DeriveKeyPair" || contextString etc).
162func nx_opq_hash_to_scalar(msg: *u8, msg_n: i64, dst: *u8, dst_n: i64, out_32: *u8) -> i64 {
163 if (msg as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
164 if (dst as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
165 if (out_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
166 let uniform: *u8 = sys_mmap(48)
167 let rc: i64 = nx_h2c_expand_message_xmd_sha256(msg, msg_n, dst, dst_n, 48, uniform)
168 if rc != NX_H2C_OK { return 0 - NX_OPQ_DERIVE_FAILED }
169 let scalar: *i64 = u256_alloc()
170 _opq_reduce_48be_mod_n(uniform, scalar)
171 u256_store_be(out_32, scalar)
172 return NX_OPQ_OK
173}
174
175// ===== DeriveKeyPair per RFC 9497 §3.2 =================================================
176//
177// deriveInput = seed || I2OSP(len(info), 2) || info
178// loop counter 0..255: sk = HashToScalar(deriveInput || I2OSP(counter,1),
179// DST = "DeriveKeyPair" || contextString)
180// until sk != 0; pk = ScalarMultGen(sk) compressed.
181// contextString = the OPRF one (20 bytes, mode 0x00, P256-SHA256) -- reuses nx_voprf's loader.
182
183func nx_opq_derive_keypair(
184 seed_32: *u8,
185 info: *u8, info_n: i64,
186 out_sk_32: *u8,
187 out_pk_33: *u8
188) -> i64 {
189 if (seed_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
190 if (info as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
191 if info_n < 0 { return 0 - NX_OPQ_BAD_INPUT }
192 if info_n > 255 { return 0 - NX_OPQ_BAD_INPUT }
193 if (out_sk_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
194
195 // DST = "DeriveKeyPair" (13) || contextString (20) = 33 bytes
196 let dst: *u8 = sys_mmap(40)
197 let dkp: *u8 = "DeriveKeyPair" as *u8
198 var d: i64 = 0
199 while d < 13 { dst[d] = dkp[d]; d = d + 1 }
200 let ctx20: *u8 = sys_mmap(NX_VOPRF_CONTEXT_STR_LEN)
201 _voprf_load_context_string(ctx20)
202 var c: i64 = 0
203 while c < NX_VOPRF_CONTEXT_STR_LEN { dst[13 + c] = ctx20[c]; c = c + 1 }
204 let dst_n: i64 = 13 + NX_VOPRF_CONTEXT_STR_LEN
205
206 // deriveInput || counter byte
207 let din_n: i64 = NX_OPQ_NSEED + 2 + info_n
208 let buf: *u8 = sys_mmap(din_n + 1)
209 var i: i64 = 0
210 while i < NX_OPQ_NSEED { buf[i] = seed_32[i]; i = i + 1 }
211 buf[NX_OPQ_NSEED] = ((info_n >> 8) & 0xFF) as u8
212 buf[NX_OPQ_NSEED + 1] = (info_n & 0xFF) as u8
213 var k: i64 = 0
214 while k < info_n { buf[NX_OPQ_NSEED + 2 + k] = info[k]; k = k + 1 }
215
216 let sk_limbs: *i64 = u256_alloc()
217 var counter: i64 = 0
218 var found: i64 = 0
219 while counter < 256 {
220 if found == 0 {
221 buf[din_n] = (counter & 0xFF) as u8
222 let rc: i64 = nx_opq_hash_to_scalar(buf, din_n + 1, dst, dst_n, out_sk_32)
223 if rc != NX_OPQ_OK { return rc }
224 u256_load_be(sk_limbs, out_sk_32)
225 if u256_is_zero(sk_limbs) == 0 { found = 1 }
226 }
227 counter = counter + 1
228 }
229 if found == 0 { return 0 - NX_OPQ_DERIVE_FAILED }
230
231 if (out_pk_33 as i64) != 0 {
232 let g: *P256Point = p256_point_alloc()
233 p256_point_load_g(g)
234 let pk_pt: *P256Point = p256_point_alloc()
235 p256_scalar_mul(pk_pt, sk_limbs, g)
236 return nx_voprf_serialize_element(pk_pt, out_pk_33)
237 }
238 return NX_OPQ_OK
239}
240
241// ===== DiffieHellman per RFC 9807 §6.4.1.2 (P-256) =================================================
242//
243// out = SerializeElement(sk * Deserialize(pk_33)) -- 33-byte compressed shared point.
244
245func nx_opq_dh(sk_32: *u8, pk_33: *u8, out_33: *u8) -> i64 {
246 if (sk_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
247 if (pk_33 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
248 if (out_33 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
249 let peer: *P256Point = p256_point_alloc()
250 if nx_voprf_deserialize_element(pk_33, peer) != NX_VOPRF_OK {
251 return 0 - NX_OPQ_DH_FAILED
252 }
253 let sk_limbs: *i64 = u256_alloc()
254 u256_load_be(sk_limbs, sk_32)
255 if u256_is_zero(sk_limbs) == 1 { return 0 - NX_OPQ_DH_FAILED }
256 let shared: *P256Point = p256_point_alloc()
257 p256_scalar_mul(shared, sk_limbs, peer)
258 if p256_point_is_infinity(shared) == 1 { return 0 - NX_OPQ_DH_FAILED }
259 return nx_voprf_serialize_element(shared, out_33)
260}
261
262// ===== Expand-Label / Derive-Secret per RFC 9807 §6.4.2.1 =================================================
263//
264// CustomLabel = I2OSP(Length, 2) || I2OSP(7 + len(label), 1) || "OPAQUE-" || label
265// || I2OSP(len(context), 1) || context
266// Expand-Label(secret, label, context, L) = Expand(secret, CustomLabel, L)
267
268func nx_opq_expand_label(
269 secret_32: *u8,
270 label: *u8, label_n: i64,
271 context: *u8, context_n: i64,
272 out_len: i64, out: *u8
273) -> i64 {
274 if (secret_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
275 if (label as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
276 if label_n < 1 { return 0 - NX_OPQ_BAD_INPUT }
277 if label_n > 248 { return 0 - NX_OPQ_BAD_INPUT }
278 if context_n < 0 { return 0 - NX_OPQ_BAD_INPUT }
279 if context_n > 255 { return 0 - NX_OPQ_BAD_INPUT }
280 if (out as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
281
282 let cl_n: i64 = 2 + 1 + 7 + label_n + 1 + context_n
283 let cl: *u8 = sys_mmap(cl_n + 8)
284 var pos: i64 = 0
285 cl[0] = ((out_len >> 8) & 0xFF) as u8
286 cl[1] = (out_len & 0xFF) as u8
287 cl[2] = ((7 + label_n) & 0xFF) as u8
288 pos = 3
289 let prefix: *u8 = "OPAQUE-" as *u8
290 var i: i64 = 0
291 while i < 7 { cl[pos + i] = prefix[i]; i = i + 1 }
292 pos = pos + 7
293 var j: i64 = 0
294 while j < label_n { cl[pos + j] = label[j]; j = j + 1 }
295 pos = pos + label_n
296 cl[pos] = (context_n & 0xFF) as u8
297 pos = pos + 1
298 var k: i64 = 0
299 while k < context_n { cl[pos + k] = context[k]; k = k + 1 }
300 pos = pos + context_n
301
302 if hkdf_expand(secret_32, cl, pos, out_len, out) != 0 { return 0 - NX_OPQ_DERIVE_FAILED }
303 return NX_OPQ_OK
304}
305
306// Derive-Secret(secret, label, transcript_hash_32) -> Nx bytes.
307func nx_opq_derive_secret(
308 secret_32: *u8,
309 label: *u8, label_n: i64,
310 transcript_hash: *u8, transcript_hash_n: i64,
311 out_32: *u8
312) -> i64 {
313 return nx_opq_expand_label(secret_32, label, label_n,
314 transcript_hash, transcript_hash_n,
315 NX_OPQ_NX, out_32)
316}
317
318// ===== KSF Stretch + randomized_password per RFC 9807 §5.2.3 =================================================
319//
320// stretched = Stretch(oprf_output); randomized_password = Extract("", oprf_output || stretched).
321// ksf_mode IDENTITY exists ONLY so the RFC KAT (whose config is KSF=Identity) can gate the
322// surrounding protocol; production callers (nx_modern_auth_flow) pass ARGON2ID.
323
324func nx_opq_stretch(
325 oprf_output_32: *u8,
326 ksf_mode: i64, ksf_m_kib: i64, ksf_t: i64,
327 out_stretched_32: *u8
328) -> i64 {
329 if (oprf_output_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
330 if (out_stretched_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
331 if ksf_mode == NX_OPQ_KSF_IDENTITY {
332 var i: i64 = 0
333 while i < 32 { out_stretched_32[i] = oprf_output_32[i]; i = i + 1 }
334 return NX_OPQ_OK
335 }
336 if ksf_mode != NX_OPQ_KSF_ARGON2ID { return 0 - NX_OPQ_BAD_INPUT }
337
338 // Sizes are NAMED once and used by BOTH the map and the release below, so they cannot drift apart.
339 let mem_bytes: i64 = ksf_m_kib * NX_OPQ_KIB
340 let ctx: *NxArgon2idCtx = sys_mmap(NX_ARGON2ID_CTX_BYTES) as *NxArgon2idCtx
341 ctx.memory_blocks = sys_mmap(mem_bytes)
342 ctx.h0_buf = sys_mmap(NX_OPQ_A2_SMALL)
343 ctx.prepend_buf = sys_mmap(NX_OPQ_A2_PREPEND)
344 ctx.prev_buf = sys_mmap(NX_OPQ_A2_SMALL)
345 ctx.curr_buf = sys_mmap(NX_OPQ_A2_SMALL)
346 ctx.zero_block = sys_mmap(NX_OPQ_A2_BLOCK)
347 ctx.z_buf = sys_mmap(NX_OPQ_A2_BLOCK)
348 ctx.tmp_block = sys_mmap(NX_OPQ_A2_BLOCK)
349 ctx.addr_block = sys_mmap(NX_OPQ_A2_BLOCK)
350 ctx.final_block = sys_mmap(NX_OPQ_A2_BLOCK)
351 ctx.h0_input = sys_mmap(NX_OPQ_A2_BLOCK)
352 ctx.b2b_ctx = sys_mmap(NX_BLAKE2B_CTX_BYTES) as *NxBlake2b
353 ctx.b2b_buf = sys_mmap(NX_OPQ_A2_B2B)
354 ctx.b2b_sv = sys_mmap(NX_OPQ_A2_B2B) as *i64
355 ctx.b2b_sm = sys_mmap(NX_OPQ_A2_B2B) as *i64
356 ctx.g_r = sys_mmap(NX_OPQ_A2_BLOCK) as *i64
357 ctx.g_rs = sys_mmap(NX_OPQ_A2_BLOCK) as *i64
358 ctx.g_col = sys_mmap(NX_OPQ_A2_B2B) as *i64
359
360 let salt: *u8 = sys_mmap(NX_OPQ_KSF_SALT_BYTES)
361 var s: i64 = 0
362 while s < NX_OPQ_KSF_SALT_BYTES { salt[s] = 0 as u8; s = s + 1 }
363
364 let rc: i64 = nx_argon2id_hash(ctx, oprf_output_32, 32,
365 salt, NX_OPQ_KSF_SALT_BYTES,
366 1, 32, ksf_m_kib, ksf_t,
367 out_stretched_32)
368 // RELEASE THE KSF ARENA (2026-08-18, lane F box health). This function mapped ksf_m_kib KiB (19-64 MiB in
369 // production) plus ~10 KB of scratch PER CALL -- one call per login/register/recover -- and never freed a
370 // byte, so every login left tens of MiB of PASSWORD-DERIVED argon2 state resident (then swapped) for the
371 // daemon's whole life. WIPE FIRST, THEN UNMAP: munmap frees pages, it does not scrub what swap already holds,
372 // but a wiped page is never written back and a wiped arena-class buffer never resurfaces in a later
373 // allocation. Everything that saw secret-derived bytes is wiped; the >NXA_SMALL_MAX mappings are then
374 // released (sub-256 B buffers live in the syscall arena, where munmap is a documented no-op).
375 // out_stretched_32 is the ONLY output; nx_argon2id_hash retains no pointer into ctx.
376 _opq_wipe(ctx.memory_blocks, mem_bytes)
377 _opq_wipe(ctx.h0_buf, NX_OPQ_A2_SMALL); _opq_wipe(ctx.prepend_buf, NX_OPQ_A2_PREPEND)
378 _opq_wipe(ctx.prev_buf, NX_OPQ_A2_SMALL); _opq_wipe(ctx.curr_buf, NX_OPQ_A2_SMALL)
379 _opq_wipe(ctx.z_buf, NX_OPQ_A2_BLOCK); _opq_wipe(ctx.tmp_block, NX_OPQ_A2_BLOCK); _opq_wipe(ctx.addr_block, NX_OPQ_A2_BLOCK)
380 _opq_wipe(ctx.final_block, NX_OPQ_A2_BLOCK); _opq_wipe(ctx.h0_input, NX_OPQ_A2_BLOCK)
381 _opq_wipe(ctx.b2b_ctx as *u8, NX_BLAKE2B_CTX_BYTES); _opq_wipe(ctx.b2b_buf, NX_OPQ_A2_B2B)
382 _opq_wipe(ctx.b2b_sv as *u8, NX_OPQ_A2_B2B); _opq_wipe(ctx.b2b_sm as *u8, NX_OPQ_A2_B2B)
383 _opq_wipe(ctx.g_r as *u8, NX_OPQ_A2_BLOCK); _opq_wipe(ctx.g_rs as *u8, NX_OPQ_A2_BLOCK); _opq_wipe(ctx.g_col as *u8, NX_OPQ_A2_B2B)
384 sys_munmap(ctx.memory_blocks, mem_bytes)
385 sys_munmap(ctx.prepend_buf, NX_OPQ_A2_PREPEND)
386 sys_munmap(ctx.zero_block, NX_OPQ_A2_BLOCK); sys_munmap(ctx.z_buf, NX_OPQ_A2_BLOCK); sys_munmap(ctx.tmp_block, NX_OPQ_A2_BLOCK)
387 sys_munmap(ctx.addr_block, NX_OPQ_A2_BLOCK); sys_munmap(ctx.final_block, NX_OPQ_A2_BLOCK); sys_munmap(ctx.h0_input, NX_OPQ_A2_BLOCK)
388 sys_munmap(ctx.g_r as *u8, NX_OPQ_A2_BLOCK); sys_munmap(ctx.g_rs as *u8, NX_OPQ_A2_BLOCK)
389 if rc != NX_AR2_OK { return 0 - NX_OPQ_KSF_FAILED }
390 return NX_OPQ_OK
391}
392
393func nx_opq_randomized_password(
394 oprf_output_32: *u8,
395 stretched_32: *u8,
396 out_rwd_32: *u8
397) -> i64 {
398 if (oprf_output_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
399 if (stretched_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
400 if (out_rwd_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT }
401 let ikm: *u8 = sys_mmap(64)
402 var i: i64 = 0
403 while i < 32 { ikm[i] = oprf_output_32[i]; i = i + 1 }
404 var j: i64 = 0
405 while j < 32 { ikm[32 + j] = stretched_32[j]; j = j + 1 }
406 hkdf_extract(0 as *u8, 0, ikm, 64, out_rwd_32)
407 return NX_OPQ_OK
408}