Files
zeekayandHanzo Dev 9a7c2e7f63 The argument order was right the first time, and the library had drifted
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]>
2026-09-08 02:02:37 -07:00
..