Repository navigation
Commit 5d52632
fix(mpc): output stage selects payload limbs against the dummy encoding (#7098)
* refactor(mpc): route the remaining output stages through the randomness seam
Issue #6117 step 6. Every seam-taking caller's oblivious output stage now
draws from the SAME `DistributedRandomness` source as the stage before it,
instead of minting a second `backend.dealer()` that restarted the keystream
mid-protocol:
- `join::HiddenValueJoin::batched_join_through` -> `oblivious_join_output_through`
- `hidden_distinct::distinct_hidden_pairs_slots_through`,
`hidden_path::eval_exact_k_chain_hidden_slots_through` and
`hidden_path::eval_bounded_path_hidden_slots_through` ->
`oblivious_set_output_through`
- `oblivious_join::oblivious_set_output_hidden_keys` gains an in-crate
`oblivious_set_output_hidden_keys_through` that draws both its per-pair
equality bits and its output stage from one source; the public fn passes
`&mut backend.dealer()`. The check that the source's `(n, t)` matches the
backend is made up front, because the equality chain draws first.
- the three public twins that minted two dealers of their own
(`distinct_hidden_pairs_oblivious`, `eval_exact_k_chain_hidden`,
`eval_bounded_path_hidden`) now hand their one dealer to the output stage,
so they stay byte-identical to their `_slots` siblings.
This is a REAL randomness change. Captured on unmodified origin/main and
re-checked after: every revealed multiset (canonicalised) and every modelled
cost is identical. What moved is the revealed slot ORDER and the trailing
witness draws, because the shuffle now samples a different point of one
continuous keystream. Pins re-captured and each one's comment says why;
every transcript pin additionally asserts that the pre-routing whole-log
fingerprint is still exactly the PREFIX of the new transcript, so only the
output-stage suffix is new. No assertion weakened.
PRSS consequence, stated honestly: the output stage always shares the
real-row tag and the payload limbs, so the shapes whose own stage drew
nothing (an empty DISTINCT, `k = 1` over distinct endpoint pairs, `p?`) no
longer run under PRSS — they fail closed with `NotYetImplemented`, never
panic, and the tests now assert that for every shape. The formerly
draw-free shapes are still asserted to evaluate correctly through the seam
on a dealer source.
Semi-honest, honest-majority only; the new checks are shape checks only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QrHZ34TTVfgvvQxbPEKf7
* docs(mpc): scope the transcript-prefix claim in the randomness SKILL note
The note said the prefix of "each transcript fingerprint" is asserted
unchanged. Only the MOVED pins have a prefix assertion; the hidden-key
transcript is a new pin with no pre-routing value to compare against. Say
that precisely.
Also correcting a line in 99661ec's message: the two `PartialResult`
entry points (`eval_exact_k_chain_hidden`, `eval_bounded_path_hidden`)
hand their one dealer to the output stage, but they are not "byte-identical
to their `_slots` siblings" — they return canonicalised rows, not slots.
Only `distinct_hidden_pairs_oblivious` is asserted slot-identical to
`distinct_hidden_pairs_slots`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QrHZ34TTVfgvvQxbPEKf7
* fix(mpc): select payload limbs against the dummy encoding in the output stage
The oblivious output stage selected only the slot TAG by the candidate's secret
bit; the payload limbs rode through the shuffle unselected, so every slot's real
payload was opened. Dummy slots now carry the dummy encoding, so the opened
payload of a non-matching candidate is the pad, not its row.
Construction: before the shuffle, each packed payload limb is obliviously
selected by the slot's selector against the DUMMY limb block — the limbs of
`encode_payload_row` applied to the all-unbound row of the (public) arity,
padded to the uniform width with PAD_LIMB:
limb_out = PAD + sel * (limb - PAD)
one `mul_shares_raw` per limb, with the public PAD shifted out and back locally.
`sel = 1` reproduces the limb exactly, so real rows' payloads are unchanged;
`sel = 0` yields the dummy block, which decodes to exactly what a pure padding
slot decodes to. Public / disclosed match bits behave identically — they are
lifted to a sharing before the select, as the tag already did.
Degrees: every element of a packed entry (tag and limbs alike) is now a
degree-2t product, so the limb opens move to `reconstruct_degree(.., 2t)`. That
needs n >= 2t+1, which the stage already requires for the tag select. Adding
the public constant back is local and does not change the degree.
The tag is now classified before any payload is decoded, and a dummy-tagged
slot's decoded row is CHECKED against the dummy row (fail closed) rather than
assumed, so the tag and the payload must agree on what a slot is.
Cost model (modelled, not measured): new `ObliviousOutputCost::
payload_select_mults` counts the limb selects, one per limb per slot, so a slot
costs 1 + limb_capacity multiplications in the same parallel round. Honestly
more multiplications per slot than before. Every pre-existing cost field is
unchanged for the existing fixtures.
Tests: `rejected_candidates_payloads_open_to_the_dummy_encoding` and
`rejected_secret_shared_candidates_open_to_the_dummy_encoding` (oblivious_join),
`rejected_two_hop_pairs_payloads_open_to_the_dummy_encoding` (hidden_path a->b->c,
where only (a,c) is connected) and
`rejected_batched_join_rows_payloads_open_to_the_dummy_encoding` (join) compare
the candidates' OWN payload rows against what every one of the B slots actually
opened to, through test-only instrumentation on the real path
(`opened_slot_rows_through`), and assert the opened set is exactly
{matched rows} u {dummy encoding}. Plus
`a_rejected_candidate_and_a_padding_slot_open_to_the_same_payload` and
`payload_select_mults_are_counted_per_limb_per_slot`.
Pins: the stage draws the same sharings in the same order (the select is local
plus one `mul_shares_raw`, which draws nothing), so only the shuffled column the
source returns differs. Seven whole-log transcript fingerprints were re-captured;
every revealed-slot multiset, revealed slot ORDER, witness draw and pre-existing
modelled cost is unchanged. Two prefix-equality assertions were added to the
output-stage transcript pins (the draw prefix is pinned equal to its pre-change
value), alongside the ones #7090 already has elsewhere.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QrHZ34TTVfgvvQxbPEKf7
* test(mpc): dealer check for the empty DISTINCT, one path per hidden-path entry, B>0 wording
Three small follow-ups from the #7090 review, in the files this branch touches.
L1 — `prss_fails_closed_on_every_distinct_shape` now first runs the empty
DISTINCT on a DEALER source (`distinct_hidden_pairs_slots_through(.., &[], 2)`)
and asserts two `Dummy` slots and `cost.rows == 0`, before the PRSS refusal. The
refusal below belongs to PRSS, not to a shape the operator cannot run.
L2 — `eval_exact_k_chain_hidden` and `eval_bounded_path_hidden` now delegate to
their `_slots_through` on ONE dealer and narrow the slots with a new
`oblivious_join::partial_result_from_slots` (the dummy filter plus canonical row
order that `oblivious_join_output_through` already did, factored out). One code
path per operator, so nothing can quietly mint a second dealer. Results are
identical, as the existing differential and seeded tests assert.
L3 — the "under PRSS every shape fails closed" wording in the three test docs and
in `skills/mpc/SKILL.md` now says "for any `B > 0`": at `B = 0` an empty input
draws nothing and returns Ok. The doc on
`oblivious_set_output_hidden_keys_through` now says that with empty key lists the
PRSS refusal comes from the output stage, not from inside
`secure_equal_to_bit_through`. And the distinct seeded test asserts the
output-stage `ObliviousOutputCost`, unchanged at (4, 4, 4, 4, 35, 3), plus the one
new field: 24 payload-limb selects (six limbs per slot over four slots).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QrHZ34TTVfgvvQxbPEKf7
* fix(mpc): re-randomise every selected element before the output shuffle
The oblivious output stage selects the slot tag and every packed payload limb
by the slot's secret match bit, so each selected element leaves the select as a
degree-`2t` product sharing. A product sharing opened without re-randomisation
reveals more than its value: its opened share vector still determines the
factor polynomials, so for `sel = 0` the x-coefficients of a limb and of the
tag stand in a fixed ratio to that candidate's own limb. The opened shares of a
non-matching candidate's slot were therefore not the same object as a padding
slot's, even though both decode to the dummy row.
Every selected element -- the tag and all its limbs -- is now degree-reduced
through the randomness seam (`shamir::degree_reduce_through`, 2t+1 re-sharings)
before it is packed and shuffled. The reduction returns a fresh degree-`t`
sharing of the same value whose non-constant coefficients are independent of
the factors, so the opened vector carries the value and nothing else. The
tags and the limbs are consequently opened at degree `t`.
Also in this round:
- The output stage opens and classifies ALL `B` tags BEFORE any payload limb is
opened, so an out-of-set tag refuses (`Tampered`) with zero limb opens.
- The dummy check compares the opened LIMB VECTOR against the pad block
exactly, not the decoded row: several wrong subsets of selected limbs still
decode to the all-unbound row.
- Decoding is strict. `limbs_to_bytes` refuses a non-pad byte past the length
prefix and `decode_payload_row` refuses trailing bytes, so one limb block
carries exactly one byte stream.
- `ObliviousOutputCost` gains `degree_reductions` (one per selected element,
`B * (1 + limb_capacity)`), and `opens` is documented as the degree-`t` tag
opens.
- Residual leakage documented honestly in the module docs and SKILL.md: the
payload width is the longest candidate encoding rounded up to whole limbs and
is visible to the parties, and the slot tags are opened to the compute
parties, so they learn the number of real rows. Behaviour unchanged there; a
caller-agreed public width bound and a reconstruction private to the
recipient are separate changes.
- The mixed-degree wording in `prss.rs`, its tests and SKILL.md now describes
the column the stage actually presents (degree `t`), and the PRSS refusal
site notes that a future PRSS integration must reduce the tags AND the limbs.
Tests: `every_opened_element_interpolates_to_degree_at_most_t`,
`the_share_level_recovery_never_returns_a_rejected_row`,
`the_share_level_recovery_at_n3_over_many_trials` (with a positive control that
the x-coefficient-ratio formula does recover the value from an unreduced
product), `a_dummy_slots_opened_limbs_are_exactly_the_pad_block`,
`the_pad_block_is_the_dummy_encoding_at_the_uniform_width`,
`an_out_of_set_tag_refuses_before_any_limb_is_opened`,
`a_well_formed_run_opens_every_slots_limbs`,
`a_tampered_payload_on_a_dummy_slot_is_refused`,
`limbs_to_bytes_refuses_content_past_the_length_prefix`,
`decode_payload_row_refuses_trailing_bytes`, plus a `TamperingShuffleSource` in
`randomness::test_sources`.
The reduction draws, so the transcript pins, draw counts and revealed slot
orders moved; each one was re-captured and every prefix assertion that still
applies holds unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QrHZ34TTVfgvvQxbPEKf7
* test(mpc): pin the distinct stage's degree-reduction count
One reduction per selected element: the slot tag plus its six payload limbs,
for each of the four revealed slots.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QrHZ34TTVfgvvQxbPEKf7
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a240e1e commit 5d52632
9 files changed
Lines changed: 1773 additions & 334 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
364 | | - | |
365 | | - | |
366 | | - | |
367 | | - | |
368 | | - | |
369 | | - | |
370 | | - | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
371 | 374 | | |
372 | 375 | | |
373 | 376 | | |
374 | 377 | | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
375 | 382 | | |
376 | 383 | | |
377 | 384 | | |
| |||
456 | 463 | | |
457 | 464 | | |
458 | 465 | | |
459 | | - | |
460 | | - | |
461 | | - | |
462 | | - | |
463 | | - | |
464 | | - | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
465 | 472 | | |
466 | 473 | | |
467 | 474 | | |
| 475 | + | |
468 | 476 | | |
469 | 477 | | |
470 | 478 | | |
471 | | - | |
472 | 479 | | |
473 | 480 | | |
474 | 481 | | |
| |||
486 | 493 | | |
487 | 494 | | |
488 | 495 | | |
489 | | - | |
490 | | - | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
491 | 499 | | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
492 | 518 | | |
493 | 519 | | |
494 | 520 | | |
| |||
497 | 523 | | |
498 | 524 | | |
499 | 525 | | |
500 | | - | |
| 526 | + | |
501 | 527 | | |
502 | 528 | | |
503 | 529 | | |
504 | 530 | | |
505 | 531 | | |
506 | 532 | | |
507 | 533 | | |
508 | | - | |
| 534 | + | |
| 535 | + | |
509 | 536 | | |
510 | 537 | | |
511 | 538 | | |
| |||
517 | 544 | | |
518 | 545 | | |
519 | 546 | | |
520 | | - | |
521 | | - | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
522 | 550 | | |
523 | | - | |
| 551 | + | |
524 | 552 | | |
525 | 553 | | |
526 | 554 | | |
527 | | - | |
| 555 | + | |
528 | 556 | | |
529 | 557 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
117 | 117 | | |
118 | 118 | | |
119 | 119 | | |
120 | | - | |
121 | | - | |
| 120 | + | |
122 | 121 | | |
123 | 122 | | |
124 | 123 | | |
| |||
130 | 129 | | |
131 | 130 | | |
132 | 131 | | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
133 | 140 | | |
134 | 141 | | |
135 | 142 | | |
| |||
378 | 385 | | |
379 | 386 | | |
380 | 387 | | |
381 | | - | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
382 | 391 | | |
383 | 392 | | |
384 | 393 | | |
385 | 394 | | |
386 | 395 | | |
387 | 396 | | |
388 | | - | |
389 | | - | |
390 | | - | |
391 | | - | |
392 | | - | |
393 | | - | |
394 | | - | |
395 | | - | |
396 | | - | |
397 | | - | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
398 | 400 | | |
399 | 401 | | |
400 | 402 | | |
| |||
440 | 442 | | |
441 | 443 | | |
442 | 444 | | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
443 | 468 | | |
444 | 469 | | |
445 | 470 | | |
| |||
813 | 838 | | |
814 | 839 | | |
815 | 840 | | |
816 | | - | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
817 | 844 | | |
818 | 845 | | |
819 | 846 | | |
820 | 847 | | |
821 | 848 | | |
822 | 849 | | |
823 | 850 | | |
824 | | - | |
825 | | - | |
826 | | - | |
827 | | - | |
828 | | - | |
829 | | - | |
830 | | - | |
831 | | - | |
832 | | - | |
833 | | - | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
834 | 854 | | |
835 | 855 | | |
836 | 856 | | |
| |||
848 | 868 | | |
849 | 869 | | |
850 | 870 | | |
851 | | - | |
| 871 | + | |
852 | 872 | | |
853 | 873 | | |
854 | 874 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
567 | 567 | | |
568 | 568 | | |
569 | 569 | | |
570 | | - | |
571 | | - | |
572 | | - | |
573 | | - | |
574 | | - | |
575 | | - | |
576 | | - | |
577 | | - | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
578 | 578 | | |
579 | 579 | | |
580 | 580 | | |
| |||
693 | 693 | | |
694 | 694 | | |
695 | 695 | | |
696 | | - | |
697 | | - | |
698 | | - | |
699 | | - | |
700 | | - | |
701 | | - | |
702 | | - | |
703 | | - | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
704 | 703 | | |
705 | 704 | | |
706 | 705 | | |
707 | 706 | | |
708 | | - | |
| 707 | + | |
709 | 708 | | |
| 709 | + | |
710 | 710 | | |
711 | | - | |
712 | | - | |
| 711 | + | |
713 | 712 | | |
714 | 713 | | |
715 | 714 | | |
| |||
742 | 741 | | |
743 | 742 | | |
744 | 743 | | |
745 | | - | |
| 744 | + | |
746 | 745 | | |
747 | 746 | | |
748 | 747 | | |
749 | 748 | | |
750 | 749 | | |
751 | 750 | | |
752 | 751 | | |
753 | | - | |
| 752 | + | |
| 753 | + | |
754 | 754 | | |
755 | 755 | | |
756 | 756 | | |
| |||
759 | 759 | | |
760 | 760 | | |
761 | 761 | | |
762 | | - | |
763 | | - | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
764 | 765 | | |
765 | | - | |
| 766 | + | |
766 | 767 | | |
767 | 768 | | |
768 | 769 | | |
769 | | - | |
| 770 | + | |
770 | 771 | | |
771 | 772 | | |
0 commit comments