Skip to content

Commit 11cbb52

Browse files
refactor(OracleReduction): generic challenge-oracle reindexing; add lawful + disjoint append inclusions
Addresses review findings on the challenge-oracle append inclusions. 1. Right abstraction instead of two bespoke copies. The two inclusions were built from four hand-written helper defs (`challengeInl/InrOn Query/Response`) that differed only in which index map and transport lemma they used. Replace them with one construction in `ProtocolSpec/Basic.lean`, beside `challengeOracleInterface`: an index map `f : p.ChallengeIdx → q.ChallengeIdx` plus a transport `∀ i, q.Challenge (f i) = p.Challenge i` induces `subSpecOfChallengeReindex`. That data determines the lift uniquely (the transport is an equality of types), which is what makes the induced inclusion canonical rather than merely well-typed. `seqCompose` is the intended second client; it is not instantiated here since nothing consumes it yet. 2. `LawfulSubSpec` (the substantive gap). `SubSpec` is pure data: on its own it does not force the lift to preserve the uniform distribution on challenges. `LawfulSubSpec` -- `onResponse` bijective on every fibre, i.e. a cartesian lens -- is what VCV-io requires for `evalDist_liftComp`, `probEvent_liftComp`, `probOutput_liftComp` and `support_liftComp`, and for `lawfulSubSpec_right_add_right_add` to carry lawfulness through the `oSpec + .` layer that `Prover.append_run` actually lifts across. Neither inclusion had it, so `append_run` could not have fed the security proofs even once proved. Now provided generically (`lawfulSubSpecOfChallengeReindex`, from `challengeReindexResponse_bijective`) and instantiated on both sides; verified that `(oSpec + [pSpecᵢ.Challenge]ₒ) ˡ⊂ₒ (oSpec + [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ)` now synthesises. 3. `DisjointSubSpec`, both orientations, matching VCV-io's `disjointSubSpec_add_left_right` / `..._right_left`: a left-injected round index is `< m` and a right-injected one is `≥ m`, so the two components' challenge queries cannot alias. 4. Deduplication. `challenge_append_inl` / `_inr` re-derived by `simp` what `append_Type_castAdd` / `append_Type_natAdd` already state 320 lines up in the same file; they are now those lemmas at the underlying round index. Conversely `CoordinateWiseSpecialSoundness/SeqCompose.lean` was re-deriving the same transport inline as `cast (by simp [...])`; it now cites `challenge_append_inl` / `_inr`, removing a fourth copy and giving the lemmas real consumers. Also: dropped the `SampleableType` `variable` line left dangling with no declarations after it, and documented the `pSpec ++ₚ pSpec` instance-overlap (resolution picks `..._right`, as upstream does for `spec + spec`). Verified: `lake build` green (4135 jobs, 0 errors); `#print axioms` reports [propext, Classical.choice, Quot.sound] for all 11 new/changed declarations; `Prover.append_run` still resolves through `subSpec_challenge_append_left` / `_right` under `pp.explicit`; no style-lint error and no build-linter warning inside the changed ranges. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 4397764 commit 11cbb52

4 files changed

Lines changed: 122 additions & 56 deletions

File tree

ArkLib/OracleReduction/Composition/Sequential/Append.lean

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,10 @@ variable {P₁ : Prover oSpec Stmt₁ Wit₁ Stmt₂ Wit₂ pSpec₁}
331331

332332
-- theorem append_runToRound
333333

334-
-- The challenge-oracle inclusions used below are now provided (proved) by
335-
-- `ProtocolSpec.subSpec_challenge_append_left` / `..._right` in `ProtocolSpec/SeqCompose.lean`.
334+
-- The challenge-oracle inclusions that `append_run`'s statement lifts along are provided (proved)
335+
-- by `ProtocolSpec.subSpec_challenge_append_left` / `..._right` in `ProtocolSpec/SeqCompose.lean`,
336+
-- together with their `LawfulSubSpec` instances — the latter are what make the lifts distribution-
337+
-- and support-preserving, hence usable by the security proofs below.
336338

