code wiki / hub / nx_voprf_finalize.nx

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 }