nx_voprf_finalize.nx source
↩ module page · 72 lines · 3508 B
1// nx_voprf_finalize.nx -- RFC 9497 §3.3.1 Finalize, RELOCATED out of nx_voprf.nx.
2//
3// WHY ITS OWN FILE: the byte-identical Finalize body MISCOMPILED when defined inside nx_voprf.nx
4// (2026-06-10) -- it returned a memory-state-dependent wrong OPRF output (155bd560../6f3846e4.. vs
5// RFC A.3.1.1 a0b34de5..), while a byte-identical replica defined in a DIFFERENT module compiled
6// correctly. Root cause is a backend codegen heisenbug tied to this function's definition site in
7// that specific translation unit; every in-file workaround (guard-factoring, fresh name, non-terminal
8// position, encoding rewrite) reproduced the corruption. Relocating to a small dedicated module is the
9// robust fix (matches the proven-correct nx_voprf_rfc_kat replica). FILED: NXCC-VOPRF-FINALIZE-MISCOMPILE.
10//
11// Composes the still-in-nx_voprf primitives (deserialize/serialize) + the P-256 modn/scalar stack.
12// license_tier: ORIGINAL
13import "nx_voprf.nx"
14import "nx_syscalls.nx"
15
16func _vf_args_ok(input: *u8, input_n: i64, blind_32: *u8, evaluated_element_33: *u8, out_output_32: *u8) -> i64 {
17 if (input as i64) == 0 { return 0 }
18 if input_n < 0 { return 0 }
19 if input_n > NX_VOPRF_MAX_INPUT_LEN { return 0 }
20 if (blind_32 as i64) == 0 { return 0 }
21 if (evaluated_element_33 as i64) == 0 { return 0 }
22 if (out_output_32 as i64) == 0 { return 0 }
23 return 1
24}
25
26// RFC 9497 §3.3.1 Finalize(input, blind, evaluatedElement) -> 32-byte OPRF output.
27func nx_voprf_finalize(
28 input: *u8, input_n: i64,
29 blind_32: *u8,
30 evaluated_element_33: *u8,
31 out_output_32: *u8
32) -> i64 {
33 if _vf_args_ok(input, input_n, blind_32, evaluated_element_33, out_output_32) == 0 {
34 return 0 - NX_VOPRF_BAD_INPUT
35 }
36 // 1. evaluatedElement = DeserializeElement
37 let eval_pt: *P256Point = p256_point_alloc()
38 nx_voprf_deserialize_element(evaluated_element_33, eval_pt)
39 // 2. blindInverse = ModInverse(blind, n)
40 let blind: *i64 = u256_alloc()
41 u256_load_be(blind, blind_32)
42 let blind_inv: *i64 = u256_alloc()
43 p256_modn_inv(blind_inv, blind)
44 // 3. unblindedElement = blindInverse * evaluatedElement
45 let unblinded_pt: *P256Point = p256_point_alloc()
46 p256_scalar_mul(unblinded_pt, blind_inv, eval_pt)
47 // 4. unblindedBytes = SerializeElement(unblindedElement)
48 let unblinded_bytes: *u8 = sys_mmap(NX_VOPRF_ELEMENT_BYTES)
49 nx_voprf_serialize_element(unblinded_pt, unblinded_bytes)
50 // 5. hashInput = I2OSP(input_n,2) || input || I2OSP(33,2) || unblindedBytes || "Finalize"
51 let total_n: i64 = 2 + input_n + 2 + NX_VOPRF_ELEMENT_BYTES + 8
52 let buf: *u8 = sys_mmap(total_n + 16)
53 var pos: i64 = 0
54 buf[pos] = ((input_n >> 8) & 0xFF) as u8; buf[pos + 1] = (input_n & 0xFF) as u8; pos = pos + 2
55 var i: i64 = 0
56 while i < input_n { buf[pos + i] = input[i]; i = i + 1 }
57 pos = pos + input_n
58 buf[pos] = ((NX_VOPRF_ELEMENT_BYTES >> 8) & 0xFF) as u8; buf[pos + 1] = (NX_VOPRF_ELEMENT_BYTES & 0xFF) as u8; pos = pos + 2
59 var j: i64 = 0
60 while j < NX_VOPRF_ELEMENT_BYTES { buf[pos + j] = unblinded_bytes[j]; j = j + 1 }
61 pos = pos + NX_VOPRF_ELEMENT_BYTES
62 let fin: *u8 = "Finalize" as *u8
63 var k: i64 = 0
64 while k < 8 { buf[pos + k] = fin[k]; k = k + 1 }
65 pos = pos + 8
66 // 6. SHA-256(hashInput) -> output_32
67 sha256_digest(buf, pos, out_output_32)
68 return NX_VOPRF_OK
69}
70
71// non-terminal sentinel (keep finalize out of the file's terminal slot, belt-and-suspenders).
72func _vf_tail() -> i64 { return 0 }