The commitment format in the announcement-privacy model
Status: research note feeding the security-analysis write-up
(SECURITY_ANALYSIS.md review priority 2). It instantiates the proved generic
ofKEMFull announcement-privacy chain at the D-024 commitment profile
(format 0x02), states exactly which assumptions that instantiation needs,
and contrasts it with the Construction A (0x01) instantiation the chain was
originally built for. Analysis only; no protocol change is proposed.
Every theorem cited is sorry-free in lean/PqStealth/ (guarded by the
Axioms.lean audit); line numbers are from main at 7d5e15e. The ROM
argument of §3 is a sketch for review, not a theorem: it rests on a
composite-oracle abstraction whose bridge to the real commitment/CREATE2
instantiation is not proved, and its 2^-175 figure is an estimate
under that abstraction. Its qH is a joint budget over the tag, opener, and
address-composite oracle calls; §3 and §5 state the boundary precisely.
Classical ROM only: quantum oracle access, ANO-CCA (active) and the MLWE
reduction of the KEM terms remain outside this note exactly as in the rest of
the development.
1. The instantiation
The commitment derivation (python/pq_stealth/commit.py, D-024; wire format
0x02 ‖ spend_key(32) ‖ kem_ek, 1,217 B at ML-KEM-768) is a deterministic
function of the KEM shared secret ss and the recipient's public
meta-address material:
opener = SHA-256(OPEN_DOMAIN ‖ ss) (32 B)
commitment = keccak256(COMMIT_DOMAIN ‖ spend_key ‖ opener) (32 B)
address = CREATE2(factory, salt, initcode(commitment, …)) (20 B)
view_tag = SHA-256(ss)[0:1] (1 B)
announcement = (stealth_address, ct, view_tag)
The generic announcement-privacy model (KEMAnonymity.lean) is
StealthScheme.ofKEMFull kem auxGen with auxGen : K → PK → Aux, announcement
(ct, auxGen ss pk), and a scan that recomputes aux from the decapsulated
secret and the recipient's own public key (KEMAnonymity.lean:145-155; why
the second argument is load-bearing, and why the scan must recompute rather
than test decapsulation, is in lean/docs/announcement-model.md). The
format-0x02 instantiation is:
PK := CommitMetaPublic = (spend_key, kem_ek) — both public
SK := kem_dk — the ML-KEM decapsulation key alone
K := 32-byte shared secret
Aux := Tag × Addr — view tag + stealth address
auxGen ss pk := ( SHA-256(ss)[0:1],
CREATE2(keccak256(COMMIT ‖ pk.spend_key ‖ SHA-256(OPEN ‖ ss))) )
Two structural facts make this the faithful instance of the generic model:
- The view tag is a function of the shared secret alone — exactly the
taggedAux viewTag restshape (Soundness.lean:305). This establishes the syntactic shape required by the generic detection theorem; applyingsoundWithin_ofKEMFull_oneByteTagadditionally requires itshUnifhypothesis, here a classical-ROM assumption that truncatedSHA-256of a uniform secret is uniform. That concrete assumption is not formalized. - The address is a deterministic function of
(ss, spend_key)— the recipient dependence enters through the publicspend_keyslot ofauxGen's second argument and nowhere else. There is no recipient-side lattice mask to model at receive time: unlike Construction A, whose announced key ishashAddr (pack rho (power2Round (u + t)))withrhoandtthe recipient's own meta-address material and only(s', e')from the secret, the commitment address contains no recipient secret-derived structure at all. The account's spending scheme is behind the commitment hash — that is the point of D-024 (and D-018 for C13).
The generic chain supplies the result shapes needed by this manual mapping:
the four-term bound unlinkAdvantage_ofKEMFull_le
(KEMAnonymity.lean:236), the IND-CPA identification of the hiding terms
(sharedSecretHiding_eq_indCpaAdvantage, SharedSecretHiding.lean:187), the
multi-payment hybrid unlinkAdvantageMulti_ofKEMFull_le (MultiUnlink.lean:281),
and the n-recipient pair-guessing bound unlinkAdvantageN_ofKEMFull_le
(MultiRecipient.lean:358). No checked commitKem / commitAuxGen wrapper
yet instantiates these definitions for the deployed profile, so this note is
not itself a concrete Lean instantiation. The generic chain fixes the shape
of the intended bound; auxKeyIndependence (KEMAnonymity.lean:228) is the
term whose value depends on auxGen's concrete structure.
2. The single-payment bound, instantiated
At the commitment auxGen, the four-term decomposition
(KEMAnonymity.lean:236-241) reads
Adv_unlink ≤ ssHiding(true) + auxKeyIndependence + KEM.anonAdvantage + ssHiding(false)
with each term now concrete:
| Term | What it is at format 0x02 | Status |
|---|---|---|
ssHiding(b), one per branch | Exactly the ML-KEM-768 IND-CPA advantage of the reduction adversary (sharedSecretHiding_eq_indCpaAdvantage, SharedSecretHiding.lean:187) | Named KEM term; the FO-to-MLWE bridge is the same open upstream lemma as for Construction A (mlkem768_sprAdv_le_mlwe, SPRTwoHop.lean:605, covers the anonymity side) |
auxKeyIndependence | The only commitment-specific term. With the secret idealized to a fresh uniform ss' (randAuxBranch, KEMAnonymity.lean:203), aux is (SHA-256(ss')[0:1], CREATE2(keccak256(‖spend_key‖SHA-256(ss')))) — a deterministic function of a uniform 256-bit string and a public constant | Bounded in §3 under ROM assumptions on SHA-256 and keccak256 — no lattice assumption enters |
KEM.anonAdvantage | ML-KEM-768 ciphertext anonymity (key privacy) | Decomposed to seeded-MLWE plus named assumption records via anonAdvantage_le_sprAdv (AnonymityFromSPR.lean:49) and mlkem768_sprAdv_le_mlwe (SPRTwoHop.lean:605) |
What disappears relative to Construction A. The 0x01 instantiation
needs the seeded-MLWE blinding hop (ConstructionA.lean's
blindingProblem / mlweAdvOfUnlinkAdv, with its ExpandIsIdeal
XOF-idealization assumption), and where a proof-grade ROM bound is wanted, the
point-mass machinery of BlindingROM.lean / BlindingEntropy.lean — which
exists to handle a lattice address point of low min-entropy: at ML-DSA-65
(k = 6, ringDegree = 256) the point-mass is
betaAddr = (2¹³/q)^(6·256) ≈ 2^-15358 per address-point guess
(blindPointMassBound_mldsa65; the ^2048 form is ML-DSA-87, k = 8), and
the ROM bound is 2·qH·betaAddr
(blindBadProb_le_queryBound, BlindingEntropy.lean:94). The commitment
address point is a full 256-bit hash preimage-slot: the recipient's input to
the address hash is a public constant (spend_key), and the only secret input
is the uniform ss'. The aux term is therefore a plain ROM argument with
point mass 2^-256 — no MLWE hop, no ExpandIsIdeal, no lattice
point-mass. The commitment profile is not merely "less proved" than the
supporting Construction A track; its aux argument is structurally cleaner,
and that contrast is what the ERC security-considerations text needs to
convey. (§5 records that the ROM bound itself is a sketch, not a theorem.)
3. auxKeyIndependence at format 0x02 — ROM bound sketch
The intermediate game randAuxBranch kem auxGen adv b (KEMAnonymity.lean:203)
replaces the real ss by a fresh uniform ss' but keeps building aux from
recipient b's public key. The adversary sees (ct, tag, addr) plus both
meta-addresses (spend_key₀, ek₀), (spend_key₁, ek₁) — the full public
view, per unlinkSetup. The term asks: can aux built from spend_key_b and
uniform ss' be told from aux built from spend_key_{1−b} and uniform ss'?
The view tag is part of the public view; it is not learned through an oracle
query. The ROM sketch must therefore give the adversary access to the
domain-separated tag hash as well as the opener and address composite, and
qH below counts all such calls jointly. This prevents the tag from
creating an uncharged filtering step: an adversary can hash candidate secrets,
keep those whose first byte matches the announced tag, and only then query the
opener/address path.
Subject to that joint budget, the argument is the ROM argument for a hash chain whose secret input is uniform and whose recipient-dependence enters via a public constant in the preimage:
- Inner hash (opener). Model
ss' ↦ SHA-256(OPEN_DOMAIN ‖ ss')as a random oracle. For a freshss'never queried before,openeris a fresh uniform 256-bit string independent of everything the adversary has seen — the uniform-output property, the same shape as BlindingROM'srun_hashAddrRO_empty(BlindingROM.lean:67): uniform output whatever the input hashed. The adversary may program/observe queries on the inner oracle too. A query on exactly the challenger'sss'(probability2^-256per candidate before the public tag is used) is the event that can correlate the branches. - Outer hash (commitment) and CREATE2 chain (address). Model the
composite
(spend_key, opener) ↦ address— i.e.keccak256(0xff ‖ factory ‖ salt ‖ keccak256(initcode))withinitcodecarryingkeccak256(COMMIT ‖ spend_key ‖ opener)— as one random oracleBytes → Addr, exactly the "composite as ONE function" readingBlindingROM.leanadopts for Construction A'shashAddr ∘ pack. For a fresh(spend_key, opener)pair not previously queried, the address is a fresh uniform2^160-point. The recipient'sspend_keysits in the preimage: distinguishing branches is distinguishing oracle outputs on two public prefixes — impossible without querying the oracle on a point whose preimage involves the branches' differing secret draw. - Bad event and budget, defined precisely (review F3/F4). Two distinct
events must not be conflated:
- (a) Hidden-preimage query: the adversary queries an oracle at a
preimage whose secret slot is the challenger's
ss'. Per-query probability is2^-256against a uniform 256-bit secret it has not seen, when the query is charged to the joint budget. - (b) Unrelated colliding output: some query happens to produce the
challenge address (a 160-bit target) without touching the hidden
preimage — e.g. programming the outer oracle, or querying a different
preimage that happens to land on the same address.
Event (a) is bounded by
qH · 2^-256only whenqHincludes tag-hash candidate tests as well as opener and composite calls. If it counted only outer-composite calls, tag-filtered candidates would instead have conditional point mass2^-248, so the stated2^-256per-outer-query step would be invalid. Event (b) does not identify the hidden preimage and is not a branch discriminator under the composite-oracle abstraction. The earlier draft's2^-160union-bound term remains absent because of that abstraction, not because it is dominated by2^-256.
- (a) Hidden-preimage query: the adversary queries an oracle at a
preimage whose secret slot is the challenger's
auxKeyIndependence ≤ 2·qH·2^-256
— under the composite-oracle abstraction and the joint query budget;
the factor two accounts for the two comparison branches. The two branches
share the same hidden ss', so this is not a fresh 2^-160 address guess
per branch.
The composite-oracle abstraction is a stronger assumption, stated as
such (review F3). Modeling commitment-hash + CREATE2 as one ideal oracle
Bytes → Addr ignores that the adversary can query the intermediate
hashes (the real commitment hash, the real keccak256 initcode hash) on
adversarially chosen inputs. A faithful reduction would have to simulate
those intermediate oracles and argue the composite inherits their
uniformity — the natural plan mirrors BlindingROM.lean (one composite
oracle, identical-until-bad, query-budget bound), but no such module
exists in the Lean tree. Until CommitmentAnonymity.lean lands, §3 is a
sketch under an unproved abstraction bridge, and the 2^-175 figure
below is an estimate under that abstraction, not a theorem-derived bound.
Assumptions, stated precisely:
- ROM on SHA-256 (opener) and on the keccak256 composite (commitment +
CREATE2 chain), modeled as ONE composite oracle. Stronger than the
standard per-hash ROM the development carries elsewhere: the abstraction
hides the adversary's ability to query the real intermediate hashes.
Labeled here as an unproved bridge (see above); discharging it is the
recorded open obligation (
CommitmentAnonymity.lean, future work). - Joint query budget, including the public tag oracle.
qHbounds every adversarial query toSHA-256(·)used for the announced tag,SHA-256(OPEN_DOMAIN ‖ ·)used for the opener, and the modeled address composite. The domains keep outputs independent in the classical ROM; the shared budget prevents tag filtering from being free. ss'uniform by construction. In therandAuxBranchgame the challenger drawsss'uniform — that is by construction, not an assumption. Getting there from the real game is the IND-CPA step, which the four-term decomposition already performs and prices into thessHidingterms — so the aux argument consumes, rather than duplicates, that assumption. No circularity.- Both recipients share the deployment binding (and profile). The
factory, creation code, salt, verifier, and frame context enter the
address preimage as public constants common to both branches. If the
two recipients use different bindings or profiles, their aux
distributions differ for public reasons — that channel is auxiliary
information outside the
auxGenmodel (unlinkSetupdraws both recipients from onekeygen), and SECURITY_ANALYSIS.md's profile-narrowing paragraph covers it. The ERC text must state the single-profile/common-binding condition as a property. - The tag function is the deployed one-byte tag.
tagOf = SHA-256(ss)[0:1]is public and branch-independent oncess'is fixed. Branch-independence alone is NOT a generic harmless property: a tag function that leaked the secret (e.g.tagOf(ss) = ss) would let the adversary compute both candidate destinations through the public hashes. The deployed tag only offers an eight-bit candidate filter; §3 charges its oracle evaluations to the joint budget. The generictaggedAuxmachinery makes no claim about other tag choices; each profile's tag must be analyzed jointly with its opener and address derivation.
Reading the estimate (review F3). Under the composite-oracle abstraction
above, the commitment-side channel contributes at most 2·qH·2^-256 — with
a generous qH = 2^80, 2^-175 per payment pair. This is an estimate
under an unproved abstraction, not a bound discharged by a Lean theorem;
the four-term decomposition itself is proved, but this aux term is the
sketched one (see §3 and §5). The
dominant terms of the format-0x02 privacy bound are the KEM terms — ML-KEM
IND-CPA (twice) and ML-KEM ciphertext anonymity — which remain named
MLWE-level terms exactly as for Construction A. The privacy story for format
0x02 is: the KEM carries all the weight; the commitment side adds only the
ROM assumptions already on the books. That sentence is what
SECURITY_ANALYSIS.md's review priority 2 asks for ("justify its
auxiliary-data independence under explicit hash assumptions"), and it is the
argument the ERC's security-considerations section should carry.
4. Multi-payment and multi-recipient composition
The multi-challenge hybrid is proved generically for every auxGen and
adversary: unlinkAdvantageMulti_ofKEMFull_le (MultiUnlink.lean:281) gives
Adv_unlink_q ≤ q · ε_single for q announcements between the same two
recipients (linear loss — unlinkAdvantageMulti_le_mul, MultiUnlink.lean:241),
and unlinkAdvantageN_ofKEMFull_le (MultiRecipient.lean:358) gives
Adv ≤ n·(n−1)·ε_single when the adversary names the challenge pair among
n published meta-addresses. Both are instantiations of proved theorems,
not paper composites — the per-hybrid adversary's four-term sum is the §2
bound each time.
A concrete reading for the write-up — conditional on the §3 abstraction:
with qH = 2^80, the aux term at q observed announcements is q · 2^-175.
Even a full-chain window of q = 2^20 announcements to the pair keeps it
at 2^-155; a 2^40 window at 2^-135. These figures inherit the §3
sketch's status: proved decomposition, sketched aux term. Whatever window
the threat model picks, the commitment-side channel stays negligible
against the KEM terms — the statement the security-analysis write-up should
make quantitative, with the window q stated explicitly
(announcement-model.md's guidance: the single-challenge target must be
ε ≤ 2^-k / q for a k-bit claim).
Correction to the in-tree reading. lean/docs/announcement-model.md says
the composition of MultiUnlink/MultiRecipient with unlinkAdvantage_ofKEMFull_le
is "on paper, not in Lean" (§ multi-challenge: "the composition is
nevertheless legitimate"; § n-recipients: "that composition is not in
Lean"). That was true when the essay was written; it is stale now:
unlinkAdvantageMulti_ofKEMFull_le (MultiUnlink.lean:281) and
unlinkAdvantageN_ofKEMFull_le (MultiRecipient.lean:358) discharge exactly
those instantiations. The essay is updated in this branch's companion commit.
4b. Detection soundness (the tag side)
The scan-side theorem is generic and conditional. Given hUnif — that the
one-byte tag of a uniform secret is uniform — the false-positive rate of
ofKEMFull is at most 1/256 + decapsRoR
(soundWithin_ofKEMFull_oneByteTag, Soundness.lean:373). decapsRoR is the
same named real-or-random IND-CPA-shaped term as in the unlinkability chain (a
distinguisher that tells the recipient's real decapsulated secret from a fresh
uniform one inside FalsePositiveExp; Soundness.lean names it rather than
sweeping it into the statement). The commitment profile has the required
taggedAux shape (Soundness.lean:305), but no Lean definition establishes
hUnif for truncated SHA-256; that is a classical-ROM assumption here. No
lattice term appears on the soundness side either. The n-fold union bound for
a scanner sweeping n announcements is on paper (not in Lean), as the essay
already records.
5. What is and is not discharged
Discharged — generic, sorry-free result shapes (not a checked 0x02
instantiation):
- The four-term decomposition and its IND-CPA identification
(
KEMAnonymity.lean:236,SharedSecretHiding.lean:187). - The multi-payment and n-recipient composites (
MultiUnlink.lean:281,MultiRecipient.lean:358). - Detection soundness conditional on
hUnif(Soundness.lean:373-377), withtaggedAuxatSoundness.lean:305supplying only the required shape. - Detection completeness (
perfectlyComplete_ofKEMFull,KEMAnonymity.lean:160).
Instantiation-specific — argued here, not proved:
- The concrete
commitKem/commitAuxGenwrapper connecting the deployed profile to the generic Lean types and experiments. - The
auxKeyIndependenceROM bound (§3). The shape isrun_hashAddrRO_empty's uniform-output property plus theblindBadProb_le_queryBoundunion bound, but the commitmentauxGenhas a different input structure (public constant in the preimage vs a masked lattice term), so it needs its own module in the Lean tree (CommitmentAnonymity.lean, future work; the natural plan mirrorsBlindingROM.lean: one composite oracle, identical-until-bad, query-budget bound). Until it lands, §3 is a review artifact for the ERC's security-considerations text — the assumption list is rigorous, the2^-175figure is an estimate under the stated composite-oracle abstraction (an unproved bridge), not an established bound. Per review F3: do not quote2^-175as a derived quantity; quote it as "estimated under the §3 abstraction, open proof obligation". - Uniformity of
SHA-256(ss)[0:1]for uniformss, thehUnifpremise of the generic detection theorem; this note treats it as a classical-ROM assumption.
Explicitly not claimed:
- No reduction of the ML-KEM IND-CPA or anonymity terms to MLWE for this
format — the FO bridge is the same open upstream lemma as for Construction
A (
SPRTwoHop.lean's open item). No ANO-CCA for active attacks; the chain proves the passive game. Quantum ROM access remains outside, as everywhere in the development. - No theorem covers profile narrowing: two recipients on different profiles
(or deployment bindings) have differently-distributed aux for public
reasons. The model's
unlinkSetupdraws both recipients from onekeygen, i.e. the same profile; the narrowing channel is auxiliary information, covered by SECURITY_ANALYSIS.md's existing paragraph and stated as a property in the ERC text. - Nothing here covers spending: the account behind the commitment has its own
authorization and disclosure story (D-027's linkage analysis, the widened-
zroute-class channel for0x01blinded signing). Receive-time privacy and spend-time linkability are separate assurance tracks, exactly as SECURITY_ANALYSIS.md frames them.
Reproduce
Numbers in §2–§4 from exact arithmetic (no dependencies). The aux_term
line is the §3 sketch's estimate under the composite-oracle abstraction
— it reproduces the arithmetic, not the proof (review F3):
from fractions import Fraction as F
beta_commitment = F(1, 2**256) # hidden-preimage point mass (§3 event (a))
qH = 2**80 # generous joint oracle-query budget
aux_term = 2 * qH * beta_commitment # = 2^-175 (under the §3 abstraction)
assert aux_term == F(1, 2**175)
# Construction A contrast (ML-DSA-65): lattice point-mass
# mldsa65.k = 6, ringDegree = 256, so the exponent is 6 * 256 = 1536:
# betaAddr = (2^13/q)^1536, q = 8380417 ≈ 2^-15358
# (blindPointMassBound_mldsa65; needs the seeded-MLWE hop + ExpandIsIdeal)
# ML-DSA-87 (k = 8) is the ^2048 form, ≈ 2^-20477.
# Compare in log space: the value underflows a float.
import math
assert math.log2(F(2**13, 8380417)) * 1536 < -15357Cross-checks: the four-term shape and randAuxBranch/cipherOf definitions
are KEMAnonymity.lean:203/228/236; the taggedAux + one-byte-tag chain is
Soundness.lean:305/373; 1/256 is the machine-checked tag bound's own
constant; the q-fold linear loss is MultiUnlink.lean:241.