337339
/--
338340
States that running an appended prover `P₁.append P₂` with an initial statement `stmt₁` and

ArkLib/OracleReduction/ProtocolSpec/Basic.lean

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,64 @@ def challengeQueryImpl {pSpec : ProtocolSpec n} [∀ i, SampleableType (pSpec.Ch
681681
QueryImpl ([pSpec.Challenge]ₒ'challengeOracleInterface) ProbComp :=
682682
fun q => $ᵗ (pSpec.Challenge q.1)
683683

684+
section ChallengeReindex
685+
686+
/-! ### Reindexing challenge oracles
687+
688+
A protocol's challenge oracles embed into another's whenever challenge *indices* embed in a way
689+
that preserves the challenge *types*. That data — an index map `f` together with the transport
690+
`∀ i, q.Challenge (f i) = p.Challenge i` — is all a `SubSpec` needs, and it determines the lift
691+
uniquely, so we build the whole `SubSpec` / `LawfulSubSpec` / `DisjointSubSpec` package from it
692+
once here rather than case by case.
693+
694+
Clients are the composition operators, each of which supplies an index map and a transport lemma.
695+
`++ₚ` supplies `ChallengeIdx.inl` / `ChallengeIdx.inr` with `challenge_append_inl` /
696+
`challenge_append_inr`; `seqCompose` would supply `sigmaChallengeIdxToSeqCompose`, whose transport
697+
follows from `seqCompose_challenge_eq` and `seqComposeChallengeEquiv.left_inv` (not instantiated
698+
here, as nothing consumes it yet). -/
699+
700+
variable {k l : ℕ} {p : ProtocolSpec k} {q : ProtocolSpec l}
701+
(f : p.ChallengeIdx → q.ChallengeIdx) (hf : ∀ i, q.Challenge (f i) = p.Challenge i)
702+
703+
/-- Forward map on challenge queries induced by an index map: reindex, keep the (trivial) query. -/
704+
@[reducible] def challengeReindexQuery (t : [p.Challenge]ₒ.Domain) : [q.Challenge]ₒ.Domain :=
705+
⟨f t.1, t.2
706+
707+
/-- Backward map on challenge responses induced by an index map with matching challenge types:
708+
transport the response along `hf`. Note that this is the *only* possible choice, since `hf` is an
709+
equality of types — which is what makes `subSpecOfChallengeReindex` canonical. -/
710+
@[reducible] def challengeReindexResponse (t : [p.Challenge]ₒ.Domain)
711+
(r : [q.Challenge]ₒ.Range (challengeReindexQuery f t)) : [p.Challenge]ₒ.Range t :=
712+
show p.Challenge t.1 from (hf t.1) ▸ (show q.Challenge (f t.1) from r)
713+
714+
/-- Transporting a challenge response along an equality of challenge types is a bijection.
715+
This is what makes the induced inclusion *lawful*, i.e. uniform-challenge preserving. -/
716+
theorem challengeReindexResponse_bijective (t : [p.Challenge]ₒ.Domain) :
717+
Function.Bijective (challengeReindexResponse f hf t) :=
718+
(Equiv.cast (hf t.1)).bijective
719+
720+
/-- An embedding of challenge indices that preserves challenge types induces an inclusion of
721+
challenge oracles.
722+
723+
`monadLift` is spelled out in lens form (rather than left to the class default) so that the lifted
724+
query reduces during `simp` / `rw` matching; see the `OracleSpec.SubSpec` docstring. -/
725+
@[reducible] def subSpecOfChallengeReindex : [p.Challenge]ₒ ⊂ₒ [q.Challenge]ₒ where
726+
monadLift qry := ⟨challengeReindexQuery f qry.input,
727+
qry.cont ∘ challengeReindexResponse f hf qry.input⟩
728+
onQuery := challengeReindexQuery f
729+
onResponse := challengeReindexResponse f hf
730+
731+
/-- The induced inclusion is lawful: `onResponse` is bijective on every fibre, which is exactly
732+
what VCV-io needs to preserve the uniform distribution on challenges under the lift
733+
(`evalDist_liftComp`, `probEvent_liftComp`, `support_liftComp`). -/
734+
theorem lawfulSubSpecOfChallengeReindex :
735+
letI := subSpecOfChallengeReindex f hf
736+
[p.Challenge]ₒ ˡ⊂ₒ [q.Challenge]ₒ := by
737+
letI := subSpecOfChallengeReindex f hf
738+
exact ⟨challengeReindexResponse_bijective f hf⟩
739+
740+
end ChallengeReindex
741+
684742
/-- The oracle interface for state-restoration and (basic) Fiat-Shamir.
685743
686744
This is the version where we hash the input statement and the entire transcript up to

ArkLib/OracleReduction/ProtocolSpec/SeqCompose.lean

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -432,64 +432,70 @@ instance : ∀ i, OracleInterface ((pSpec₁ ++ₚ pSpec₂).Challenge i) := cha
432432

433433
/-- The challenge type of an appended protocol at a left-injected challenge index agrees with the
434434
challenge type of the left component. This is the transport fact needed to move challenge data
435-
across `++ₚ`. -/
435+
across `++ₚ`; it is `append_Type_castAdd` at the underlying round index, since `ChallengeIdx.inl`
436+
is `Fin.castAdd` on rounds. -/
436437
theorem challenge_append_inl (i : ChallengeIdx pSpec₁) :
437-
(pSpec₁ ++ₚ pSpec₂).Challenge (ChallengeIdx.inl i) = pSpec₁.Challenge i := by
438-
simp [ProtocolSpec.Challenge, ChallengeIdx.inl, ProtocolSpec.append,
439-
Fin.vappend_eq_append, Fin.append_left]
438+
(pSpec₁ ++ₚ pSpec₂).Challenge (ChallengeIdx.inl i) = pSpec₁.Challenge i :=
439+
append_Type_castAdd (pSpec₁ := pSpec₁) (pSpec₂ := pSpec₂) i.1
440440

441441
/-- The challenge type of an appended protocol at a right-injected challenge index agrees with the
442-
challenge type of the right component. -/
442+
challenge type of the right component. Dually to `challenge_append_inl`, this is
443+
`append_Type_natAdd` at the underlying round index. -/
443444
theorem challenge_append_inr (i : ChallengeIdx pSpec₂) :
444-
(pSpec₁ ++ₚ pSpec₂).Challenge (ChallengeIdx.inr i) = pSpec₂.Challenge i := by
445-
simp [ProtocolSpec.Challenge, ChallengeIdx.inr, ProtocolSpec.append,
446-
Fin.vappend_eq_append, Fin.append_right]
447-
448-
/-- Forward map on challenge queries: inject a left-component challenge index. -/
449-
@[reducible] def challengeInlOnQuery (t : [pSpec₁.Challenge]ₒ.Domain) :
450-
[(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ.Domain :=
451-
⟨ChallengeIdx.inl t.1, t.2
452-
453-
/-- Backward map on challenge responses for the left injection, transporting along
454-
`challenge_append_inl`. -/
455-
@[reducible] def challengeInlOnResponse (t : [pSpec₁.Challenge]ₒ.Domain)
456-
(r : [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ.Range (challengeInlOnQuery (pSpec₂ := pSpec₂) t)) :
457-
[pSpec₁.Challenge]ₒ.Range t :=
458-
show pSpec₁.Challenge t.1 from
459-
(challenge_append_inl (pSpec₂ := pSpec₂) t.1) ▸
460-
(show (pSpec₁ ++ₚ pSpec₂).Challenge (ChallengeIdx.inl t.1) from r)
461-
462-
/-- The challenge oracles of the left component embed into those of the appended protocol. -/
445+
(pSpec₁ ++ₚ pSpec₂).Challenge (ChallengeIdx.inr i) = pSpec₂.Challenge i :=
446+
append_Type_natAdd (pSpec₁ := pSpec₁) (pSpec₂ := pSpec₂) i.1
447+
448+
/-- The challenge oracles of the left component embed into those of the appended protocol,
449+
by reindexing along `ChallengeIdx.inl`. -/
463450
instance subSpec_challenge_append_left :
464-
[pSpec₁.Challenge]ₒ ⊂ₒ [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ where
465-
monadLift q := ⟨challengeInlOnQuery (pSpec₂ := pSpec₂) q.input,
466-
q.cont ∘ challengeInlOnResponse (pSpec₂ := pSpec₂) q.input⟩
467-
onQuery := challengeInlOnQuery (pSpec₂ := pSpec₂)
468-
onResponse := challengeInlOnResponse (pSpec₂ := pSpec₂)
469-
470-
/-- Forward map on challenge queries: inject a right-component challenge index. -/
471-
@[reducible] def challengeInrOnQuery (t : [pSpec₂.Challenge]ₒ.Domain) :
472-
[(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ.Domain :=
473-
⟨ChallengeIdx.inr t.1, t.2
474-
475-
/-- Backward map on challenge responses for the right injection, transporting along
476-
`challenge_append_inr`. -/
477-
@[reducible] def challengeInrOnResponse (t : [pSpec₂.Challenge]ₒ.Domain)
478-
(r : [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ.Range (challengeInrOnQuery (pSpec₁ := pSpec₁) t)) :
479-
[pSpec₂.Challenge]ₒ.Range t :=
480-
show pSpec₂.Challenge t.1 from
481-
(challenge_append_inr (pSpec₁ := pSpec₁) t.1) ▸
482-
(show (pSpec₁ ++ₚ pSpec₂).Challenge (ChallengeIdx.inr t.1) from r)
483-
484-
/-- The challenge oracles of the right component embed into those of the appended protocol. -/
451+
[pSpec₁.Challenge]ₒ ⊂ₒ [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ :=
452+
subSpecOfChallengeReindex ChallengeIdx.inl (challenge_append_inl (pSpec₂ := pSpec₂))
453+
454+
/-- The left inclusion is lawful, so lifting along it preserves the distribution and support of
455+
challenge queries. -/
456+
instance lawfulSubSpec_challenge_append_left :
457+
[pSpec₁.Challenge]ₒ ˡ⊂ₒ [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ :=
458+
lawfulSubSpecOfChallengeReindex ChallengeIdx.inl (challenge_append_inl (pSpec₂ := pSpec₂))
459+
460+
/-- The challenge oracles of the right component embed into those of the appended protocol,
461+
by reindexing along `ChallengeIdx.inr`.
462+
463+
Note that for the degenerate `pSpec ++ₚ pSpec` both this and `subSpec_challenge_append_left` apply,
464+
and typeclass resolution picks this one (declared later), routing challenges into the second copy.
465+
That mirrors VCV-io's `subSpec_add_left` / `subSpec_add_right` for `spec + spec`; pass the intended
466+
instance explicitly if the two components can coincide. -/
485467
instance subSpec_challenge_append_right :
486-
[pSpec₂.Challenge]ₒ ⊂ₒ [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ where
487-
monadLift q := ⟨challengeInrOnQuery (pSpec₁ := pSpec₁) q.input,
488-
q.cont ∘ challengeInrOnResponse (pSpec₁ := pSpec₁) q.input⟩
489-
onQuery := challengeInrOnQuery (pSpec₁ := pSpec₁)
490-
onResponse := challengeInrOnResponse (pSpec₁ := pSpec₁)
491-
492-
variable [∀ i, SampleableType (pSpec₁.Challenge i)] [∀ i, SampleableType (pSpec₂.Challenge i)]
468+
[pSpec₂.Challenge]ₒ ⊂ₒ [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ :=
469+
subSpecOfChallengeReindex ChallengeIdx.inr (challenge_append_inr (pSpec₁ := pSpec₁))
470+
471+
/-- The right inclusion is lawful, so lifting along it preserves the distribution and support of
472+
challenge queries. -/
473+
instance lawfulSubSpec_challenge_append_right :
474+
[pSpec₂.Challenge]ₒ ˡ⊂ₒ [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ :=
475+
lawfulSubSpecOfChallengeReindex ChallengeIdx.inr (challenge_append_inr (pSpec₁ := pSpec₁))
476+
477+
/-- The two inclusions occupy disjoint parts of the appended challenge interface: a left-injected
478+
round index is `< m` and a right-injected one is `≥ m`. This is what rules out the two components'
479+
challenge queries aliasing each other after composition. -/
480+
instance disjointSubSpec_challenge_append_left_right :
481+
OracleSpec.DisjointSubSpec
482+
[pSpec₁.Challenge]ₒ [pSpec₂.Challenge]ₒ [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ where
483+
disjoint_onQuery t₁ t₂ h := by
484+
have hv := congrArg
485+
(fun (d : [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ.Domain) => (d.1.1 : ℕ))
486+
(show challengeReindexQuery ChallengeIdx.inl t₁
487+
= challengeReindexQuery ChallengeIdx.inr t₂ from h)
488+
simp only [ChallengeIdx.inl, ChallengeIdx.inr, Fin.val_castAdd, Fin.val_natAdd] at hv
489+
have := t₁.1.1.isLt
490+
omega
491+
492+
/-- `disjointSubSpec_challenge_append_left_right` with the two components swapped, matching
493+
VCV-io's pairing of `disjointSubSpec_add_left_right` / `disjointSubSpec_add_right_left`. -/
494+
instance disjointSubSpec_challenge_append_right_left :
495+
OracleSpec.DisjointSubSpec
496+
[pSpec₂.Challenge]ₒ [pSpec₁.Challenge]ₒ [(pSpec₁ ++ₚ pSpec₂).Challenge]ₒ where
497+
disjoint_onQuery t₂ t₁ h :=
498+
(disjointSubSpec_challenge_append_left_right.disjoint_onQuery t₁ t₂ h.symm)
493499

494500
end Append
495501

ArkLib/OracleReduction/Security/CoordinateWiseSpecialSoundness/SeqCompose.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ theorem append_nodeOk_inl (S₁ : ChallengeTreeShape p₁) (S₂ : ChallengeTree
165165
(challenges : Fin ((S₁.append S₂).arity (ChallengeIdx.inl i₁)) →
166166
(p₁ ++ₚ p₂).Challenge (ChallengeIdx.inl i₁)) :
167167
(S₁.append S₂).nodeOk (ChallengeIdx.inl i₁) challenges
168-
= S₁.nodeOk i₁ (fun j => cast (by simp [ProtocolSpec.append, ChallengeIdx.inl])
168+
= S₁.nodeOk i₁ (fun j => cast (ProtocolSpec.challenge_append_inl i₁)
169169
(challenges (Fin.cast (by
170170
change S₁.arity i₁ = ChallengeTree.appendArity S₁.arity S₂.arity (ChallengeIdx.inl i₁)
171171
simp only [ChallengeTree.appendArity, Function.comp_apply,
@@ -186,7 +186,7 @@ theorem append_nodeOk_inr (S₁ : ChallengeTreeShape p₁) (S₂ : ChallengeTree
186186
(challenges : Fin ((S₁.append S₂).arity (ChallengeIdx.inr i₂)) →
187187
(p₁ ++ₚ p₂).Challenge (ChallengeIdx.inr i₂)) :
188188
(S₁.append S₂).nodeOk (ChallengeIdx.inr i₂) challenges
189-
= S₂.nodeOk i₂ (fun j => cast (by simp [ProtocolSpec.append, ChallengeIdx.inr])
189+
= S₂.nodeOk i₂ (fun j => cast (ProtocolSpec.challenge_append_inr i₂)
190190
(challenges (Fin.cast (by
191191
change S₂.arity i₂ = ChallengeTree.appendArity S₁.arity S₂.arity (ChallengeIdx.inr i₂)
192192
simp only [ChallengeTree.appendArity, Function.comp_apply,

0 commit comments

Comments
 (0)