StatementBinaryHarnessCore.lean
Declarations on this page (57)
- def bhReadProstVarint
- def bhParseOptPreKeyId
- def bhParseUint32Field
- def bhParseDjbKeyField
-
def bhParse
Inner Msg Field With Rest - def bhParseKyberCtField
- def bhHdrOfPreKeyMsg
- def bhDecodePreKeyMsgERaw
- def bhDecodePreKeyMsgE
- def bhIdKeyOf
- def mkBundleEStrict
- def bhExpectedPreKeyMsg
- inductive BHDRCtlQuery
- def bhDrCtlSpec
- abbrev cliGameSmSpec
- def bhBytesWord
- def bhKeySOf
- def bhDrvSessionInitPrin?
- def bhInitRegId
- structure ModelDriverInstanceMeta
- structure ModelDriverBase
- def bhDrvAcceptIkOk
- def bhDrvInitiateIkOk
- def bhDrvKiQueryOk
- def bhNoOpkRespPromoted
- def bhDrvInstClean
- def allTestedSessionsFresh
- def bhBaseMirror
- def bhDrvMirror
- def bhBaseOpkOk
- def bhDrvGuard
- def bhIsBobRecvSMQuery
- def bhIsBobAcceptSMResp
- def bhModelDrSpecWithSpqr
- def bhTargetGameSpec
- def bhCtlUnStart
- def e2eTargetGameCodec
- def bhQueryKind
- def bhKindCounts
- def keyIndReductionBudget
- structure BHModelInst
- structure BHE2EDrvState
- def bhModelSmDefault
- abbrev BHE2ESmResp
- def bhPackSmResp
- def bhUnpackSmResp
- def bhE2EMirror
- def bhE2EDrvInstClean
- def bhE2EDrvWon
- def bhE2EGuard
- def bhE2EInstIkACorr
- def bhE2EStepLocalSm
- def bhE2EFinishStartSpk
- def bhE2ECostBudget
- def bhE2ECostBudgetByKind
- def GRed2Bounded
- def stageOneAsProbComp
Core definitions of the composed CLI game: wire parsers, driver, KeyInd handler, cost budgets
Trusted: definitions used by the statement. Every declaration in this file lies in the closure of Computational/ (via Computational/, Computational/, and Computational/). See Computational/.
Non-ELF building blocks of the composed PQXDH + Double Ratchet security game played against the libsignal CLI binary (StatementPq.lean). The bh / BH ("binary harness") prefix denotes the game in which sessions carry wire-byte state (BinState) rather than abstract model state (GhostState Bytes). The adversary interacts with four oracle families: uniform coins, the PQXDH key-indistinguishability game KeyInd, the session-promotion control oracle startDR sid, and per-instance ACD19 secure-messaging (SM) oracles; the driver mirrors the public KeyInd transcript, guards promotion, and runs each promoted instance as an SM game.
This file also connects the composed game specifications (bhGameSpec, bhTargetGameSpec) to the ReductionDsl operation tables and codecs of AtomicOps.lean and TargetCodecs.lean, and defines the closed-form step-cost budgets bhAdapterCostBudget (for the wire-to-model adapter) and bhE2ECostBudget (for the multi-session composition reductions in StatementPqReds.lean), along with their online/offline per-kind counterparts bhAdapterCostBudgetByKind and bhE2ECostBudgetByKind.
@[expose] public sectionset_option autoImplicit falseopen OracleComp OracleSpec MeasureTheory ENNRealnamespace CryptoVerification.E2Eopen CryptoGames CryptoGames.LibsignalSM Ratchet LibsignalInst LibsignalSession
CryptoGames.LibsignalSessionApi HMAC HKDF CBC LibsignalAead SM SM.Construction CKA AEADScheme
MACScheme WireAEAD FSAEAD CryptoGames.Ratchet.LibsignalSim CryptoGames.FSAEAD.Thm5
CryptoVerification.LibsignalProtoCodec CryptoVerification.LibsignalSession
CryptoVerification.LibsignalSpqrV1open CryptoVerification.SignalSession (stdinCapBytes)
Canonical tag-3 PreKeySignalMessage wire parser (DEV-C18)
def bhReadProstVarint
Decode one LEB128 varint, returning (value, remainingBytes).
def bhReadProstVarint : List (BitVec 8) → Option (ℕ × List (BitVec 8))
| [] => none
| b :: rest =>
if b.toNat < 128 then some (b.toNat, rest)
else (bhReadProstVarint rest).map (fun (hi, rest') => (b.toNat - 128 + 128 * hi, rest'))def bhParseOptPreKeyId
Parse optional protobuf field 1 (preKeyId, tag 0x08#8).
def bhParseOptPreKeyId : List (BitVec 8) → Option (Option UInt32 × List (BitVec 8))
| [] => some (none, [])
| b :: rest =>
if b == 0x08#8 then (bhReadProstVarint rest).map (fun (n, r) => (some (UInt32.ofNat n), r))
else some (none, b :: rest)def bhParseUint32Field
Parse a required uint32 varint field with tag byte keyByte.
def bhParseUint32Field (keyByte : BitVec 8) : List (BitVec 8) → Option (UInt32 × List (BitVec 8))
| k :: rest =>
if k == keyByte then (bhReadProstVarint rest).map (fun (n, r) => (UInt32.ofNat n, r)) else none
| [] => nonedef bhParseDjbKeyField
Parse a 33-byte DJB Curve25519 public-key field (type byte 0x05#8) with tag keyByte.
def bhParseDjbKeyField (keyByte : BitVec 8) :
List (BitVec 8) → Option (List (BitVec 8) × List (BitVec 8))
| k :: 0x21#8 :: 0x05#8 :: rest =>
if k == keyByte && decide (32 ≤ rest.length) then some (rest.take 32, rest.drop 32) else none
| _ => none
def bhParse
Parse length-delimited field 4 (message, tag 0x22#8), returning (inner, rest).
def bhParseInnerMsgFieldWithRest : List (BitVec 8) → Option (List (BitVec 8) × List (BitVec 8))
| 0x22#8 :: rest =>
(bhReadProstVarint rest).bind (fun (len, afterLen) =>
if decide (len ≤ afterLen.length) then some (afterLen.take len, afterLen.drop len) else none)
| _ => nonedef bhParseKyberCtField
Parse field 8 (kyberCiphertext, tag 0x42#8, length 1569, type byte 0x08#8).
def bhParseKyberCtField : List (BitVec 8) → Option (List (BitVec 8))
| 0x42#8 :: 0xa1#8 :: 0x0c#8 :: 0x08#8 :: rest =>
if decide (1568 ≤ rest.length) then some (rest.take 1568) else none
| _ => nonedef bhHdrOfPreKeyMsg
Extract the PQXDH HandshakeHdr from PreKeyMessageFields.
def bhDecodePreKeyMsgERaw
Parse a tag-3 PreKeySignalMessage wire buffer without checking canonical re-encoding.
def bhDecodePreKeyMsgERaw : Bytes → Option PreKeyMessageFields
| t :: v :: proto =>
if t == wirePrekey && v == versionByte currentVersion then
(bhParseOptPreKeyId proto).bind fun (opkId?, after1) =>
(bhParseDjbKeyField 0x12#8 after1).bind fun (bkBytes, after2) =>
(bhParseDjbKeyField 0x1a#8 after2).bind fun (idBytes, after3) =>
(bhParseInnerMsgFieldWithRest after3).bind fun (inner, after4) =>
(bhParseUint32Field 0x28#8 after4).bind fun (regId, after5) =>
(bhParseUint32Field 0x30#8 after5).bind fun (spkId, after6) =>
(bhParseUint32Field 0x38#8 after6).bind fun (pqspkId, after7) =>
(bhParseKyberCtField after7).bind fun kemCtBytes =>
if hbk : bkBytes.length = 32 then if hid : idBytes.length = 32 then
if hct : kemCtBytes.length = 1568 then
some { regId := regId.toBitVec, opkId := opkId?.map UInt32.toBitVec,
spkId := spkId.toBitVec, kpkId := pqspkId.toBitVec, kyberCt := ⟨kemCtBytes, hct⟩,
baseKey := ⟨bkBytes, hbk⟩, idPub := ⟨idBytes, hid⟩, inner := inner }
else none
else none else none
else none
| _ => nonedef bhDecodePreKeyMsgE
Parse a canonical tag-3 PreKeySignalMessage wire buffer (wirePrekey :: m.
def bhDecodePreKeyMsgE (c : Bytes) : Option PreKeyMessageFields :=
match bhDecodePreKeyMsgERaw c with
| some m => if c == wirePrekey :: m.bytes then some m else none | none => nonedef bhIdKeyOf
Wrap a 32-byte PQXDH public key as a session IdentityKey.
def bhIdKeyOf (bs : SignalPqxdh.Bytes) : IdentityKey := ⟨⟨u8Vec 32 bs⟩⟩6 declarations not shown (lines 136–169): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
def mkBundleEStrict
Build PreKeyBundleFields for registration ID u, failing on any key- or signature-length mismatch.
def mkBundleEStrict (u : ℕ) (bnd : SignalPqxdh.PreKeyBundleInput) : Option PreKeyBundleFields := do
let spkPub ← fitU8VecStrict 32 bnd.spkB
let spkSig ← fitU8VecStrict 64 bnd.spkSig
let kpkPub ← fitU8VecStrict 1568 bnd.pqspkB
let kpkSig ← fitU8VecStrict 64 bnd.pqspkSig
let idPub ← fitU8VecStrict 32 bnd.ikB
let opkV ← match bnd.opkB? with
| none => some none
| some (id, pk) => do let v ← fitU8VecStrict 32 pk; some (some (BitVec.ofNat 32 id.toNat, v))
some { regId := BitVec.ofNat 32 u, opk := opkV, spkId := BitVec.ofNat 32 bnd.spkId.toNat,
spkPub := spkPub, spkSig := spkSig, kpkId := BitVec.ofNat 32 bnd.pqspkId.toNat,
kpkPub := kpkPub, kpkSig := kpkSig, idPub := idPub }Tag-3 routing, in-transit alignment, and injection filter
(DEV-C11, DEV-C18, DEV-C19)
def bhExpectedPreKeyMsg
Canonical PreKeyMessageFields expected from initiator regIdA with header hdr and payload inner (DEV-C11).
def bhExpectedPreKeyMsg (regIdA : BitVec 32) (hdr : SignalPqxdh.HandshakeHdr) (inner : Bytes) :
PreKeyMessageFields :=
{ regId := regIdA, opkId := hdr.opkId?.map (fun id => BitVec.ofNat 32 id.toNat),
spkId := BitVec.ofNat 32 hdr.spkId.toNat, kpkId := BitVec.ofNat 32 hdr.pqspkId.toNat,
kyberCt := u8Vec SignalPqxdh.kyberCtLen hdr.kemCt, baseKey := u8Vec 32 hdr.ekAPk,
idPub := u8Vec 32 hdr.ikAPk, inner := inner }7 declarations not shown (lines 198–271): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
Oracle signatures, driver state, and KeyInd handler
inductive BHDRCtlQuery
Session-promotion control query startDR sid (DEV-C9).
inductive BHDRCtlQuery (S : SignalPqxdh.Setup) where
| startDR (sid : SignalPqxdh.SessionId S)
deriving DecidableEq, Reprdef bhDrCtlSpec
Control oracle returning the new instance index on promotion, or none if refused.
@[reducible] def bhDrCtlSpec (S : SignalPqxdh.Setup) : OracleSpec (BHDRCtlQuery S) :=
fun _ => Option ℕabbrev cliGameSmSpec
5 declarations not shown (lines 286–306): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
def bhBytesWord
def bhBytesWord (bs : SignalPqxdh.Bytes) : BitVec 256 := HKDF.bytesToKey32 (ofU8 bs)def bhKeySOf
Pack a 96-byte PQXDH DRKey and Alice's 32-byte ratchet seed into a SampledSessionKey.
def bhKeySOf (dk : SignalPqxdh.DRKey) (aliceCoins : BitVec 256) : SampledSessionKey :=
((bhBytesWord dk.rootKey, bhBytesWord dk.chainKey), (aliceCoins, bhBytesWord dk.pqrKey))def bhDrvSessionInitPrin?
def bhDrvSessionInitPrin? {S : SignalPqxdh.Setup} : SignalPqxdh.SessionId S → Option (Fin S.N_id)
| .sendOPK u _ | .sendNoOPK u _ => some u
| _ => nonedef bhInitRegId
Registration ID BitVec. 32 u.0#32 for responder IDs).
def bhInitRegId {S : SignalPqxdh.Setup} (sid : SignalPqxdh.SessionId S) : BitVec 32 :=
match bhDrvSessionInitPrin? sid with | some u => BitVec.ofNat 32 u.val | none => 0#32structure ModelDriverInstanceMeta
Per-instance freshness and responder-slot metadata.
DEV-C17 (formalization): initiator-identity freshness at Bob's first accept, and no adversary revealSpkSk. In KeyInd, initiator freshness (isKeyIndInitStillFresh) inspects only the responder's keys, whereas Bob's handshake binds ikA only via DH(spkB_sk, ikA). Corrupting ikA or revealing spkB_sk before Bob's first accept would let an adversary forge a pre-key message under ikA while Alice's session remains KeyInd-fresh (DEV-PQ24). Each promoted instance records whether Bob has accepted (bobAccepted), whether ikA was corrupted at that first accept (ikACorrAtAccept), and its responder slot (respPrin, respSpkIdx), while revealedSpks logs adversarial revealSpkSk queries (excluding the driver's own reveal at startDR, DEV-C10). Once bhDrvInstClean (or bhE2EDrvInstClean) fails for instance i, SM queries on i return none without affecting other instances. Effect: restricts the adversary.
structure ModelDriverInstanceMeta (S : SignalPqxdh.Setup) where
bobAccepted : Bool := false
ikACorrAtAccept : Bool := false
respPrin : Option (Fin S.N_id) := none
respSpkIdx : Option (Fin S.N_recv) := none
instOpkId? : Option UInt32 := none
deriving DecidableEq, Repr1 declaration not shown (lines 342–353): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
structure ModelDriverBase
Mirrored public KeyInd transcript (prins, bundles, opks, sends).
structure ModelDriverBase (S : SignalPqxdh.Setup) where
prins : List (Fin S.N_id × SignalPqxdh.Bytes) := []
bundles : List ((Fin S.N_id × Fin S.N_recv) × (SignalPqxdh.Bytes × SignalPqxdh.Bytes)) := []
opks : List ((Fin S.N_id × Fin S.N_recv × Fin S.N_opk) × SignalPqxdh.Bytes) := []
sends : List (SignalPqxdh.SessionId S × SignalPqxdh.PreKeyBundleInput ×
SignalPqxdh.HandshakeHdr) := []1 declaration not shown (lines 363–371): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
def bhDrvAcceptIkOk
Byte-exact ikA pin on responder accepts.
DEV-C15 (formalization): byte-exact identity pins on KeyInd handshake queries, and no revealDhSecrets. KeyInd matches identity keys modulo the cofactor projection P., so an 8-torsion twin ikA + T of a mirrored honest principal's key passes acceptOPK / acceptNoOPK and consumes the responder's pre-key slot and Kyber replay tuple. The driver forwards a responder accept only if hdr. is byte-equal to a mirrored principal key (bhDrvAcceptIkOk), and an initiateOPK / initiateNoOPK only if no mirrored principal key is a distinct pow8-twin of bnd. (bhDrvInitiateIkOk); the signed pre-key is checked byte-for-byte at startDR (DEV-C9). bhDrvKiQueryOk also refuses revealDhSecrets (DEV-PQ19, DEV-PQ23) and testSession (DEV-C12). Effect: restricts the adversary.
def bhDrvAcceptIkOk {S : SignalPqxdh.Setup} (ds : ModelDriverBase S)
(hdr : SignalPqxdh.HandshakeHdr) : Bool :=
ds.prins.any (fun e => e.2 == hdr.ikAPk)def bhDrvInitiateIkOk
Reject bundles whose ikB is a distinct pow8-twin of a mirrored honest principal key (DEV-C19).
def bhDrvInitiateIkOk {S : SignalPqxdh.Setup} (P : SignalPqxdh.Primitives) (ds : ModelDriverBase S)
(bnd : SignalPqxdh.PreKeyBundleInput) : Bool :=
!ds.prins.any (fun (_, pk) => P.pow8 pk == P.pow8 bnd.ikB && pk != bnd.ikB)def bhDrvKiQueryOk
Admissibility filter on KeyIndQuery in the composed game (DEV-C12, DEV-C15, DEV-C19).
def bhDrvKiQueryOk {S : SignalPqxdh.Setup} (P : SignalPqxdh.Primitives) (ds : ModelDriverBase S) :
SignalPqxdh.KeyIndQuery S → Bool
| .testSession _ | .revealDhSecrets => false
| .initiateOPK _ _ bnd => bnd.opkB?.isSome && bhDrvInitiateIkOk P ds bnd
| .initiateNoOPK _ _ bnd => bnd.opkB?.isNone && bhDrvInitiateIkOk P ds bnd
| .acceptOPK _ _ _ hdr | .acceptNoOPK _ _ _ hdr => bhDrvAcceptIkOk ds hdr
| _ => truedef bhNoOpkRespPromoted
Whether u is the responder of a promoted no-OPK instance (respPrin = some u and instOpkId? = none). Because startDR reveals the responder's signed pre-key (DEV-C10), a subsequent corruptIK u on a no-OPK session would violate isKeyIndInitStillFresh and void the entire run via allTestedSessionsFresh; the composed drivers therefore refuse corruptIK u once this holds, complementing the !corrPrins. check in bhDrvGuard.
def bhNoOpkRespPromoted {S : SignalPqxdh.Setup} (instMeta : List (ModelDriverInstanceMeta S))
(u : Fin S.N_id) : Bool :=
instMeta.any (fun im => im.respPrin == some u && im.instOpkId?.isNone)1 declaration not shown (lines 412–417): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification.
def bhDrvInstClean
Per-instance cleanliness check on ModelDriverState (DEV-C17): requires an initiator principal, uncorrupted ikA at Bob's first accept, and no adversarial revealSpkSk on bi..
def bhDrvInstClean {S : SignalPqxdh.Setup} {ℓ : ℕ} (ds : ModelDriverState S ℓ) (i : ℕ) : Bool :=
match ds.bhInsts[i]?, ds.instMeta[i]? with
| some bi, some im =>
(bhDrvSessionInitPrin? bi.sid).isSome && !im.ikACorrAtAccept &&
!ds.revealedSpks.contains bi.respSlot
| _, _ => truedef allTestedSessionsFresh
End-of-game freshness predicate requiring every tested initiator and responder session in st to remain fresh (DEV-C10).
def allTestedSessionsFresh {S : SignalPqxdh.Setup} (P : SignalPqxdh.Primitives)
(st : SignalPqxdh.KeyIndState S) : Bool :=
st.sessions.all (fun s =>
!s.tested ||
SignalPqxdh.isKeyIndInitStillFresh P true st.principals st.spkBundles st.respSessions s) &&
st.respSessions.all (fun r =>
!r.tested ||
SignalPqxdh.isKeyIndRespStillFresh P true st.principals st.spkBundles st.sessions r)1 declaration not shown (lines 440–442): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification.
def bhBaseMirror
Record public keys, bundles, OPKs, and initiator headers from a KeyInd step in ModelDriverBase.
def bhBaseMirror {S : SignalPqxdh.Setup} (ds : ModelDriverBase S)
(q : SignalPqxdh.KeyIndQuery S) (r : SignalPqxdh.KeyIndResponse) : ModelDriverBase S :=
match q, r with
| .initPrin u, .initPrinResp ikPk _ => { ds with prins := (u, ikPk) :: ds.prins }
| .genSPK u j, .bundleResp bnd =>
{ ds with bundles := ((u, j), (bnd.spkB, bnd.pqspkB)) :: ds.bundles }
| .genOPK u j o, .pkResp opkPk =>
{ ds with opks := ((u, j, o), opkPk) :: ds.opks }
| .initiateOPK u i bnd, .hdrResp hdr =>
{ ds with sends := (.sendOPK u i, bnd, hdr) :: ds.sends }
| .initiateNoOPK u i bnd, .hdrResp hdr =>
{ ds with sends := (.sendNoOPK u i, bnd, hdr) :: ds.sends }
| _, _ => dsdef bhDrvMirror
Extend bhBaseMirror on ModelDriverState with corrupted principals and adversarially revealed SPK slots.
def bhDrvMirror {S : SignalPqxdh.Setup} {ℓ : ℕ} (ds : ModelDriverState S ℓ)
(q : SignalPqxdh.KeyIndQuery S) (r : SignalPqxdh.KeyIndResponse) : ModelDriverState S ℓ :=
let dsBase := bhBaseMirror ds.toModelDriverBase q r
let corrPrins' :=
match q, r with | .corruptIK u, .corrResp _ _ => u :: ds.corrPrins | _, _ => ds.corrPrins
let revSpks' := match q, r with
| .revealSpkSk u j, .spkSkResp _ => (u, j) :: ds.revealedSpks
| _, _ => ds.revealedSpks
{ ds with toModelDriverBase := dsBase, corrPrins := corrPrins', revealedSpks := revSpks' }def bhBaseOpkOk
Whether bnd. matches a mirrored OPK of responder slot (u, j) (DEV-C20).
def bhBaseOpkOk {S : SignalPqxdh.Setup} (ds : ModelDriverBase S)
(u : Fin S.N_id) (j : Fin S.N_recv)
(bnd : SignalPqxdh.PreKeyBundleInput) : Bool :=
match bnd.opkB? with
| none => true
| some (opkId, opkPk) =>
ds.opks.any (fun ((u', j', o), pk) =>
u' == u && j' == j && UInt32.ofNat o.val == opkId &&
o.val == opkId.toNat && pk == opkPk)def bhDrvGuard
Session-promotion guard for startDR sid (DEV-C9, DEV-C10, DEV-C20): checks uniqueness of sid and ekAPk, matches the bundle and optional OPK against mirrored honest keys of some slot (u, j), and for no-OPK handshakes requires !ds..
def bhDrvGuard {S : SignalPqxdh.Setup} {ℓ : ℕ} (ds : ModelDriverState S ℓ)
(sid : SignalPqxdh.SessionId S) :
Option (SignalPqxdh.HandshakeHdr × SignalPqxdh.PreKeyBundleInput ×
(Fin S.N_id × Fin S.N_recv)) :=
if ds.bhInsts.any (fun bi => bi.sid == sid) then none
else
match ds.sends.find? (fun (s, _, _) => s == sid) with
| none => none
| some (_, bnd, hdr) =>
if ds.bhInsts.any (fun bi => bi.hdr.ekAPk == hdr.ekAPk) then none
else
match ds.bundles.find? (fun ((_, j), (spkB, pqspkB)) =>
spkB == bnd.spkB && pqspkB == bnd.pqspkB &&
hdr.spkId == UInt32.ofNat j.val &&
hdr.pqspkId == UInt32.ofNat j.val) with
| none => none
| some ((u, j), _) =>
if ds.prins.any (fun (u', pk) => u' == u && pk == bnd.ikB) &&
bhBaseOpkOk ds.toModelDriverBase u j bnd &&
(bnd.opkB?.isSome || !ds.corrPrins.contains u) then
some (hdr, bnd, (u, j))
else none4 declarations not shown (lines 509–620): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
def bhIsBobRecvSMQuery
Whether an SM query is deliver-B or inject-B.
def bhIsBobRecvSMQuery {ℓ : ℕ} {PQC : Type} : (cliGameSmSpec ℓ PQC).Domain → Bool
| .inl (.inl (.inr (.B, _))) | .inl (.inr (.B, _)) => true
| _ => falsedef bhIsBobAcceptSMResp
Whether a deliver-B or inject-B query returned some (.accepted ..).
2 declarations not shown (lines 634–646): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification.
def bhModelDrSpecWithSpqr
@[reducible] def bhModelDrSpecWithSpqr (ℓ : ℕ) (PQC : Type) :
OracleSpec (ℕ × (SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes).Domain) :=
fun x => (SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes).Range x.2def bhTargetGameSpec
Non-coin game oracle interface over model party state GhostState Bytes.
@[reducible] def bhTargetGameSpec (S : SignalPqxdh.Setup) (ℓ : ℕ) (PQC : Type) :=
SignalPqxdh.keyIndSpec S + bhDrCtlSpec S + bhModelDrSpecWithSpqr ℓ PQCdef bhCtlUnStart
def bhCtlUnStart {S : SignalPqxdh.Setup} : BHDRCtlQuery S → SignalPqxdh.SessionId S
| .startDR sid => siddef e2eTargetGameCodec
@[reducible] def e2eTargetGameCodec (S : SignalPqxdh.Setup) (ℓ : ℕ) (Pt : Type := Empty) :
ReductionDsl.GameCodec (ReductionDsl.atomicSig ℓ Pt) (bhTargetGameSpec S ℓ (BitVec 512)) :=
ReductionDsl.e2eGameCodec S ℓ Pt bhCtlUnStart3 declarations not shown (lines 663–676): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification.
def bhQueryKind
Query-kind classifier shared by bhGameSpec and bhTargetGameSpec: offline KeyInd random-oracle queries (.roQuery) have kind 1, and all online queries (non-RO KeyInd, startDR, and per-instance SM) have kind 0.
def bhQueryKind {S : SignalPqxdh.Setup} {ι : Type} :
(SignalPqxdh.KeyIndQuery S ⊕ BHDRCtlQuery S) ⊕ ι → Fin 2
| .inl (.inl (.roQuery _)) => 1
| _ => 0def bhKindCounts
Per-kind query budgets under bhQueryKind: kind 0 bounds online queries (min b. non-RO KeyInd queries plus qStart promotions and qDR SM queries), and kind 1 bounds offline .roQuery calls (min b.).
def bhKindCounts (b : SignalPqxdh.QueryBudget) (qStart qDR : ℕ) : Fin 2 → ℕ
| 0 => min b.q_on b.q_total + qStart + qDR
| 1 => min b.q_ro b.q_total8 declarations not shown (lines 693–797): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
def keyIndReductionBudget
KeyInd query budget of the composed reduction, adding up to 2 * min qStart S. queries for the testSession and revealSpkSk issued by each startDR (DEV-C9).
def keyIndReductionBudget (S : SignalPqxdh.Setup) (qStart : ℕ) (b : SignalPqxdh.QueryBudget) :
SignalPqxdh.QueryBudget :=
{ b with
q_total := b.q_total + 2 * min qStart S.N_send_tot
q_on := b.q_on + 2 * min qStart S.N_send_tot }1 declaration not shown (lines 807–821): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification.
structure BHModelInst
Promoted Double Ratchet instance over GhostState Bytes in BHE2EDrvState.
structure BHModelInst (S : SignalPqxdh.Setup) (ℓ : ℕ) where
sid : SignalPqxdh.SessionId S
hdr : SignalPqxdh.HandshakeHdr
idA : IdentityKey
idB : IdentityKey
spkCoins : BitVec 256
gs : Option (SM.GameState (GhostState Bytes) (Plain ℓ) Bytes)structure BHE2EDrvState
State of the model-state E2E driver and composition reductions.
structure BHE2EDrvState (S : SignalPqxdh.Setup) (ℓ : ℕ) extends ModelDriverBase S where
corrPrins : List (Fin S.N_id) := []
revealedSpks : List (Fin S.N_id × Fin S.N_recv) := []
insts : List (BHModelInst S ℓ) := []
instMeta : List (ModelDriverInstanceMeta S) := []
st : SignalPqxdh.KeyIndState S := SignalPqxdh.KeyIndState.empty S false
b : Bool := false
paused : Bool := falsedef bhModelSmDefault
def bhModelSmDefault {ℓ : ℕ} {PQC : Type} :
(x : (SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes).Domain) →
(SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes).Range x
| .inl (.inl (.inl (.inl _))) | .inl (.inl (.inl (.inr _))) | .inl (.inl (.inr _))
| .inl (.inr _) | .inr _ => noneabbrev BHE2ESmResp
Sum-flattened response of a single-instance GhostState Bytes SM query.
abbrev BHE2ESmResp (ℓ : ℕ) :=
Option Bytes × Option (SM.RecvResult (Plain ℓ)) × Option (GhostState Bytes)def bhPackSmResp
def bhPackSmResp {ℓ : ℕ} {PQC : Type} :
(q : (SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes).Domain) →
(SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes).Range q → BHE2ESmResp ℓ
| .inl (.inl (.inl (.inl _))), r => (r, none, none)
| .inl (.inl (.inl (.inr _))), r => (r, none, none)
| .inl (.inl (.inr _)), r => (none, r, none)
| .inl (.inr _), r => (none, r, none)
| .inr _, r => (none, none, r)def bhUnpackSmResp
def bhUnpackSmResp {ℓ : ℕ} {PQC : Type} :
(q : (SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes).Domain) →
BHE2ESmResp ℓ → (SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes).Range q
| .inl (.inl (.inl (.inl _))), (r, _, _) => r
| .inl (.inl (.inl (.inr _))), (r, _, _) => r
| .inl (.inl (.inr _)), (_, r, _) => r
| .inl (.inr _), (_, r, _) => r
| .inr _, (_, _, r) => rdef bhE2EMirror
Mirror a KeyInd query/response pair into BHE2EDrvState.
def bhE2EMirror {S : SignalPqxdh.Setup} {ℓ : ℕ} (σ : BHE2EDrvState S ℓ)
(q : SignalPqxdh.KeyIndQuery S) (r : SignalPqxdh.KeyIndResponse) : BHE2EDrvState S ℓ :=
let base' := bhBaseMirror σ.toModelDriverBase q r
let corr' :=
match q, r with | .corruptIK u, .corrResp _ _ => u :: σ.corrPrins | _, _ => σ.corrPrins
let revSpks' := match q, r with
| .revealSpkSk u j, .spkSkResp _ => (u, j) :: σ.revealedSpks
| _, _ => σ.revealedSpks
{ σ with toModelDriverBase := base', corrPrins := corr', revealedSpks := revSpks' }def bhE2EDrvInstClean
Per-instance cleanliness check on BHE2EDrvState (DEV-C17).
def bhE2EDrvInstClean {S : SignalPqxdh.Setup} {ℓ : ℕ} (σ : BHE2EDrvState S ℓ) (i : ℕ) : Bool :=
match σ.insts[i]?, σ.instMeta[i]? with
| some d, some im =>
(bhDrvSessionInitPrin? d.sid).isSome && !im.ikACorrAtAccept &&
(match im.respPrin, im.respSpkIdx with
| some u, some j => !σ.revealedSpks.contains (u, j)
| _, _ => false)
| _, _ => truedef bhE2EDrvWon
Whether some promoted instance in σ with challenge bit b has gs..
def bhE2EDrvWon {S : SignalPqxdh.Setup} {ℓ : ℕ} (σ : BHE2EDrvState S ℓ) (b : Bool) : Bool :=
σ.insts.any (fun d => match d.gs with | some gs => gs.b == b && gs.win | none => false)def bhE2EGuard
Session-promotion guard on BHE2EDrvState (DEV-C9, DEV-C10, DEV-C20), mirroring bhDrvGuard.
def bhE2EGuard {S : SignalPqxdh.Setup} {ℓ : ℕ} (σ : BHE2EDrvState S ℓ)
(sid : SignalPqxdh.SessionId S) :
Option (SignalPqxdh.HandshakeHdr × SignalPqxdh.PreKeyBundleInput ×
(Fin S.N_id × Fin S.N_recv)) :=
if σ.insts.any (fun d => d.sid == sid) then none
else
match σ.sends.find? (fun (s, _, _) => s == sid) with
| none => none
| some (_, bnd, hdr) =>
if σ.insts.any (fun d => d.hdr.ekAPk == hdr.ekAPk) then none
else
match σ.bundles.find? (fun ((_, j), (spkB, pqspkB)) =>
spkB == bnd.spkB && pqspkB == bnd.pqspkB &&
hdr.spkId == UInt32.ofNat j.val &&
hdr.pqspkId == UInt32.ofNat j.val) with
| none => none
| some ((u, j), _) =>
if σ.prins.any (fun (u', pk) => u' == u && pk == bnd.ikB) &&
bhBaseOpkOk σ.toModelDriverBase u j bnd &&
(bnd.opkB?.isSome || !σ.corrPrins.contains u) then
some (hdr, bnd, (u, j))
else nonedef bhE2EInstIkACorr
Whether the initiator identity key of d belongs to a corrupted principal in σ.
def bhE2EInstIkACorr {S : SignalPqxdh.Setup} {ℓ : ℕ} (σ : BHE2EDrvState S ℓ)
(d : BHModelInst S ℓ) : Bool :=
(match bhDrvSessionInitPrin? d.sid with | some u => σ.corrPrins.contains u | none => true) ||
σ.prins.any (fun (u, pk) => pk == d.hdr.ikAPk && σ.corrPrins.contains u)def bhE2EStepLocalSm
Execute one model-state SM query on promoted instance i of BHE2EDrvState (DEV-C11, DEV-C19).
def bhE2EStepLocalSm {S : SignalPqxdh.Setup} {ℓ : ℕ} {Pt : Type}
(C : Crypto) (K : LibsignalSpqrV1.MlKem) (qep : ℕ)
(decT : PublicKey → Option Pt) (encT : Pt → PublicKey)
(σ : BHE2EDrvState S ℓ) (i : ℕ)
(q : (SM.spec (GhostState Bytes) (Plain ℓ) (BitVec 512) Bytes).Domain) :
ProbComp (BHE2ESmResp ℓ × BHE2EDrvState S ℓ) := do
if !bhE2EDrvInstClean σ i then return ((none, none, none), σ)
else
match σ.insts[i]?, σ.instMeta[i]? with
| some d, some im =>
if !im.bobAccepted && bhIsBobRecvSMQuery q && bhE2EInstIkACorr σ d then
let imCorr := { im with bobAccepted := true, ikACorrAtAccept := true }
let σ' := { σ with instMeta := σ.instMeta.set i imCorr }
return ((none, none, none), σ')
else
match d.gs with
| none => return ((none, none, none), σ)
| some gs =>
let spqr := LibsignalSpqrV1.spqrV1SessionUniformCoins C.F K
let sch := sessionSchemeFixedSpk (paramsIdsWithSpqr C spqr d.idA d.idB) ℓ d.spkCoins
let cfg := configRefuseEarlyBobCorr (St := GhostState Bytes) (Msg := Plain ℓ) decT encT
let runStd (q' : (SM.spec (GhostState Bytes) (Plain ℓ) (BitVec 512) Bytes).Domain)
(σ0 : BHE2EDrvState S ℓ) : ProbComp (BHE2ESmResp ℓ × BHE2EDrvState S ℓ) := do
let (r, gs') ← (SM.oracleImplR sch (2 + 1) qep cfg q').run gs
let accepted := bhIsBobAcceptSMResp q' r
let im' := if !im.bobAccepted && accepted then
{ im with bobAccepted := true, ikACorrAtAccept := bhE2EInstIkACorr σ0 d }
else im
let σ1 := { σ0 with insts := σ0.insts.set i { d with gs := some gs' },
instMeta := σ0.instMeta.set i im' }
return (bhPackSmResp q' r, σ1)
match q with
| .inl (.inl (.inr (.A, c))) =>
if (bhDecodePreKeyMsgE c).isSome then return ((none, none, none), σ)
else runStd q σ
| .inl (.inr (.A, c)) =>
if (bhDecodePreKeyMsgE c).isSome then return ((none, none, none), σ)
else runStd q σ
| .inl (.inl (.inr (.B, c))) =>
if !im.bobAccepted then
match bhDecodePreKeyMsgE c with
| some m =>
if c == wirePrekey ::
(bhExpectedPreKeyMsg (bhInitRegId d.sid) d.hdr m.inner).bytes then
runStd (.inl (.inl (.inr (.B, m.inner)))) σ
else return ((none, none, none), σ)
| none => return ((none, none, none), σ)
else
match bhDecodePreKeyMsgE c with
| some m =>
if bhHdrOfPreKeyMsg m == d.hdr then
runStd (.inl (.inl (.inr (.B, m.inner)))) σ
else return ((none, none, none), σ)
| none => runStd (.inl (.inl (.inr (.B, c)))) σ
| .inl (.inr (.B, c)) =>
if !im.bobAccepted then return ((none, none, none), σ)
else
match bhDecodePreKeyMsgE c with
| some m =>
if bhHdrOfPreKeyMsg m == d.hdr && shortCiphertext c &&
decide (m.inner.length + 2048 < 2 ^ 32) then
runStd (.inl (.inr (.B, m.inner))) σ
else return ((none, none, none), σ)
| none => runStd (.inl (.inr (.B, c))) σ
| _ => runStd q σ
| _, _ => return ((none, none, none), σ)def bhE2EFinishStartSpk
Complete startDR sid on BHE2EDrvState from KeyInd responses (kr1, kr2) and Alice's ratchet seed aliceCoins.
def bhE2EFinishStartSpk {S : SignalPqxdh.Setup} {ℓ : ℕ} (C : Crypto) (K : LibsignalSpqrV1.MlKem)
(bMode? : Option Bool) (σ : BHE2EDrvState S ℓ) (sid : SignalPqxdh.SessionId S)
(kr1 kr2 : SignalPqxdh.KeyIndResponse) (aliceCoins : BitVec 256) :
Option ℕ × BHE2EDrvState S ℓ :=
match bhE2EGuard σ sid, kr1, kr2 with
| some (hdr, bnd, (u, j)), .drKeyResp dk, .spkSkResp spkSk =>
if dk.rootKey.length == 32 && dk.chainKey.length == 32 &&
dk.pqrKey.length == 32 && spkSk.length == 32 then
let idA := bhIdKeyOf hdr.ikAPk; let idB := bhIdKeyOf bnd.ikB
let spkCoins := bhBytesWord spkSk
let kS : SampledSessionKey := bhKeySOf dk aliceCoins
let spqr := LibsignalSpqrV1.spqrV1SessionUniformCoins C.F K
let sch := sessionSchemeFixedSpk (paramsIdsWithSpqr C spqr idA idB) ℓ spkCoins
let gs := bMode?.map (fun b => SM.initState sch.sendPart kS b)
let inst : BHModelInst S ℓ := { sid, hdr, idA, idB, spkCoins, gs }
let im : ModelDriverInstanceMeta S :=
{ respPrin := some u, respSpkIdx := some j, instOpkId? := hdr.opkId? }
(some σ.insts.length,
{ σ with insts := σ.insts ++ [inst], instMeta := σ.instMeta ++ [im] })
else (none, σ)
| _, _, _ => (none, σ)def bhE2ECostBudget
Step-cost budget for the model-state composition reductions, bounding per-query dispatch and clamped table scans (min b., min qStart nSend) together with the post-game allTestedSessionsFresh pass.
def bhE2ECostBudget (S : SignalPqxdh.Setup) (b : SignalPqxdh.QueryBudget)
(qStart qDR qInst qep ℓ : ℕ) : ℕ :=
let nId := min b.q_total S.N_id
let nSpk := min b.q_total S.N_spk_tot
let nOpk := min b.q_total S.N_opk_tot
let nSend := min b.q_total S.N_send_tot
let nRecv := min b.q_total S.N_recv_tot
let nInst := min qStart nSend
2 ^ 10 * (b.q_total + qStart + qDR + 1) *
(b.q_total + nOpk + nInst + qInst + Nat.log 2 qep +
10 * (nId + nSpk + nSend + nRecv) + maxForwardJumps + ℓ + 1) +
2 ^ 13 * nInst * (nId + nSpk + nRecv + 1)def bhE2ECostBudgetByKind
Per-kind step-cost budget for the model-state composition reductions under bhQueryKind: clamps session and key table scans at the online count M_on = min b. (Q_on = M_on + qStart + qDR), adds logarithmic random-oracle table lookup/insert cost across at most M_ro + 2 * Q_on + 1 entries (M_ro = min b.), and retains the post-game allTestedSessionsFresh pass. The table term can pay for a balanced lookup-tree walk of O(bits K) steps per offline query, K = M_ro + 2 * Q_on + 1; the current E2E proofs forward each .roQuery to the target game in O(bits M_on) steps, which roStep_mul_le_roTableTerm (Proofs/) bounds by the table term.
def bhE2ECostBudgetByKind (S : SignalPqxdh.Setup) (b : SignalPqxdh.QueryBudget)
(qStart qDR qInst qep ℓ : ℕ) : ℕ :=
let M_on := min b.q_on b.q_total
let M_ro := min b.q_ro b.q_total
let Q_on := M_on + qStart + qDR
let nId := min M_on S.N_id
let nSpk := min M_on S.N_spk_tot
let nOpk := min M_on S.N_opk_tot
let nSend := min M_on S.N_send_tot
let nRecv := min M_on S.N_recv_tot
let nInst := min qStart nSend
2 ^ 10 * (Q_on + 1) *
(M_on + nOpk + nInst + qInst + Nat.log 2 qep +
10 * (nId + nSpk + nSend + nRecv) + maxForwardJumps + ℓ + 1) +
2 ^ 13 * (M_ro + Q_on) * (ReductionDsl.bits (M_ro + 2 * Q_on + 1) + 4) +
2 ^ 13 * nInst * (nId + nSpk + nRecv + 1)def GRed2Bounded
Step-cost, program-size, query-count, size-honesty, and run-local block-width bound on a ReductionDsl.: whenever every adversary game query and every target oracle answer on a run fits width B, the stage-1 checkpoint and stage-2 output fit 2 * (costBudget + q + 1) * (B + 1).
def GRed2Bounded {S : ReductionDsl.Sig}
{O : ReductionDsl.Ops S} {ι κ : Type}
{T : OracleSpec ι} {I : ReductionDsl.Iface S T} {G : OracleSpec κ}
{C : ReductionDsl.GameCodec S G} {rIn aIn aOut ch rIn2 out : ReductionDsl.Ty S}
(R : ReductionDsl.TwoStageCostedReduction O I G C rIn aIn aOut ch rIn2 out)
(wTag : I.Tag → ℕ) (wSamp : I.smp.Samp → ℕ)
(q qOut costBudget : ℕ) (dflt : aOut.denote) : Prop :=
R.totalCost q ≤ costBudget ∧
R.size ≤ ReductionDsl.reductionSizeBudget + 2 ^ 12 * ReductionDsl.bits costBudget ∧
R.init.queries wTag wSamp + R.handlePre.queries wTag wSamp + R.chDone.queries wTag wSamp = 0 ∧
R.totalQueries wTag wSamp q ≤ qOut ∧
CostedOpsSizeHonest O ∧
CostedSampSizeHonest I.smp ∧
∀ (B : ℕ),
(∀ v : aOut.denote, CostedFits B aOut v) →
∀ (A : aIn.denote → OracleComp (unifSpec + G) aOut.denote),
(∀ a, ReductionDsl.AllGameQueriesFit B C (A a)) →
∀ (x : rIn.denote), CostedFits B rIn x →
let widthB := 2 * (costBudget + q + 1) * (B + 1)
∀ cm ∈ ReductionDsl.supportWhen (I.AnswerFits B) (R.eval1 A x q dflt),
CostedFits widthB ch cm.1 ∧
∀ (y : rIn2.denote), CostedFits B rIn2 y →
∀ z ∈ ReductionDsl.supportWhen (I.AnswerFits B) (R.eval2 cm.2 y q dflt),
CostedFits widthB out zdef stageOneAsProbComp
Evaluate a coin-only stage-1 computation in ProbComp by answering unused SM queries with bhModelSmDefault.
def stageOneAsProbComp {ℓ : ℕ} {PQC α : Type}
(oa : OracleComp (unifSpec + SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes) α) :
ProbComp α :=
simulateQ
(QueryImpl.ofLift unifSpec ProbComp +
(fun q => pure (bhModelSmDefault q) :
QueryImpl (SM.spec (GhostState Bytes) (Plain ℓ) PQC Bytes) ProbComp))
oaend CryptoVerification.E2E