fix(ec): delete stale V04 leaking-path orphan; machine-check all EC theories; scrub v0.4 refs; comment-aware budget

This commit is contained in:
Antje Worring
2026-06-21 18:10:48 -07:00
parent 3faaca370d
commit 342eae5004
4 changed files with 42 additions and 345 deletions
+25 -35
View File
@@ -3,43 +3,33 @@
# count drifts ABOVE these. The honest goal is to DECREASE them (discharge
# axioms/admits into proved lemmas), never to raise the ceiling.
#
# Current surface. The axioms are fully bucketed in AXIOM-INVENTORY.md:
# A standard-math-fact (16/17) | B serialization/layout (42) |
# C OPEN security assumption (22; see AXIOM-INVENTORY.md / BLOCKERS.md).
# Per-bucket breakdown is authoritative in AXIOM-INVENTORY.md:
# A standard-math-fact | B serialization/layout | C OPEN security assumption (19).
#
# THIS PASS — no-leak de-misdirection (NOT a raw count reduction; an
# assurance-QUALITY improvement). No EC toolchain on host => no hand EC
# proof can be machine-rechecked here, and every in-file-derivable axiom is
# already a proved lemma. What changed:
# * Added proofs/easycrypt/Pulsar_N1_NoLeak.ec, the HONEST production model:
# the masked Lagrange aggregate equals central z WITHOUT forming the
# master secret, the hint is recovered from PUBLIC w', and the ONLY open
# assumption is a STANDARD Module-LWE/MSIS reduction (no implementation
# reconstruct). This ADDS 2 axioms (public_hint_roundtrip [B],
# no_leak_reduction [C-standard]) -> AXIOM 87 -> 89.
# * The CORRECTNESS CORE of that model is MACHINE-CHECKED in Lean 4+Mathlib
# on this host (lake build green, 0 sorry): Crypto.Pulsar.NoLeakAggregate
# + Crypto.Pulsar.BoundaryClearance + Crypto.Threshold_Lagrange. The 2 new
# EC axioms are procedure-level wrappers of those machine-checked facts
# (public_hint_roundtrip) and the disclosed standard reduction
# (no_leak_reduction); both are recheck-pending ec-compile (CI gate).
# * The reconstruct-then-sign combine_body_axiom cone is RE-LABELLED in
# AXIOM-INVENTORY.md / reviewed-axioms.txt as the IDEALISED-CORRECTNESS
# model; the production residual is now the standard reduction above.
# Net: +2 disclosed standard-assumption axioms, in exchange for replacing the
# headline reconstruct-then-sign residual with a Module-LWE/MSIS reduction
# whose algebraic core is machine-checked. Honesty over a flat count.
# EC TOOLCHAIN IS LIVE ON THIS HOST. EasyCrypt (opam switch `proofs`) +
# z3/alt-ergo machine-check all top-level + lemmas/ theories via
# `easycrypt compile`; the framework gate runs checks/ec-machine-check.sh
# every invocation, and pulsar passes 14/14. (The earlier "no EC toolchain on
# host / recheck-pending ec-compile" note was FALSE and is removed.)
#
# COUNT NOTE: AXIOM=89 is what axiom-budget.sh's regex counts. Only 80 are
# real `axiom`/`declare axiom` DECLARATIONS (78 prior + 2 new) -- the regex
# also matches 9 comment lines inside (* ... *) blocks that begin with the
# word "axiom". The gate only fails on an INCREASE, so this neither hides nor
# inflates.
# Two things changed the count since the no-leak pass:
# * Deleted proofs/easycrypt/V04_Sign_Ctx.ec -- a stale orphan for the v0.4
# ctx-bound AlgebraicAggregate leaking path (sibling of the removed v0.3
# path). It never compiled (missing FIPS204_Axioms theory, no .eco). This
# removes 5 axiom declarations (empty_bytes_len/byte_of_zero/bytes_cat_empty_l
# [was B41], mldsa_sign_ctx_axiom [was C15], algebraic_aggregate_ctx_body_axiom
# [was C16 -- the reconstruct-then-sign axiom in its starkest form]).
# * The no-leak production model (Pulsar_N1_NoLeak.ec) remains: the masked
# Lagrange aggregate equals central z WITHOUT forming the master secret;
# hint from PUBLIC w'; the only open assumption is a STANDARD Module-LWE/MSIS
# reduction. Correctness core MACHINE-CHECKED in Lean (Crypto.Pulsar.
# NoLeakAggregate + BoundaryClearance + Crypto.Threshold_Lagrange).
#
# Real `admit.` tactics = 0 (the repo's own scripts/checks/ec-admits.sh, which
# matches ^admit.$, is authoritative). ADMIT=12 below is the budget regex
# counting the WORD "admit" in comments; informational only.
# Counts below are the comment-aware machine counts (axiom-budget.sh strips
# (* *) comments first, so prose like "DISCHARGED (no admit)" no longer
# inflates): real `admit`/`abort` tactics = 0; AXIOM = real declaration count
# (was 80 hand-counted; 80 - 5 deleted V04 axioms = 75).
SORRY=0
ADMIT=12
AXIOM=89
ADMIT=0
AXIOM=75
VACUOUS=0
+5 -1
View File
@@ -471,7 +471,11 @@ ok github.com/luxfi/pulsar/ref/go/pkg/pulsar 6.026s
```
Jasmin corpus: 23/23 compile (per commit `04cdc24`). EasyCrypt theory
`V04_Sign_Ctx.ec` ships in `proofs/easycrypt/`.
`V04_Sign_Ctx.ec` was **deleted** (forward-only): it proved byte-equality for
the v0.4 ctx-bound `AlgebraicAggregateCtx` — the ctx sibling of the removed
v0.3 reconstruct-then-sign leaking path — never compiled (missing
`FIPS204_Axioms` theory, no `.eco`), and is no longer in `proofs/easycrypt/`.
The remaining 12 top-level theories machine-check clean under `easycrypt compile`.
## 6. Audit verdict
+12 -8
View File
@@ -212,15 +212,18 @@ underlying op/constant (a verify-gated refactor); not a security claim.
| B39 | `committee_quorum_uniq` | …:209 | the canonical quorum is duplicate-free. |
| B40 | `committee_quorum_nonempty` | …:210 | the canonical quorum is non-empty. |
### B.5 — V04 ctx-encoding byte facts
### B.5 — V04 ctx-encoding byte facts — **REMOVED (forward-only)**
| # | Axiom | File:line | Justification |
|---|---|---|---|
| B41 | `empty_bytes_len` / `byte_of_zero` / `bytes_cat_empty_l` | V04_Sign_Ctx.ec:120122 | Empty-byte algebra used by `mu_ctx_empty_eq_mu_empty` (counted as one row; three declarations over abstract `empty_bytes`/`byte_of`/`bytes_cat`). |
The `V04_Sign_Ctx.ec` proof file was **deleted**: it proved byte-equality for
the v0.4 ctx-bound `AlgebraicAggregateCtx` path — the ctx sibling of the v0.3
reconstruct-then-sign leaking path removed under PULSAR-V13-HINT-LEAK. The file
never compiled (it `require`d a non-existent `FIPS204_Axioms` theory; no `.eco`).
Its former axioms `empty_bytes_len` / `byte_of_zero` / `bytes_cat_empty_l` (was
B41) are gone from the live set. Historical note only; no proof target imports it.
---
## Bucket C — OPEN SECURITY ASSUMPTION (21) — **MUST STAY OPEN**
## Bucket C — OPEN SECURITY ASSUMPTION (19) — **MUST STAY OPEN**
These carry security-relevant content the EC reduction has **not** closed.
Every one assumes the extracted/aggregated body equals the **centralised
@@ -250,12 +253,13 @@ EC-proven**. Tracked: `BLOCKERS.md` § "EC reconstruct-then-sign model".
| C12 | `sign_body_cs1_spec` | …:662 | extracted c·s₁ = `apply_c_to_s1 …` (z-stage sub). |
| C13 | `sign_body_w_low_spec` | …:704 | extracted libjade w_low = `central_w_low …` (h-stage). |
| C14 | `sign_no_reject_on_accepted_honest_layout` | …:826 | layout + accept ⇒ status=0 (libjade κ-loop accept-path). |
| C15 | `mldsa_sign_ctx_axiom` | V04_Sign_Ctx.ec:163 | FIPS 204 §5.4 ctx-bound single-party sign is the trusted base (circl SignTo with ctx). |
| C16 | `algebraic_aggregate_ctx_body_axiom` | V04_Sign_Ctx.ec:201 | **The v0.4 reconstruct-then-sign axiom in its starkest form**: `AlgebraicAggregateCtx` output = FIPS 204 §5.4 `SignCtx(sk_master, ctx, M)` where `sk_master` is the **reconstructed** master secret (existentially quantified, "NOT materialised" in Go but assumed to exist and to be what the aggregate equals). Go-discharge is by test + AST sweep, not EC. |
| ~~C15~~ | ~~`mldsa_sign_ctx_axiom`~~ | **REMOVED** | Lived in the deleted `V04_Sign_Ctx.ec`; gone with the v0.4 ctx path. |
| ~~C16~~ | ~~`algebraic_aggregate_ctx_body_axiom`~~ | **REMOVED** | The v0.4 reconstruct-then-sign axiom in its starkest form**deleted** with `V04_Sign_Ctx.ec` (forward-only). It is no longer in the proof set; the production no-leak path never instantiated it. |
> C-cone bundling note: C1/C2 are the section-local module contracts; C3C14
> are the per-stage byte-walk axioms the `*_byte_equality` lemma transitively
> rests on; C15C16 are the V04 ctx path. The histogram lists 21; the table
> rests on; C15C16 (the V04 ctx path) are **removed** with the deleted file.
> The histogram lists 19; the table
> above enumerates 16 rows. The remaining 5 C-declarations are the
> `combine_body_{matrix_a,mask_y}` / `sign_body_{matrix_a,mask_y}` /
> `*_no_reject` already shown — i.e. per *declaration* the C set is the 16
-301
View File
@@ -1,301 +0,0 @@
(* -------------------------------------------------------------------- *)
(* Pulsar -- Class N1 byte-equality reduction, v0.4 ctx-bound path *)
(* -------------------------------------------------------------------- *)
(* *)
(* STATUS *)
(* Discharged at the abstract level: under the FIPS 204 dispatch *)
(* axiom and the polynomial-Shamir reconstruction axiom (both *)
(* reused verbatim from Pulsar_N1.ec for the empty-ctx case), the *)
(* v0.4 ctx-bound threshold output is byte-equal to a single-party *)
(* FIPS 204 §5.4 SignCtx on the same (sk, ctx, M) tuple. The *)
(* refinement of the concrete v0.4 algebraic-aggregate body to the *)
(* abstract `AlgebraicAggregateCtxAbs` model is the section-local *)
(* declared axiom `algebraic_aggregate_ctx_body_axiom` -- discharged *)
(* Go-side by the existing graduation gates (TestOrchestrateV03Sign *)
(* Ctx_VerifyMatchesFIPS204, TestAlgebraic_NoSkAccess/ *)
(* AlgebraicAggregateCtx) and AST-structurally by the *)
(* `runAlgebraicAggregateASTChecks` sweep over BOTH *)
(* AlgebraicAggregate and AlgebraicAggregateCtx in *)
(* threshold_v03_test.go. *)
(* *)
(* WHAT THIS FILE GIVES REVIEWERS *)
(* 1. The v0.4 module-level abstraction: FIPS 204 §5.4 step-2 mu *)
(* encoding is decomplected into ONE EasyCrypt operator *)
(* `mu_ctx` matching the Go-side `deriveMuCtx`. *)
(* 2. A backwards-compat lemma `mu_ctx_empty_eq_mu_empty` that *)
(* pins `mu_ctx(tr, nil, M) = mu_empty(tr, M)` where *)
(* `mu_empty` is Pulsar_N1.ec's existing empty-ctx mu. *)
(* 3. The headline theorem *)
(* `threshold_sign_ctx_equiv_fips204_sign_with_ctx` *)
(* reducing AlgebraicAggregateCtx output bytes to *)
(* MLDSA.signCtx(sk, ctx, M) under the same abstract reductions *)
(* Pulsar_N1.ec discharges for the empty-ctx path. *)
(* *)
(* REFERENCES *)
(* - FIPS 204 §5.4 (Algorithm 22 -- M' = 0x00 || |ctx| || ctx || M) *)
(* - Pulsar_N1.ec (the empty-ctx companion theory) *)
(* - threshold_v03.go::deriveMuCtx (the single Go-side helper this *)
(* EasyCrypt operator mirrors byte-for-byte) *)
(* -------------------------------------------------------------------- *)
require import AllCore List Distr SmtMap.
require import FIPS204_Axioms. (* reuses the dispatch axioms from N1 *)
require import Pulsar_N1. (* mu_empty, MLDSA.sign, etc. *)
(* ---------------------------------------------------------------------- *)
(* Section 1. FIPS 204 §5.4 ctx-bound mu encoding. *)
(* ---------------------------------------------------------------------- *)
(* `bytes` is the standard byte-string type carried over from Pulsar_N1. *)
type bytes.
(* SHAKE-256 absorption-and-squeeze, modeled as a deterministic operator
over input bytes. The concrete Go implementation lives in
golang.org/x/crypto/sha3.NewShake256(); EasyCrypt models it as a ROM
in the security argument and as a deterministic function here for the
byte-equality reduction (a single dispatch chain, not a security
property). *)
op shake256_64 (input : bytes) : bytes.
(* `bytes_cat` is the EasyCrypt concatenation of byte strings (already
declared in Pulsar_N1.ec; re-declared here for self-containment). *)
op bytes_cat : bytes -> bytes -> bytes.
(* Single-byte encoding of a length value -- FIPS 204 §5.4 single-byte
length prefix. The Go side: `[]byte{byte(len(ctx))}`. *)
op byte_of (n : int) : bytes.
(* The 0x00 length-delimiter byte. *)
op zero_byte : bytes.
(* `bytes_len` reads the byte-length of a byte-string. *)
op bytes_len : bytes -> int.
(* ---------------------------------------------------------------------- *)
(* The Go-side `deriveMuCtx` mirrored EXACTLY in EasyCrypt. *)
(* *)
(* The byte-for-byte mirror lets us state *)
(* `algebraic_aggregate_ctx_body_axiom : forall sk ctx M, ...` *)
(* with the abstract aggregator using `mu_ctx` and the concrete Go *)
(* aggregator using `deriveMuCtx`. The structural test *)
(* `runAlgebraicAggregateASTChecks` enforces that BOTH *)
(* AlgebraicAggregate and AlgebraicAggregateCtx call ONLY this helper. *)
(* *)
(* mu_ctx(tr, ctx, M) *)
(* := shake256_64(tr || 0x00 || byte_of(|ctx|) || ctx || M) *)
(* *)
(* When ctx = [], |ctx| = 0 and byte_of(0) = 0x00, so *)
(* *)
(* mu_ctx(tr, [], M) = shake256_64(tr || 0x00 || 0x00 || [] || M) *)
(* = shake256_64(tr || 0x00 || 0x00 || M) *)
(* *)
(* which IS mu_empty(tr, M) verbatim. This is the *)
(* `mu_ctx_empty_eq_mu_empty` lemma below. *)
(* ---------------------------------------------------------------------- *)
op mu_ctx (tr ctx M : bytes) : bytes =
shake256_64
(bytes_cat tr
(bytes_cat zero_byte
(bytes_cat (byte_of (bytes_len ctx))
(bytes_cat ctx M)))).
(* ---------------------------------------------------------------------- *)
(* Section 2. Backwards-compat: empty-ctx ≡ Pulsar_N1.mu_empty. *)
(* ---------------------------------------------------------------------- *)
(* Empty bytes -- the empty string carrying length 0. *)
op empty_bytes : bytes.
(* The Pulsar_N1 mu_empty operator (re-imported here for the equality
lemma; the model in N1.ec is the same SHAKE-256 absorption shape we
restate in `mu_ctx_empty_unfold` for clarity). *)
op mu_empty (tr M : bytes) : bytes =
shake256_64
(bytes_cat tr
(bytes_cat zero_byte
(bytes_cat zero_byte M))).
(* AXIOM-FREE algebraic facts about empty bytes. *)
axiom empty_bytes_len : bytes_len empty_bytes = 0.
axiom byte_of_zero : byte_of 0 = zero_byte.
axiom bytes_cat_empty_l (b : bytes) : bytes_cat empty_bytes b = b.
(* ----------------------------------------------------------------------
THE BACKWARDS-COMPAT INVARIANT.
Holds STRUCTURALLY by the encoding: when ctx = empty, the FIPS 204
§5.4 prefix is `0x00 0x00` which is byte-identical to the historical
empty-ctx prefix used by Pulsar_N1.mu_empty.
The Go-side mirror of this lemma is
TestOrchestrateV03SignCtx_EmptyCtx_MatchesV03Sign which compares
wire bytes from OrchestrateV03Sign(msg) and OrchestrateV03SignCtx(
nil, msg) under identical deterministic RNG seeds.
---------------------------------------------------------------------- *)
lemma mu_ctx_empty_eq_mu_empty (tr M : bytes) :
mu_ctx tr empty_bytes M = mu_empty tr M.
proof.
rewrite /mu_ctx /mu_empty empty_bytes_len byte_of_zero.
by rewrite (bytes_cat_empty_l M).
qed.
(* ---------------------------------------------------------------------- *)
(* Section 3. FIPS 204 §5.4 SignCtx as a model dispatch. *)
(* ---------------------------------------------------------------------- *)
(* `MLDSA.signCtx` is the FIPS 204 §5.4 single-party ctx-bound sign *)
(* dispatch. The empty-ctx variant `MLDSA.sign` in Pulsar_N1.ec is *)
(* recovered as `MLDSA.signCtx sk empty_bytes M`. *)
module type MLDSA_SIGNER_CTX = {
proc sign_ctx (sk : bytes, ctx : bytes, M : bytes) : bytes
}.
(* The FIPS 204 dispatch axiom for ctx-bound sign. This is the v0.4 *)
(* sibling of the empty-ctx `mldsa_sign_axiom` in Pulsar_N1.ec. *)
(* *)
(* In words: cloudflare/circl's mldsa{44,65,87}.SignTo with ctx parameter*)
(* is the FIPS 204 reference implementation; this axiom names the *)
(* trusted base. *)
axiom mldsa_sign_ctx_axiom :
forall (sk ctx M : bytes),
bytes_len ctx <= 255 =>
exists (s : bytes),
Pr[ MLDSA_SIGNER_CTX.sign_ctx (sk, ctx, M) @ &m : true ] = 1%r /\
s = mu_ctx (tr_of sk) ctx M.
(* `tr_of sk` extracts the FIPS 204 §6 tr = SHAKE-256(pk, 64) hash *)
(* from sk. Same operator used in Pulsar_N1.ec. *)
op tr_of : bytes -> bytes.
(* ---------------------------------------------------------------------- *)
(* Section 4. Abstract v0.4 ctx-bound aggregator. *)
(* ---------------------------------------------------------------------- *)
(* Abstract model of AlgebraicAggregateCtx. Carries ctx as an explicit *)
(* parameter; the underlying field arithmetic and rejection-restart *)
(* loop are byte-identical to AlgebraicAggregate, modulo mu_ctx replacing*)
(* mu_empty as the prehash. *)
module type ALGEBRAIC_AGGREGATE_CTX = {
proc aggregate_ctx
(setup : bytes, (* group public material (no sk) *)
ctx : bytes,
M : bytes,
round1 : bytes list,
round2 : bytes list) : bytes
}.
(* Refinement axiom: the concrete Go-side AlgebraicAggregateCtx body *)
(* is observationally equivalent to FIPS 204 §5.4 SignCtx on the *)
(* RECONSTRUCTED master sk, with mu derived via mu_ctx. The Go-side *)
(* discharge: TestOrchestrateV03SignCtx_VerifyMatchesFIPS204 (mldsa65. *)
(* Verify accepts the wire bytes under (pk, M, ctx)). The structural *)
(* discharge: runAlgebraicAggregateASTChecks /AlgebraicAggregateCtx *)
(* confirms the body cannot reach a sk-bearing primitive, so the Class *)
(* N1 dispatch must factor through the FIPS 204 verifier verbatim. *)
axiom algebraic_aggregate_ctx_body_axiom :
forall (setup ctx M : bytes) (round1 round2 : bytes list)
(sk : bytes),
(* sk is the master ML-DSA secret reconstructed from any t-quorum of
the algebraic shares -- the value EXISTS in the security model
but is NOT materialised anywhere in the Go body (enforced AST-
structurally by TestAlgebraic_NoSkAccess/AlgebraicAggregateCtx). *)
is_master_sk_for_setup sk setup =>
bytes_len ctx <= 255 =>
Pr[ ALGEBRAIC_AGGREGATE_CTX.aggregate_ctx
(setup, ctx, M, round1, round2) @ &m : true ] = 1%r =>
exists (s : bytes),
(* The byte-identity step: AlgebraicAggregateCtx output = FIPS 204
§5.4 SignCtx(sk, ctx, M). *)
Pr[ MLDSA_SIGNER_CTX.sign_ctx (sk, ctx, M) @ &m : true ] = 1%r =>
s = mu_ctx (tr_of sk) ctx M.
(* `is_master_sk_for_setup` is the predicate stating that sk is the
master ML-DSA private key whose public key is bound into setup.Pub.
The Pulsar threshold protocol guarantees existence of such an sk
(by FIPS 204 keygen + Shamir-share reconstruction in the security
model) but the aggregator NEVER computes it. *)
op is_master_sk_for_setup : bytes -> bytes -> bool.
(* ---------------------------------------------------------------------- *)
(* Section 5. THE HEADLINE THEOREM. *)
(* ---------------------------------------------------------------------- *)
(* THEOREM threshold_sign_ctx_equiv_fips204_sign_with_ctx *)
(* *)
(* For any setup, any t-quorum's (round1, round2) transcripts, any *)
(* ctx string of length <= 255, and any message M, the output of *)
(* AlgebraicAggregateCtx is byte-equal to FIPS 204 §5.4 *)
(* SignCtx(sk_master, ctx, M) where sk_master is the (existentially- *)
(* quantified) master secret key whose public key is bound into *)
(* setup.Pub. *)
(* *)
(* This is the v0.4 sibling of `pulsar_n1_byte_equality` in *)
(* Pulsar_N1.ec. *)
lemma threshold_sign_ctx_equiv_fips204_sign_with_ctx :
forall (setup ctx M : bytes) (round1 round2 : bytes list)
(sk : bytes),
is_master_sk_for_setup sk setup =>
bytes_len ctx <= 255 =>
Pr[ ALGEBRAIC_AGGREGATE_CTX.aggregate_ctx
(setup, ctx, M, round1, round2) @ &m : true ] = 1%r =>
Pr[ MLDSA_SIGNER_CTX.sign_ctx (sk, ctx, M) @ &m : true ] = 1%r =>
exists (s_agg s_mldsa : bytes),
s_agg = mu_ctx (tr_of sk) ctx M /\
s_mldsa = mu_ctx (tr_of sk) ctx M /\
s_agg = s_mldsa.
proof.
move=> setup ctx M round1 round2 sk Hmaster Hctxlen Hagg Hsign.
have Hb := algebraic_aggregate_ctx_body_axiom
setup ctx M round1 round2 sk Hmaster Hctxlen Hagg.
have Hsx := mldsa_sign_ctx_axiom sk ctx M Hctxlen.
case: Hb => s_agg Hagg_eq.
case: Hsx => s_mldsa [_ Hmldsa_eq].
exists s_agg s_mldsa.
split; first by apply Hagg_eq; apply Hsign.
split; first by apply Hmldsa_eq.
have Hagg_val : s_agg = mu_ctx (tr_of sk) ctx M.
by apply Hagg_eq; apply Hsign.
by rewrite Hagg_val Hmldsa_eq.
qed.
(* ---------------------------------------------------------------------- *)
(* Section 6. Corollary -- empty-ctx specialisation. *)
(* ---------------------------------------------------------------------- *)
(* When ctx = empty_bytes the headline theorem specialises to the *)
(* v0.3 empty-ctx byte-equality from Pulsar_N1.ec, modulo the rename *)
(* MLDSA.sign = MLDSA.signCtx(_, empty_bytes, _). *)
lemma threshold_sign_empty_ctx_equiv_v03 :
forall (setup M : bytes) (round1 round2 : bytes list)
(sk : bytes),
is_master_sk_for_setup sk setup =>
Pr[ ALGEBRAIC_AGGREGATE_CTX.aggregate_ctx
(setup, empty_bytes, M, round1, round2) @ &m : true ] = 1%r =>
Pr[ MLDSA_SIGNER_CTX.sign_ctx (sk, empty_bytes, M) @ &m : true ] = 1%r =>
exists (s : bytes),
s = mu_empty (tr_of sk) M.
proof.
move=> setup M round1 round2 sk Hmaster Hagg Hsign.
have Hctxlen : bytes_len empty_bytes <= 255.
by rewrite empty_bytes_len; smt().
have Hthm := threshold_sign_ctx_equiv_fips204_sign_with_ctx
setup empty_bytes M round1 round2 sk
Hmaster Hctxlen Hagg Hsign.
case: Hthm => s_agg s_mldsa [Hagg_val [Hmldsa_val Hbyte_eq]].
exists s_agg.
rewrite Hagg_val.
by apply mu_ctx_empty_eq_mu_empty.
qed.
(* -------------------------------------------------------------------- *)
(* End of theory V04_Sign_Ctx. *)
(* -------------------------------------------------------------------- *)