Skip to content

feat(threshold): add authorized redistribution over owner-closed rounds; Details: TC-2221 - #493

Draft
samgbafa wants to merge 16 commits into
mainfrom
skgbafa/tc-2221-threshold-reshare
Draft

samgbafa wants to merge 16 commits into
mainfrom
skgbafa/tc-2221-threshold-reshare

Conversation

@samgbafa

@samgbafa samgbafa commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Adds the S0.4 authorized redistribution to tinycloud-threshold: reshare and refresh of the current master secret from the members of a verified certificate to an owner-authorized cohort, with a possibly different threshold and size, over owner-closed, member-signed rounds R1–R3. Includes reshare_verify_transcript, which recomputes the outcome from the raw signed round records. The master public key is unchanged; the output is a candidate for the successor certificate. The crate remains an unwired workspace member.

Stacked on #492. Base: skgbafa/tc-2220-threshold-dkg at 82ab38cbbdb64005eff7d4c694c99fb12cb0ce4f. Head: 45341d995a1f535483a2461385b20da25055d076.

What it does

  • reshare_deal, reshare_start_receiver and reshare_verify_transcript accept only an owner-authorized ceremony of kind reshare or refresh whose current-certificate digest equals the supplied VerifiedCertificate. Refresh additionally requires the same t, n and index→DID directory as the current certificate. A dealer's KeyShare must carry the current key context. A recipient's ceremony key must match its directory entry. These checks precede any randomness, opening or emitted message.
  • R1, from current-certificate dealers: dealer i deals g_i of degree t′−1 with g_i(0) = s_i. It publishes exactly t′ Feldman coefficients and seals g_i(j) to each authorized recipient. The public checks are: B_i0 equals the dealer's current public share; the top coefficient is non-identity; the recipient set is exactly 1..=n′.
  • R2, from authorized recipients: complaints against dealers whose dealing fails to open or fails the Feldman relation.
  • R3, from dealers: answers for counted complaints. A valid required answer replaces the complainer's value. A dealer with t′ or more distinct complainers is excluded and publishes no answers.
  • The selected dealers are the first t eligible dealers in the order of their first closed-R1 message. Each recipient's candidate is Σ λ_i·g_i(j) over those dealers. Fewer than t eligible dealers return InsufficientQualified at every recipient and the verifier, and the current epoch stays in force.
  • Each transition first requires the closed round's ceremony digest, round number and previous transcript link to match the state. The initial link is TH("TC-THRESHOLD-V1-RESHARE-START", authorized_ceremony_digest) from reshare_initial_transcript, for both reshare and refresh.
  • R1 and R3 count only current-certificate-dealer envelopes, and R2 counts only recipient envelopes. An authenticated envelope under the other cohort is ignored and attributes nothing. Malformed R2 and conflicting R2 supply no complaints. Missing or malformed R3 excludes a dealer only when required answers are absent. An invalid answer excludes its dealer whether or not it was required. A valid unsolicited answer is ignored. Conflicting R1 or R3 excludes the dealer.
  • reshare_verify_transcript bounds the whole transcript to 4 MiB before checking signatures, requires exactly three rounds and trusts no supplied outcome fields.
  • R1 and R2 reuse the genesis R1 and R2 layouts (ReshareDeal, ReshareComplaints). R3 is 01 || count:u16 || [(recipient:u16 || value:32)]. No error variant, dependency or shared-file change.
  • SPEC.md gains the S0.4 redistribution appendix (layouts, rule order, selection, scope and lifecycle). README.md is updated. vectors/v1/reshare.json holds five fixed-seed transcripts with recomputed outcomes or errors; a test regenerates and replays them byte for byte.

Completion assumes the trusted owner relay of §3.0, which delivers every honest message, and at least t old dealers that pass the rules. With fewer, the ceremony refuses with InsufficientQualified. This implementation does not provide reliable broadcast. Redistribution does not erase earlier shares: old shares remain valid for the old certificate until integration erases them, and old and new shares carry different key contexts.

Commits

  • 1b9bccdb test: new src/reshare.rs (module doc and tests only), new src/reshare_relay.rs (test-only signed owner relay and scenarios), and a new checkpoint_2of3_reshare appended to src/checkpoint.rs.
  • 6e5e9287 build: lib.rs module, exports and test-module wiring.
  • 50160879 feat: redistribution implementation in src/reshare.rs.
  • 77ecc9c8 docs: SPEC appendix and README.
  • 65e49139 style: rustfmt on reshare.rs, reshare_relay.rs and the appended checkpoint test (whitespace, trailing commas and one closure brace pair).
  • 45341d99 test: generated vectors/v1/reshare.json.

No test or fixture that existed before this PR is edited; checkpoint.rs gains one new test function. No other module changes.

