LibsignalTripleRatchetRepr.lean
Declarations on this page (18)
Memory layouts of libsignal's session state, messages and results in the compiled image
Trusted in part. The theorems of the default library (Computational/ outside CliElfLib) use only ServiceIdParser, UuidParser, addrBytes, hyphenatedLength, kindByte, pniPrefix, serviceIdParse, tryParseHyphenated, utf8, uuidTryParse, hexNibble, decodeHex, decodeHex32, parseSimple, hyphen, parseHyphenated, urnPrefix from this file: read them. To trust the theorems of Computational/ (CliElfLib), also read errTag. Everything else here is checked by Lean. See Computational/.
This file gives the separation-logic predicates that say how the Rust values the triple-ratchet entry points (LibsignalBinary/) read and write are laid out in memory, relating bytes to the session model (LibsignalBinary/). It extends LibsignalBinary/ (whose predicates it reuses: chainAt, messageKeyAt, optChainKeyAt, VecGhost, VecPin, ...). The trusted part is the pure address-parsing model: uuidTryParse (uuid 1.26.1's Uuid::try_parse), serviceIdParse (libsignal's ServiceId::parse_) and the per-address bytes addrBytes, from which serializeAddresses builds the sender / recipient address bytes.
Every layout fact below was read off the disassembly of the session image; the evidence is cited by instruction offset at each definition. Independently, a Rust replica of the types (110 const assertions compiled by rustc 1.98.1 for aarch64-unknown-linux-gnu) confirms every offset, size and niche, including the fields no instruction touches. The niche choices (-1 for a single-niche Option, -2 for Result<Vec<u8>, SPE>::Ok, 2^63 + i for the SPE) depend on the rustc version: rerun the replica on any toolchain bump. Kernel-checked guards: issStr_lengths, macFailedStr_length, pniPrefix_word.
Glossary: SPE = SignalProtocolError (48 bytes); SS = proto::storage::SessionStructure (the payload of SessionState, 344 bytes); OTR / TR = OutgoingTripleRatchet (200 bytes) / TripleRatchet; IK = identity key; srk = sender ratchet key; pq / SPQR = the Sparse Post-Quantum Ratchet and its serialized state Vec<u8>; ISS = InvalidSessionStructure; UUID = universally unique identifier; ACI = Account Identity; PNI = Phone Number Identity; UTF-8 = 8-bit Unicode Transformation Format; LE = little-endian; RNG = random number generator; prost = the Rust protobuf (Protocol Buffers) library; niche = an invalid bit pattern of a field reused as an enum discriminant; sret = the caller-provided result buffer; Box<[u8]> = an owned byte slice (ptr, len) (no capacity word); rodata = the image's read-only data (↦ᵣ, duplicable read-only points-to).
Niches
-
Option<Vec-like>(Option<Chain>,Option<chain::ChainKey>,Option<PendingPreKey>,Option<PendingKyberPreKey>,spqr::MessageKey):None= capacity wordusize::MAX(noneCap). Evidence:OTR::apply_to_session_state0xbd00: cmn x22, #1(SS + 0x90);clear_writesunacknowledged_ pre_ key_ message -1to SS + 0xf8 and SS + 0x128. -
SPE: word 0 =
2^63 + variant indexfor every non-dataful variant (speWord). -
Result<Vec<u8>, SPE>(try_decrypt):Ok= word 0-2(okTagVec), theVecat +8;Err= the SPE inline at +0.Result<T, SPE>forT=SignalMessage/OutgoingTripleRatchet:Ok= the payload at +0;Err= word 0-1(errTag), the SPE at +8.
What is not modelled (framed byte-exactly by the ghost)
alice_base_key, the registration ids, the pending pre-key's ids / timestamp, the whole pending_kyber_pre_key, and every Vec the session model does not determine (VecGhost).
Main definitions
-
uuidTryParse(withhexNibble,decodeHex,decodeHex32,parseSimple,parseHyphenated,hyphen,urnPrefix): the model of uuid 1.26.1'sUuid::try_parse. -
serviceIdParse(withtryParseHyphenated,hyphenatedLength,pniPrefix): the model ofServiceId::parse_;from_ service_ id_ string ServiceIdParser/UuidParserare their types. -
addrBytes,kindByte,serializeAddresses: the 36-byte sender / recipient encoding (serialize_addresses). -
utf8: a string literal's UTF-8 bytes. -
speAt,vecResultAt,errResultAt,signalMessageResultAt,otrResultAt: an SPE and theResultbuffers of the entry points. -
signalMessageAt,protocolAddressAt,sessionStateAt,otrAt: aSignalMessage, aProtocolAddress, aSessionStructureand anOutgoingTripleRatchetin memory (with their ghost structures). -
tapeAt,tapeAfter,coinsOf,decryptDraws: theHookRngrandom tape and how much of it a decrypt draws. -
SessionGhost.,AfterTryDecrypt SessionGhost.,AgreeExceptPq SessionGhost.: how the ghost layout may change across each entry point.AfterOutgoingRatchetApply
@[expose] public sectionnamespace CryptoVerification.LibsignalTripleRatchetRepropen Arm64 CryptoGames CryptoGames.HMAC CryptoGames.HKDF CryptoGames.LibsignalSessionopen CryptoVerification.RustHeap CryptoVerification.LibsignalSessionRepr1 declaration not shown (lines 101–103): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification.
def errTag
The Err tag word of Result<T, SPE> for the 120 / 200-byte payloads (-1; try_from 0x258a4: str x9, [x19], OTR::from_session_state 0xb93c, OTR::encrypt 0x34058).
def errTag : BitVec 64 := asm64% resultErrTag2 declarations not shown (lines 109–116): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification., CryptoVerification.
def utf8
A string literal's UTF-8 bytes.
def utf8 (s : String) : List (BitVec 8) := s.toUTF8.toList.map fun b => BitVec.ofNat 8 b.toNat16 declarations not shown (lines 121–316): 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.
abbrev ServiceIdParser
A ServiceId parser on name bytes: the ServiceId of a name, or none. The Bool is the kind (false = Aci, tag 0; true = Pni, tag 1), the vector the UUID. The session specs use serviceIdParse uuidParse (below), the model of ServiceId::parse_.
abbrev ServiceIdParser := List (BitVec 8) → Option (Bool × List.Vector (BitVec 8) 16)abbrev UuidParser
A uuid::Uuid::try_parse on the input bytes: the 16 UUID bytes (Ok), or none (Err). The session specs are generic in it; the statement instantiates them at uuidTryParse.
abbrev UuidParser := List (BitVec 8) → Option (List.Vector (BitVec 8) 16)
uuid::Uuid::try_parse (uuid 1.26.1)
Upstream: ~/. Uuid::try_parse(&str) (:110–112) = try_parse_ascii(input. (:136–144), which maps try_parse (:147–163) to Ok(Uuid::from_bytes(bytes)) (the 16 bytes, unchanged) or to Err(Error(ErrorKind::ParseOther)) on every failure. try_parse dispatches on the length:
-
32 bytes:
parse_simple(:188–225), 32 hex digits; -
36 bytes:
parse_hyphenated(:228–278), the whole input; -
38 bytes
{…}:parse_hyphenatedof the 36 bytes inside the braces (:156); -
45 bytes
urn:uuid:…:parse_hyphenatedof the 36 bytes after the prefix (:157); -
any other length or shape:
Err(:161).decode_hex32(:283–322) maps'0'..='9'to0..9and'a'..='f'/'A'..='F'(via| 0x20) to10..15, and rejects every other byte; bytejof the UUID is(nibble 2j << 4) | nibble 2j+1.
libsignal only reaches the 36-byte (hyphenated) path: try_parse_hyphenated (core/, tryParseHyphenated below) checks the length is hyphenatedLength = 36 before it calls Uuid::try_parse. The other shapes are modelled so that uuidTryParse is the whole function, as compiled into the image.
def hexNibble
decode_hex32's per-byte map (src/): digit = c - '0' if < 10, else alpha = (c | 0x20) - 'a' if < 6, as alpha + 10; any other byte is bad (none).
def hexNibble (c : BitVec 8) : Option (BitVec 8) :=
if c - 0x30#8 < 10#8 then some (c - 0x30#8)
else if (c ||| 0x20#8) - 0x61#8 < 6#8 then some ((c ||| 0x20#8) - 0x61#8 + 10#8)
else nonedef decodeHex
decode_hex32's byte assembly (src/) on a list of hex digits: each pair (hi, lo) gives (hi << 4) | lo; none if any digit is bad.
def decodeHex32
decode_hex32(hex: &[u8; 32]) (src/): the 16 bytes, or none.
def decodeHex32 (hex : List (BitVec 8)) : Option (List.Vector (BitVec 8) 16) :=
(decodeHex hex).bind fun bs => if h : bs.length = 16 then some ⟨bs, h⟩ else nonedef parseSimple
parse_simple(s, _) (src/): 32 hex digits.
def parseSimple (s : List (BitVec 8)) : Option (List.Vector (BitVec 8) 16) :=
if s.length = 32 then decodeHex32 s else nonedef hyphen
The byte '-'.
def hyphen : BitVec 8 := 0x2d#8def parseHyphenated
parse_hyphenated(s) (src/): 36 bytes, '-' at positions 8, 13, 18 and 23 (:244–247), the other 32 bytes hex digits, gathered in order (:251–272).
def parseHyphenated (s : List (BitVec 8)) : Option (List.Vector (BitVec 8) 16) :=
if s.length = 36 ∧ s[8]? = some hyphen ∧ s[13]? = some hyphen ∧ s[18]? = some hyphen ∧
s[23]? = some hyphen then
decodeHex32 (s.take 8 ++ (s.drop 9).take 4 ++ (s.drop 14).take 4 ++ (s.drop 19).take 4 ++
s.drop 24)
else nonedef urnPrefix
The prefix urn:uuid: (src/).
def urnPrefix : List (BitVec 8) := utf8 "urn:uuid:"def uuidTryParse
uuid::Uuid::try_parse (uuid 1.26.1, src/; the shapes are listed above): some u = Ok(Uuid::from_bytes(u)), none = Err(ParseOther).
def uuidTryParse : UuidParser := fun input =>
if input.length = 32 then parseSimple input
else if input.length = 36 then parseHyphenated input
else if input.length = 38 ∧ input.head? = some 0x7b#8 ∧ input.getLast? = some 0x7d#8 then
parseHyphenated ((input.drop 1).take 36)
else if input.length = 45 ∧ urnPrefix.isPrefixOf input then
parseHyphenated (input.drop urnPrefix.length)
else none1 declaration not shown (lines 397–400): theorems, which Lean checks, or definitions that no headline statement uses
_private.
def pniPrefix
The prefix "PNI:" of a PNI's string form (core/).
def pniPrefix : List (BitVec 8) := utf8 "PNI:"1 declaration not shown (lines 405–409): theorems, which Lean checks, or definitions that no headline statement uses
CryptoVerification.
def hyphenatedLength
uuid::fmt::Hyphenated::LENGTH (uuid 1.26.1, src/).
def hyphenatedLength : Nat := 36def tryParseHyphenated
try_parse_hyphenated (core/): Uuid::try_parse(input).ok() if the input has exactly hyphenatedLength bytes, else None without calling it.
def tryParseHyphenated (uuidParse : UuidParser) (input : List (BitVec 8)) :
Option (List.Vector (BitVec 8) 16) :=
if input.length = hyphenatedLength then uuidParse input else nonedef serviceIdParse
ServiceId::parse_ (core/) over a Uuid::try_parse: if the input starts with "PNI:" (input., a byte-prefix test), the PNI of the rest; otherwise the ACI of the whole input; each None unless the parsed part has exactly 36 bytes and uuidParse accepts it. In the image this and Uuid::try_parse are in-image code (libsignal-core and uuid are whole crates), verified against serviceIdParse uuidTryParse.
def serviceIdParse (uuidParse : UuidParser) : ServiceIdParser := fun input =>
if pniPrefix.isPrefixOf input then
(tryParseHyphenated uuidParse (input.drop pniPrefix.length)).map fun u => (true, u)
else
(tryParseHyphenated uuidParse input).map fun u => (false, u)def kindByte
ServiceIdKind's byte: the tag of the 17-byte Option<ServiceId> that the in-image parse_ writes (Aci = 0, +0x50: strb wzr, [x19]; Pni = 1, +0xb4..+0xb8: mov w8, #1; strb w8, [x19]; None = 2, +0x98..+0x9c), stored unchanged by serialize_addresses (core/'s ServiceIdKind order).
def kindByte (pni : Bool) : BitVec 8 := if pni then 1#8 else 0#8def addrBytes
The 18 bytes kind ‖ uuid ‖ device of one address (ServiceId::service_, core/, then the device).
def addrBytes (sid : Bool × List.Vector (BitVec 8) 16) (device : BitVec 8) : List (BitVec 8) :=
kindByte sid.1 :: (sid.2.toList ++ [device])16 declarations not shown (lines 443–657): 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.
end CryptoVerification.LibsignalTripleRatchetRepr