Harmonise the axiom accounting everywhere:
22 named axioms total — 17 narrow implementation-refinement
(14 byte-walk + 1 signature-codec round-trip + 2 honest-
execution no-reject post-conditions) plus 5 Lean-bridged
algebraic, each with file:line in EC and Lean.
This replaces the prior split phrasings ("14 implementation-refinement"
in README vs "17 implementation-refinement axioms in dependency cone"
in EC README) — both were technically true under different counting
bases but looked like a contradiction.
- README.md: hard-boundary block + axiom table use canonical phrasing.
- SUBMISSION.md: trust-footprint table + "Path toward full mechanized
closure" + per-axiom closure-paths table use canonical phrasing.
- docs/proof-axiom-inventory.md: §1-§4 rewritten to current v12+ state
(sign_body_z_spec is the only surviving stage-level byte-walk;
combine/sign h derived through make_hint_of_w; w-stage split into
matrix_a / mask_y; w_low sub-axioms; ExternalMu codec layout split
into combine 3 per-range + sign 1 collapsed).
- docs/proof-claims.md: scope-boundary framing on adversarial
robustness section.
- docs/tcb.md: §3.2 reframed as "deployed-binary targets" (downstream
packaging) rather than "production targets (out of scope for v0.1)".
- docs/cryptographer-sign-off.md: "Boundary of this sign-off" instead
of "Out-of-scope for this sign-off"; minor TBD scrubs.
- proofs/easycrypt/README.md: trust-boundary table uses canonical
phrasing.
- proofs/easycrypt/Pulsar_N1_Extracted.ec: header comment refreshed
from v7-era trust accounting to v12+ (22 named axioms total).
- proofs/lean-easycrypt-bridge.md: "Future work" → "Closure paths";
summary uses canonical phrasing.
- proofs/easycrypt/extraction/{combine,sign}-byte-walk-roadmap.md:
"followup B" → "v10 no-reject decomposition".
11 KiB
Sign-side byte-walk roadmap
Status — current
sign_body_compute_sig_spec is no longer an axiom. It is
proved as a lemma at
proofs/easycrypt/Pulsar_N1_Sign_Refinement.ec:776, composing
the per-stage component-level lemmas
(sign_body_compute_components_spec) with the structural pack
identity (Pulsar_N1.pack_n1_signature shared on both extracted
and centralised sides). The 7-step sub-claim decomposition that
the original version of this roadmap sketched (S1..S7) has been
landed implicitly via the v3 → v12 refactor — the per-stage
axioms named below sit at strictly narrower granularity than the
original sign_body_compute_sig_spec.
The active byte-walk + codec-layout axiom family on the sign
side (post-v12, all in Pulsar_N1_Sign_Refinement.ec):
| Axiom | Line | Category | Replaces |
|---|---|---|---|
sign_layout_m_buffer_external_mu |
510 | wrapper byte-layout (load_bytes ↔ external_mu) | v8 sign_body_mu_input_spec (narrowed in v9 to drop the abstract sign_body_mu_input op) |
sign_body_matrix_a_spec |
560 | w-stage sub-axiom (extracted matrix A = central_matrix_a) |
half of v11 sign_body_w_spec |
sign_body_mask_y_spec |
570 | w-stage sub-axiom (extracted mask y at accepting kappa) | half of v11 sign_body_w_spec |
sign_body_y_spec |
649 | z-stage sub-axiom (extracted y = central_y_at_accepted_kappa) |
half of v10 sign_body_z_spec |
sign_body_cs1_spec |
662 | z-stage sub-axiom (extracted cs1 = apply_c_to_s1) |
half of v10 sign_body_z_spec |
sign_body_w_low_spec |
704 | h-stage sub-axiom (extracted w_low = central_w_low) |
v9 sign_body_h_spec (after MakeHint structural split, v10) |
sign_no_reject_on_accepted_honest_layout |
826 | accepted-path companion: layout + accept event ⇒ status = 0 | landed in the v10 no-reject decomposition; pairs with sign_body_compute_sig_spec |
The sign side does NOT consume threshold_partial_response_identity —
that bridge is only required on the combine side (Lagrange
aggregation across the quorum). The sign-side z-stage uses the
structural add_response_vec composition shared with
Pulsar_N1.mldsa_compute_z.
This document is retained for historical context: the
S1..S7 stage map below corresponds to FIPS 204 §6.2 stages and
the kappa rejection-sampling loop, and it remains a useful
reading guide to sign.ec:3603-3641 and sign_inner at
sign.ec:3469. The previous high-level framing — "the sign-side
proof is harder because of the kappa loop" — has been
side-stepped in the current source by introducing
combine_body_compute_status / sign_body_compute_status and
conditioning each per-stage lemma on status = 0 (the
accepted-path precondition), with the no-reject companion axiom
discharging the status bit from the protocol-level
accept_signing_attempt event.
Historical: original axiom statement
The pre-v3 shape of the obligation:
axiom sign_body_compute_sig_spec :
forall mem_pre ptrs full,
Pulsar_N1_Sign_Layout.layout_sign_args
mem_pre ptrs (wire_sign_args_of_full full) =>
refine_sig_to_n1_sign (sign_body_compute_sig mem_pre ptrs)
= sign_abs_op full.
where sign_abs_op full unfolds (by definition) to:
Pulsar_N1.mldsa_sign_op
full.`sgn_sk_n1 full.`sgn_m_n1
full.`sgn_ctx_n1 full.`sgn_rnd_n1
i.e., the centralised FIPS 204 ML-DSA-65 signature on the four
protocol-level ghost fields. The current shape (now a lemma)
adds one precondition — sign_body_compute_status mem_pre ptrs = 0 — which threads through sign_no_reject_on_accepted_honest_layout
into the wrapper bridge.
Ghost contract: ctx and rho_rnd are not direct libjade
parameters. The wrapper carries them as ghost fields; the
obligation includes the claim that the wrapper's mu derivation
(SHAKE256(0x00 || ctxlen || ctx || M) per FIPS 204 §5.4.1
ExternalMu) and K-derived randomness correspond to FIPS 204
Sign_internal on the four-tuple. See the named ghost contract
block in Pulsar_N1_Sign_Refinement.ec and
../../lean-easycrypt-bridge.md. In v9 the prior bundled
sign_body_mu_input_spec axiom was narrowed to
sign_layout_m_buffer_external_mu (a pure byte-layout claim
about the wrapper-assembled ptr_m buffer; no libjade-body read
appears in the statement).
Historical: extracted procedure shape
M.sign lives in sign.ec:3603-3641. Its signature:
proc sign (ptr_signature : W64.t, ptr_m : W64.t,
m_len : W64.t, ptr_sk : W64.t) : W32.t
The body is thin: it reads 4000 bytes of secret-key bytes from
ptr_sk, delegates to sign_inner for the actual signature
computation, and writes 3293 bytes of packed signature at
ptr_signature.
The real work is in sign_inner (sign.ec:3469). The kappa
rejection loop, the SHAKE-based mu computation, the
expandA + expandMask + matrix-vector multiplication + decompose
- rejection-check + pack flow — all of it lives in
sign_inner.
Historical: stage map (FIPS 204 §6.2)
| Stage | Extracted location | Functional op | Sub-claim (now landed as a lemma) |
|---|---|---|---|
| 1. Read sk bytes from input pointer | sign.ec:3625-3630 |
decode_sk (Sign_Layout) |
folded into layout_sign_args |
| 2. sign_inner: sk unpacking | sign_inner body |
Pulsar_N1.unpack_sk |
folded into the structural unpack on both sides; pack_unpack_sk_roundtrip (axiom in Pulsar_N1.ec:950) pins the realisation |
| 3. sign_inner: mu = SHAKE256(tr ‖ M) | sign_inner, SHAKE call |
Pulsar_N1.compute_mu |
sign_layout_m_buffer_external_mu (axiom) ⇒ sign_body_mu_input_spec (lemma) ⇒ sign_body_mu_spec (lemma) via structural shake256_to_mu; compute_mu_injective (axiom in Pulsar_N1.ec:953) pins the realisation |
| 4. sign_inner: rho_prime derivation | deterministic / hedged | (folded into sign_abs_op's sgn_rnd_n1) |
wrapper ghost-contract obligation; not separately mechanized |
| 5. sign_inner: kappa rejection loop | sign_inner body |
composition of expand_mask, mat_vec_mul, decompose, sample_in_ball, mul, sub, make_hint | sign_body_{matrix_a,mask_y}_spec ⇒ sign_body_w_spec (lemma); sign_body_{y,cs1}_spec ⇒ sign_body_z_spec (lemma) via mldsa_compute_z structural; sign_body_w_low_spec ⇒ h-stage; sign_no_reject_on_accepted_honest_layout (axiom) discharges the status bit |
| 6. sign_inner: pack_signature(c_tilde*, z*, h*) | pack_signature call |
Pulsar_N1.pack_n1_signature |
folded into structural pack_n1_signature; pack_unpack_n1_signature_roundtrip (axiom in Pulsar_N1.ec:817) pins the realisation |
| 7. M.sign: write sig_packed at ptr_signature | sign.ec:3634-3640 |
write_sig_sign (Sign_Layout) |
proved in Pulsar_N1_Sign_Layout.ec |
Composition
The lemma chain that closes sign_body_compute_sig_spec:
sign_layout_m_buffer_external_mu
⇒ sign_body_mu_input_spec
⇒ sign_body_mu_spec (via shake256_to_mu)
sign_body_{matrix_a,mask_y}_spec
⇒ sign_body_w_spec (via central_w)
⇒ sign_body_w1_spec (via high_bits_of_w)
sign_body_mu_spec ∧ sign_body_w1_spec
⇒ sign_body_c_tilde_spec (via shake_mu_w1)
sign_body_{y,cs1}_spec ∧ sign_body_c_tilde_spec
⇒ sign_body_z_spec (via mldsa_compute_z = add_response_vec)
sign_body_w_spec ∧ sign_body_w_low_spec
⇒ sign_body_h_spec (via make_hint_of_w)
sign_body_c_tilde_spec ∧ sign_body_z_spec ∧ sign_body_h_spec
⇒ sign_body_compute_components_spec
sign_body_compute_components_spec
⇒ sign_body_compute_sig_spec (via pack_n1_signature congruence)
The composition is fully landed in Pulsar_N1_Sign_Refinement.ec
through versions v3 → v12. See the per-version delta in the
ACCOUNTING block at the end of that file.
Concrete attack surface (post-v12)
What remains to mechanically close on the sign side, in suggested attack order:
sign_layout_m_buffer_external_mu(wrapper byte-layout). The narrowest remaining mu obligation: the bytes the wrapper writes atptr_mform the FIPS 204 §5.4.1 ExternalMu layout. Singleload_bytesclaim; the wrapper-side writer is a straightstore_bytesof[0; |ctx|] ++ ctx ++ M. Mechanical.sign_body_w_low_spec(h-stage sub-axiom). Low-bits side of the decompose at the accepting kappa. Direct mirror ofcombine_body_w_low_spec— shared proof effort with the combine side.sign_body_y_spec(z-stage). Extracted mask y at the accepting kappa equalscentral_y_at_accepted_kappa. Effectivelycentral_y_at_accepted_kappaIS the mask-y stage conditioned onstatus = 0— the obligation reduces to a structural identity once the accepted-path is fixed.sign_body_cs1_spec(z-stage). Extracted c·s1 mixed product equalsapply_c_to_s1. The c value comes fromcentral_c_from_c_tildeapplied to the extracted c_tilde intermediate — composes with the c_tilde lemma chain.sign_body_{matrix_a,mask_y}_spec(w-stage). Mirrors of the combine-side axioms; same BArray ↔ R_q polynomial-view bridge required.sign_no_reject_on_accepted_honest_layout(companion). Conditional status-bit claim: layout-conforming inputs +accept_signing_attempt⇒sign_body_compute_status = 0. Same shape as the combine-side companion. The kappa-loop convergence (~ 1 − (3/4)^256 per attempt) is tracked operationally viaPulsar_N1.mldsa_accept_lower_bound; probabilistic Hoare logic for the loop is NOT in scope.
Why the kappa loop is no longer the bottleneck
The original framing in the pre-v3 version of this roadmap
treated the kappa rejection-sampling loop in sign_inner as the
load-bearing obstacle — "proving termination + correctness of an
unbounded rejection loop in EC is significantly harder than
proving a fixed-length aggregation".
The current structure side-steps that obstacle by reducing each
per-stage obligation to its accepted-path form (conditioned on
sign_body_compute_status mem_pre ptrs = 0). The deterministic
content of sign_inner at the accepting kappa is what
sign_body_{y,cs1,matrix_a,mask_y,w_low}_spec capture; the
probabilistic content of the kappa loop is what
sign_no_reject_on_accepted_honest_layout discharges from the
protocol-level accept_signing_attempt event.
The probability bound on accept_signing_attempt is operational
(Pulsar_N1.mldsa_accept_lower_bound), not probabilistic-Hoare.
That choice is the same one BBDFGHHLW (CRYPTO 2023) makes for
single-party Dilithium and matches FIPS 204's standard treatment
of the rejection sampler.
What this roadmap does NOT do
It does not produce mechanical closure of any of the axioms above. The historical S-map (S1..S7) is now landed implicitly via the per-stage axiom family; the residual obligations sit at the narrower granularity in the table above. Each axiom is independently attackable; the suggested order mirrors the combine-side ordering where shared.
Cross-references
Pulsar_N1_Sign_Refinement.ec:830-1120— the per-file ACCOUNTING block (per-version axiom delta v1 → v12)Pulsar_N1_Extracted.ec:34-92— authoritative trust-boundary accounting../README.md— current axiom enumeration with file:line refs../../lean-easycrypt-bridge.md— Lean↔EC bridge correspondencecombine-byte-walk-roadmap.md— combine-side counterpart- Ghost contract: named block in
Pulsar_N1_Sign_Refinement.ec - Libjade jasmin-ct dependency:
../../../ct/jasmin-ct-libjade.md(separately blocking issue #2) - Linear issue tracker: #3