AlphaProof Code: Verifying Signal’s Security at the Machine-Code Level

Paper › LibsignalBinary/Specifications › LibsignalTripleRatchet.leanTier 2 · definitions

LibsignalTripleRatchet.lean🔗

Tier 2 · definitions used by the computational proof and symbolic proof 4 headline theorems depend on it 43 of 1,020 lines trusted trust banner B3, trusted in part source ↗

Read the definitions shown. Only the declarations that the headline statements use are shown, with the namespace, section, open and variable commands needed to read them. The collapsed runs are theorems, which Lean checks, and definitions that no headline statement uses.

Triple-ratchet encrypt / decrypt and SignalMessage parse on the compiled session image

Trusted in part. The theorems of Computational/Theorems/ use only the following definitions from this file (see Computational/docs/TRUSTED_BASE.md):

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/Specifications/CryptoGames/Ratchet/LibsignalSession.lean). 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/Proofs/SpecLemmas/LibsignalTripleRatchetProofDefs.lean. Memory layouts and constants are in LibsignalBinary/Specifications/LibsignalTripleRatchetRepr.lean; the image guards are in LibsignalBinary/Specifications/LibsignalTripleRatchetGuards.lean.

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)SpecSession model
session_management::try_decrypt_with_state::<HookRng> (0x3439c)tryDecryptSpecSessionState.tryDecryptWithState
OutgoingTripleRatchet::encrypt::<HookRng> (0x33e0c)otrEncryptSpecOutgoingTripleRatchet.encrypt

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/Proofs/SpecLemmas/LibsignalTripleRatchetProofDefs.lean.

In addition, recordDecryptSpec is a region contract for try_decrypt_from_record, which is inlined into session_message_decrypt_signal (model SessionState.tryDecryptFromRecord).

