Amarula.Protocol.Signal.SessionBuilder (amarula v0.1.0)

View Source

X3DH responder session establishment, ported from node_modules/libsignal/src/session_builder.js.

Only the incoming (responder) path is implemented — we build a session when we receive a PreKeyWhisperMessage. The outgoing/initiator path (needed only to send the first message in a new conversation) is out of scope for the decrypt-only milestone.

Keys here are raw 32-byte X25519 keys (the leading 0x05 type byte is stripped before DH). our_identity, prekeys etc. come from the session store.

Summary

Functions

Set up the initiator's first sending chain (used only on the initiator path).

Process an incoming PreKeyWhisperMessage: build a responder session into record and return {updated_record, pre_key_id}.

Initiator X3DH: build an outgoing session from a fetched prekey bundle, ported from libsignal session_builder.js initOutgoing.

Build the session state (X3DH + initial ratchet). Mirrors libsignal initSession. Public keys are passed wire-form (33 bytes with 0x05 prefix); DH strips it.

Functions

calculate_sending_ratchet(session, remote_key)

Set up the initiator's first sending chain (used only on the initiator path).

init_incoming(record, message, store)

Process an incoming PreKeyWhisperMessage: build a responder session into record and return {updated_record, pre_key_id}.

store must provide:

  • load_pre_key.(id) -> %{public, private} | nil

  • load_signed_pre_key.(id) -> %{public, private}
  • our_identity -> %{public, private}

message is the decoded PreKeyWhisperMessage (see WhisperProtocol).

init_outgoing(record, device, store)

Initiator X3DH: build an outgoing session from a fetched prekey bundle, ported from libsignal session_builder.js initOutgoing.

device is a map with the peer's bundle:

  • :registration_id — integer
  • :identity_key — wire-form pubkey (33B 0x05-prefixed)
  • :signed_pre_key => %{key_id, public, signature}
  • :pre_key => %{key_id, public} | nil (one-time prekey, optional)

Returns the updated SessionRecord with an open session whose pending_pre_key makes the first send a pkmsg (SessionCipher.encrypt wraps it). store.our_identity supplies our identity for the DH legs.

init_session(is_initiator, our_ephemeral_key, our_signed_key, their_identity_pub_key, their_ephemeral_pub_key, their_signed_pub_key, registration_id, store)

Build the session state (X3DH + initial ratchet). Mirrors libsignal initSession. Public keys are passed wire-form (33 bytes with 0x05 prefix); DH strips it.