agent: initialize double ratchet from the invitation via contact address#1829
agent: initialize double ratchet from the invitation via contact address#1829epoberezkin wants to merge 21 commits into
Conversation
There was a problem hiding this comment.
RFC + implementation plan for one-off requests to service addresses (PRC pattern without a duplex connection). Two new markdown files; no code changes.
I read both files in full and cross-checked the design against the existing code:
- Address type: plan's
CCTService/'S'matches theContactConnTypepattern inProtocol.hs(ctTypeChar/ctTypeP, 1744-1759) and thecontactTypeABNF inagent-protocol.md:414. UserContactDataextension: the appendedserviceKeys :: Maybe ServiceKeyBundleand theoptional smpP+takeByteStringparse are consistent with the current tail-ignoring forward-compat convention (Protocol.hs:1974-1982). Both "field absent" (older data) and "present as0" collapse toNothingcorrectly.- Hybrid
PubHeader: additive'2'variant, preserves the existing0/1Maybe-key encoding (Protocol.hs:1096-1104). - Sizes: sntrup761 pk 1158 / ct 1039 and
userDataPaddedLength = 13784(Crypto/ShortLink.hs:45) match the RFC. SSNDencoding, agent envelope tags (Q/P), signed-reply hash chain, and hybrid secret derivation are internally consistent between the RFC ABNF and the plan's Haskell.
The security objectives (unlinkability, PQ encryption bounded by key rotation, reply authenticity via root-key signature + hash chain, replay protection via agent dedup) are coherent and the tradeoffs (tail-truncation detected only by deadline; dedup bounded by a fixed window) are stated honestly.
No substantive correctness, security, concurrency, or architecture defects found. Names are explicitly provisional, so I did not raise naming nits. Nothing blocks merge of the design docs.
Co-authored-by: Evgeny <evgeny@poberezkin.com>
There was a problem hiding this comment.
Reviewed the whole branch (agent DR-from-invitation via contact address). The change is internally consistent and I found no blocking issues.
What I verified against the code, not the description:
- Version gating.
currentSMPAgentVersionbumped to 8;AgentConfirmationgainsratchetKeyId :: Maybe RatchetKeyId, encoded/decoded symmetrically behindaddressDRSMPAgentVersion. Normal confirmations setratchetKeyId = Nothing, so pre-v8 peers still parse and v8 peers round-trip the extra tag byte. - Dispatch. The new
(PHEmpty, AgentConfirmation {e2eEncryption_ = Just, ratchetKeyId = Just})case precedes the genericPHEmptycase and can't shadow it — onlyjoinAddressDRever setsratchetKeyId = Just.smpAddressConfirmationadditionally guards onContactConnection. - Ratchet flow. Requester builds the snd ratchet in
joinAddressDR(createSndRatchet) and later decrypts the owner's reply viagetRatchet(the snd-initialized ratchet is a fullRatchetX448, matching the existing joiner path). Owner builds the recv ratchet ininitRcvRatchetDecrypt, persists it inside the storedDRRequest, and on accept re-creates it against the new connId and creates its reply queue viasecureConfirmQueue rq_=Nothing → createReplyQueue. Duplex is fully established. - Composition operators.
.:::/.::.(Data.Composition) match the new arities ofcreateConnectionForLink'/joinConn. - Store & migrations.
M20260712_address_drappended in date order for both SQLite and Postgres;agent_schema.sqlregenerated.ContactRequestfield disambiguatesCRInvitation(URI) vsCRConfirmation(JSON, leading{). JSON derivation ofDRRequestis satisfied by existing instances plus the newly addedStrEncoding/StrJSON SMPQueueInfo. - Encoding compat. The
optional smpP <* A.takeByteStringforratchetKeysinUserContactDatais deliberate: the standard taggedMaybeencoding would fail on pre-existing tail bytes, whereasoptionalbacktracks toNothingand ignores the tail. - Test expectation changes. The
False→Trueflips intestMatrix2/testBasicMatrix2etc. are correct:prevRangemoves the prev agent version from 6 to 7, andagentSecureSndQueuereturnsconnAgentVersion >= ratchetOnConfSMPAgentVersion(v7).acceptContactAsync'correctly rejectsCRConfirmation(async accept unsupported for DR). NewtestContactDRMatrixcovers the DR and classic-ignore paths across PQ settings.
Non-blocking observations (no action requested):
address_ratchet_keys.retired_atand key rotation/retirement are schema-only; nothing setsretired_atyet. Keys are looked up per request and reused for all requesters (documented forward-secrecy tradeoff), and cleaned up only viaON DELETE CASCADE. Fine for this first cut.getAddressRatchetKeysreusesSEX3dhKeysNotFound; acceptable since these are X3DH keys.- DR address keys are wired through
createConnectionForLinkonly;setConnShortLinklink-data updates don't (re)generate them. Consistent with the plan docs; flagging only so it isn't assumed covered.
No description provided.