agent: use double ratchet from the first message in contact addresses, request rejection, RPC - #1831
agent: use double ratchet from the first message in contact addresses, request rejection, RPC#1831epoberezkin wants to merge 4 commits into
Conversation
…ess (#1829) * rfc: agent support for PRC pattern without duplex connection * update rfc, plan * corrections Co-authored-by: Evgeny <evgeny@poberezkin.com> * split rfcs, add plan * update rfc * update rfc and plan * update plan * add double ratchet keys to address * agent schema * fix version in test * change ContactRequest encoding * fix tests * update plan * tests, refactor * fix compilation * refactor * refactor more * fix * add export * fix * refactor * refactor * update schema * rename * refactor * refactor 2 * comment, type * type * rename * refactor more * move type * refactor * remove comment * diff * refactor * comment * puns * get correct key * rename * add autoincrement * refactor * move * move back * simplify * rename * refactor * tuple * comment * split decryption * refactor * rename * refactor * move * remove createSndQueue * remove comment * remove JoinInvitationReq * simplify * rename * fix * type synonim * getConnData * simplify * move * encoding * version * async join with ratchet keys * fix encoding * rotate address ratchet keys * rename, parameter order * refactor, include ratchet keys into ConnectionRequestUri * simplify * remove records * refactor * rename, encoding * clean up * refactor * compatible * addrKeysE2EVersion * support IKUsePQ for contact addresses * comments, refactor condition * typos --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* agent: request rejection * implement RPC requests/responses * simplify rpc, test, async * renames * clean up * fix * fix query * set flag at creation * move * command type, schema * rename * refactor * typo Co-authored-by: simplex-chat-agent[bot] <287173099+simplex-chat-agent[bot]@users.noreply.github.com> * allow overriding the service request timeout per request * fixes * use SSENT event for service replies * AgentServiceError encoding * sign service requests * add bad signature error * update the plan * clean up * refactor * empty line Co-authored-by: simplex-chat-agent[bot] <287173099+simplex-chat-agent[bot]@users.noreply.github.com> * add indices --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> Co-authored-by: simplex-chat-agent[bot] <287173099+simplex-chat-agent[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Summary
Two features across the agent: (1) contact addresses can publish double-ratchet keys so the first message is DR-encrypted end-to-end (AddressRatchetKeys, address_ratchet_keys table, AgentContactRequest envelope), and (2) service RPC over contact addresses — sendServiceRequest/Async send a signed-or-unsigned request and block for a single response, with rejectContact/sendServiceReply on the other side. Supporting changes: JoinRequest sum type replacing the JOIN argument list, ContactRequest (URI vs stored DRInvitation), BinaryConnectionRequestUri splitting the binary (link-data) encoding from the URI encoding, StoredPrivateKey for persisting an Ed25519 signing key, EncList, and a schema migration.
I read Protocol.hs, Agent.hs (join/accept/reject/service flows, the JOIN command worker, message-delivery result handling, smpConfirmation/smpContactRequest), Store.hs, AgentStore.hs, the migrations and reference schemas, and the Crypto/Encoding/Client/Ratchet changes, plus the connection-request tests and the RFCs/plans. I did not build.
I found no blocking correctness, concurrency, or security defect. The wire tags are collision-free (AgentMessageType A/P/J, envelope 'A' vs inner-message 'A' live in separate layers), the JoinRequest/NEW/UserContactData encodings round-trip and stay backward-compatible (trailing-optional fields, takeByteString tail), the signature binding to rcAD is sound, and address ratchet keys are carried in the signed user-data (not the fixed link data), so rotation keeps the link ID stable while remaining authenticated by the root key.
Non-blocking observations
-
smpContactRequestrecords the dedup hash before the request is fully processed (unlessM duplicateRequest $ … getAddressRatchetKeys …, Agent.hs ~3981).duplicateRequestcommitsaddProcessedRatchetKeyHashin its own transaction, so if a later step throws (unknown ratchetKeyId, decrypt error, or astoreInvitationfailure) the sender's retry is treated as a duplicate and silently dropped — noREQ/SREQis ever delivered. This mirrors the existingnewRatchetKey/ratchetExistspattern, so it's consistent, but worth confirming it's the intended trade-off for the service-request path where a dropped request is a lost RPC. -
sendServiceRequestAsyncblocks the caller. Both sync and async variants funnel throughserviceRequest_, which waits on the responseTMVar(or theregisterDelaytimeout). "Async" only means the send is enqueued as a retriable command, not that the call returns before the response. After a process restart the persistedJRServiceReqJOIN finds no in-memory entry inserviceRequestsand becomes a no-op (Nothing -> pure ()), so the request is not resent — the connection is later reaped bygetExpiredServiceConns. That's defensible (the caller is gone), but the naming may mislead; a note atsendServiceRequestAsync'would help. -
Repeated confirmation delivery of a service response (ack lost) re-enters
dispatchServiceReply; onceserviceRequest_has removed the map entry this emits a spuriousERR (A_SERVICE ASENoPendingRequest). Cosmetic. -
serviceRequest_inserts into theserviceRequestsmap before thetryErrorblock and cleans up after it (not via bracket). An async exception between insert and cleanup leaks the map entry. Low risk and consistent with surrounding code. -
Minor: the Postgres migration sets
connections.created_atdefault'1970-01-01 00:00:00'(no zone) while the dumped schema shows+01; harmless since the value isn't read for pre-existing rows. TheM20260712SQLite migration's stray,placement in the reference schema is cosmetic.
No description provided.