mirror of
https://github.com/lux-cpp/node.git
synced 2026-10-30 14:48:15 +00:00
I reported an argument-order bug in mldsa65_sign_ctx and I had the direction backwards. This file was written for the PUBLISHED library, which takes the message before the context. /home/z/work/lux/crypto was a stale snapshot with no remote whose _ctx pair is the other way round, I compiled against its header, saw the disagreement, and moved the C++ to match the copy nobody publishes. Built against the real library, every signature and verification in the link failed and six of fifty-four tests went red. So the calls go back to message-first, and mldsa65_sign_ctx_det -- which I introduced upstream in v1.20.10 carrying the snapshot's order -- is corrected to match its siblings in luxfi/crypto v1.20.11. THE CAP HYPOTHESIS IS REFUTED, and the number that fires is 255. FIPS 204 caps a CONTEXT at 255 bytes and says nothing about message length, so a swap is invisible below 256 and returns -2 above it: message length 1 100 254 255 256 512 3199 6512 9615 16384 correct order 0 0 0 0 0 0 0 0 0 0 swapped 0 0 0 0 -2 -2 -2 -2 -2 -2 That reads exactly like a message-size limit, which is what made LUX_GPU_MLDSA_MSG_LEN_CAP such a good suspect. It is not that: the constant lives in an accelerator plugin that is not loaded here, backend.Resolved() never returns GPU without one, and batchVerifyGPU returns (false, nil) at its first gate -- the ErrInvalidArgument hard-error path is unreachable on this host. With the order right, the published library signs and verifies 16 KiB. WHY IT SURVIVED, which is the part worth keeping: Go's own handshake signs through cloudflare/circl directly and never calls the C ABI. That ABI is for C++, Rust, Python and TypeScript, so no Go test exercises it and an argument-order change there is invisible to the entire Go suite. Only a C caller can guard it, so pq_handshake_test now round-trips sign and verify through the ABI with a 6512-byte message -- and asserts that a 255-byte one proves nothing, because 255 is the longest input the two orders agree on. The test's responder harness was in the snapshot's order too, and when it threw it closed its socket, so four refusal cases passed on "recv failed or peer hung up" rather than the refusal they name. That has now masked a broken signer twice, so the harness records why it gave up and the happy path asserts it did not. The vector is regenerated against the published stack -- luxfi/node's mesh/peer as the responder, luxfi/crypto v1.20.11 here -- and still re-makes Go's own signature byte for byte. 54/54 green. Co-authored-by: Hanzo Dev <[email protected]>