Checks

These ran locally at 65e49139 with the pinned 1.97.1 toolchain, isolated homes and target, two jobs and two test threads. 45341d99 adds only the generated vector file that these runs compared byte for byte.

  • cargo test --locked -p tinycloud-threshold --release --no-run: ok.
  • TC_THRESHOLD_REGEN_VECTORS=1 cargo test --locked -p tinycloud-threshold --release reshare_vectors_regenerate_byte_for_byte_and_replay: ok.
  • PROPTEST_CASES=1024 cargo test --locked -p tinycloud-threshold --release: 166 passed, 0 failed.
  • cargo test --locked -p tinycloud-threshold --release checkpoint_2of3 -- --nocapture: three checkpoints passed and printed their outcomes. They include checkpoint_2of3_reshare, which runs a DKG genesis redistributed to 3-of-4 with one answered complaint, the successor certificate and binding, all four 3-member subsets deriving and unwrapping, old/new context refusal, and a refused redistribution below t valid dealers with the 3-of-4 epoch still usable.
  • cargo clippy --locked -p tinycloud-threshold --all-targets -- -D warnings: ok.
  • cargo fmt -p tinycloud-threshold -- --check: ok. The first run at 77ecc9c8 reported formatting differences, fixed in 65e49139.
  • A separate program built against the normal release library, without cfg(test), ran the following:
    • a signed 2-of-3 DKG, its certificate and binding;
    • a refusal of a recipient key outside the directory;
    • a 2-of-3 → 3-of-4 redistribution in which all four recipients and reshare_verify_transcript agreed;
    • CertificateBody::successor, verify_successor and binding;
    • derivation and unwrapping through every 3-member subset, with NotEnoughShares for two;
    • ContextMismatch and MixedContexts for an old share under the new context;
    • InsufficientQualified at every recipient and the verifier when only two of three required dealers dealt, with the 3-of-4 epoch still unwrapping.

The two enumeration tests run every behaviour assignment of the in-tree fault model: 2-of-3 → 2-of-3 under two seeds (96 ceremonies) and 3-of-5 → 3-of-4 (108 ceremonies). Every case keeps at least t valid dealers. They cover that model only.

Named tests: genuine_2of3_to_3of4_preserves_the_master_key_and_binds_every_candidate, dkg_genesis_reshare_chain_keeps_the_master_key_through_refresh, old_shares_stay_in_their_own_context_after_redistribution, fewer_than_t_valid_dealers_abort_everywhere_and_the_old_epoch_stays_usable, within_the_secrecy_budget_but_below_t_valid_dealers_refuses_and_keeps_the_old_epoch, a_member_of_both_cohorts_signs_distinct_roles, t_prime_complainers_exclude_the_dealer_and_it_publishes_no_answer, false_complaint_is_refuted_by_a_valid_answer, one_recipient_bad_value_is_replaced_by_a_valid_answer, invalid_answer_excludes_the_dealer_and_the_next_dealer_is_selected, missing_r3_excludes_only_with_required_answers, malformed_r3_excludes_only_with_required_answers, invalid_unsolicited_answer_excludes_its_dealer, valid_unsolicited_answer_is_ignored, conflicting_r3_excludes_its_dealer, r3_naming_a_recipient_outside_the_new_cohort_is_malformed, malformed_r2_supplies_no_complaints, conflicting_r2_supplies_no_complaints_and_does_not_affect_the_same_nodes_dealer_role, duplicate_complaints_count_once, r2_naming_a_dealer_outside_the_old_cohort_supplies_no_complaints, relay_omitted_complaint_fails_only_the_affected_receiver, r1_public_checks_exclude_malformed_dealings_and_keep_the_master_key, missing_and_equivocated_r1_exclude, dealers_are_the_first_t_in_closed_round_order, wrong_cohort_r1_is_ignored_without_blame, wrong_cohort_r2_is_ignored_without_blame, wrong_cohort_r3_is_ignored_without_blame, dealing_refuses_unauthorized_contexts_before_any_randomness, receiver_start_checks_ceremony_index_and_recipient_key, forged_statement_fields_fail_owner_authentication, transitions_refuse_other_ceremonies_rounds_and_histories, retry_under_a_new_ceremony_cannot_reuse_an_earlier_dealing, verifier_recomputes_from_authenticated_records_only, transcript_bound_of_4_mib_precedes_verification, every_within_budget_fault_pattern_with_t_valid_dealers_agrees_2of3, every_within_budget_fault_pattern_with_t_valid_dealers_agrees_3of5_to_3of4, reshare_vectors_regenerate_byte_for_byte_and_replay, reshare_start_label_is_distinct_from_and_not_a_prefix_of_every_frozen_tag, initial_transcript_hashes_the_exact_ceremony_digest_for_reshare_and_refresh, answer_payload_has_the_exact_layout_and_rejects_noncanonical_bytes, redistribution_rounds_reuse_the_genesis_deal_and_complaint_layouts, random_answer_bytes_reject_or_reencode_canonically, structured_answer_mutations_reject_or_reencode_canonically, checkpoint_2of3_reshare.

