LibsignalTripleRatchet.lean
Triple-ratchet encrypt / decrypt and SignalMessage parse on the compiled session image
Trusted in part. The theorems of Computational/ use only the following definitions from this file (see Computational/):
-
The SPQR interface: the structure
SpqrModeland its fieldssend,recv,sendPanics,recvPanics(the PQXDH-mode and SPQR V1 theorems). -
Concrete hooks and a helper:
pubFromSecretHook,diffieHellmanHook,zeroizeSecretHook,readVec32(rows of the CLI's hook tablecliHookTablePlaced,SignalSession/, which the statements about the CLI's ELF use). Everything else here, the layered programCliRun.lean tripleRatchetProg/tripleRatchetExterns,entryOfandrngFillHookincluded, is checked by Lean.
This file states what the compiled AArch64 code of libsignal's triple ratchet (the Double Ratchet combined with the post-quantum SPQR ratchet) does, in terms of the pure session model (LibsignalBinary/). It defines the session program tripleRatchetProg (a loaded session image img with the base hooks sessionExterns plus the triple-ratchet hooks), the contracts of the callees it does not verify, the Hoare specs of the two entry points below and of the inlined try_decrypt_from_record region. The run statements (...RunStmt) the proofs establish are proof-side, in LibsignalBinary/. Memory layouts and constants are in LibsignalBinary/; the image guards are in LibsignalBinary/.
Glossary: ABI = application binary interface; SS = SessionStructure; OTR = OutgoingTripleRatchet; SPE = SignalProtocolError; sret = the caller-provided structure-return buffer (here x0 for the two HookRng-generic entry points, x8 otherwise); SPQR / spqr = the Sparse Post-Quantum Ratchet; ML-KEM-768 = Module-Lattice-Based Key-Encapsulation Mechanism (ML-KEM-768); RNG = random number generator; UUID = universally unique identifier; ZST = zero-sized type; tape = the deterministic byte stream HookRng draws from (libsignal_session_rng_fill, DEV-E8); abs = the abstract hooks (any functions meeting the contracts of TripleRatchetHooks); IK = identity key; C = the session model's cryptographic primitives (Crypto: SHA-256 (Secure Hash Algorithm 256-bit) compression F, AES-256-CBC (Advanced Encryption Standard with 256-bit keys in Cipher Block Chaining mode) block cipher E / D, X25519 (Curve25519 elliptic-curve Diffie-Hellman) X, and public-key derivation pubKey); A = the Rust allocator model (RustAlloc); ghost = the logical (proof-only) parameters a spec is stated over; HMAC = Hash-based Message Authentication Code; HKDF = HMAC-based Key Derivation Function.
The entry points (register conventions differ; see each spec)
| Entry point (image offset) | Spec | Session model |
|---|---|---|
session_management::try_decrypt_with_state::<HookRng> (0x3439c) | tryDecryptSpec | SessionState. |
OutgoingTripleRatchet::encrypt::<HookRng> (0x33e0c) | otrEncryptSpec | OutgoingTripleRatchet. |
The specs of OutgoingTripleRatchet::from_session_state, ::apply_to_session_state and <SignalMessage as TryFrom<&[u8]>>::try_from (otrFromSpec, otrApplySpec, tryFromSpec) are proof-side, in LibsignalBinary/.
In addition, recordDecryptSpec is a region contract for try_decrypt_from_record, which is inlined into session_ (model SessionState.).
Trusted boundary (tripleRatchetExterns)
-
everything in
LibsignalSession.: SHA-256 compression,sessionExterns memcpy/memmove/memset, the no-alloc shim,black_box,Vec::remove::assert_failed, the allocator; -
concrete hooks: X25519
diffie_hellman=C.andX PublicKey::from(&StaticSecret) =C., the two secret drops = zeroize,pubKey libsignal_session_rng_fill= the deterministic tape (DEV-E8); -
abstract hooks (
abs, contracts inTripleRatchetHooks): the allocator (AllocHooksA, and__rust_alloc_zeroedfrom the sameA,rustAllocZeroedSpec), AES-256-CBC encrypt / decrypt (named hypotheses matching the byte-levelaesCbcEncrypt/aesCbcDecryptfunctions),alloc::fmt::format_innerandcore::fmt::writeinto aString(contracted to produce some ownedString, since only error texts depend on them); -
no hook (reaching them halts, which every spec forbids, so the proofs must show they are not reached):
__aarch64_ldadd8_rel/_relax,__aarch64_cas8_acq_rel,log::Log::log(logging off:logOff); -
the in-image
spqr::send::<HookRng>andspqr::recvare not verified on non-empty post-quantum states: the general encrypt / decrypt statements take the named hypothesesSpqrSendOk/SpqrRecvOk,subroutine_okstatements in terms ofSpqrModel./send SpqrModel.(panics:recv SpqrModel./sendPanics recvPanics), stated for the givenabs. On a non-empty state their closures reach libcrux ML-KEM-768 / HMAC stubs with no contract here (lists atSpqrSendOk/SpqrRecvOk). On an empty state they reach none of them, so the proofs establish the empty-state instances without the hypotheses (proof-side statements inLibsignalBinary/);Proofs/ SpecLemmas/ Libsignal Triple Ratchet Proof Defs.lean -
the Rust panic entry points =
Rust.; every other stub =withPanics abs(no contract: a proof can only cross it throughSpqrSendOk/SpqrRecvOk).
libsignal-core, bytes, uuid, prost and spqr are whole crates in the image, so their functions are in-image code that the proofs execute, not hooks: in particular <PublicKey as TryFrom<&[u8]>>::try_from, PrivateKey::serialize, <ProtocolAddress as Display>::fmt (which calls core::fmt::write), ServiceId::parse_ (which calls Uuid::try_parse), bytes::panic_advance, Uuid::try_parse, prost's decode_varint_slow and DecodeError::from / push, spqr::decode_state and spqr::recv (guard inImageCallees_code).
Preconditions
Local IK ≠ remote IK (DEV-LS28: else upstream's self_session is true and max_forward_jumps differs from the model's); logging off; addresses parsed by serviceIdParse uuidParse (the specs are generic in the UUID parser; the statements instantiate them at the uuid 1.26.1 model uuidTryParse, which the in-image Uuid::try_parse computes). Sizes: plaintexts < 2^32, the parsed buffer < 2^32, message byte strings < 2^40 (signalMessageAt), decrypt: fewer than 2^32 receiver chains. Decrypt only (DEV-LS9): every stored skipped-key seed is 32 bytes (SeedsLen32), as libsignal itself always stores them.
Main definitions
-
tripleRatchetExterns/tripleRatchetProg: the hook table and the program (image + hooks + panics) every statement runs. -
diffieHellmanHook,pubFromSecretHook,zeroizeSecretHook,rngFillHook: the concrete hooks (X25519, public-key derivation, secret zeroization, the RNG tape). -
aesEncSpec/aesDecSpec,formatInnerSpec,fmtWriteSpec,rustAllocZeroedSpec, collected inTripleRatchetHooks: the contracts assumed of the abstract hooks. -
SpqrModel: the abstract SPQR functions on serialized bytes;spqrSendSpec/spqrRecvSpec: the contracts of the in-imagespqr::send/spqr::recv;SpqrSendOk/SpqrRecvOk: the named hypotheses that they meet them. -
SeedsLen32: the decrypt precondition that stored skipped-key seeds are 32 bytes (DEV-LS9). -
tryDecryptSpec,otrEncryptSpec: the binding specs of the two entry points (each with its ghost structure and.model). -
region_okandrecordDecryptSpec: the region-triple judgment and the region contract of the inlinedtry_decrypt_from_record.
Main statements
-
tripleRatchetExterns_abs(kernel-checked guard): the abstract hook names reachabsunchanged.
@[expose] public sectionnamespace CryptoVerification.LibsignalTripleRatchetopen Arm64 ProofLib CryptoGames CryptoGames.HMAC CryptoGames.HKDF CryptoGames.LibsignalSessionopen CryptoVerification.RustHeap CryptoVerification.LibsignalSessionRepropen CryptoVerification.LibsignalTripleRatchetRepr CryptoVerification.LibsignalProtoCodec29 declarations not shown (lines 149–243): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
Concrete hooks
def readVec32
The 32 bytes at a as a vector.
def diffieHellmanHook
<StaticSecret>::diffie_hellman(self = x0, their_public = x1) -> SharedSecret (sret x8): mem[x8 .. +32] := X mem[x0 .. +32] mem[x1 .. +32] (the secret is already clamped by calculate_agreement); pc := lr.
def pubFromSecretHook
def zeroizeSecretHook
<StaticSecret as Drop>::drop / <SharedSecret as Drop>::drop (x0 = self, zeroize): mem[x0 .. +32] := 0; pc := lr.
15 declarations not shown (lines 271–406): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification.
structure SpqrModel
The abstract SPQR (spqr 1.6.0) on serialized bytes: send / recv as in the session model's Spqr interface (states and messages are byte strings, PQC = the tape), and draws st tape = the number of tape bytes spqr::send consumes (a function of its inputs: the code is deterministic given the tape).
spqr panic predicates. recvPanics st msg / sendPanics st tape = the inputs on which spqr::recv / spqr::send panic (Bool, so that the session model's Spqr interface can branch on them: SpqrModel. passes them to Spqr. / Spqr., and the triple-ratchet model aborts with .panic exactly there). Upstream sites (spqr git checkout 06959b4):
-
recv:KeyHistory::getassert_eq!(data.and thelen() % 36, 0) at + max_ooooverflow (chain.rs:191–192),KeyHistory::gcassert!(current_key >= max_ooo)(chain.rs:149), the version-negotiationassert!/expects (lib.rs:425, 429, 433),msg.(epoch - 1 lib.rs:462, ifMessage::deserializelets epoch 0 through), and the panics of its V1 helpers; -
send:assert!(key.(is_none()) lib.rs:292),msg.(epoch - 1 lib.rs:299), and the panics of its V1 helpers (all stubs,SpqrSendOk). On an empty statesendcannot panic (SpqrModel.).SendEmptyOk
structure SpqrModel where
send : List (BitVec 8) → List (BitVec 8) → Option (List (BitVec 8) × Salt × List (BitVec 8))
draws : List (BitVec 8) → List (BitVec 8) → Nat
recv : List (BitVec 8) → List (BitVec 8) → Except Bool (List (BitVec 8) × Salt)
recvPanics : List (BitVec 8) → List (BitVec 8) → Bool
sendPanics : List (BitVec 8) → List (BitVec 8) → Bool40 declarations not shown (lines 432–1018): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., CryptoVerification., …
end CryptoVerification.LibsignalTripleRatchet