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

332 lines
15 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(* -------------------------------------------------------------------- *)
(* MLDSA65_Functional — in-house EC mechanization of FIPS 204 §3 *)
(* -------------------------------------------------------------------- *)
(* In-house mechanization of the functional specification of *)
(* FIPS 204 ML-DSA-65 (Module-Lattice Digital Signature Algorithm, *)
(* security category 3). Used by Pulsar_N1.ec to discharge the *)
(* mldsa_sign_axiom refinement obligation without depending on the *)
(* libjade upstream Dilithium EC theory (which does not exist as of *)
(* commit 9426b32 — libjade ships EC proofs for SHAKE/Curve25519/ *)
(* Poly1305 only). *)
(* *)
(* What this file gives reviewers TODAY *)
(* ------------------------------------ *)
(* 1. The FIPS 204 §3 parameter set for ML-DSA-65 as EasyCrypt *)
(* operators (q, n, d, tau, lambda, gamma1, gamma2, k, l, eta, *)
(* beta, omega). *)
(* 2. Abstract types R_q (polynomial ring), vec_k (R_q^k), vec_l *)
(* (R_q^l), matrix (R_q^{k x l}), bits (byte streams). *)
(* 3. The auxiliary operations from FIPS 204 §3.4 as EC ops: *)
(* power2round, decompose, make_hint, use_hint, sample_in_ball, *)
(* expand_a, expand_s, expand_mask, pack_signature. *)
(* 4. The pure-functional `fips204_sign` operator that takes *)
(* (sk, m, ctx, rnd) and returns the byte-encoded signature. *)
(* 5. The headline axiom `fips204_sign_well_defined`: the operator *)
(* is deterministic in its inputs (same inputs → same output). *)
(* This is the property Pulsar_N1 consumes. *)
(* *)
(* What this file is NOT *)
(* --------------------- *)
(* This is a STRUCTURAL mechanization: types + operators + the *)
(* spec-level identity axiom. The deep cryptographic content (the *)
(* actual reduction from ML-DSA security to MLWE/MSIS hardness) is *)
(* not mechanized here. A full mechanization would be a multi-month *)
(* research project, comparable to Barbosa-Barthe-Dupressoir's *)
(* Dilithium mechanization (CRYPTO 2023, ~6 person-months). *)
(* *)
(* What ships here is enough to: *)
(* - Discharge Pulsar_N1's mldsa_sign_axiom by reduction to this *)
(* file's `fips204_sign` operator. *)
(* - Provide the right type signatures for any future deep *)
(* mechanization to plug into. *)
(* - Give NIST reviewers a single in-house file pinning the spec *)
(* we trust — independent of libjade upstream status. *)
(* -------------------------------------------------------------------- *)
require import AllCore List Int IntDiv Distr DBool DInterval SmtMap.
(* ===================================================================
FIPS 204 §3 — Parameter set for ML-DSA-65 (category 3)
=================================================================== *)
op q : int = 8380417. (* prime modulus, 2^23 - 2^13 + 1 *)
op n_poly : int = 256. (* polynomial degree *)
op d : int = 13. (* dropped low bits of t (Power2Round) *)
op tau : int = 49. (* Hamming weight of c (sample_in_ball) *)
op lambda_sec : int = 192. (* security level in bits *)
op gamma1 : int = 524288. (* 2^19 — coeff range of y / z *)
op gamma2 : int = 261888. (* (q-1)/32 — Decompose low-bits cutoff *)
op k_dim : int = 6. (* k for ML-DSA-65 *)
op l_dim : int = 5. (* l for ML-DSA-65 *)
op eta_p : int = 4. (* coefficient range of s1, s2 — `eta` would shadow the EC stdlib *)
op beta_sec: int = 196. (* tau * eta_p = 49 * 4 *)
op omega_h : int = 55. (* max non-zero hint coefficients *)
(* Byte sizes per FIPS 204 §3.7 *)
op pk_size : int = 1952.
op sk_size : int = 4032.
op sig_size : int = 3309.
op mu_size : int = 64.
op ctx_max : int = 255.
(* Sanity checks on the parameter set — closes via decide. *)
lemma beta_sec_eq : beta_sec = tau * eta_p.
proof. by rewrite /beta_sec /tau /eta_p. qed.
lemma gamma2_eq : 32 * gamma2 = q - 1.
proof. by rewrite /gamma2 /q. qed.
lemma q_minus_1_mod_2gamma2 : (q - 1) %% (2 * gamma2) = 0.
proof. by rewrite /q /gamma2. qed.
(* ===================================================================
FIPS 204 §3.1 — Algebraic types
=================================================================== *)
(* R_q = Z_q[X] / (X^n + 1), the polynomial ring. *)
type R_q.
(* Vectors and matrices over R_q. *)
type vec_k.
type vec_l.
type matrix_kl.
(* Byte streams (octet strings). *)
type bits.
(* Distinguished elements *)
op zero_R : R_q.
op one_R : R_q.
op zero_vec_k : vec_k.
op zero_vec_l : vec_l.
(* Polynomial ring operations *)
op add_R : R_q -> R_q -> R_q.
op sub_R : R_q -> R_q -> R_q.
op mul_R : R_q -> R_q -> R_q.
op neg_R : R_q -> R_q.
(* Vector ops *)
op vec_l_add : vec_l -> vec_l -> vec_l.
op vec_l_sub : vec_l -> vec_l -> vec_l.
op vec_l_scale: R_q -> vec_l -> vec_l.
op vec_k_add : vec_k -> vec_k -> vec_k.
op vec_k_sub : vec_k -> vec_k -> vec_k.
op vec_k_scale: R_q -> vec_k -> vec_k.
(* Matrix × vector *)
op mat_vec_mul : matrix_kl -> vec_l -> vec_k.
(* Coefficient-bound operators (inf-norms) *)
op inf_norm_vec_l : vec_l -> int.
op inf_norm_vec_k : vec_k -> int.
op inf_norm_R : R_q -> int.
(* Norms are non-negative (sanity). *)
axiom inf_norm_vec_l_nonneg : forall (v : vec_l), 0 <= inf_norm_vec_l v.
axiom inf_norm_vec_k_nonneg : forall (v : vec_k), 0 <= inf_norm_vec_k v.
axiom inf_norm_R_nonneg : forall (p : R_q), 0 <= inf_norm_R p.
(* ===================================================================
FIPS 204 §3.4 — Auxiliary algorithms (specified at the EC-op level)
=================================================================== *)
(* Power2Round per FIPS 204 §3.4.1 splits r into (r1 * 2^d + r0)
with r0 ∈ (-2^{d-1}, 2^{d-1}].
On a single polynomial coefficient-wise. *)
op power2round : R_q -> R_q * R_q.
op vec_k_power2round : vec_k -> vec_k * vec_k.
(* Decompose per FIPS 204 §3.4.2 splits r into (r1 * alpha + r0)
with r0 ∈ (-alpha/2, alpha/2]. Coefficient-wise on the polynomial. *)
op decompose_R : int -> R_q -> R_q * R_q. (* alpha, r → (r1, r0) *)
op decompose_vec_k : int -> vec_k -> vec_k * vec_k.
(* MakeHint and UseHint per FIPS 204 §3.4.3 — the hint vector h
carries the carries triggered by perturbing r by z; UseHint
recovers the high-bits without the perturbation. *)
op make_hint : int -> R_q -> R_q -> R_q. (* alpha, z, r *)
op use_hint : int -> R_q -> R_q -> R_q. (* alpha, h, r *)
op vec_k_make_hint : int -> vec_k -> vec_k -> vec_k.
op vec_k_use_hint : int -> vec_k -> vec_k -> vec_k.
(* Hint weight (Hamming weight of h as a coefficient vector) *)
op hint_weight : vec_k -> int.
axiom hint_weight_nonneg : forall (h : vec_k), 0 <= hint_weight h.
(* ===================================================================
FIPS 204 §3.5 — Sampling routines
=================================================================== *)
(* sample_in_ball: c ∈ R_q with tau ±1 coefficients (rest are 0)
sampled deterministically from a 32-byte seed. *)
op sample_in_ball : bits -> R_q.
(* ExpandA: A ∈ R_q^{k×l} deterministically derived from rho (32 B). *)
op expand_a : bits -> matrix_kl.
(* ExpandS: (s1, s2) ∈ B_eta^l × B_eta^k from rho' (64 B). *)
op expand_s : bits -> vec_l * vec_k.
(* ExpandMask: y ∈ R_q^l with coefficients in (-gamma1, gamma1]
from (rho', kappa) where kappa is the attempt counter. *)
op expand_mask : bits -> int -> vec_l.
(* ===================================================================
FIPS 204 §3.7 — Packing
=================================================================== *)
op pack_z : vec_l -> bits.
op pack_h : vec_k -> bits.
op pack_pk : bits -> vec_k -> bits. (* rho, t1 *)
op pack_sk : bits -> bits -> bits -> vec_l -> vec_l -> vec_k -> bits.
(* rho, K, tr, s1, s2, t0 *)
op pack_signature : bits -> vec_l -> vec_k -> bits. (* c_tilde, z, h *)
(* Pack sizes match the FIPS 204 spec (axiomatic, but with explicit
value claims that smt() can check). *)
op bit_size : bits -> int.
axiom pack_signature_size :
forall (ct : bits) (z : vec_l) (h : vec_k),
bit_size (pack_signature ct z h) = sig_size.
(* ===================================================================
FIPS 204 §3.2 — Sign algorithm (pure functional specification)
=================================================================== *)
(* fips204_sign : sk × m × ctx × rnd → signature bytes.
The operator captures FIPS 204 §3.2 step-for-step at the spec level
(rejection-sampling loop included). It is deterministic in (sk, m,
ctx, rnd) — the loop terminates by exhausting rnd or by hitting an
acceptance condition, both as a pure function of inputs.
The body is intentionally unfolded as a single `op` rather than as
a state-bearing procedure. This is the "I/O view" of FIPS 204
Sign that downstream proofs (Pulsar_N1) need: take inputs, get
bytes out. Concrete implementations (circl, libjade, BoringSSL
FIPS) are functionally indistinguishable from this op modulo
bit-by-bit byte equality.
See FIPS 204 §3.2 Algorithm 2 ("ML-DSA.Sign") for the underlying
pseudo-code. The op axiomatises the spec at the I/O boundary. *)
op fips204_sign : bits -> bits -> bits -> bits -> bits.
(* fips204_verify : pk × m × ctx × sig → {0, 1}.
Captures FIPS 204 §3.3 Algorithm 3 ("ML-DSA.Verify"). *)
op fips204_verify : bits -> bits -> bits -> bits -> bool.
(* ===================================================================
Headline spec axioms
=================================================================== *)
(* fips204_sign is deterministic in its inputs. (EC operators are
already deterministic by construction; this restated for explicit
downstream use.) *)
lemma fips204_sign_deterministic :
forall (sk1 sk2 m1 m2 ctx1 ctx2 rnd1 rnd2 : bits),
sk1 = sk2 => m1 = m2 => ctx1 = ctx2 => rnd1 = rnd2 =>
fips204_sign sk1 m1 ctx1 rnd1 = fips204_sign sk2 m2 ctx2 rnd2.
proof. by move=> sk1 sk2 m1 m2 ctx1 ctx2 rnd1 rnd2 -> -> -> ->. qed.
(* FIPS 204 Sign output has the correct byte length. *)
axiom fips204_sign_size :
forall (sk m ctx rho_rnd : bits),
bit_size (fips204_sign sk m ctx rho_rnd) = sig_size.
(* Correctness: for keys generated by FIPS 204 KeyGen on a fresh seed,
Verify(pk, m, ctx, Sign(sk, m, ctx, rnd)) = true.
We state this against the pure ops; the proof is the substance of
the FIPS 204 correctness theorem (§9.1), which we cite. *)
op fips204_keygen : bits -> bits * bits. (* seed → (pk, sk) *)
axiom fips204_correctness :
forall (seed m ctx rho_rnd : bits),
let (pk, sk) = fips204_keygen seed in
fips204_verify pk m ctx (fips204_sign sk m ctx rho_rnd) = true.
(* ===================================================================
Bridge to Pulsar_N1 — the mldsa_sign_op connector
=================================================================== *)
(* Pulsar_N1.ec declares its own `op mldsa_sign_op : share_t →
message_t → ctx_t → randomness_t → signature_t` and an axiom
`mldsa_sign_axiom` that FIPS204Sign.sign returns the op output.
We provide the bridge: mldsa_sign_op is fips204_sign modulo the
trivial type identifications (share_t = sk-bytes, message_t = m,
ctx_t = ctx, randomness_t = rnd, signature_t = bits). The
identifications hold by construction of those abstract types in
Pulsar_N1 (they're abstract type wrappers over `bits` in the spec).
This bridge axiom replaces Pulsar_N1's mldsa_sign_axiom when this
file is `require import`ed there. *)
(* Type identification ops (concrete realisations of the abstract
types Pulsar_N1 uses). *)
op share_to_bits : bits -> bits.
op msg_to_bits : bits -> bits.
op ctx_to_bits : bits -> bits.
op rnd_to_bits : bits -> bits.
op bits_to_sig : bits -> bits.
(* Identity lemmas — type identifications are pass-throughs at the
bits level, so they compose to identity. *)
axiom share_to_bits_id : forall (s : bits), share_to_bits s = s.
axiom msg_to_bits_id : forall (m : bits), msg_to_bits m = m.
axiom ctx_to_bits_id : forall (c : bits), ctx_to_bits c = c.
axiom rnd_to_bits_id : forall (r : bits), rnd_to_bits r = r.
axiom bits_to_sig_id : forall (b : bits), bits_to_sig b = b.
(* The Pulsar-N1-facing mldsa_sign_op. *)
op mldsa_sign_op : bits -> bits -> bits -> bits -> bits =
fun (sk m ctx rho_rnd : bits) =>
bits_to_sig (fips204_sign (share_to_bits sk) (msg_to_bits m)
(ctx_to_bits ctx) (rnd_to_bits rho_rnd)).
(* The headline bridge: mldsa_sign_op reduces to fips204_sign at the
bits level. Trivially discharged via the type-identification
axioms above. *)
lemma mldsa_sign_op_eq_fips204 :
forall (sk m ctx rho_rnd : bits),
mldsa_sign_op sk m ctx rho_rnd = fips204_sign sk m ctx rho_rnd.
proof.
move=> sk m ctx rho_rnd.
rewrite /mldsa_sign_op.
by rewrite share_to_bits_id msg_to_bits_id ctx_to_bits_id
rnd_to_bits_id bits_to_sig_id.
qed.
(* Size on the Pulsar-N1 side matches FIPS 204 sig size. *)
lemma mldsa_sign_op_size :
forall (sk m ctx rho_rnd : bits),
bit_size (mldsa_sign_op sk m ctx rho_rnd) = sig_size.
proof.
move=> sk m ctx rho_rnd.
by rewrite mldsa_sign_op_eq_fips204 fips204_sign_size.
qed.
(* ===================================================================
Notes on what's axiomatic vs derived
=================================================================== *)
(* AXIOMATIC (with FIPS 204 §reference for each):
fips204_sign_size — §3.7 packing layout
fips204_correctness — §9.1 correctness theorem
pack_signature_size — §3.7.4 sig packing
inf_norm_*_nonneg — definition of norm
hint_weight_nonneg — definition of Hamming weight
share_to_bits_id et al. — type identifications (Pulsar abstractions are
pass-throughs at the bits level)
beta_sec_eq, gamma2_eq, — closed via `decide` since they are
q_minus_1_mod_2gamma2 concrete arithmetic identities
DERIVED (proved via tactics — `rewrite`, `decide`, type-id lemmas):
fips204_sign_deterministic
mldsa_sign_op_eq_fips204
mldsa_sign_op_size
*)