Skip to content

InfoPackages

An InfoPackage is how identity reaches someone who has no way to receive a message from you yet. It is an encrypted drop at an unguessable URL, with the key travelling out of band - in a QR code, or in a link fragment.

The server stores bytes it cannot read, at a segment it did not choose the contents of, for a party it cannot identity. It is the same shape as the blob store, with a different key-distribution story.

CompactInfoQR Structure
struct CompactInfoQR {
// Server URL to fetch encrypted data from
info_package_url: String, // e.g., "https://chat.example.com/api/v1/infopackage/abc123xyz"
// Decryption key (client holds this, never sent to server)
info_package_key: [u8; 32],
// Type of package (for display before fetching)
package_type: InfoPackageType,
// Display name (for UI, e.g., "Alice" or "Team Chat")
display_name: String,
}
#[serde(tag = "type")]
enum InfoPackageType {
#[serde(rename = "identity")]
Identity,
#[serde(rename = "group_invite")]
GroupInvite { group_id: GroupId },
}

Stores an encrypted package and returns its location.

  • Input: InfoPackageUpload { ciphertext, ttl_seconds, max_uses }
  • Output: InfoPackageReceipt { url, revocation_token, expires_at, max_uses }
  • Auth: PrivacyPass
  • Idempotent: No
  • Errors: TOKEN_INVALID, TOKEN_REPLAYED, BUDGET_EXHAUSTED, TTL_OUT_OF_RANGE, MAX_USES_OUT_OF_RANGE
  • Upload MUST spend one token
  • Clients MUST encrypt before upload; the key MUST NOT reach the server
  • Servers MUST treat the payload as opaque and MUST NOT parse it
  • Servers MUST generate the URL segment with a CSPRNG
  • Servers MUST return a revocation_token and MUST NOT accept one supplied by the client
  • Servers MUST reject ttl_seconds outside 60 seconds to 30 days
  • Servers MUST reject max_uses outside 1 to 1000
  • Servers MUST NOT associate an upload with a device, a user, or a prior upload
POST /api/v1/infopackages
Authorization: PrivateToken token="<base64url>"
Content-Type: application/octet-stream

Retrieves a package by segment.

  • Input: None - the segment is in the path
  • Output: ciphertext
  • Auth: Capability handle
  • Idempotent: No - decrements max_uses
  • Errors: INFOPACKAGE_NOT_FOUND, INFOPACKAGE_EXPIRED, INFOPACKAGE_EXHAUSTED
  • Fetch MUST NOT require a token or any authentication
  • Servers MUST decrement the remaining uses and delete the package when they reach zero
  • Servers MUST delete an expired package rather than serving it
  • Servers MUST NOT log the fetcher’s identity or source address against a segment
  • Servers MUST return the ciphertext and nothing derived from it
GET /api/v1/infopackages/{segment}

Destroys a package before its TTL or use limit is reached.

  • Input: None - the revocation token is in a header
  • Output: None
  • Auth: Revocation token
  • Idempotent: Yes
  • Errors: INFOPACKAGE_NOT_FOUND, REVOCATION_TOKEN_INVALID
  • Servers MUST compare revocation tokens in constant time
  • Servers MUST return REVOCATION_TOKEN_INVALID rather than INFOPACKAGE_NOT_FOUND when the package exists but the token is wrong
  • The revocation token MUST NOT appear in a QR code, a link, or a CompactInfoQR
  • Revocation spends no token
DELETE /api/v1/infopackages/{segment}
X-Revocation-Token: <32 bytes, base64url>

The key never touches the server. Two ways it reaches a recipient.

A CompactInfoQR encoded directly. The scanner gets the URL, the key, the type, and a display name, so it can show “Add contact ‘Alice’” before fetching anything.

https://chat.example.com/api/v1/infopackages/a1b2c3d4#<32-byte key, hex>
^ never sent to the server

Everything after # is a URL fragment, which browsers and HTTP clients do not transmit. The server receives a request for the segment and nothing else.

  • Clients MUST place the key in the fragment, never in the path or query
  • Clients MUST NOT follow a link whose fragment is absent; there is nothing to decrypt with

A package is gone when the first of these happens: its TTL expires, max_uses reaches zero, or the uploader revokes it.

Bound Range Default
ttl_seconds 60 to 2,592,000 deployment choice
max_uses 1 to 1,000 deployment choice

A single-use package with a 5-minute TTL is the right shape for an in-person QR scan. A group invite posted in a forum wants a longer life and a higher count, and accepts the exposure that follows.