Raxol.Agent.Backend.Lumo.Crypto (Raxol Agent v2.6.0)

Copy Markdown View Source

Cryptographic operations for Proton Lumo's U2L (User-to-LLM) encryption.

Handles:

  • AES-256-GCM encryption/decryption with Associated Data (AEAD)
  • PGP encryption of the per-request AES key to Lumo's public key (via gpg)

The protocol: each request generates a fresh AES-256-GCM key. Message turns are encrypted with this key using AD string lumo.request.{id}.turn. The key itself is PGP-encrypted to Lumo's ECDH public key. Response chunks arrive encrypted and are decrypted with the same key using AD string lumo.response.{id}.chunk.

Summary

Functions

Decrypt base64-encoded AES-256-GCM ciphertext.

Decrypt a response chunk from Lumo.

Encrypt plaintext with AES-256-GCM.

Encrypt the AES request key to Lumo's PGP public key using gpg.

Encrypt a turn's content for Lumo U2L.

Generate a UUID v4 request ID.

Generate a random 32-byte AES-256-GCM key.

Check if gpg is available on the system.

Returns Lumo's production PGP public key.

Functions

decrypt(encrypted_b64, key, ad \\ nil)

@spec decrypt(binary(), <<_::256>>, binary() | nil) ::
  {:ok, binary()} | {:error, :decryption_failed | :invalid_base64}

Decrypt base64-encoded AES-256-GCM ciphertext.

Expects the format: base64(IV || ciphertext || tag).

decrypt_chunk(encrypted_b64, key, request_id)

@spec decrypt_chunk(binary(), binary(), String.t()) ::
  {:ok, binary()} | {:error, :decryption_failed}

Decrypt a response chunk from Lumo.

Uses AD string lumo.response.{request_id}.chunk.

encrypt(plaintext, key, ad \\ nil)

@spec encrypt(binary(), <<_::256>>, binary() | nil) :: binary()

Encrypt plaintext with AES-256-GCM.

Returns the concatenation of IV (12 bytes) + ciphertext + tag (16 bytes), base64-encoded.

encrypt_request_key(raw_key)

@spec encrypt_request_key(binary()) :: {:ok, binary()} | {:error, String.t()}

Encrypt the AES request key to Lumo's PGP public key using gpg.

Returns {:ok, base64_encrypted_key} or {:error, reason}.

Requires gpg (GnuPG) to be installed. Uses a temporary homedir to avoid polluting the user's keyring.

encrypt_turn_content(content, key, request_id)

@spec encrypt_turn_content(binary(), binary(), String.t()) :: binary()

Encrypt a turn's content for Lumo U2L.

Uses AD string lumo.request.{request_id}.turn.

generate_request_id()

@spec generate_request_id() :: String.t()

Generate a UUID v4 request ID.

generate_request_key()

@spec generate_request_key() :: binary()

Generate a random 32-byte AES-256-GCM key.

gpg_available?()

@spec gpg_available?() :: boolean()

Check if gpg is available on the system.

lumo_pub_key()

@spec lumo_pub_key() :: String.t()

Returns Lumo's production PGP public key.