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.
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 },}UploadInfoPackage
Section titled “UploadInfoPackage”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
Requirements
Section titled “Requirements”- 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_tokenand MUST NOT accept one supplied by the client - Servers MUST reject
ttl_secondsoutside 60 seconds to 30 days - Servers MUST reject
max_usesoutside 1 to 1000 - Servers MUST NOT associate an upload with a device, a user, or a prior upload
HTTP Binding
Section titled “HTTP Binding”POST /api/v1/infopackagesAuthorization: PrivateToken token="<base64url>"Content-Type: application/octet-streamFetchInfoPackage
Section titled “FetchInfoPackage”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
Requirements
Section titled “Requirements”- 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
HTTP Binding
Section titled “HTTP Binding”GET /api/v1/infopackages/{segment}RevokeInfoPackage
Section titled “RevokeInfoPackage”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
Requirements
Section titled “Requirements”- Servers MUST compare revocation tokens in constant time
- Servers MUST return
REVOCATION_TOKEN_INVALIDrather thanINFOPACKAGE_NOT_FOUNDwhen 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
HTTP Binding
Section titled “HTTP Binding”DELETE /api/v1/infopackages/{segment}X-Revocation-Token: <32 bytes, base64url>Key Distribution
Section titled “Key Distribution”The key never touches the server. Two ways it reaches a recipient.
QR Code
Section titled “QR Code”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.
Link Fragment
Section titled “Link Fragment”https://chat.example.com/api/v1/infopackages/a1b2c3d4#<32-byte key, hex> ^ never sent to the serverEverything 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
Lifetime
Section titled “Lifetime”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.