Clean room

Clean-room statement. tinycloud-threshold is written from published papers and standards only: Shamir 1979; Feldman 1987; Pedersen 1991; Gennaro–Jarecki–Krawczyk–Rabin 1999/2007; Boneh–Franklin 2001/2003; Fujisaki–Okamoto 1999; Desmedt–Jajodia 1997; Wong–Wang–Wing 2002; Herzberg–Jarecki–Krawczyk–Yung 1995; CHURP (ePrint 2019/017); vetKeys (ePrint 2023/616); RFC 9380; RFC 9180; the IRTF pairing-friendly-curves draft; and TinyCloud's own design and plan. No source code, tests, issues or other material from TACo, nucypher, nucypher-core, ferveo, umbral or taco-web (GPL-3.0/AGPL-3.0) was opened, fetched, cloned, searched or used. Dependencies are permissively licensed and listed in the PR. Each PR keeps a provenance log of the sources consulted.

Dependencies: no change (existing blst, sha2, zeroize, subtle, rand_core, thiserror, hpke 0.12 with x25519; development ed25519-dalek, proptest, rand_chacha, serde, serde_json, hex).

Provenance log

  • Desmedt–Jajodia 1997: redistribution of shares to a new access structure, with g_i(0) = s_i and Lagrange recombination over the selected dealers.
  • Wong–Wang–Wing 2002: Feldman verification of redistributed sub-shares against commitments pinned to the old public shares.
  • Herzberg–Jarecki–Krawczyk–Yung 1995: refresh at the next epoch under the same access structure.
  • Feldman 1987: commitments b·g2 and share checks. Shamir 1979: polynomial evaluation and Lagrange interpolation.
  • RFC 9180: base-mode sealing through the existing crate seal module.
  • TinyCloud design and plan (§3.0, §3.8, §3.10–§3.12), the crate's SPEC.md and existing crate APIs (authorized ceremonies, closed rounds, certificates, the genesis R1/R2 codecs).

Details: TC-2221

Update: follow-up at a429c48

One commit on 45341d99, changing only src/reshare_relay.rs (tests) and one sentence of SPEC.md. No production code, protocol, fixture, dependency or existing assertion changes.

  • SPEC.md, S0.4 Scope: a selected dealer that can read other dealers' R1 before its own R1 is closed, acting with a new member, can choose a dealing that passes every check but makes a new public share the identity. Every recipient and the verifier then refuse with IdentityPoint without attributing a dealer. This affects completion only, and the current epoch stays in force.
  • agreed() asserts that each listed recipient finished successfully before reading its candidate.
  • r2_naming_an_old_dealer_above_the_new_cohort_size_counts: from old 3-of-5 to new 3-of-4, a complaint naming dealer 5 counts against the old cohort size and is answered.
  • different_signature_encoding_is_not_equivocation: a retransmitted R1, R2 or R3 that differs only in its signature encoding is the same message, so it causes no exclusion and drops no complaint.
  • dealer_answers_only_for_its_accepted_commitments: a dealer whose accepted R1 carries other commitments than its own state, with one complaint below t′, publishes no answers and is excluded with MissingAnswer.

Checks at a429c480 (pinned 1.97.1, isolated, two jobs and two test threads, private copy of the exact head):

  • PROPTEST_CASES=1024 cargo test --locked -p tinycloud-threshold --release: 169 passed, 0 failed.
  • checkpoint_2of3 -- --nocapture: three checkpoints passed.
  • cargo clippy --locked -p tinycloud-threshold --all-targets -- -D warnings and cargo fmt -p tinycloud-threshold -- --check: ok.
  • Each change below, applied alone to src/reshare.rs, made its named test fail at an assertion in src/reshare_relay.rs; the file was restored to its original hash after each:
    • envelope comparison including the signature: different_signature_encoding_is_not_equivocation at the exclusion assertion;
    • complaint bound using the new cohort size: r2_naming_an_old_dealer_above_the_new_cohort_size_counts at the answer assertion;
    • valid answer replacement dropped: one_recipient_bad_value_is_replaced_by_a_valid_answer at the recipient-success assertion in agreed();
    • own-commitment condition removed: dealer_answers_only_for_its_accepted_commitments at the empty-answer assertion.

Details: TC-2221

… accepted-commitment answers; Details: TC-2221
@samgbafa samgbafa mentioned this pull request Nov 1, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant