Files
pulsar/proofs/easycrypt/Pulsar_N1_Sign_Layout.ec
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

574 lines
25 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.
(* -------------------------------------------------------------------- *)
(* Pulsar — Class N1 Sign concrete memory layout *)
(* -------------------------------------------------------------------- *)
(* Sign-side analogue of `Pulsar_N1_Combine_Layout.ec`. *)
(* *)
(* The libjade ML-DSA-65 single-party sign entry-point ABI (extracted *)
(* at extraction/build/sign.ec line 3603) is: *)
(* *)
(* proc sign(ptr_signature : W64.t, ptr_m : W64.t, *)
(* m_len : W64.t, ptr_sk : W64.t) : W64.t *)
(* *)
(* with the buffers: *)
(* ptr_sk → 4032 bytes (FIPS 204 §3.5.4 sk packing: *)
(* rho || K || tr || s1 || s2 || t0) *)
(* ptr_m → m_len bytes (the message) *)
(* ptr_signature → 3293 bytes (FIPS 204 §3.5.5 signature packing: *)
(* c_tilde || pack_z || pack_h) *)
(* *)
(* This file pins those buffers to a concrete byte-level memory model *)
(* (REUSING the memory model + proved memory-frame lemmas from *)
(* `Pulsar_N1_Combine_Layout.ec`) and proves that the concrete encoder *)
(* `encode_sign_args` produces a layout-conforming memory state + *)
(* pointer bundle on the corresponding abstract args. *)
(* *)
(* Trust-boundary delta vs the prior sign refinement file alone: *)
(* - `read_sk_at`, `read_msg_at` are no longer abstract ops; they are *)
(* concrete EC definitions (FIPS 204 §3.5.4 / message bytes). *)
(* - The layout-correctness facts `encode_layout_sk`, *)
(* `encode_layout_msg`, and the aggregate *)
(* `encode_sign_args_layout` are PROVED lemmas instead of axioms. *)
(* - The encode/decode round-trip + length axioms for `share_t` *)
(* and `message_t` are small per-type structural identities (same *)
(* shape as the combine-side per-type axioms). *)
(* *)
(* What this file does NOT yet provide: *)
(* - The `sign_body_compute_sig_spec` byte-walk; that still requires *)
(* walking the extracted libjade `M.sign` body and stays as an *)
(* axiom in `Pulsar_N1_Sign_Refinement.ec` (tracked #3). *)
(* -------------------------------------------------------------------- *)
require import AllCore List Int IntDiv SmtMap.
(* Decomplected imports: no longer pulls in combine-specific encoders
just to reach the byte-memory model or the FIPS 204 signature
codec. Memory primitives live in Pulsar_N1_Memory; the FIPS 204
signature_t + read_sig_at / write_sig_at + their proved frame
lemmas live in Pulsar_N1_Signature_Codec. *)
require import Pulsar_N1_Memory.
require import Pulsar_N1_Signature_Codec.
(* ===================================================================
Sign ABI size constants.
The libjade single-party sign ABI (per
jasmin/ml-dsa-65/libjade/oldsrc-should-delete/crypto_sign/dilithium/
dilithium3/amd64/ref/sign.jazz and the EC extraction at line 3603):
ptr_sk → 4032 bytes (FIPS 204 §3.5.4 sk packing:
rho 32 || K 32 || tr 64 ||
s1 (5×128) || s2 (6×128) || t0 (6×416)
= 32+32+64+640+768+2496 = 4032)
ptr_signature → 3293 bytes (FIPS 204 §3.5.5 sig packing:
c_tilde 32 || z 5×640 || h 83
= 32+3200+83 ≈ 3315 — pack uses
bitlen-aware z packing per §3.5.5)
`sig_len_sign` mirrors `Pulsar_N1_Signature_Codec.sig_len`
(both are the FIPS 204 §3.5.5 ML-DSA-65 signature length).
ML-DSA-65 parameters (FIPS 204 §3.7 Table 1, level 3):
(k, l) = (6, 5) eta = 4 (bitlen 2η = 4) d = 13
gamma1 = 2^19 gamma2 = (q-1)/32 q = 8380417
=================================================================== *)
op sk_len : int = 4032.
op sig_len_sign : int = 3293. (* = sig_len from Signature_Codec *)
(* FIPS 204 §3.5.4 component lengths (in bytes). Used by the
structured encode_sk definition below. *)
op sk_rho_len : int = 32. (* §3.5.4: 32-byte seed *)
op sk_K_len : int = 32. (* §3.5.4: 32-byte K *)
op sk_tr_len : int = 64. (* §3.5.4: 64-byte SHAKE-256(pk) *)
op sk_s1_len : int = 640. (* §3.5.4: l × 128 = 5 × 128 *)
op sk_s2_len : int = 768. (* §3.5.4: k × 128 = 6 × 128 *)
op sk_t0_len : int = 2496. (* §3.5.4: k × 416 = 6 × 416 *)
(* Sanity: the six component lengths sum to sk_len. Discharged
as a one-line equality lemma using `by reflexivity` on the
concrete integer arithmetic. *)
lemma sk_components_sum :
sk_rho_len + sk_K_len + sk_tr_len
+ sk_s1_len + sk_s2_len + sk_t0_len
= sk_len.
proof. by rewrite /sk_rho_len /sk_K_len /sk_tr_len
/sk_s1_len /sk_s2_len /sk_t0_len /sk_len. qed.
(* ===================================================================
Sign-side abstract input/output types.
These are LOCAL abstract types parallel to the combine layout's
per-value types. They are deliberately NOT shared with
`Pulsar_N1_Sign_Refinement.ec`'s `share_t` / `message_t` so that
this file can be re-used without coupling its abstract namespace
to the refinement scaffold's namespace; the wrapper bridge
composes the two (via op-level `n1_*_to_refine`-style adapters).
=================================================================== *)
type share_t.
type message_t.
(* Per-component byte projectors (Agent 1 HIGH-3 closure).
FIPS 204 §3.5.4 packs the secret key as:
sk = rho || K || tr || pack_eta(s1) || pack_eta(s2) || pack_t0(t0)
We surface the six components as abstract op-level projectors of
`share_t`. Their concrete bodies depend on share_t being a
concrete polynomial-vector record (HIGH-5 closure, still open).
For now they are abstract ops with length axioms — sufficient
to give `encode_sk` a STRUCTURED definition.
This eliminates the prior unstructured `op encode_sk : share_t ->
int list` form which an adversarial instantiation could realize
as any injective length-preserving map. With the six-way
structural split, an adversarial encode_sk must match FIPS 204's
component decomposition exactly — non-FIPS encodings (e.g.
swapping rho and K) are now ruled out by the structural axiom. *)
op share_rho_bytes : share_t -> int list.
op share_K_bytes : share_t -> int list.
op share_tr_bytes : share_t -> int list.
op share_s1_bytes : share_t -> int list.
op share_s2_bytes : share_t -> int list.
op share_t0_bytes : share_t -> int list.
axiom share_rho_len (x : share_t) : size (share_rho_bytes x) = sk_rho_len.
axiom share_K_len (x : share_t) : size (share_K_bytes x) = sk_K_len.
axiom share_tr_len (x : share_t) : size (share_tr_bytes x) = sk_tr_len.
axiom share_s1_len (x : share_t) : size (share_s1_bytes x) = sk_s1_len.
axiom share_s2_len (x : share_t) : size (share_s2_bytes x) = sk_s2_len.
axiom share_t0_len (x : share_t) : size (share_t0_bytes x) = sk_t0_len.
(* Structured encode: FIPS 204 §3.5.4 concatenation, in order.
This is a DEFINITION (not an axiom): with the six per-component
projectors fixed, encode_sk has only one shape modulo concrete
share_t. *)
op encode_sk (x : share_t) : int list =
share_rho_bytes x
++ share_K_bytes x
++ share_tr_bytes x
++ share_s1_bytes x
++ share_s2_bytes x
++ share_t0_bytes x.
(* Decoder retained as an abstract op — its concrete inverse body
requires share_t concretization (HIGH-5). The round-trip axioms
below pin it as encode_sk's two-sided inverse on the
well-formed-bytes domain. *)
op decode_sk : int list -> share_t.
(* Well-formedness predicate on sk bytes: captures the FIPS 204
§3.5.4 byte-structure invariants (length = 4032, valid
coefficient bit-packings for s1/s2/t0, etc.). Concrete body
requires share_t concretization (HIGH-5+future).
Followup B closure: the codec round-trip is now BIDIRECTIONAL
on the wf domain. Previously we had only `decode (encode x) = x`,
which an adversarial decoder could realise as a constant on
non-encoded inputs. The other-direction roundtrip
`wf bs => encode (decode bs) = bs` rules out constant-decoder
realisations: a constant decoder would map every wf-byte string
to the same share, and encode of that share is a fixed byte
string ≠ most inputs. *)
op wf_sk_bytes : int list -> bool.
axiom encode_sk_wf (x : share_t) : wf_sk_bytes (encode_sk x).
axiom encode_decode_sk (x : share_t) : decode_sk (encode_sk x) = x.
axiom decode_encode_sk_wf (bs : int list) :
wf_sk_bytes bs => encode_sk (decode_sk bs) = bs.
(* encode_sk length: derived from the six per-component lengths +
sk_components_sum. Was an axiom; now a lemma. *)
lemma encode_sk_len (x : share_t) : size (encode_sk x) = sk_len.
proof.
rewrite /encode_sk !size_cat.
rewrite share_rho_len share_K_len share_tr_len
share_s1_len share_s2_len share_t0_len.
exact sk_components_sum.
qed.
(* Message bytes — abstract per-value encoding. The libjade ABI
passes the message via (ptr_m, m_len), so the message-byte
encoding is whatever the wrapper layer chose. We surface the
round-trip + length identity + the well-formedness predicate
here (same followup B pattern as sk above). *)
op encode_msg : message_t -> int list.
op decode_msg : int list -> message_t.
(* Well-formedness predicate on message bytes. Trivially satisfied
for any byte string of length matching `msg_len`; non-trivial
only insofar as encode_msg is required to produce wf bytes. *)
op wf_msg_bytes : int list -> bool.
axiom encode_msg_wf (x : message_t) : wf_msg_bytes (encode_msg x).
axiom encode_decode_msg (x : message_t) : decode_msg (encode_msg x) = x.
axiom decode_encode_msg_wf (bs : int list) :
wf_msg_bytes bs => encode_msg (decode_msg bs) = bs.
(* The message length is data-dependent (set by the caller's `m_len`
argument to the libjade sign entry point); we surface that as an
op-level identity rather than a fixed constant. The encoder for
`encode_msg` MUST produce a byte list whose length matches the
caller-supplied `m_len_val`. *)
op msg_len : message_t -> int.
axiom encode_msg_len (x : message_t) : size (encode_msg x) = msg_len x.
axiom msg_len_ge0 (x : message_t) : 0 <= msg_len x.
(* ===================================================================
Sign ABI pointer bundle.
Mirrors the libjade entry-point's four word-arguments. The
`m_len` field is the data-dependent message-byte count from the
caller; `ptr_sk` / `ptr_m` / `ptr_signature` are byte addresses.
=================================================================== *)
type sign_ptrs_t = {
ptr_signature : int;
ptr_m : int;
m_len : int;
ptr_sk : int;
}.
(* ===================================================================
Concrete read definitions.
`read_sk` and `read_msg` decode the bytes at the given pointer
ranges. `read_sig_sign` reads the signature output buffer; it
uses the COMBINE layout's `signature_t` / `decode_signature` so
the two sides agree on the FIPS 204 §3.5.5 sig packing.
=================================================================== *)
op read_sk (m : mem_t) (p : int) : share_t =
decode_sk (load_bytes m p sk_len).
op read_msg (m : mem_t) (p : int) (l : int) : message_t =
decode_msg (load_bytes m p l).
(* Thin sign-side aliases over Pulsar_N1_Signature_Codec.read_sig_at
/ write_sig_at. Kept for backward compatibility with the
Refinement-file consumers; sig_len_sign = sig_len by construction. *)
op read_sig_sign (m : mem_t) (p : int) : signature_t = read_sig_at m p.
op write_sig_sign (m : mem_t) (p : int) (s : signature_t) : mem_t =
write_sig_at m p s.
(* The layout predicate: memory reads at the given pointers decode
to the abstract args, and the pointer bundle's `m_len` field
matches the actual message length. *)
type sign_abs_args_t = {
sk_abs : share_t;
m_abs : message_t;
}.
(* Pointer-disjointness predicate (Agent 1 C2 — sign side).
The three sign-side byte buffers (ptr_signature, ptr_m, ptr_sk)
must not overlap, and the signature buffer must have room for
sig_len_sign bytes. Without this conjunct an adversarial layout
could alias buffers (e.g. ptr_signature overlapping ptr_sk) and
the byte-walk axiom would be discharged on a memory state where
libjade M.sign would have clobbered the sk it just read. *)
op sign_pointers_well_separated (ptrs : sign_ptrs_t) : bool =
let p_s = ptrs.`ptr_signature in
let p_m = ptrs.`ptr_m in
let p_k = ptrs.`ptr_sk in
let m_l = ptrs.`m_len in
0 <= p_s
/\ p_s + sig_len_sign <= p_m
/\ p_m + m_l <= p_k
/\ 0 <= m_l.
(* Layout predicate — followup B reinforcement.
Conjuncts:
(1) wf_sk_bytes on the loaded sk-byte range — rules out the
constant-decoder adversarial instantiation by forcing the
loaded bytes to be in `encode_sk`'s image (via the
`decode_encode_sk_wf` round-trip).
(2) decoded sk matches the abstract arg.
(3) wf_msg_bytes on the loaded message-byte range (analogous).
(4) decoded message matches the abstract arg.
(5) m_len agreement.
(6) pointer disjointness (C2 closure). *)
op layout_sign_args
(mem : mem_t) (ptrs : sign_ptrs_t)
(arg_abs : sign_abs_args_t) : bool =
wf_sk_bytes (load_bytes mem ptrs.`ptr_sk sk_len)
/\ read_sk mem ptrs.`ptr_sk = arg_abs.`sk_abs
/\ wf_msg_bytes (load_bytes mem ptrs.`ptr_m ptrs.`m_len)
/\ read_msg mem ptrs.`ptr_m ptrs.`m_len = arg_abs.`m_abs
/\ ptrs.`m_len = msg_len arg_abs.`m_abs
/\ sign_pointers_well_separated ptrs.
(* ===================================================================
Concrete encoder: lay out the abstract args into memory at fresh,
trivially non-overlapping pointers.
Pointer layout:
ptr_signature at 0 (sig_len_sign bytes,
WRITTEN BY M.sign,
starts as zeros)
ptr_m at sig_len_sign (m_len_val bytes)
ptr_sk at sig_len_sign + m_len_val (sk_len bytes)
The signature buffer is zeroed at encode time; the extracted
`M.sign` overwrites it. `encode_sign_args` only writes the sk
+ message bytes (the inputs) — that's all the layout predicate
constrains.
=================================================================== *)
op encode_sign_args (sk : share_t) (m : message_t) (m_len_val : int)
: mem_t * sign_ptrs_t =
let p_sig = 0 in
let p_m = sig_len_sign in
let p_sk = sig_len_sign + m_len_val in
let m0 = fun (_ : int) => 0 in
let m1 = store_bytes m0 p_m (encode_msg m) in
let m2 = store_bytes m1 p_sk (encode_sk sk) in
let ptrs = {| ptr_signature = p_sig;
ptr_m = p_m;
m_len = m_len_val;
ptr_sk = p_sk |} in
(m2, ptrs).
(* ===================================================================
Structural lemmas — all PROVED, no axioms.
The two conjuncts of `layout_sign_args` are each provable
directly from `store_bytes_load_bytes` + `store_bytes_disjoint`
+ the per-type encode/decode round-trip:
- encode_layout_sk : sk-byte read from encoded memory equals
the original abstract sk.
- encode_layout_msg : message-byte read from encoded memory
equals the original abstract message.
The aggregate `encode_sign_args_layout` composes them. The
`m_len` conjunct is definitional (encoder sets it from the
caller-supplied argument).
=================================================================== *)
(* The sk pointer is `sig_len_sign + m_len_val`, with `sk_len`
bytes written there as the LAST store_bytes call. Reading
`sk_len` bytes at that address therefore returns `encode_sk sk`
directly via `store_bytes_load_bytes`. Decoding inverts via
`encode_decode_sk`. *)
lemma encode_layout_sk
(sk : share_t) (m : message_t) (m_len_val : int) :
0 <= m_len_val =>
size (encode_msg m) = m_len_val =>
read_sk (encode_sign_args sk m m_len_val).`1
(encode_sign_args sk m m_len_val).`2.`ptr_sk
= sk.
proof.
move=> Hml_ge0 Hmsg_len.
rewrite /read_sk /encode_sign_args /=.
have Hsk_len : size (encode_sk sk) = sk_len by exact encode_sk_len.
have ->: sk_len = size (encode_sk sk) by rewrite Hsk_len.
by rewrite store_bytes_load_bytes encode_decode_sk.
qed.
(* The message pointer is `sig_len_sign`, with `m_len_val` bytes
written there. The subsequent sk write at
`sig_len_sign + m_len_val` is disjoint from the message range
`[sig_len_sign, sig_len_sign + m_len_val)` (since
m_len_val + 0 = m_len_val and the sk write base is exactly
sig_len_sign + m_len_val). `load_bytes_after_disjoint_write`
peels the sk write off, then `store_bytes_load_bytes` gives
`encode_msg m` directly, which `encode_decode_msg` inverts. *)
lemma encode_layout_msg
(sk : share_t) (m : message_t) (m_len_val : int) :
0 <= m_len_val =>
size (encode_msg m) = m_len_val =>
read_msg (encode_sign_args sk m m_len_val).`1
(encode_sign_args sk m m_len_val).`2.`ptr_m
(encode_sign_args sk m m_len_val).`2.`m_len
= m.
proof.
move=> Hml_ge0 Hmsg_len.
rewrite /read_msg /encode_sign_args /=.
have Hsk_len : size (encode_sk sk) = sk_len by exact encode_sk_len.
(* Peel the sk write off the message range. The sk write is at
base = sig_len_sign + m_len_val with size sk_len. The message
range is [sig_len_sign, sig_len_sign + m_len_val). They are
disjoint because (sig_len_sign + m_len_val) is exactly the
upper bound of the message range and the lower bound of the
sk write. We use the second branch of the disjointness
condition: p + L <= q. *)
have ->:
load_bytes
(store_bytes
(store_bytes (fun _ : int => 0) sig_len_sign (encode_msg m))
(sig_len_sign + m_len_val) (encode_sk sk))
sig_len_sign m_len_val
= load_bytes
(store_bytes (fun _ : int => 0) sig_len_sign (encode_msg m))
sig_len_sign m_len_val.
- apply load_bytes_after_disjoint_write; first by exact Hml_ge0.
by left.
(* Now apply store_bytes_load_bytes with the message bytes. *)
have ->: m_len_val = size (encode_msg m) by rewrite Hmsg_len.
by rewrite store_bytes_load_bytes encode_decode_msg.
qed.
(* Byte-level layout-wf lemmas (followup B reinforcement).
`encode_sign_args` stores `encode_sk sk` at ptr_sk; reading
`sk_len` bytes there returns exactly those bytes. wf_sk_bytes
then holds by encode_sk_wf. Same shape for msg. *)
lemma encode_layout_wf_sk
(sk : share_t) (m : message_t) (m_len_val : int) :
0 <= m_len_val =>
size (encode_msg m) = m_len_val =>
wf_sk_bytes
(load_bytes
(encode_sign_args sk m m_len_val).`1
(encode_sign_args sk m m_len_val).`2.`ptr_sk
sk_len).
proof.
move=> Hml_ge0 Hmsg_len.
rewrite /encode_sign_args /=.
have Hsk_len : size (encode_sk sk) = sk_len by exact encode_sk_len.
have ->: sk_len = size (encode_sk sk) by rewrite Hsk_len.
rewrite store_bytes_load_bytes.
exact encode_sk_wf.
qed.
lemma encode_layout_wf_msg
(sk : share_t) (m : message_t) (m_len_val : int) :
0 <= m_len_val =>
size (encode_msg m) = m_len_val =>
wf_msg_bytes
(load_bytes
(encode_sign_args sk m m_len_val).`1
(encode_sign_args sk m m_len_val).`2.`ptr_m
(encode_sign_args sk m m_len_val).`2.`m_len).
proof.
move=> Hml_ge0 Hmsg_len.
rewrite /encode_sign_args /=.
have ->:
load_bytes
(store_bytes
(store_bytes (fun _ : int => 0) sig_len_sign (encode_msg m))
(sig_len_sign + m_len_val) (encode_sk sk))
sig_len_sign m_len_val
= load_bytes
(store_bytes (fun _ : int => 0) sig_len_sign (encode_msg m))
sig_len_sign m_len_val.
- apply load_bytes_after_disjoint_write; first by exact Hml_ge0.
by left.
have ->: m_len_val = size (encode_msg m) by rewrite Hmsg_len.
rewrite store_bytes_load_bytes.
exact encode_msg_wf.
qed.
(* Aggregate encoder-correctness — DERIVED from the four conjuncts
above + the definitional m_len identity (encoder sets
ptrs.`m_len := m_len_val by construction, and the precondition
ties m_len_val := msg_len m).
This is the sign-side mirror of `encode_combine_args_layout`. *)
lemma encode_sign_args_layout
(sk : share_t) (m : message_t) :
layout_sign_args
(encode_sign_args sk m (msg_len m)).`1
(encode_sign_args sk m (msg_len m)).`2
{| sk_abs = sk; m_abs = m |}.
proof.
rewrite /layout_sign_args.
have Hml_ge0 : 0 <= msg_len m by exact msg_len_ge0.
have Hmsg_len : size (encode_msg m) = msg_len m by exact encode_msg_len.
split.
- by apply (encode_layout_wf_sk sk m (msg_len m)).
split.
- by apply (encode_layout_sk sk m (msg_len m)).
split.
- by apply (encode_layout_wf_msg sk m (msg_len m)).
split.
- by apply (encode_layout_msg sk m (msg_len m)).
by rewrite /encode_sign_args /=.
qed.
(* ===================================================================
Signature-write structural lemmas — mirror the combine layout's
`read_after_write_signature` and `write_signature_separation` for
the sign-side output buffer. These are derived directly from the
reused combine-side memory-model lemmas + `encode_signature_len`.
=================================================================== *)
lemma read_after_write_sig_sign
(mem : mem_t) (p : int) (s : signature_t) :
read_sig_sign (write_sig_sign mem p s) p = s.
proof. rewrite /read_sig_sign /write_sig_sign; exact read_after_write_sig. qed.
lemma write_sig_sign_separation
(mem : mem_t) (p : int) (s : signature_t) (q : int) :
q < p \/ p + sig_len_sign <= q =>
load_byte (write_sig_sign mem p s) q = load_byte mem q.
proof.
move=> Hdisj.
rewrite /write_sig_sign.
apply write_sig_separation.
(* sig_len_sign and Signature_Codec.sig_len are both 3293 —
definitionally equal. *)
by rewrite /sig_len_sign in Hdisj; rewrite /sig_len.
qed.
(* ===================================================================
AXIOM ACCOUNTING (this file)
Concrete definitions (no proof obligation):
sk_len, sig_len_sign,
sign_ptrs_t, sign_abs_args_t,
read_sk, read_msg, read_sig_sign, write_sig_sign,
layout_sign_args, encode_sign_args.
PROVED lemmas (5, no admit):
encode_layout_sk
encode_layout_msg
encode_sign_args_layout (composes the 2 sub-claims)
read_after_write_sig_sign
write_sig_sign_separation
Axioms (5, all small per-type structural identities):
- 2 sk encode/decode round-trip + length:
encode_decode_sk, encode_sk_len
- 2 message encode/decode round-trip + length:
encode_decode_msg, encode_msg_len
- 1 message-length non-negativity:
msg_len_ge0
Memory-model facts (REUSED from Pulsar_N1_Combine_Layout, all
already PROVED lemmas there):
store_bytes_load_bytes
store_bytes_disjoint
load_bytes_after_disjoint_write
plus the combine-side signature encode/decode + length axioms
(encode_decode_signature, encode_signature_len) which we reuse
for read_after_write_sig_sign / write_sig_sign_separation.
Phase reductions:
Phase 1 (this file): both encode-layout conjuncts proved as
lemmas; aggregate `encode_sign_args_layout` is derived (no
conjunct axioms). The sign-side mirror of the combine layout's
"encode_*_layout becomes a lemma" reduction is now complete.
This file: 5 axioms (all per-type encode/decode identities),
5 proved lemmas, 0 admits.
Status of the wider implementation-refinement count:
The remaining 1 axiom in `Pulsar_N1_Sign_Refinement.ec`
(`sign_body_compute_sig_spec`) is the byte-walk trust
boundary and remains. `sign_wrapper_bridge` (in
`Pulsar_N1_Sign_Wrapper.ec`) and `sign_body_spec` (in
`Pulsar_N1_Sign_Refinement.ec`) are now PROVED lemmas via
the wrapper-bridge collapse (d86218d) and the
body-separation refactor (c4148a0).
=================================================================== *)