Trusted boundary (tripleRatchetExterns)

  • everything in LibsignalSession.sessionExterns: SHA-256 compression, memcpy / memmove / memset, the no-alloc shim, black_box, Vec::remove::assert_failed, the allocator;

  • concrete hooks: X25519 diffie_hellman = C.X and PublicKey::from(&StaticSecret) = C.pubKey, the two secret drops = zeroize, libsignal_session_rng_fill = the deterministic tape (DEV-E8);

  • abstract hooks (abs, contracts in TripleRatchetHooks): the allocator (AllocHooks A, and __rust_alloc_zeroed from the same A, rustAllocZeroedSpec), AES-256-CBC encrypt / decrypt (named hypotheses matching the byte-level aesCbcEncrypt / aesCbcDecrypt functions), alloc::fmt::format_inner and core::fmt::write into a String (contracted to produce some owned String, 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> and spqr::recv are not verified on non-empty post-quantum states: the general encrypt / decrypt statements take the named hypotheses SpqrSendOk / SpqrRecvOk, subroutine_ok statements in terms of SpqrModel.send / SpqrModel.recv (panics: SpqrModel.sendPanics / recvPanics), stated for the given abs. On a non-empty state their closures reach libcrux ML-KEM-768 / HMAC stubs with no contract here (lists at SpqrSendOk / SpqrRecvOk). On an empty state they reach none of them, so the proofs establish the empty-state instances without the hypotheses (proof-side statements in LibsignalBinary/Proofs/SpecLemmas/LibsignalTripleRatchetProofDefs.lean);

  • the Rust panic entry points = Rust.withPanics; every other stub = abs (no contract: a proof can only cross it through SpqrSendOk / 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_from_service_id_string (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

Main statements

@[expose] public section
namespace CryptoVerification.LibsignalTripleRatchet
open Arm64 ProofLib CryptoGames CryptoGames.HMAC CryptoGames.HKDF CryptoGames.LibsignalSession
open CryptoVerification.RustHeap CryptoVerification.LibsignalSessionRepr
open CryptoVerification.LibsignalTripleRatchetRepr CryptoVerification.LibsignalProtoCodec
29 declarations not shown (lines 149–243): theorems, which Lean checks, or definitions that no headline statement uses

CryptoVerification.LibsignalTripleRatchet.tryDecryptName, CryptoVerification.LibsignalTripleRatchet.otrFromName, CryptoVerification.LibsignalTripleRatchet.otrEncryptName, CryptoVerification.LibsignalTripleRatchet.otrApplyName, CryptoVerification.LibsignalTripleRatchet.tryFromName, CryptoVerification.LibsignalTripleRatchet.spqrSendName, CryptoVerification.LibsignalTripleRatchet.dhName, CryptoVerification.LibsignalTripleRatchet.pubFromSecretName, CryptoVerification.LibsignalTripleRatchet.staticDropName, CryptoVerification.LibsignalTripleRatchet.sharedDropName, CryptoVerification.LibsignalTripleRatchet.rngFillName, CryptoVerification.LibsignalTripleRatchet.aesEncName, CryptoVerification.LibsignalTripleRatchet.aesDecName, CryptoVerification.LibsignalTripleRatchet.formatInnerName, CryptoVerification.LibsignalTripleRatchet.fmtWriteName, CryptoVerification.LibsignalTripleRatchet.allocZeroedName, CryptoVerification.LibsignalTripleRatchet.noHookNames, CryptoVerification.LibsignalTripleRatchet.pkTryFromName, CryptoVerification.LibsignalTripleRatchet.parseSidName, CryptoVerification.LibsignalTripleRatchet.panicAdvanceName, CryptoVerification.LibsignalTripleRatchet.addrDisplayName, CryptoVerification.LibsignalTripleRatchet.privSerializeName, CryptoVerification.LibsignalTripleRatchet.uuidTryParseName, CryptoVerification.LibsignalTripleRatchet.varintSlowName, CryptoVerification.LibsignalTripleRatchet.decodeErrorFromName, CryptoVerification.LibsignalTripleRatchet.decodeErrorPushName, CryptoVerification.LibsignalTripleRatchet.spqrRecvName, CryptoVerification.LibsignalTripleRatchet.decodeStateName, CryptoVerification.LibsignalTripleRatchet.inImageCalleeNames

Concrete hooks

def readVec32L247–249 ↗#

The 32 bytes at a as a vector.

def readVec32 (m : Memory) (a : BitVec 64) : List.Vector (BitVec 8) 32 := ⟨m.readBytes a 32, Memory.readBytes_length m a 32⟩

def diffieHellmanHookL251–256 ↗#

<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 diffieHellmanHook (X : Ratchet.X25519Fn) (s : State) : State := let r := X (readVec32 s.mem (s.readReg64 .x0)) (readVec32 s.mem (s.readReg64 .x1)) retHook (fun s => s.setMem (s.mem.writeBytes (s.readReg64 .x8) r.toList)) s

def pubFromSecretHookL258–264 ↗#

<x25519_dalek::PublicKey as From<&StaticSecret>>::from(x0 = &secret) (sret x8, 32 bytes; tail-called by the in-image derive_public_key_bytes, 0x3db4c): mem[x8 .. +32] := pubKey mem[x0 .. +32] (Crypto.pubKey; the secret is clamped inline before the tail call); pc := lr.

def pubFromSecretHook (pubKey : PubKeyFn) (s : State) : State := let r := pubKey (readVec32 s.mem (s.readReg64 .x0)) retHook (fun s => s.setMem (s.mem.writeBytes (s.readReg64 .x8) r.toList)) s

def zeroizeSecretHookL266–269 ↗#

<StaticSecret as Drop>::drop / <SharedSecret as Drop>::drop (x0 = self, zeroize): mem[x0 .. +32] := 0; pc := lr.

def zeroizeSecretHook (s : State) : State := retHook (fun s => s.setMem (s.mem.writeBytes (s.readReg64 .x0) (List.replicate 32 0#8))) s
15 declarations not shown (lines 271–406): theorems, which Lean checks, or definitions that no headline statement uses

CryptoVerification.LibsignalTripleRatchet.rngFillHook, CryptoVerification.LibsignalTripleRatchet.tripleRatchetExterns, CryptoVerification.LibsignalTripleRatchet.tripleRatchetProg, CryptoVerification.LibsignalTripleRatchet.entryOf, CryptoVerification.LibsignalTripleRatchet.logOff, CryptoVerification.LibsignalTripleRatchet.extHookStack, CryptoVerification.LibsignalTripleRatchet.spqrRecvStack, CryptoVerification.LibsignalTripleRatchet.spqrSendStack, CryptoVerification.LibsignalTripleRatchet.rootStack, CryptoVerification.LibsignalTripleRatchet.smallRootStack, CryptoVerification.LibsignalTripleRatchet.AesCall.key, CryptoVerification.LibsignalTripleRatchet.AesCall.keyA, CryptoVerification.LibsignalTripleRatchet.AesCall.inA, CryptoVerification.LibsignalTripleRatchet.AesCall.ivA, CryptoVerification.LibsignalTripleRatchet.AesCall.input, CryptoVerification.LibsignalTripleRatchet.AesCall, CryptoVerification.LibsignalTripleRatchet.AesCall.iv, CryptoVerification.LibsignalTripleRatchet.AesCall.ret, CryptoVerification.LibsignalTripleRatchet.AesCall.pre, CryptoVerification.LibsignalTripleRatchet.AesCall.frame, CryptoVerification.LibsignalTripleRatchet.aesEncSpec, CryptoVerification.LibsignalTripleRatchet.aesDecSpec

structure SpqrModelL408–430 ↗#

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.spqr passes them to Spqr.recvPanics / Spqr.sendPanics, and the triple-ratchet model aborts with .panic exactly there). Upstream sites (spqr git checkout 06959b4):

  • recv: KeyHistory::get assert_eq!(data.len() % 36, 0) and the at + max_ooo overflow (chain.rs:191–192), KeyHistory::gc assert!(current_key >= max_ooo) (chain.rs:149), the version-negotiation assert! / expects (lib.rs:425, 429, 433), msg.epoch - 1 (lib.rs:462, if Message::deserialize lets 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 state send cannot 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) → Bool
40 declarations not shown (lines 432–1018): theorems, which Lean checks, or definitions that no headline statement uses

CryptoVerification.LibsignalTripleRatchet.SpqrModel.spqr, CryptoVerification.LibsignalTripleRatchet.spqrSendKeyAt, CryptoVerification.LibsignalTripleRatchet.spqrRecvKeyAt, CryptoVerification.LibsignalTripleRatchet.VecArg.v, CryptoVerification.LibsignalTripleRatchet.VecArg.bytes, CryptoVerification.LibsignalTripleRatchet.VecArg, CryptoVerification.LibsignalTripleRatchet.VecArg.cap, CryptoVerification.LibsignalTripleRatchet.VecArg.ptr, CryptoVerification.LibsignalTripleRatchet.VecArg.at, CryptoVerification.LibsignalTripleRatchet.spqrRecvResultAt, CryptoVerification.LibsignalTripleRatchet.spqrRecvSpec, CryptoVerification.LibsignalTripleRatchet.formatInnerSpec, CryptoVerification.LibsignalTripleRatchet.fmtWriteSpec, CryptoVerification.LibsignalTripleRatchet.rustAllocZeroedSpec, CryptoVerification.LibsignalTripleRatchet.TripleRatchetHooks.alloc, CryptoVerification.LibsignalTripleRatchet.TripleRatchetHooks, CryptoVerification.LibsignalTripleRatchet.TripleRatchetHooks.fmtWrite, CryptoVerification.LibsignalTripleRatchet.TripleRatchetHooks.allocZeroed, CryptoVerification.LibsignalTripleRatchet.TripleRatchetHooks.aesEnc, CryptoVerification.LibsignalTripleRatchet.TripleRatchetHooks.formatInner, CryptoVerification.LibsignalTripleRatchet.TripleRatchetHooks.aesDec, CryptoVerification.LibsignalTripleRatchet.SendCall, CryptoVerification.LibsignalTripleRatchet.SendCall.st, CryptoVerification.LibsignalTripleRatchet.SendCall.rest, CryptoVerification.LibsignalTripleRatchet.SendCall.cur, CryptoVerification.LibsignalTripleRatchet.SendCall.ret, CryptoVerification.LibsignalTripleRatchet.spqrSendResultAt, CryptoVerification.LibsignalTripleRatchet.spqrSendSpec, CryptoVerification.LibsignalTripleRatchet.SpqrSendOk, CryptoVerification.LibsignalTripleRatchet.SpqrModel.SendEmptyOk.draws, CryptoVerification.LibsignalTripleRatchet.SpqrModel.SendEmptyOk, CryptoVerification.LibsignalTripleRatchet.SpqrModel.SendEmptyOk.send, CryptoVerification.LibsignalTripleRatchet.SpqrModel.SendEmptyOk.noPanic, CryptoVerification.LibsignalTripleRatchet.SpqrRecvOk, CryptoVerification.LibsignalTripleRatchet.SpqrModel.RecvEmptyOk.recv, CryptoVerification.LibsignalTripleRatchet.SpqrModel.RecvEmptyOk.noPanic, CryptoVerification.LibsignalTripleRatchet.SpqrModel.RecvEmptyOk, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.ret, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.msg, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.msgG, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.cur, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.msgA, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.ss, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.loc, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.rest, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.st, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.msgType, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.locA, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.rem, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.remA, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.G, CryptoVerification.LibsignalTripleRatchet.TryDecryptGhost.model, CryptoVerification.LibsignalTripleRatchet.SeedsLen32, CryptoVerification.LibsignalTripleRatchet.tryDecryptSpec, CryptoVerification.LibsignalTripleRatchet.optAddrAt, CryptoVerification.LibsignalTripleRatchet.OtrEncryptGhost.ptext, CryptoVerification.LibsignalTripleRatchet.OtrEncryptGhost.o, CryptoVerification.LibsignalTripleRatchet.OtrEncryptGhost.cur, CryptoVerification.LibsignalTripleRatchet.OtrEncryptGhost.loc, …

end CryptoVerification.LibsignalTripleRatchet