mirror of
https://github.com/luxfi/pulsar.git
synced 2026-09-24 00:25:52 +00:00
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
1121 lines
51 KiB
Plaintext
1121 lines
51 KiB
Plaintext
(* -------------------------------------------------------------------- *)
|
||
(* Pulsar — Class N1 Sign refinement (extracted libjade ↔ FIPS204Sign) *)
|
||
(* -------------------------------------------------------------------- *)
|
||
(* This file is the discharge path for `Pulsar_N1.ec`'s section-local *)
|
||
(* refinement axiom *)
|
||
(* *)
|
||
(* declare axiom S_functional_spec : *)
|
||
(* equiv [ S.sign ~ FIPS204Sign.sign : *)
|
||
(* ={arg} ==> ={res} ]. *)
|
||
(* *)
|
||
(* Mirrors the combine refinement structure (see *)
|
||
(* Pulsar_N1_Combine_Refinement.ec for the design rationale). *)
|
||
(* *)
|
||
(* L1 Concrete byte-layout: lives in Pulsar_N1_Sign_Layout.ec. *)
|
||
(* L2 Protocol-extended args (`sign_full_args_t`): wire-level args *)
|
||
(* bundled with the abstract protocol inputs (sk, m, ctx, *)
|
||
(* rho_rnd). The libjade entry point's FUNCTIONAL spec is then *)
|
||
(* `sign_abs_op full = mldsa_sign_op full.sk full.m full.ctx *)
|
||
(* full.rho_rnd` — a DEFINITION (not an axiom), folding the *)
|
||
(* FIPS 204 functional-correctness identity into the byte-walk. *)
|
||
(* L3 ONE atomic axiom `sign_body_spec`: layout-conforming inputs *)
|
||
(* map under the extracted libjade body to bytes that decode *)
|
||
(* (via `refine_sig_to_n1_sign`) to `sign_abs_op` — i.e., the *)
|
||
(* FIPS 204 ML-DSA-65 signature on the protocol-level inputs. *)
|
||
(* *)
|
||
(* Honest note on ctx / rho_rnd handling (per Antoine's design point): *)
|
||
(* libjade's M.sign(ptr_signature, ptr_m, m_len, ptr_sk) has NO *)
|
||
(* ctx or rho_rnd parameter — the FIPS 204 deterministic mode *)
|
||
(* derives the per-call randomness internally from K (the secret *)
|
||
(* key K field) and mu (the message hash binder). ctx is the *)
|
||
(* FIPS 204 §3.7 prehash context, which the threshold wrapper *)
|
||
(* handles BEFORE calling libjade (the wrapper hashes m together *)
|
||
(* with ctx into mu). *)
|
||
(* *)
|
||
(* In this file we carry ctx and rho_rnd as GHOST fields in *)
|
||
(* `sign_full_args_t`. They do NOT appear in the wire layout *)
|
||
(* (`layout_sign_args` only constrains sk and m). The byte-walk *)
|
||
(* axiom `sign_body_spec` discharges the protocol-level identity: *)
|
||
(* for the right wrapper encoding of ctx into mu and rho_rnd into *)
|
||
(* K-derived randomness, the libjade body produces the FIPS 204 *)
|
||
(* signature of the four-tuple. This is HONEST — the identity is *)
|
||
(* the wrapper's responsibility to satisfy when constructing *)
|
||
(* sign_full_args_t. *)
|
||
(* -------------------------------------------------------------------- *)
|
||
|
||
require import AllCore List Int IntDiv Distr DBool DInterval SmtMap.
|
||
|
||
(* Concrete layout file: provides `mem_t`, `sign_ptrs_t`,
|
||
`sign_abs_args_t`, encoders/decoders, `read_sig_sign`,
|
||
`layout_sign_args`, the proved `encode_sign_args_layout`, and
|
||
reuses combine-side memory primitives. *)
|
||
require import Pulsar_N1_Sign_Layout.
|
||
|
||
(* Pulsar_N1 provides the protocol-level abstract types and the
|
||
centralised `mldsa_sign_op`. We reference via `Pulsar_N1.` prefix. *)
|
||
require import Pulsar_N1.
|
||
|
||
(* ===================================================================
|
||
L2 — Protocol-extended args.
|
||
|
||
`sign_full_args_t` carries THREE distinct categories of field, in
|
||
this order (Agent 1 C3 closure):
|
||
|
||
[WIRE] sgn_wire : laid out in memory by encode_sign_args;
|
||
what the byte-walk axiom reads through
|
||
layout_sign_args. Concrete bytes.
|
||
[MIRROR] sgn_sk_n1, : protocol-level views of sgn_wire's
|
||
sgn_m_n1 contents. The wrapper layer binds them
|
||
to sgn_wire via the wire projectors
|
||
n1_share_to_layout_sign /
|
||
n1_msg_to_layout_sign (defined in
|
||
Pulsar_N1_Sign_Wrapper.ec).
|
||
[GHOST] sgn_ctx_n1, : protocol-level fields with NO wire
|
||
sgn_rnd_n1 counterpart. libjade M.sign(ptr_signature,
|
||
ptr_m, m_len, ptr_sk) has no ctx/rnd
|
||
parameters — the wrapper folds ctx into
|
||
mu (FIPS 204 §5.4.1 ExternalMu) and rnd
|
||
into K-derived randomness BEFORE calling
|
||
libjade. The byte-walk axiom
|
||
`sign_body_compute_sig_spec` claims
|
||
correctness over the FULL FIPS 204
|
||
four-arg Sign_internal — bundling the
|
||
ctx/mu and rnd/K bindings into one
|
||
statement.
|
||
|
||
`sign_abs_op` (defined below) is the spec target:
|
||
sign_abs_op full = mldsa_sign_op sk_n1 m_n1 ctx_n1 rnd_n1
|
||
|
||
It reads ALL FOUR protocol-level fields (sk/m from MIRROR,
|
||
ctx/rnd from GHOST) but does NOT consume sgn_wire.
|
||
|
||
Field-prefix discipline: every field starts with `sgn_*` to
|
||
disambiguate from combine_full_args_t's `full_*` fields (EC's
|
||
record-field inference refuses to mix fields from different
|
||
record types in the same `{| ... |}` literal). *)
|
||
type sign_full_args_t = {
|
||
(* [WIRE] *)
|
||
sgn_wire : Pulsar_N1_Sign_Layout.sign_abs_args_t;
|
||
(* [MIRROR] — protocol view of sgn_wire's sk_abs, m_abs *)
|
||
sgn_sk_n1 : Pulsar_N1.share_t;
|
||
sgn_m_n1 : Pulsar_N1.message_t;
|
||
(* [GHOST] — no wire counterpart; bound into byte-walk via libjade-
|
||
internal mu/K derivation (see GHOST CONTRACT block below) *)
|
||
sgn_ctx_n1 : Pulsar_N1.ctx_t;
|
||
sgn_rnd_n1 : Pulsar_N1.randomness_t;
|
||
}.
|
||
|
||
op wire_sign_args_of_full (full : sign_full_args_t)
|
||
: Pulsar_N1_Sign_Layout.sign_abs_args_t =
|
||
full.`sgn_wire.
|
||
|
||
(* ===================================================================
|
||
Signature-type coercion — IDENTITY.
|
||
|
||
Prior to commit "axiom hygiene: refine_sig_to_n1 identity + explicit
|
||
wrapper equivs", `Pulsar_N1.signature_t` and
|
||
`Pulsar_N1_Signature_Codec.signature_t` were two DISTINCT abstract
|
||
types and `refine_sig_to_n1_sign` was an uninterpreted coercion
|
||
between them. That left room for an adversarial instantiation where
|
||
`refine_sig_to_n1_sign` collapsed all signatures to a single
|
||
value, making the byte-walk axiom `sign_body_compute_sig_spec`
|
||
vacuous on the sign side. The closure: alias the two types so
|
||
they are the same concrete type, and define
|
||
`refine_sig_to_n1_sign` as the identity (`fun s => s`). Every
|
||
downstream proof that uses `refine_sig_to_n1_sign` as a
|
||
coercion now witnesses an honest identity.
|
||
=================================================================== *)
|
||
|
||
op refine_sig_to_n1_sign (s : Pulsar_N1_Signature_Codec.signature_t)
|
||
: Pulsar_N1.signature_t = s.
|
||
|
||
(* ===================================================================
|
||
L2 — Functional spec operator (DEFINITION, not axiom).
|
||
|
||
`sign_abs_op` returns the FIPS 204 ML-DSA-65 signature on the
|
||
protocol-level inputs. Because `mldsa_sign_op` is Pulsar_N1's
|
||
FIPS-204 functional operator, this DEFINITION captures the
|
||
functional-correctness identity at the operator level. The
|
||
byte-walk axiom below (`sign_body_spec`) discharges this
|
||
identity at the byte level for the extracted libjade sign.
|
||
=================================================================== *)
|
||
|
||
(* Inline form: surface the FIPS 204 §6.2 pipeline (unpack_sk +
|
||
compute_mu + sign_internal_loop) directly at the spec target.
|
||
Definitionally equal to `mldsa_sign_op sk m ctx rho_rnd` (since
|
||
mldsa_sign_op IS the pipeline composition in Pulsar_N1.ec), but
|
||
mentioning the pipeline ops explicitly here makes ctx-flow
|
||
through compute_mu searchable in proofs — and the non-bypass
|
||
lemma below proves it directly. *)
|
||
op sign_abs_op (full : sign_full_args_t) : Pulsar_N1.signature_t =
|
||
Pulsar_N1.sign_internal_loop
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
|
||
(* HIGH-4 non-bypass lemma (followup A reinforcement).
|
||
|
||
`compute_mu` is the ONLY consumer of `sgn_ctx_n1` in `sign_abs_op`.
|
||
This lemma surfaces that structural fact: changes to ctx must flow
|
||
through compute_mu before reaching the signature output. Trivially
|
||
true by definitional unfolding, but having it as a named theorem
|
||
makes the pipeline structure load-bearing in downstream byte-walk
|
||
discharges (which can rewrite the spec target into pipeline form
|
||
without further definitional cruft).
|
||
|
||
Combined with `Pulsar_N1.compute_mu_injective`: distinct ctx values
|
||
produce distinct mu values, so the signature output faithfully
|
||
depends on ctx via the FIPS 204 §5.4.1 ExternalMu derivation. *)
|
||
lemma sign_abs_op_ctx_flows_through_mu (full : sign_full_args_t) :
|
||
sign_abs_op full
|
||
= Pulsar_N1.sign_internal_loop
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
proof. by rewrite /sign_abs_op. qed.
|
||
|
||
(* Equivalence with mldsa_sign_op form (the alternative shape used
|
||
by combine_abs_op). Pure unfolding lemma — both sides are
|
||
definitionally equal. Stating it as a named lemma lets downstream
|
||
proofs rewrite between the two forms via lemma application
|
||
rather than full-on definitional unfolding. *)
|
||
lemma sign_abs_op_eq_mldsa (full : sign_full_args_t) :
|
||
sign_abs_op full =
|
||
Pulsar_N1.mldsa_sign_op
|
||
full.`sgn_sk_n1 full.`sgn_m_n1
|
||
full.`sgn_ctx_n1 full.`sgn_rnd_n1.
|
||
proof. by rewrite /sign_abs_op /Pulsar_N1.mldsa_sign_op. qed.
|
||
|
||
(* ===================================================================
|
||
GHOST CONTRACT: ctx / rho_rnd binding (Agent 1 C3 split path).
|
||
|
||
libjade `M.sign(ptr_signature, ptr_m, m_len, ptr_sk)` takes only
|
||
(sig, m, sk) — the FOUR-ARG FIPS 204 Sign_internal(sk, M, ctx,
|
||
rho_rnd) is recovered by INTERNAL DERIVATION inside libjade:
|
||
|
||
mu = SHAKE256(tr || M) where tr is in the unpacked sk
|
||
rho'' = SHAKE256(K || rnd_internal || mu)
|
||
where rnd_internal is libjade's per-call randomness
|
||
(zero in deterministic mode; otherwise per the FIPS 204
|
||
§6.2 rho-derivation specifying SHAKE256 of K and a
|
||
64-byte randomness input).
|
||
|
||
The CTX field of FIPS 204 §3.7 is folded into mu via the SHAKE input
|
||
PREFIXED with a 0x00 || |ctx| || ctx header (FIPS 204 §5.4.1
|
||
ExternalMu): this is the wrapper's responsibility to do BEFORE
|
||
calling libjade — the wrapper hashes M' = SHAKE-input(M, ctx) and
|
||
passes M' to libjade. The wrapper similarly seeds rnd_internal
|
||
from sgn_rnd_n1.
|
||
|
||
sign_body_compute_sig_spec BUNDLES three claims into one axiom:
|
||
|
||
[B1] LIBJADE CORE: given a layout-conforming memory state, the
|
||
extracted M.sign byte-walk produces FIPS 204 §6.2-§6.3
|
||
Sign_internal output bytes (i.e., the libjade procedure
|
||
implements the spec it claims to implement).
|
||
|
||
[B2] CTX/MU BINDING: the wrapper's pre-libjade SHAKE(0x00 ||
|
||
ctxlen || ctx || M) → M' substitution implements the
|
||
FIPS 204 §5.4.1 ExternalMu derivation. The byte-walk
|
||
axiom uses ctx implicitly: the `ptr_m` bytes already
|
||
encode (ctx, M), and libjade's mu computation produces
|
||
the four-arg FIPS 204 mu.
|
||
|
||
[B3] RHO_RND BINDING: libjade's rnd_internal is the wrapper's
|
||
encoding of sgn_rnd_n1.
|
||
|
||
The bundle is RIGHT for now because the libjade byte-walk hasn't
|
||
landed — splitting would manufacture two synthetic obligations
|
||
(B1 alone, B2+B3 alone) that aren't independently dischargeable
|
||
until B1 is closed.
|
||
|
||
REFACTOR PATH (executes once libjade byte-walk #3 has a draft):
|
||
|
||
axiom sign_body_compute_sig_core :
|
||
forall mem ptrs (sk : libjade_sk_t)
|
||
(m : libjade_msg_t) (mu : libjade_mu_t),
|
||
layout_sk_at mem ptrs.`sk_ptr sk =>
|
||
layout_msg_at mem ptrs.`m_ptr m =>
|
||
sign_body_compute_status mem ptrs = 0 =>
|
||
refine_sig_to_n1_sign (sign_body_compute_sig mem ptrs)
|
||
= fips204_sign_internal_op (decode_sk sk)
|
||
(decode_m m)
|
||
mu
|
||
(libjade_rho_rnd sk).
|
||
|
||
axiom ctx_mu_binding_op :
|
||
forall (sk : Pulsar_N1.share_t) (m : Pulsar_N1.message_t)
|
||
(ctx : Pulsar_N1.ctx_t) (rho_rnd : Pulsar_N1.randomness_t),
|
||
fips204_sign_internal_op
|
||
(encode_sk_op sk)
|
||
(encode_m_op m)
|
||
(external_mu m ctx)
|
||
(libjade_rho_rnd (encode_sk_op sk))
|
||
= mldsa_sign_op sk m ctx rho_rnd.
|
||
|
||
With those, `sign_body_compute_sig_spec` becomes a LEMMA composing
|
||
the core + binding axioms via `encode_sign_args`'s layout invariant.
|
||
|
||
Right now neither libjade_sk_t nor fips204_sign_internal_op nor
|
||
external_mu has been introduced (they all live downstream of the
|
||
byte-walk landing). Surfacing them prematurely would force a
|
||
placeholder vocabulary that future work has to replace anyway.
|
||
|
||
Net of C3 closure:
|
||
- field-categorisation comments at sign_full_args_t make the
|
||
wire/mirror/ghost split visible at the type-decl site.
|
||
- this block names the three bundled claims B1/B2/B3 and gives
|
||
the concrete refactor signatures.
|
||
- the refactor itself is GATED on the libjade byte-walk close.
|
||
=================================================================== *)
|
||
|
||
(* ===================================================================
|
||
L3 — ATOMIC AXIOM (libjade-extraction trust boundary).
|
||
|
||
`sign_body_spec` says: given inputs whose wire-level layout
|
||
matches the abstract wire args, the extracted libjade `M.sign`
|
||
body writes at `ptr_signature` a byte string that (under
|
||
`refine_sig_to_n1_sign`) equals `sign_abs_op full` — i.e., the
|
||
FIPS 204 ML-DSA-65 signature on the protocol-level inputs.
|
||
|
||
This is the ONLY remaining axiom in this file (plus the
|
||
separation axiom below). Closing it is the byte-walk through
|
||
`extraction/build/sign.ec` (libjade ML-DSA-65 sign at
|
||
line 3603 onward):
|
||
|
||
- sk unpacking: rho, K, tr, s1, s2, t0 (FIPS 204 §3.5.4).
|
||
- mu = SHAKE(tr || M) — message binder.
|
||
- Kappa rejection-sampling loop on attempt counter.
|
||
- Pack signature and write to ptr_signature.
|
||
|
||
Tracked: https://github.com/luxfi/pulsar/issues/3
|
||
=================================================================== *)
|
||
|
||
(* Per-component "compute" outputs of the extracted libjade sign
|
||
body — mirrors the combine-side per-stage split. One op per
|
||
FIPS 204 §6.2 inner-loop output stage.
|
||
|
||
sign_body_compute_c_tilde
|
||
NOW STRUCTURAL: factored as `shake_mu_w1` over two extracted
|
||
intermediates (sign_body_compute_mu and
|
||
sign_body_compute_w1, declared below). The c_tilde-stage
|
||
byte-walk obligation `sign_body_c_tilde_spec` is now a
|
||
DERIVED LEMMA from `sign_body_mu_spec` + `sign_body_w1_spec`.
|
||
|
||
sign_body_compute_z
|
||
STILL ABSTRACT. Pure §6.2 z (no Lagrange aggregation on the
|
||
single-party side).
|
||
|
||
sign_body_compute_h
|
||
STILL ABSTRACT. Hint h from MakeHint.
|
||
|
||
The composite `sign_body_compute_components` is DEFINED as the
|
||
tuple of the three per-stage ops, preserving the downstream API
|
||
surface. *)
|
||
|
||
(* Extracted intermediates feeding c_tilde — surfaced so the
|
||
c_tilde-stage byte-walk decomposes along the FIPS 204 §6.2
|
||
"SHAKE(mu || w1Encode(w1))" boundary. Mirrors the combine-side
|
||
extracted intermediates.
|
||
|
||
`sign_body_compute_mu` is now STRUCTURAL: factored as a SHAKE
|
||
over the libjade body's ExternalMu input buffer
|
||
(`sign_body_mu_input`). On the libjade sign side this maps
|
||
directly to the SHAKE call libjade makes during mu derivation.
|
||
|
||
v9 closure (sign side): `sign_body_mu_input` is no longer an
|
||
abstract op. The libjade `M.sign(ptr_signature, ptr_m, m_len,
|
||
ptr_sk)` body reads the message bytes at `ptr_m` for length
|
||
`m_len` and feeds them DIRECTLY to SHAKE-256 in its mu
|
||
derivation. Per FIPS 204 §5.4.1, the bytes the wrapper has
|
||
placed at `ptr_m` (covering `m_len` bytes) are precisely the
|
||
ExternalMu layout `[0; |ctx|] || ctx || M`. So
|
||
`sign_body_mu_input mem_pre ptrs = load_bytes mem_pre ptr_m
|
||
m_len` is the libjade-level identity (extracted-body-level),
|
||
while the claim that those bytes equal `external_mu_layout m
|
||
ctx` is the wrapper's responsibility (byte-layout axiom
|
||
`sign_layout_m_buffer_external_mu` below). *)
|
||
op sign_body_mu_input
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1.mu_shake_input_t =
|
||
Pulsar_N1_Memory.load_bytes
|
||
mem_pre
|
||
ptrs.`Pulsar_N1_Sign_Layout.ptr_m
|
||
ptrs.`Pulsar_N1_Sign_Layout.m_len.
|
||
|
||
op sign_body_compute_mu
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1.mu_t =
|
||
Pulsar_N1.shake256_to_mu (sign_body_mu_input mem_pre ptrs).
|
||
|
||
(* v12: w-stage structural decomposition. Mirror of combine side. *)
|
||
op sign_body_compute_matrix_a :
|
||
Pulsar_N1_Memory.mem_t ->
|
||
Pulsar_N1_Sign_Layout.sign_ptrs_t ->
|
||
Pulsar_N1.matrix_a_t.
|
||
|
||
op sign_body_compute_mask_y :
|
||
Pulsar_N1_Memory.mem_t ->
|
||
Pulsar_N1_Sign_Layout.sign_ptrs_t ->
|
||
Pulsar_N1.mask_vec_t.
|
||
|
||
op sign_body_compute_w
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1.w_value_t =
|
||
Pulsar_N1.apply_mat_vec_mul
|
||
(sign_body_compute_matrix_a mem_pre ptrs)
|
||
(sign_body_compute_mask_y mem_pre ptrs).
|
||
|
||
op sign_body_compute_w1
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1.w1_value_t =
|
||
Pulsar_N1.high_bits_of_w (sign_body_compute_w mem_pre ptrs).
|
||
|
||
op sign_body_compute_c_tilde
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1.c_tilde_n1_t =
|
||
Pulsar_N1.shake_mu_w1
|
||
(sign_body_compute_mu mem_pre ptrs)
|
||
(sign_body_compute_w1 mem_pre ptrs).
|
||
|
||
(* v11: per-stage extracted intermediates for z = y + c·s_1.
|
||
Mirrors `Pulsar_N1.mldsa_compute_z`'s v11 structural definition. *)
|
||
op sign_body_compute_y :
|
||
Pulsar_N1_Memory.mem_t ->
|
||
Pulsar_N1_Sign_Layout.sign_ptrs_t ->
|
||
Pulsar_N1.response_vec_t.
|
||
|
||
op sign_body_compute_cs1 :
|
||
Pulsar_N1_Memory.mem_t ->
|
||
Pulsar_N1_Sign_Layout.sign_ptrs_t ->
|
||
Pulsar_N1.response_vec_t.
|
||
|
||
op sign_body_compute_z
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1.z_n1_t =
|
||
Pulsar_N1.add_response_vec
|
||
(sign_body_compute_y mem_pre ptrs)
|
||
(sign_body_compute_cs1 mem_pre ptrs).
|
||
|
||
(* w_low polynomial-vector intermediate the extracted libjade sign
|
||
body produces at the accepting kappa. Mirror of `sign_body_compute_w`
|
||
from v7. Together (w, w_low) are the two inputs MakeHint consumes
|
||
to produce h.
|
||
|
||
For sign, this is libjade's decompose-vector low-bits side at the
|
||
accepting kappa. `sign_body_compute_h` is now DEFINED as
|
||
`make_hint_of_w` applied to the (w, w_low) pair, mirroring the
|
||
structural definition of `Pulsar_N1.mldsa_compute_h`. *)
|
||
op sign_body_compute_w_low :
|
||
Pulsar_N1_Memory.mem_t ->
|
||
Pulsar_N1_Sign_Layout.sign_ptrs_t ->
|
||
Pulsar_N1.w_low_value_t.
|
||
|
||
op sign_body_compute_h
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1.h_n1_t =
|
||
Pulsar_N1.make_hint_of_w
|
||
(sign_body_compute_w mem_pre ptrs)
|
||
(sign_body_compute_w_low mem_pre ptrs).
|
||
|
||
op sign_body_compute_components
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1.c_tilde_n1_t * Pulsar_N1.z_n1_t * Pulsar_N1.h_n1_t =
|
||
(sign_body_compute_c_tilde mem_pre ptrs,
|
||
sign_body_compute_z mem_pre ptrs,
|
||
sign_body_compute_h mem_pre ptrs).
|
||
|
||
(* Status-aware byte-walk (Agent 1 HIGH-2 closure, sign-side).
|
||
|
||
libjade M.sign returns a status (rejection of the kappa loop or
|
||
bounded-kappa exhaustion). On non-zero status the signature
|
||
buffer at ptr_signature is in an undefined state. Earlier
|
||
versions claimed byte-equality unconditionally — vacuously
|
||
satisfiable on the rejection branch. *)
|
||
op sign_body_compute_status :
|
||
Pulsar_N1_Memory.mem_t ->
|
||
Pulsar_N1_Sign_Layout.sign_ptrs_t ->
|
||
int.
|
||
|
||
(* sign_body_compute_sig is now DEFINED constructively as the
|
||
FIPS 204 §3.5.5 pack of the inner-loop components. Same structural
|
||
pattern as combine_body_compute_sig in Pulsar_N1_Combine_Refinement
|
||
— the pack step is identical to what `Pulsar_N1.sign_internal_loop`
|
||
does on the centralised reference, so byte-equality reduces to
|
||
triple-equality of the components. *)
|
||
op sign_body_compute_sig
|
||
(mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1_Signature_Codec.signature_t =
|
||
let cz_h = sign_body_compute_components mem_pre ptrs in
|
||
Pulsar_N1.pack_n1_signature cz_h.`1 cz_h.`2 cz_h.`3.
|
||
|
||
(* Definition: sign_body_fn writes the computed signature at
|
||
ptr_signature and leaves all other memory untouched, by virtue
|
||
of write_sig_sign's definition (single store_bytes call at the
|
||
given pointer of exactly sig_len_sign = 3293 bytes).
|
||
|
||
This decomposition is what makes the separation property a
|
||
DERIVED LEMMA rather than an axiom: the "writes only at
|
||
ptr_signature" invariant is now BY CONSTRUCTION. *)
|
||
op sign_body_fn (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
: Pulsar_N1_Memory.mem_t =
|
||
Pulsar_N1_Sign_Layout.write_sig_sign
|
||
mem_pre
|
||
ptrs.`Pulsar_N1_Sign_Layout.ptr_signature
|
||
(sign_body_compute_sig mem_pre ptrs).
|
||
|
||
(* Per-stage byte-walk axioms on the libjade sign side. Mirrors
|
||
the combine-side: the c_tilde stage is decomposed into two
|
||
narrower sub-axioms (sign_body_mu_spec + sign_body_w1_spec);
|
||
the c_tilde axiom itself is a DERIVED LEMMA. The z and h stages
|
||
remain single bundled axioms (closure paths analogous to the
|
||
combine side but without the Lagrange-aggregation complication).
|
||
*)
|
||
|
||
(* === c_tilde-stage sub-axioms (NARROW), mirror of combine =====
|
||
After v6: mu sub-stage axiom decomposed into a byte-layout claim
|
||
(classified under FIPS 204 codec layouts). sign_body_mu_spec is
|
||
a derived lemma.
|
||
|
||
v9 (sign side): the prior `sign_body_mu_input_spec` axiom was
|
||
stated about an abstract op `sign_body_mu_input`. With
|
||
`sign_body_mu_input` now CONSTRUCTIVELY defined as
|
||
`load_bytes mem_pre ptr_m m_len` (the libjade body's actual
|
||
read), the same statement decomposes into:
|
||
|
||
(a) `sign_body_mu_input mem_pre ptrs = load_bytes mem_pre
|
||
ptr_m m_len` — TRIVIAL, by the constructive definition.
|
||
(b) `load_bytes mem_pre ptr_m m_len = external_mu_layout m
|
||
ctx` — PURE BYTE-LAYOUT CLAIM (the wrapper assembles
|
||
ExternalMu and writes it at ptr_m so m_len covers the
|
||
prefix+ctx+M bytes; per FIPS 204 §5.4.1).
|
||
|
||
(a) is folded into the constructive definition (no obligation).
|
||
(b) is the strictly narrower wrapper-layer byte-layout axiom
|
||
below. The old `sign_body_mu_input_spec` becomes a derived
|
||
lemma combining (a) and (b) via a single rewrite. The axiom's
|
||
logical content is unchanged (b is the only non-trivial half of
|
||
the old axiom) — narrower in that the libjade-body read is no
|
||
longer in the statement at all. *)
|
||
axiom sign_layout_m_buffer_external_mu :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
Pulsar_N1_Memory.load_bytes
|
||
mem_pre
|
||
ptrs.`Pulsar_N1_Sign_Layout.ptr_m
|
||
ptrs.`Pulsar_N1_Sign_Layout.m_len
|
||
= Pulsar_N1.external_mu_layout full.`sgn_m_n1 full.`sgn_ctx_n1.
|
||
|
||
(* sign_body_mu_input_spec — was an axiom in v6..v8; now DERIVED in
|
||
v9 via the constructive definition of `sign_body_mu_input` and
|
||
the narrower `sign_layout_m_buffer_external_mu` axiom. *)
|
||
lemma sign_body_mu_input_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_mu_input mem_pre ptrs
|
||
= Pulsar_N1.external_mu_layout full.`sgn_m_n1 full.`sgn_ctx_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
rewrite /sign_body_mu_input.
|
||
by apply sign_layout_m_buffer_external_mu.
|
||
qed.
|
||
|
||
(* sign_body_mu_spec — was a primary axiom in v5; now DERIVED. *)
|
||
lemma sign_body_mu_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_mu mem_pre ptrs
|
||
= Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
have Hinput := sign_body_mu_input_spec mem_pre ptrs full Hlay Hstatus.
|
||
rewrite /sign_body_compute_mu /Pulsar_N1.compute_mu.
|
||
by rewrite Hinput.
|
||
qed.
|
||
|
||
(* v12: two narrower w-stage sub-axioms (matrix_a + mask_y) replacing
|
||
the prior bundled sign_body_w_spec. *)
|
||
axiom sign_body_matrix_a_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_matrix_a mem_pre ptrs
|
||
= Pulsar_N1.central_matrix_a (Pulsar_N1.unpack_sk full.`sgn_sk_n1).
|
||
|
||
axiom sign_body_mask_y_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_mask_y mem_pre ptrs
|
||
= Pulsar_N1.central_mask_y_at_accepted_kappa
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
|
||
(* sign_body_w_spec — was primitive axiom in v7-v11; v12 DERIVED LEMMA. *)
|
||
lemma sign_body_w_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_w mem_pre ptrs
|
||
= Pulsar_N1.central_w
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
have HA := sign_body_matrix_a_spec mem_pre ptrs full Hlay Hstatus.
|
||
have Hy := sign_body_mask_y_spec mem_pre ptrs full Hlay Hstatus.
|
||
rewrite /sign_body_compute_w /Pulsar_N1.central_w.
|
||
by rewrite HA Hy.
|
||
qed.
|
||
|
||
(* sign_body_w1_spec — was a primary axiom; now DERIVED. *)
|
||
lemma sign_body_w1_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_w1 mem_pre ptrs
|
||
= Pulsar_N1.central_w1
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
have Hw := sign_body_w_spec mem_pre ptrs full Hlay Hstatus.
|
||
rewrite /sign_body_compute_w1 /Pulsar_N1.central_w1.
|
||
by rewrite Hw.
|
||
qed.
|
||
|
||
(* sign_body_c_tilde_spec — was a primary axiom; now DERIVED. *)
|
||
lemma sign_body_c_tilde_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_c_tilde mem_pre ptrs
|
||
= Pulsar_N1.mldsa_compute_c_tilde
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
have Hmu := sign_body_mu_spec mem_pre ptrs full Hlay Hstatus.
|
||
have Hw1 := sign_body_w1_spec mem_pre ptrs full Hlay Hstatus.
|
||
rewrite /sign_body_compute_c_tilde /Pulsar_N1.mldsa_compute_c_tilde.
|
||
by rewrite Hmu Hw1.
|
||
qed.
|
||
|
||
(* v11: two narrower z-stage sub-axioms (y + cs1) replacing the
|
||
prior bundled sign_body_z_spec. The structural add_response_vec
|
||
composition is shared with Pulsar_N1.mldsa_compute_z, so z-equality
|
||
reduces to y-equality + cs1-equality. *)
|
||
axiom sign_body_y_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_y mem_pre ptrs
|
||
= Pulsar_N1.central_y_at_accepted_kappa
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
|
||
axiom sign_body_cs1_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_cs1 mem_pre ptrs
|
||
= Pulsar_N1.apply_c_to_s1
|
||
(Pulsar_N1.central_c_from_c_tilde
|
||
(sign_body_compute_c_tilde mem_pre ptrs))
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1).
|
||
|
||
(* sign_body_z_spec — was primitive axiom; v11 DERIVED LEMMA. *)
|
||
lemma sign_body_z_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_z mem_pre ptrs
|
||
= Pulsar_N1.mldsa_compute_z
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
have Hy := sign_body_y_spec mem_pre ptrs full Hlay Hstatus.
|
||
have Hcs1 := sign_body_cs1_spec mem_pre ptrs full Hlay Hstatus.
|
||
have Hct := sign_body_c_tilde_spec mem_pre ptrs full Hlay Hstatus.
|
||
rewrite /sign_body_compute_z /Pulsar_N1.mldsa_compute_z.
|
||
rewrite Hy Hcs1 Hct.
|
||
done.
|
||
qed.
|
||
|
||
(* Narrower w_low-polynomial axiom: extracted w_low polynomial-vector
|
||
matches the centralised central_w_low at the same protocol-level
|
||
inputs. The MakeHint step is structural (folded into the
|
||
definitions of `sign_body_compute_h` and `Pulsar_N1.mldsa_compute_h`
|
||
on both sides via `make_hint_of_w`), so the pair (w-equality,
|
||
w_low-equality) lifts to h-equality by congruence. *)
|
||
axiom sign_body_w_low_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_w_low mem_pre ptrs
|
||
= Pulsar_N1.central_w_low
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
|
||
(* sign_body_h_spec — was a primary axiom in v4-v9; now DERIVED in
|
||
v10 via the MakeHint structural composition. Composes
|
||
`sign_body_w_spec` (v7) + `sign_body_w_low_spec` (this commit)
|
||
via the structural definitions of `sign_body_compute_h`
|
||
(= make_hint_of_w on extracted w + extracted w_low) and
|
||
`Pulsar_N1.mldsa_compute_h` (= make_hint_of_w on centralised
|
||
central_w + central_w_low). After both unfold, byte-equality
|
||
reduces to w-equality + w_low-equality. *)
|
||
lemma sign_body_h_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_h mem_pre ptrs
|
||
= Pulsar_N1.mldsa_compute_h
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
have Hw := sign_body_w_spec mem_pre ptrs full Hlay Hstatus.
|
||
have Hw_low := sign_body_w_low_spec mem_pre ptrs full Hlay Hstatus.
|
||
rewrite /sign_body_compute_h /Pulsar_N1.mldsa_compute_h.
|
||
by rewrite Hw Hw_low.
|
||
qed.
|
||
|
||
(* Composite components_spec — now DERIVED from the three per-stage
|
||
axioms via tuple destructuring. *)
|
||
lemma sign_body_compute_components_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
sign_body_compute_components mem_pre ptrs
|
||
= Pulsar_N1.run_signing_components
|
||
(Pulsar_N1.unpack_sk full.`sgn_sk_n1)
|
||
(Pulsar_N1.compute_mu full.`sgn_m_n1 full.`sgn_ctx_n1)
|
||
full.`sgn_rnd_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
have Hc := sign_body_c_tilde_spec mem_pre ptrs full Hlay Hstatus.
|
||
have Hz := sign_body_z_spec mem_pre ptrs full Hlay Hstatus.
|
||
have Hh := sign_body_h_spec mem_pre ptrs full Hlay Hstatus.
|
||
rewrite /sign_body_compute_components
|
||
/Pulsar_N1.run_signing_components.
|
||
by rewrite Hc Hz Hh.
|
||
qed.
|
||
|
||
(* Original byte-equality shape — now DERIVED from the component-
|
||
level axiom + the structural pack identity. Pack is the same on
|
||
both sides (`Pulsar_N1.pack_n1_signature` in
|
||
`sign_body_compute_sig`'s definition, and inside the unfolding of
|
||
`Pulsar_N1.sign_internal_loop` on the centralised reference);
|
||
component-level Hcomp lifts directly to byte-equality by
|
||
congruence. *)
|
||
lemma sign_body_compute_sig_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
sign_body_compute_status mem_pre ptrs = 0 =>
|
||
refine_sig_to_n1_sign (sign_body_compute_sig mem_pre ptrs)
|
||
= sign_abs_op full.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Hstatus.
|
||
have Hcomp :=
|
||
sign_body_compute_components_spec mem_pre ptrs full Hlay Hstatus.
|
||
(* sign_abs_op = sign_internal_loop (unpack_sk sk) (compute_mu m
|
||
ctx) rnd; after δ on sign_internal_loop's definition (let cz_h'
|
||
= run_signing_components ... in pack_n1_signature cz_h'.`1
|
||
cz_h'.`2 cz_h'.`3) and ζ via /=, both sides reduce to
|
||
pack_n1_signature applied at three projections of either
|
||
`sign_body_compute_components mem_pre ptrs` (LHS) or
|
||
`run_signing_components ...` (RHS). Hcomp closes the equality
|
||
at three positions ⇒ `!Hcomp`. *)
|
||
rewrite /refine_sig_to_n1_sign /sign_body_compute_sig
|
||
/sign_abs_op /Pulsar_N1.sign_internal_loop /=.
|
||
by rewrite !Hcomp.
|
||
qed.
|
||
|
||
(* Accepted-path no-reject axiom (followup B closure).
|
||
|
||
No-reject is NOT a universal property of honest signing — it is
|
||
a property of ACCEPTED signing attempts. The conditioned form:
|
||
given layout-conforming inputs AND the ML-DSA-65 accept event
|
||
holds for the protocol-level inputs (sk, m, ctx, rho_rnd), the
|
||
extracted libjade M.sign returns status = 0.
|
||
|
||
The accept event is `accept_signing_attempt`, declared at
|
||
Pulsar_N1.ec. ML-DSA's kappa rejection-sampling loop converges
|
||
with probability ≈ 1 − (3/4)^256 per attempt; the kappa-bounded
|
||
loop accumulates failure probability ≤ 2^-128 across the bound.
|
||
The probability bound is tracked operationally
|
||
(Pulsar_N1.mldsa_accept_lower_bound) rather than via
|
||
probabilistic Hoare logic.
|
||
|
||
Previous shape claimed UNCONDITIONAL status=0 on layout-
|
||
conforming inputs — too strong, since rejection is a
|
||
probabilistic event. With the accept-path precondition explicit,
|
||
the axiom is honest: "if the attempt accepts, the byte output
|
||
is the centralized FIPS 204 signature".
|
||
|
||
COMPANION to sign_body_compute_sig_spec: together they recover
|
||
the conditional byte-walk shape. *)
|
||
axiom sign_no_reject_on_accepted_honest_layout :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
Pulsar_N1.accept_signing_attempt
|
||
full.`sgn_sk_n1 full.`sgn_m_n1
|
||
full.`sgn_ctx_n1 full.`sgn_rnd_n1 =>
|
||
sign_body_compute_status mem_pre ptrs = 0.
|
||
|
||
(* The `sign_body_spec` shape — STATUS DISCHARGED by the
|
||
accepted-path no-reject axiom (followup B). Threads
|
||
`accept_signing_attempt` from the caller through to the
|
||
byte-walk axiom. *)
|
||
lemma sign_body_spec :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
Pulsar_N1.accept_signing_attempt
|
||
full.`sgn_sk_n1 full.`sgn_m_n1
|
||
full.`sgn_ctx_n1 full.`sgn_rnd_n1 =>
|
||
refine_sig_to_n1_sign
|
||
(Pulsar_N1_Sign_Layout.read_sig_sign
|
||
(sign_body_fn mem_pre ptrs)
|
||
ptrs.`Pulsar_N1_Sign_Layout.ptr_signature)
|
||
= sign_abs_op full.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Haccept.
|
||
have Hstatus :=
|
||
sign_no_reject_on_accepted_honest_layout
|
||
mem_pre ptrs full Hlay Haccept.
|
||
rewrite /sign_body_fn.
|
||
rewrite Pulsar_N1_Sign_Layout.read_after_write_sig_sign.
|
||
by apply sign_body_compute_sig_spec.
|
||
qed.
|
||
|
||
(* Memory separation: M.sign writes only to ptr_signature range.
|
||
PROVED — defined as a concrete predicate over byte-level memory,
|
||
then discharged by write_sig_sign_separation (already a proved
|
||
lemma in Sign_Layout). *)
|
||
op sig_mem_separation
|
||
(mem_post mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(p len : int) : bool =
|
||
forall (q : int),
|
||
q < p \/ p + len <= q =>
|
||
Pulsar_N1_Memory.load_byte mem_post q =
|
||
Pulsar_N1_Memory.load_byte mem_pre q.
|
||
|
||
lemma sign_body_separation :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t),
|
||
sig_mem_separation (sign_body_fn mem_pre ptrs) mem_pre
|
||
ptrs.`Pulsar_N1_Sign_Layout.ptr_signature
|
||
Pulsar_N1_Sign_Layout.sig_len_sign.
|
||
proof.
|
||
move=> mem_pre ptrs q Hdisj.
|
||
rewrite /sign_body_fn.
|
||
by apply Pulsar_N1_Sign_Layout.write_sig_sign_separation; exact Hdisj.
|
||
qed.
|
||
|
||
(* ===================================================================
|
||
DERIVED LEMMAS — fully proved from sign_body_spec + EC congruence.
|
||
=================================================================== *)
|
||
|
||
(* The combined byte-equality + abstract-op identity, applied at
|
||
a specific full_args. The wrapper-bridge collapse depends on
|
||
this lemma. Threads accept_signing_attempt through. *)
|
||
lemma sign_body_writes_abs :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
Pulsar_N1.accept_signing_attempt
|
||
full.`sgn_sk_n1 full.`sgn_m_n1
|
||
full.`sgn_ctx_n1 full.`sgn_rnd_n1 =>
|
||
refine_sig_to_n1_sign
|
||
(Pulsar_N1_Sign_Layout.read_sig_sign
|
||
(sign_body_fn mem_pre ptrs)
|
||
ptrs.`Pulsar_N1_Sign_Layout.ptr_signature)
|
||
= sign_abs_op full.
|
||
proof. exact sign_body_spec. qed.
|
||
|
||
(* The byte-equality unfolds to mldsa_sign_op of the four ghost
|
||
fields by definition of sign_abs_op. *)
|
||
lemma sign_body_writes_mldsa_sign :
|
||
forall (mem_pre : Pulsar_N1_Memory.mem_t)
|
||
(ptrs : Pulsar_N1_Sign_Layout.sign_ptrs_t)
|
||
(full : sign_full_args_t),
|
||
Pulsar_N1_Sign_Layout.layout_sign_args
|
||
mem_pre ptrs (wire_sign_args_of_full full) =>
|
||
Pulsar_N1.accept_signing_attempt
|
||
full.`sgn_sk_n1 full.`sgn_m_n1
|
||
full.`sgn_ctx_n1 full.`sgn_rnd_n1 =>
|
||
refine_sig_to_n1_sign
|
||
(Pulsar_N1_Sign_Layout.read_sig_sign
|
||
(sign_body_fn mem_pre ptrs)
|
||
ptrs.`Pulsar_N1_Sign_Layout.ptr_signature)
|
||
= Pulsar_N1.mldsa_sign_op
|
||
full.`sgn_sk_n1 full.`sgn_m_n1
|
||
full.`sgn_ctx_n1 full.`sgn_rnd_n1.
|
||
proof.
|
||
move=> mem_pre ptrs full Hlay Haccept.
|
||
rewrite (sign_body_spec mem_pre ptrs full Hlay Haccept) /sign_abs_op.
|
||
by rewrite /Pulsar_N1.mldsa_sign_op.
|
||
qed.
|
||
|
||
(* ===================================================================
|
||
AXIOM ACCOUNTING
|
||
|
||
axioms (4 byte-walks + 1 byte-layout):
|
||
Byte-walks (libjade body, conditioned on layout + status = 0):
|
||
sign_body_w_spec (polynomial vector w before HighBits)
|
||
sign_body_w_low_spec (polynomial vector w_low — low-bits side
|
||
of FIPS 204 §3.4.2 decompose; h-stage
|
||
sub-stage, narrower than h)
|
||
sign_body_y_spec (y mask vector at the accepting kappa;
|
||
z-stage sub-stage, narrower than z)
|
||
sign_body_cs1_spec (c·s_1 product at the accepting kappa;
|
||
z-stage sub-stage, narrower than z)
|
||
Byte-layout (FIPS 204 §5.4.1 ExternalMu, wrapper-layer
|
||
responsibility):
|
||
sign_layout_m_buffer_external_mu
|
||
The bytes the wrapper has placed at ptr_m for length m_len
|
||
equal `external_mu_layout m ctx`. Pure byte-layout claim;
|
||
no libjade-body read, no SHAKE semantics. Replaces the
|
||
prior abstract-op `sign_body_mu_input_spec` axiom (now a
|
||
derived lemma).
|
||
|
||
The MakeHint composition tying (w, w_low) → h is encoded as a
|
||
STRUCTURAL DEFINITION on both sides (via
|
||
`Pulsar_N1.make_hint_of_w`), not as an axiom. Symmetrically, the
|
||
§6.2 `z = y + c · s_1` composition is encoded as a STRUCTURAL
|
||
DEFINITION on both sides (via `Pulsar_N1.add_response_vec`
|
||
composed with `apply_c_to_s1 ∘ central_c_from_c_tilde`), not as
|
||
an axiom.
|
||
|
||
Each conditioned on layout + status = 0.
|
||
Tracked #3. Mirrors the combine-side per-stage split.
|
||
|
||
REFINEMENT HISTORY (this file):
|
||
v1: 2 axioms (sign_body_spec + sign_body_separation)
|
||
v2: 1 axiom (sign_body_compute_sig_spec — packed signature)
|
||
v3: 1 axiom (sign_body_compute_components_spec — triple)
|
||
v4: 3 axioms (sign_body_{c_tilde,z,h}_spec — per-stage)
|
||
v5: c_tilde-stage axiom DECOMPOSED — `sign_body_c_tilde_spec`
|
||
becomes derived; replaced by sign_body_{mu,w1}_spec axioms.
|
||
v6: mu sub-stage axiom further DECOMPOSED into byte-layout
|
||
`sign_body_mu_input_spec` over abstract `sign_body_mu_input`.
|
||
v7: w1 sub-stage axiom DECOMPOSED via HighBits structural
|
||
split; `sign_body_w1_spec` becomes derived; replaced
|
||
by narrower `sign_body_w_spec` axiom.
|
||
v8: combine side — z-stage Lean-bridged aggregation
|
||
decomposition (sign side untouched).
|
||
v9: `sign_body_mu_input` is no longer an abstract op — it's
|
||
CONSTRUCTIVELY defined as the libjade body's actual read
|
||
`load_bytes mem_pre ptr_m m_len`.
|
||
`sign_body_mu_input_spec` becomes a DERIVED LEMMA;
|
||
replaced by the strictly narrower wrapper-layer axiom
|
||
`sign_layout_m_buffer_external_mu` (a pure byte-layout
|
||
claim about the bytes the wrapper placed at ptr_m —
|
||
FIPS 204 §5.4.1 ExternalMu). Net axiom count unchanged;
|
||
obligation surface STRICTLY smaller: the abstract op
|
||
`sign_body_mu_input` no longer exists, and the
|
||
axiom no longer mentions any libjade-side operator.
|
||
v10 (this commit): h-stage axiom DECOMPOSED via MakeHint
|
||
structural split. `sign_body_h_spec` becomes a derived
|
||
lemma, replaced by `sign_body_w_low_spec` (narrower —
|
||
about polynomial vector w_low, the low-bits side of
|
||
FIPS 204 §3.4.2 decompose at the accepting kappa), plus
|
||
the structural `make_hint_of_w` definition shared with
|
||
`Pulsar_N1.mldsa_compute_h`. Composition for the derived
|
||
lemma: `sign_body_w_spec` (v7) + `sign_body_w_low_spec`
|
||
(this commit) → byte-equality of h via congruence under
|
||
`make_hint_of_w`. Same pattern as v7 (HighBits on w → w1)
|
||
and v6 (SHAKE on mu_input → mu): the structural function
|
||
is factored on both extracted and centralised sides;
|
||
equality of inputs lifts to equality of outputs by
|
||
congruence. Net axiom count unchanged (1 replaced by 1);
|
||
obligation surface narrower per axiom.
|
||
|
||
ops (DEFINITIONS — no proof obligation):
|
||
wire_sign_args_of_full (record projection)
|
||
refine_sig_to_n1_sign (structural sig-type coercion)
|
||
sign_abs_op (DEFINED — mldsa_sign_op on ghost fields)
|
||
sign_body_mu_input (DEFINED v9 — load_bytes mem ptr_m m_len;
|
||
the libjade body's actual SHAKE-mu
|
||
input read)
|
||
sign_body_compute_mu (DEFINED — shake256_to_mu of mu_input)
|
||
sign_body_compute_w1 (DEFINED — high_bits_of_w of compute_w)
|
||
sign_body_compute_c_tilde
|
||
(DEFINED — shake_mu_w1 of mu and w1)
|
||
sign_body_compute_h (DEFINED v10 — make_hint_of_w of
|
||
compute_w and compute_w_low)
|
||
sign_body_compute_sig (DEFINED — pack_n1_signature of the
|
||
compute_components output)
|
||
sign_body_compute_components
|
||
(DEFINED — tuple of per-stage ops)
|
||
sign_body_fn (DEFINED — write_sig_sign at ptr_signature
|
||
of the compute_sig output; by construction
|
||
only ptr_signature range is touched)
|
||
sig_mem_separation (DEFINED — byte-level memory disjointness)
|
||
|
||
ops (abstract — held inside the byte-walk obligation):
|
||
sign_body_compute_w (polynomial vector w; under sign_body_w_spec)
|
||
sign_body_compute_w_low (polynomial vector w_low — low-bits side
|
||
of decompose; under sign_body_w_low_spec)
|
||
sign_body_compute_z (response z; under sign_body_z_spec)
|
||
sign_body_compute_status (kappa-loop accept/reject return)
|
||
|
||
types (records):
|
||
sign_full_args_t (wire + ghost protocol-level args)
|
||
|
||
Lemmas (PROVED):
|
||
sign_body_mu_input_spec (v9 — was axiom in v6..v8)
|
||
sign_body_mu_spec
|
||
sign_body_w1_spec
|
||
sign_body_c_tilde_spec
|
||
sign_body_h_spec (v10 — was axiom in v4..v9)
|
||
sign_body_compute_components_spec
|
||
sign_body_compute_sig_spec
|
||
sign_body_spec (was axiom v1 — v3; lemma since v4)
|
||
sign_body_separation (was axiom v1; lemma since refactor)
|
||
sign_body_writes_abs
|
||
sign_body_writes_mldsa_sign
|
||
sign_abs_op_ctx_flows_through_mu
|
||
sign_abs_op_eq_mldsa
|
||
|
||
Implementation-refinement axiom delta for this file:
|
||
v1: 2 axioms (sign_body_spec + sign_body_separation)
|
||
v2: 1 axiom (sign_body_compute_sig_spec — packed signature)
|
||
v3: 1 axiom (sign_body_compute_components_spec — triple)
|
||
v4: 3 axioms (sign_body_{c_tilde,z,h}_spec — per-stage)
|
||
v5: c_tilde-stage axiom DECOMPOSED — `sign_body_c_tilde_spec`
|
||
becomes derived; replaced by sign_body_{mu,w1}_spec axioms.
|
||
v6: mu sub-stage axiom further DECOMPOSED.
|
||
v7: 4 axioms — w1 sub-stage axiom DECOMPOSED via HighBits:
|
||
`sign_body_w1_spec` becomes a derived lemma, replaced by
|
||
`sign_body_w_spec` (narrower — about polynomial vector w
|
||
before HighBits/decompose).
|
||
v9: mu sub-stage axiom further NARROWED —
|
||
`sign_body_mu_input_spec` (over abstract `sign_body_mu_input`)
|
||
becomes a derived lemma; the abstract op `sign_body_mu_input`
|
||
is replaced by the constructive definition `load_bytes
|
||
mem_pre ptr_m m_len`; replaced by `sign_layout_m_buffer_external_mu`
|
||
(pure byte-layout axiom over the wrapper-assembled ptr_m
|
||
buffer; no libjade-body read in statement).
|
||
v10 (this commit): h-stage axiom DECOMPOSED via MakeHint
|
||
structural split — `sign_body_h_spec` becomes a derived
|
||
lemma, replaced by `sign_body_w_low_spec` (narrower — about
|
||
polynomial vector w_low, the low-bits side of FIPS 204
|
||
§3.4.2 decompose at the accepting kappa). Net axiom count
|
||
unchanged on this file (1 replaced by 1); obligation
|
||
surface strictly narrower per axiom (h-stage trust shifts
|
||
from full MakeHint output to just the w_low intermediate;
|
||
MakeHint composition is structural via `make_hint_of_w`).
|
||
Remaining byte-walk axioms on this file:
|
||
sign_body_w_spec (c_tilde dependency sub-stage)
|
||
sign_body_w_low_spec (h-stage sub-stage, narrower than h)
|
||
sign_body_z_spec (stage-level)
|
||
Plus 1 codec-layout / wrapper byte-layout axiom:
|
||
sign_layout_m_buffer_external_mu
|
||
|
||
Net axiom count (this file, v10): 3 byte-walks + 1 byte-layout
|
||
= 4 axioms (same as v9). Obligation surface STRICTLY smaller
|
||
than v9 per axiom because the h-stage byte-walk now concerns
|
||
only the w_low polynomial-vector intermediate, not the
|
||
full MakeHint output; the MakeHint composition is structural
|
||
on both extracted and centralised sides.
|
||
|
||
Mirror of the combine-side post-v7/v8/v10 structure (combine v9
|
||
is the codec-side mu narrowing equivalent — on combine the
|
||
`combine_body_mu_input` stays abstract because combine doesn't
|
||
have a `ptr_m`; mu is a wire input from the threshold protocol,
|
||
not a buffer read).
|
||
|
||
The separation property is now BY CONSTRUCTION. The byte-walk
|
||
obligation reduces to a claim about pure signature bytes (the
|
||
"what" the extracted body computes), separated from the "where"
|
||
it writes them.
|
||
|
||
The wrapper bridge (`sign_wrapper_bridge`) in
|
||
Pulsar_N1_Sign_Wrapper.ec derives from `sign_body_spec` (now a
|
||
lemma, was an axiom). When the byte-walk axiom itself closes via
|
||
the extraction byte-walk (tracked #3), this file contains ZERO
|
||
axioms.
|
||
|
||
The previous rung lemmas (rung1..rung7) have been removed —
|
||
they referenced the old `fips204_sign` / `bits` / `sig_to_bits`
|
||
intermediate abstraction layer, which is no longer needed now
|
||
that `sign_abs_op` directly returns `mldsa_sign_op`'s output.
|
||
=================================================================== *)
|