test(math-cuda): pin the RPX kernel's canonicalisation loop with a deterministic witness (lane K, follow-up to #952) - #959
Merged
Conversation
…terministic witness
The review of the RPX host known-answer harness found that the final
canonicalisation loop of rpx::permute was unpinned: every oracle check
canonicalised the kernel's output before comparing, and a raw comparison
on outputs that merely happen to be canonical — all but a 2^-32 slice per
lane — would not see the loop either. A kernel that lost it would produce
Merkle nodes whose bytes differ from the host's in a few percent of
2^24-leaf trees.
The three oracle checks now compare RAW against the (canonical) tables,
and the generator gains one input built so that the comparison can see
the loop: its M-round MDS output lane 0 is p − ARK1[6][0] + 1, so the
device's final add returns the raw twin p + 1 where the field value is 1,
deterministically. No search was needed — the input is the host inverse
of the permutation from that target (x^{1/7} in GF(p^3) for the E rounds),
derived by tests/host_kat/rpx_canon_witness.py, which reproduces every
oracle row forward and inverts it back before it trusts itself, and
asserts the generator carries its exact row. A new harness check replays
the seven rounds on that row and asserts lane 0 is p + 1 before the loop,
so the witness cannot go stale silently, then asserts permute returns the
oracle's digits raw.
The witness's table row is printed by the generator on a box and lands in
a follow-up commit; until then the harness fails loudly on exactly that.
The generator at e9fb361, run on a box, prints eleven permutation rows: the ten of the phase-1 relay unchanged plus the row named "canonicalisation witness", whose output lane 0 is 1 — the field value whose raw twin p + 1 the kernel holds before its final canonicalisation loop. Leaf and parent tables are byte-identical to before (same seeds). With the row present the host-KAT passes in full, including the new pin check; a copy of rpx.cu with the loop deleted fails the witness row's raw comparison (lane 0 reads 18446744069414584322 where the oracle says 1) and the pin check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #952 (lane K), the one required finding from its review: the canonicalisation loop at the end of
rpx::permutewas unpinned. Every oracle comparison canonicalised the kernel side before comparing, and a raw comparison on outputs that merely happen to be canonical — all but a 2^-32 slice per lane — could not see the loop either. A phase-2 edit that lost it would have produced Merkle nodes whose bytes differ from the host's in a few percent of 2^24-leaf trees, as a flaky root or auth-path mismatch. Separate small PR by the coordinator's decision; phase 2 proper still waits for lane D.What changes (own files only)
crypto/math-cuda/tests/host_kat/rpx_host_kat.cpp— the permutation, leaf and parent oracle checks compare raw (s[i] == v.output[i]; the tables are canonical by construction). New checkthe_canonicalisation_loop_is_pinned_by_the_witness: finds the table row namedcanonicalisation witness, replays the seven rounds without the loop, asserts lane 0 is the raw twinp + 1(so the witness cannot go stale silently), then assertspermutereturns the oracle's digits raw.prover/tests/rpx_host_kat_vectors.rs— one hard-coded eleventh input,canonicalisation witness.crypto/math-cuda/tests/host_kat/rpx_kat_vectors.h— the generator's output at e9fb361 pasted (Table 2 → 11 rows; leaf and parent tables byte-identical to the 824d678 relay, same seeds).crypto/math-cuda/tests/host_kat/rpx_canon_witness.py— the derivation, self-checking: it reproduces every oracle row forward, inverts each back to its input, and asserts the generator carries its exact row.How the witness is built (no search)
The permutation's last operation is
out_i = add(m_i, ARK1[6][i])withm_ithe M-round MDS output. Choosing the canonical MDS outputu_0 = p − ARK1[6][0] + 1makes the device's final add returnu_0 + ARK1[6][0] = p + 1— the raw twin of 1 — deterministically: that sum cannot wrap, andm_0cannot itself be a twin (u_0 + p > 2^64), whatever representation the earlier rounds carry.u_1..u_11are seeded random; the M-round input isMDS⁻¹(u); the permutation input is the host inverse of rounds 5..0 (x^{1/7}inGF(p³)via7⁻¹ mod p³−1for the E rounds;x^7 → −ARK2 → MDS⁻¹ → x^{1/7} → −ARK1 → MDS⁻¹for the FB rounds).Gate
make test-rpx-host-katon the laptop with the witness row present:Mutant, as required: the same harness against a copy of
rpx.cuwith the canonicalisation loop deleted (the#pragma unroll+for … goldilocks::canonical(s[i])lines inpermute):Before the oracle row was pasted, the harness reported exactly one failure (
the permutation table must carry the 'canonicalisation witness' row) and nothing else. Generator run on box A at e9fb361: ``test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out(release); the relay's diff against the 824d6783 tables is exactly the count line 10 → 11 and the added row. `cargo check -p lambda-vm-prover --test rpx_host_kat_vectors`: exit 0 on this base.Requests
make linton a box (laptop rule); the only Rust delta is the generator's new row and doc comment;cargo fmt --allclean.