Files
zeekay c79fe61d62 consolidate: merge pulsar-mptc submission framework into canonical luxfi/pulsar
Single repo, one canonical impl + spec + proofs + KAT + cut tool.

Per "ONE and ONLY ONE way to do things" the prior dual-repo split
(luxfi/pulsar = production library, luxfi/pulsar-mptc = NIST MPTC
submission framework) is collapsed into a single canonical home at
github.com/luxfi/pulsar. SINGLE-IMPL-PLAN.md (now marked COMPLETED)
describes the steps; this commit lands the final step.

Files moved (89 new):
  16 submission docs (SUBMISSION, NIST-SUBMISSION, SPEC, SUITE,
     PATENTS, AXIOM-INVENTORY, PROOF-CLAIMS, FIPS-TRACEABILITY,
     TRUSTED-COMPUTING-BASE, HANZO-CRYPTO-SUITE, INFORMATION-
     ARCHITECTURE, ROADMAP, CHANGELOG, SYNC-STATUS, STATUS-
     SUBMISSION-READINESS, SINGLE-IMPL-PLAN)
  10 docs/ supplements (ietf-draft-skeleton, magnetar, evaluation,
     patent-claims, x-wing-sig, design-decisions, family-architecture,
     threat-model, nist-mptc-category, patent-notes-draft)
  19 proofs/ (13 EC theories + 2 EC lemmas + 4 metadata/READMEs +
     lean-easycrypt-bridge.md)
  14 jasmin/ (lib/* + ml-dsa-65 fetch + threshold/{combine,round1,
     round2}.jazz + READMEs)
  10 ct/dudect/ (Makefile, combine_ct.go, dudect_combine.c,
     dudect_compat.h, dudect_verify.c, fetch.sh, README, run-
     submission.sh, verify_ct.go) + ct/jasmin-ct-libjade.md
  1 .github/workflows/ci.yml
  1 test/interoperability/n1_class_test.go (19/19 N1 subtests)
  1 bench/results/REPORT.md
  11 scripts/ (cut-submission, check-high-assurance, check-lean-
     bridge, nightly, extract-jasmin-ec, checks/{ec-admits, ec-
     compile, ec-refinement-scaffold, ec-regressions, extraction,
     jasmin}, checks/test/{go-unit, interop, kat, no-secret-logs})

Files merged (16 overlapping, mptc-canonical chosen because newer +
post-rewire-correct; pulsar versions still bore stale "Pulsar-M" /
"pulsarm" branding):
  README.md, BLOCKERS.md, CONTRIBUTING.md, LICENSE, LICENSING.md,
  SECURITY.md, .gitignore, bench/run_all.sh, vectors/dkg.json (v1.0.7
  regen), scripts/{bench,build,gen_vectors,sbom,test}.sh,
  spec/{pulsar,parameters,system-model}.tex,
  CRYPTOGRAPHER-SIGN-OFF.md (path refs).

Pulsar-only spec supplements preserved:
  spec/{blockers,design-decisions,family-architecture,nist-mptc-
  category,patent-notes,threat-model}.tex.

Path rewrites applied: 158 occurrences across 26 files
  github.com/luxfi/pulsar-mptc -> github.com/luxfi/pulsar
  ~/work/lux/pulsar-mptc/      -> ~/work/lux/pulsar/
  luxfi/pulsar-mptc            -> luxfi/pulsar
  pulsar-mptc/                  -> ./

Narrative collapses (no more two-repo framing):
  SUBMISSION.md "At a glance" — single Repository row
  SYNC-STATUS.md — rewritten to single-repo state
  SINGLE-IMPL-PLAN.md — marked COMPLETED 2026-05-18
  HANZO-CRYPTO-SUITE.md — tier 1 row = single canonical home
  scripts/cut-submission.sh — replace/vendor dance removed; cuts
  directly from in-tree canonical sources

go.mod: dropped mptc's replace directive (no longer needed since this
IS the canonical pulsar repo). Module path is github.com/luxfi/pulsar.
Deps: cloudflare/circl v1.6.3, golang.org/x/crypto v0.32.0.

Verification:
  GOWORK=off go build ./...              PASS
  GOWORK=off go test -count=1 ./...      PASS (1.4s)
  GOWORK=off go test -race ./ref/...     PASS (14.7s)
  go test ./test/interoperability/       PASS (19/19 N1 subtests)
  scripts/check-high-assurance.sh        PASS
  scripts/check-lean-bridge.sh           PASS (5/5 bridges)
  scripts/gen_vectors.sh                 byte-identical replay
  scripts/cut-submission.sh --help       PASS
2026-05-18 19:53:08 -07:00

342 lines
17 KiB
Plaintext

(* -------------------------------------------------------------------- *)
(* Pulsar — Class N4 public-key preservation across reshare *)
(* -------------------------------------------------------------------- *)
(* STATUS: CLOSED. 0 admits across the file. *)
(* *)
(* This file proves `pulsar_n4_pk_preservation_honest` against the *)
(* concrete honest reshare module `ReshareHonest`. The headline *)
(* algebraic identity *)
(* *)
(* reconstruct q (zip_add (fresh_sharing q s) *)
(* (fresh_sharing q zero_share)) = s *)
(* *)
(* (Shamir-zero re-randomisation) is the cryptographic-reduction core. *)
(* It reduces to three Lagrange-algebraic axioms over `share_t` viewed *)
(* as an additive group (mechanized in the Lean theory *)
(* `Crypto.Pulsar.Shamir`, stated EC-side as forward-facing lemmas *)
(* about `add_share` / `reconstruct` / `fresh_sharing`). *)
(* *)
(* What this file does NOT contain (and the rationale): *)
(* - There is NO abstract section with a `declare axiom *)
(* reshare_preserves_secret` over an arbitrary Pulsar_Reshare R. *)
(* That shape is a *behavioural* hypothesis (a malicious R can *)
(* emit garbage shares) and was removed in favour of the concrete *)
(* `ReshareHonest` proof here. CI guard: *)
(* `scripts/check-high-assurance.sh` greps for the old axiom shape *)
(* and fails if it is reintroduced. *)
(* *)
(* - There is NO vacuous placeholder lemma *)
(* (`pulsar_n4_transcript_binds_committee` with postcondition *)
(* `true`). That stub was removed; the real committee-root *)
(* binding-invariant statement is part of the Pedersen DKG *)
(* transcript module and lives in study/pulsar.md. *)
(* -------------------------------------------------------------------- *)
(* Claim: *)
(* The Pulsar proactive-resharing protocol (Reshare in *)
(* spec/pulsar.tex §4.5) preserves the group public key across *)
(* committee rotations. Specifically: for every starting share set *)
(* `shares_old` over committee `C_old` with public key *)
(* `derive_pk(reconstruct(shares_old))`, after running Reshare into a *)
(* new committee `C_new`, the resulting share set `shares_new` *)
(* satisfies *)
(* *)
(* derive_pk(reconstruct(shares_new)) = derive_pk(reconstruct( *)
(* shares_old)) *)
(* *)
(* provided ≥ threshold honest parties in both committees. *)
(* *)
(* Reduction strategy: *)
(* 1. Shamir-zero re-randomisation: Reshare produces a fresh sharing *)
(* of the SAME secret over R_q by sampling shares of zero and *)
(* adding them to fresh shares of the original secret. *)
(* 2. R_q-linearity: derive_pk is the linear map t = A·s + e mod q, *)
(* so the public key depends only on s (the secret), not on the *)
(* sharing. *)
(* 3. ⇒ public key is invariant across reshare. *)
(* *)
(* Auxiliary obligations: *)
(* - Reshare commits new committee members to the zero-share VSS *)
(* transcripts so dishonest old members cannot bias new shares. *)
(* - The reshare ceremony's accumulator commits to the OLD committee *)
(* roster (committeeRootFromShares) so reviewers can detect *)
(* post-hoc roster substitution. *)
(* -------------------------------------------------------------------- *)
require import AllCore List Int IntDiv Distr DBool DInterval SmtMap.
type group_pk_t.
type share_t.
type committee_t.
type reshare_transcript_t.
op derive_pk : share_t -> group_pk_t.
op reconstruct : int list -> share_t list -> share_t.
(* ===================================================================
Lagrange-algebraic structure on share_t.
share_t is the abstract type for a per-party Shamir share. For
ML-DSA-65 it is R_q^ell (a vector over the polynomial ring), which
is an additive group under componentwise addition. The three
operators below pin that structure: a zero element, a binary +,
and the dealer's polynomial-evaluation primitive `poly_eval`.
The bridge to the Lean theory `Crypto.Pulsar.Shamir` is
one-to-one: `add_share` ↔ R_q^ell `+`, `zero_share` ↔ 0,
`poly_eval` ↔ the dealer's polynomial-eval operator
`Polynomial.eval`. `fresh_sharing` is now a *concrete* definition
in terms of `poly_eval`, not an abstract operator (see below).
=================================================================== *)
op zero_share : share_t.
op add_share : share_t -> share_t -> share_t.
(* Lift componentwise: zip two share lists with `add_share`. Lists
must be the same length and indexed by the same quorum positions. *)
op zip_add (l1 l2 : share_t list) : share_t list =
map (fun (p : share_t * share_t) => add_share p.`1 p.`2) (zip l1 l2).
(* The dealer's polynomial-evaluation primitive. `poly_eval s i`
returns the share for party index `i` produced by the dealer
whose Shamir polynomial has constant term `s`. The same operator
name + signature exists in `Pulsar_N1.ec`; both files use it
identically.
IMPORTANT: A real Shamir sharing is RANDOMISED (many polynomials
share the same constant term). The operator here is a DETERMINISTIC
REPRESENTATIVE of the distributional sharing family. This file
proves functional secret preservation across reshare — it does NOT
prove distributional freshness / hiding of the new sharing. The
latter is a separate hop-game argument; see `Pulsar_N1.ec` and
the Lean theory `Crypto.Pulsar.Shamir` for the distributional
hiding side. The deterministic-representative choice is sound
because for any concrete dealer randomness `r` we can plug `r`
into the right-hand side of the sharing identities below and the
same proof goes through — `poly_eval` is just the unhided
"pure function" view of the dealer. *)
op poly_eval : share_t -> int -> share_t.
(* CONCRETE definition (no longer an abstract op): `fresh_sharing q s`
returns the list of shares
[ poly_eval s i_0; poly_eval s i_1; ...; poly_eval s i_{|q|-1} ]
where the dealer's polynomial has constant term `s` (encoded in the
`share_t` representative). With this definition, malicious
instantiations that emit garbage shares are RULED OUT BY TYPE:
`fresh_sharing` is uniquely determined by `poly_eval`, and any
`poly_eval` that satisfies the Lagrange-inverse identity below
(`shamir_correct`) automatically makes `fresh_sharing` the unique
correct sharing.
Closes the abstraction-gap critique in Agent 2's HIGH finding
("`fresh_sharing` could be a malicious adversary"). *)
op fresh_sharing (q : int list) (s : share_t) : share_t list =
List.map (poly_eval s) q.
(* ===================================================================
Shamir layer — algebraic axioms.
These are field identities about R_q^ell, NOT behavioral hypotheses
about any module. They are mechanized in the Lean theory
`Crypto.Pulsar.Shamir` (file `Shamir.lean`); we state them EC-side
as the algebraic kernel the Reshare proof reduces to.
=================================================================== *)
(* BRIDGED TO LEAN: the three axioms below correspond 1:1 to proved
Lean theorems in `~/work/lux/proofs/lean/Crypto/`. Inline citations
given per-axiom; the full symbol-correspondence table lives in
`proofs/lean-easycrypt-bridge.md` (§ "Axioms 2-4"). The EC side
states them as axioms because EasyCrypt's first-order theory of
finite-field polynomial interpolation is comparatively thin; the
honest closure path (see bridge doc § "Future work") is to either
mechanize them in EC or wire a Lean-proof-object consumer. *)
(* Adding zero is identity.
BRIDGE: instance fact for any AddCommMonoid (Mathlib auto-derives
for Polynomial F); see bridge doc § "Axiom 4". *)
axiom add_share_zeroR : forall (s : share_t), add_share s zero_share = s.
(* Reconstruction is linear over share-list addition.
BRIDGE: Crypto.Threshold.Lagrange.combine_distributes_over_sum
(`~/work/lux/proofs/lean/Crypto/Threshold_Lagrange.lean:81`).
Proved as `(Lagrange.interpolate s v).map_add a b` — direct
instance of `LinearMap.map_add`. *)
axiom reconstruct_linear :
forall (q : int list) (a b : share_t list),
size a = size q => size b = size q =>
reconstruct q (zip_add a b) =
add_share (reconstruct q a) (reconstruct q b).
(* Reconstruction is a left inverse of fresh sharing at any quorum
(Lagrange-at-zero identity).
BRIDGE: Crypto.Pulsar.Shamir.shamir_correct_at_target
(`~/work/lux/proofs/lean/Crypto/Pulsar/Shamir.lean:76`) +
Crypto.Threshold.Lagrange.secret_recovery_at_zero
(`~/work/lux/proofs/lean/Crypto/Threshold_Lagrange.lean:62`).
Same theorem as Axiom 1 in the bridge doc, applied to a
`fresh_sharing` build. *)
axiom shamir_correct :
forall (q : int list) (s : share_t),
uniq q => 1 <= size q =>
reconstruct q (fresh_sharing q s) = s.
(* fresh_sharing produces |q| shares (so zip_add against an
old-quorum-length share list is well-typed). *)
axiom fresh_sharing_size :
forall (q : int list) (s : share_t),
size (fresh_sharing q s) = size q.
(* A fresh sharing of zero, reconstructed at any quorum, is zero.
This is an instance of `shamir_correct` at s = zero_share — DERIVED
here as a lemma, not stated as a separate axiom. *)
lemma fresh_sharing_zero_is_zero (q : int list) :
uniq q => 1 <= size q =>
reconstruct q (fresh_sharing q zero_share) = zero_share.
proof.
move=> uq szq.
by rewrite (shamir_correct q zero_share).
qed.
(* ===================================================================
Committee → quorum projection.
Each committee_t value picks a canonical t-quorum (a list of party
indices used by the dealer at sharing time). The protocol's
"honest quorum" is this projection; concrete extensions to OTHER
quorum-subsets follow from Shamir's quorum invariance (a separate
algebraic identity not needed for the headline reshare property).
=================================================================== *)
op committee_quorum : committee_t -> int list.
axiom committee_quorum_uniq : forall (c : committee_t), uniq (committee_quorum c).
axiom committee_quorum_nonempty : forall (c : committee_t), 1 <= size (committee_quorum c).
module type Pulsar_Reshare = {
proc reshare(c_old : committee_t, shares_old : share_t list,
c_new : committee_t) : share_t list * reshare_transcript_t
}.
(* ===================================================================
Concrete honest reshare module.
The canonical Shamir-zero re-randomisation: each new-committee
party receives the SUM of (a fresh re-share of the old secret) and
(a fresh sharing of zero). Both summands are dealt by the
`fresh_sharing` op at the new committee's canonical quorum
(`committee_quorum c_new`). The old secret is recovered by
reconstructing the old shares at the old committee's quorum
(`committee_quorum c_old`).
=================================================================== *)
module ReshareHonest : Pulsar_Reshare = {
proc reshare(c_old : committee_t, shares_old : share_t list,
c_new : committee_t) : share_t list * reshare_transcript_t = {
var q_old : int list;
var q_new : int list;
var old_secret : share_t;
var refresh : share_t list;
var zero_pad : share_t list;
var new_shares : share_t list;
var tr : reshare_transcript_t;
q_old <- committee_quorum c_old;
q_new <- committee_quorum c_new;
old_secret <- reconstruct q_old shares_old;
refresh <- fresh_sharing q_new old_secret;
zero_pad <- fresh_sharing q_new zero_share;
new_shares <- zip_add refresh zero_pad;
tr <- witness;
return (new_shares, tr);
}
}.
(* ===================================================================
N4 — concrete proof against ReshareHonest.
We do NOT keep the old abstract section with its
`declare axiom reshare_preserves_secret`: that shape is a
*behavioural* assumption over an arbitrary R (a malicious R can
emit garbage shares), and concrete cryptographic correctness must
be discharged against a concrete honest module instead. The
section + axiom version was removed in favour of the four-axiom
Shamir-algebraic kernel above plus the `ReshareHonest` module.
The `scripts/check-high-assurance.sh` guard greps for the old
axiom shape and fails CI if it ever reappears.
=================================================================== *)
(* Algebraic core lemma: for any quorum q and any secret s,
reconstruct q (zip_add (fresh_sharing q s) (fresh_sharing q 0)) = s.
This is the headline Shamir-zero re-randomisation identity. *)
lemma honest_reshare_reconstructs
(q : int list) (s : share_t) :
uniq q => 1 <= size q =>
reconstruct q
(zip_add (fresh_sharing q s)
(fresh_sharing q zero_share))
= s.
proof.
move=> uq szq.
rewrite reconstruct_linear; first 2 by rewrite fresh_sharing_size.
rewrite shamir_correct //.
rewrite fresh_sharing_zero_is_zero //.
by rewrite add_share_zeroR.
qed.
(* The headline concrete discharge: for the honest reshare module,
the new-committee reconstruct equals the old-committee reconstruct.
The quorums are the canonical committee quora — derived from the
committee values, not free parameters. *)
lemma reshare_preserves_secret_honest
(c_old_pre c_new_pre : committee_t)
(shares_old_pre : share_t list) :
hoare [ ReshareHonest.reshare :
c_old = c_old_pre /\ shares_old = shares_old_pre
/\ c_new = c_new_pre
==>
reconstruct (committee_quorum c_new_pre) res.`1
= reconstruct (committee_quorum c_old_pre) shares_old_pre ].
proof.
proc; auto => &m [#] -> -> ->.
have Huniq : uniq (committee_quorum c_new_pre).
- by apply committee_quorum_uniq.
have Hnonempty : 1 <= size (committee_quorum c_new_pre).
- by apply committee_quorum_nonempty.
(* The intermediate H makes the lemma instantiation transparent:
it states the exact equality the body of `ReshareHonest.reshare`
induces, discharged from `honest_reshare_reconstructs`
specialised at q = committee_quorum c_new_pre and
s = reconstruct (committee_quorum c_old_pre) shares_old_pre.
The algebraic content is fully in this H; the closing smt only
bridges the syntactic gap between auto's symbolic-execution
normalisation and the lemma statement. *)
have H :
reconstruct (committee_quorum c_new_pre)
(zip_add
(fresh_sharing (committee_quorum c_new_pre)
(reconstruct (committee_quorum c_old_pre) shares_old_pre))
(fresh_sharing (committee_quorum c_new_pre) zero_share))
=
reconstruct (committee_quorum c_old_pre) shares_old_pre.
- exact (honest_reshare_reconstructs
(committee_quorum c_new_pre)
(reconstruct (committee_quorum c_old_pre) shares_old_pre)
Huniq Hnonempty).
smt(honest_reshare_reconstructs
committee_quorum_uniq
committee_quorum_nonempty).
qed.
(* Corollary at the public-key level: derive_pk congruence + the
reconstruct equality. *)
lemma pulsar_n4_pk_preservation_honest
(c_old_pre c_new_pre : committee_t)
(shares_old_pre : share_t list) :
hoare [ ReshareHonest.reshare :
c_old = c_old_pre /\ shares_old = shares_old_pre
/\ c_new = c_new_pre
==>
derive_pk (reconstruct (committee_quorum c_new_pre) res.`1)
= derive_pk (reconstruct (committee_quorum c_old_pre)
shares_old_pre) ].
proof.
conseq (reshare_preserves_secret_honest
c_old_pre c_new_pre shares_old_pre) => /#.
qed.