Postvox Protocol =================== *STATUS: Draft/Proof-of-concept* *VERSION: 0.0.1* Postvox: A modern social network in the classical style. The protocol is meant to be: - Open. Any client or server can participate in the network. - Distributed. No single entity should control the flow of information through the network. - Simple to implement. It should be relatively easy to create a new client or server. - Extensible. Implementations should be able to layer new functionality on top of the protocol without breaking backwards compatibility. - Secure. Encryption should be built in. Table of contents -------------------- - [0. Overview](#0-overview) - [Postvox streams](#postvox-streams) - [Data philosophy](#data-philosophy) - [1. Interchange server protocol](#1-interchange-server-protocol) - [Synchronization](#synchronization) - [Timestamps](#timestamps) - [Input limits, validation, normalization](#input-limits-validation-normalization) - [2. Authentication and encryption](#2-authentication-and-encryption) - [Public and private encryption keys](#public-and-private-encryption-keys) - [Signing stanzas](#signing-stanzas) - [Encrypted stanzas](#encrypted-stanzas) - [Private streams](#private-streams) - [3. Accessing interchange endpoints](#3-accessing-interchange-endpoints) - [4. Interchange endpoints](#4-interchange-endpoints) - [Begin or resume a session](#begin-or-resume-a-session) - [Request push notifications for stanzas published to a stream](#request-push-notifications-for-stanzas-published-to-a-stream) - [Append a stanza to a stream](#append-a-stanza-to-a-stream) - [Read a list of stanzas from a stream](#read-a-list-of-stanzas-from-a-stream) - [Read a specific stanza from a stream](#read-a-specific-stanza-from-a-stream) - [5. Stanzas](#5-stanzas) - [Common fields](#common-fields) - [Stanza URLs](#stanza-urls) - [Message stanza](#message-stanza) - [UserProfile stanza](#userprofile-stanza) - [UserStatus stanza](#userstatus-stanza) - [Vote stanza](#vote-stanza) - [Settings stanza](#settings-stanza) - [Invite stanza](#invite-stanza) - [Envelope stanza](#envelope-stanza) 0. Overview ============== The Postvox network is a network of databases and end-user clients. Each database (called an **interchange server**) stores one or more streams of "posts" to or from users, plus user metadata. ![Postvox architecture diagram](architecture-diagram.png) - Each user owns one or more **streams**, which are time-ordered lists of stanzas (messages, invitations, and other notifications). - All the streams for a single user are hosted on an **interchange server**1. - One interchange server can host one or more users and their streams. The feature that makes Postvox a "social" network (as opposed to a point-to- point messaging protocol) is that users can *subscribe* to messages published by and sent to other users. This makes it easy and natural to be a part of larger, loosely-connected conversations. Furthermore, a "user" doesn't even have to correspond to a real person. It's possible (and reasonable) to create a "user" that is just a gathering point for a community or topical discussion. For example, one could create a user named "technews". Anyone who wished could then direct messages to @technews, and the messages would be pushed to anyone who followed @technews. 1For the purposes of this document, an "interchange server" is any service that can be identified by and reached via a single URL. That means an "interchange server" may actually be a cluster of load-balanced, distributed servers behind that one URL. It makes no difference to the protocol. Postvox streams ------------------ A Postvox stream is an ordered list of [stanzas](#5-stanzas). There are three main actions a client can perform on the Postvox network: 1. Append a stanza to a stream. 2. Read stanzas from a stream. 3. Subscribe to or unsubscribe from a stream. Streams are identified by a Postvox URL. A typical Postvox URL looks like this: vox:/ e.g.: vox:spacemaus/friends or, for the default public stream: vox:spacemaus The `` part of the URL is a user's nickname. It is translated into a real internet hostname by looking it up at the Hub. See [Hub protocol](Hub-Protocol.md) for details. (One might reasonably ask why the nickname isn't just a real hostname to begin with. The reason is so that your nickname [and therefore your identity] is not tied to any particular host or service provider. The extra level of indirection means that you can move your hosting provider freely without losing your history or connections.) If `` is omitted from a URL, it refers to the owner's default public stream. Each stream can have its own privacy settings. Private streams can be encrypted. Data philosophy ------------------ Some simple guidelines: 1. When you as a user do anything, you should publish the fact to (a) your own interchange server, and (b) the interchange server of the target of that action. *E.g., if you've subscribed to a stream, you might publish a VOTE stanza to that stream and to your own public stream.* 2. If you have a stanza you want someone else to see, you should push it to their interchange server. *E.g., if you've @mentioned someone, you should publish that message to your own server, then clone it to their server.* 3. If you are interested in a stream, you are responsible for ensuring your view of that stream is up-to-date. *E.g., the server may push messages to a client, but the client is responsible for checking that there are no gaps in the stream.* 1. Interchange server protocol ================================= The protocol is REST-shaped, although in the typical case most of the transport happens over a socket interface rather than via raw HTTP requests. An interchange server stores and serves the streams for one or more users. Clients (applications and other interchange servers) can "subscribe" to receive push messages when a user publishes new stanzas to a stream. Whenever a client wants to see a certain user's stream, they will connect to that user's registered interchange server. Whenever a client wants to direct a stanza to a certain user, they will push the stanza directly to that user's registered interchange server. A single stanza may be copied to multiple places in the network: - On the author's interchange server. - On the interchange server's of the author's followers. - On the client devices of the author and the followers. In addition, a message may be "cloned" when the author directs it to one or more other users (e.g., via "@yourname"). In this case, the clones will have a reference to the original message's URL, but will otherwise be independent of each other. It is the responsibility of the *client* program to ensure that a message is pushed to the interchange servers of its explicit addressees. Synchronization ------------------ When an interchange client is connected to an interchange server, the *client* is responsible for ensuring that its view of the server's resources is up to date. That is, if an interchange server queues a message for delivery to a connected client, but the connection is broken before the client received the message, it is the client's responsibility to (a) reconnect to the server, and (b) request any messages it may have missed. Each stanza in a stream has a `seq` field assigned by the server. The `seq` values in a stream are consecutive integers starting from 1. If a client finds a gap in the `seq` values it has for a stream, then it can send a read request for the stanzas in the missing interval. When stanzas are updated or deleted, the interchange should store a "tombstone" at the `seq` of the old version. Timestamps ------------- All timestamps are represented as UNIX timestamps in milliseconds. Implementations should reserve at least 48 bits for timestamp values. Timestamps that are named `createdAt`, `updatedAt`, or `deletedAt` are assigned by *client* programs. Thus they are subject to whatever clock values each client may provide, and are not necessarily reliable values for sorting stanzas from different clients. The only assumption is that for any given nickname, the `updatedAt` timestamps will be monotonically increasing. Timestamps named `syncedAt` are assigned by the receiving interchange server. Thus clients may sort stanzas by their `syncedAt` timestamps, as long as those stanzas come from the same interchange server. Input limits, validation, normalization ------------------------------------------ The protocol does not currently specify any particular limits on the size of inputs or rate of requests. Interchange servers may respond with a status of `413: Request Entity Too Large` or `503 Service Unavailable`, respectively. Nicknames must adhere to the [Hub protocol](Hub-Protocol.md). URLs are normalized to their Unicode lowercase form. 2. Authentication and encryption =================================== Public and private encryption keys ------------------------------------- When a user registers a nickname at the Hub, they must include a public encryption key: an RSA key with modulus = 2048 and exponent = 65537. (TODO is it necessary and sufficient to spec this?) Any user can then verify or decrypt messages from any other user by looking up their public key at the Hub. Interchange servers and the Hub will reject stanzas that claim to be from a user, but whose signatures do not match the key on file with the Hub. Signing stanzas ------------------ Every user-generated stanza is signed with the user's corresponding private key. Interchange servers and clients must verify that the stanzas' signatures match the key on record at the Hub, as of the `updatedAt` timestamp in the stanza. Whenever `sig` appears in a stanza or parameter list, it is defined as the concatenation of the other stanza fields, sorted in alphabetical order by the field name, hashed with `SHA-1`, signed by the author's private key, and base64 encoded: values = sortAndConcatenateFields(stanza) sig = base64(privateKey.hashAndSign('sha1', values)) When concatenating fields: - Each field value is terminated by '\x00'. - Integer and timestamp values are decimal encoded. - Boolean values are represented as `"true"` or `"false"`. Interchange servers and clients must reject any stanza whose signature is invalid. If an interchange server receives stanza with an invalid signature, it must respond with a status of `403: Forbidden`. Encrypted stanzas -------------------- If a user wishes to send a private, encrypted stanza to another user, they may enclose it in an [Envelope stanza](#envelope-stanza). - The envelope stanza specifies the minimum public information needed to route the message, along with the encrypted content stanza. - The envelope may include the key needed to decode the contents, or it may omit the key if it was previously distributed via the process specified in the [Private streams](#private-streams) section. - If the key is included, it is encoded with the intended recipient's registered public key. Private streams ------------------ A stream may be marked as **private**. When a stream is private, then the interchange server will accept only [Envelope stanzas](#envelope-stanza) that are signed with a user-generated, stream-specific key. A user may create a private stream and invite others via this process: 1. User "A" generates a public/private keypair (`streamPubkey` and `streamPrivkey`) and a symmetric encryption key (`contentKey`). 2. User "A" sends a [Settings stanza](#settings-stanza) to the `vox:A/friendsonly` stream. This stanza includes the *public* key from (1). 3. User "A" sends an [Envelope stanza](#envelope-stanza) to the streams of each of their friends ("B/private", "C/private", etc.). This envelope contains: - contents = an [Invite stanza](#invite-stanza) with the `inviteTo` stream name, `streamPrivkey`, and `contentKey`. The stanza is JSON-encoded and encrypted with `contentKey`. - contentKey = `publicKeyEncrypt(friendPubkey, contentKey)` Now, when a user wishes to post to the private stream: 1. User "B" creates a stanza (e.g., a [Message](#message-stanza)). 2. User "B" looks up their invitation for the private stream and locates `streamPrivkey` and `contentKey` for the stream. 3. User "B" posts an [Envelope stanza](#envelope-stanza) with these fields: - nick = "__private__" - stream = "A/friendsonly" (for example) - contents = `symmetricEncrypt(contentKey, stanza)` 3. Accessing interchange endpoints ===================================== Endpoints can be accessed over a socket connection (currently whatever is implemented by `socket.io`) or via an HTTP request: #### Socket commands When sending a command over a socket connection, the command stanza must have a event name that matches the HTTP method name (i.e., "GET", "POST", "SUBSCRIBE", or "UNSUBSCRIBE"), and its data must be a JSON object with these fields: Name | Type | Details :----|:-----|:------- url | URL | The `vox:` URL being requested, e.g. "vox:spacemaus/friends". payload | Object | The data payload of the command. See the **Parameters** sections in the documentation for the individual endpoints below. For example, to fetch recent stanzas from `spacemaus`'s "friends" stream (in Node.js): ```js var io = require('socket.io-client'); var socket = io.connect(interchangeUrl, { transports: ['websocket'] }; socket.emit('GET', { url: 'vox:spacemaus/friends', payload: { type: 'MESSAGE', seqAfter: 100, limit: 20 }}, function(reply) { console.info('Got reply status: %d, number of stanzas: %d', reply.status, reply.stanzas ? reply.stanzas.length : 0); }); ``` #### HTTP commands When sending a command via an HTTP request, the URL must have these query parameters: Name | Type | Details :----|:-----|:------- method | String | The Postvox method name, if the Postvox method is not POST or GET. E.g., "SUBSCRIBE vox:spacemaus" translates to a URL like "POST http://vanilla.postvox.net?owner=spacemaus&method=SUBSCRIBE" #### Status codes Status codes for both the socket and HTTP endpoints are HTTP status codes. Common codes: - 200: OK. - 400: Client error. Probably a missing or poorly-formatted parameter. - 404: Not found. Either a non-existent user, or a non-existent endpoint. - 403: Not authorized. The `sig` field does not match the expected signature. - 409: Conflict. Either a too-old `updatedAt` timestamp or a duplicate transaction ID was provided. - 500: Server error. - 503: Not available. Possibly due to rate-limiting. #### NOTE The Postvox protocol does not specify endpoints for certain things that a end-user client would probably like (for example, muting replies to a thread). Those are the purview of an end-client protocol, which may or may not be a superset of the interchange protocol. The interchange protocol is specifically for services that are needed for the peer-to-peer exchange of streams and user metadata. 4. Interchange endpoints =========================== - [Begin or resume a session](#begin-or-resume-a-session) - [Request push notifications for stanzas published to a stream](#request-push-notifications-for-stanzas-published-to-a-stream) - [Append a stanza to a stream](#append-a-stanza-to-a-stream) - [Read a list of stanzas from a stream](#read-a-list-of-stanzas-from-a-stream) - [Read a specific stanza from a stream](#read-a-specific-stanza-from-a-stream) Begin or resume a session ----------------------------- Socket form: POST vox:__session__/session[/] HTTP form: POST /session[/] A "session" is a relationship between an interchange server and client. It is meant to be long-lived. If a client wants to receive push messages from the server, it needs to first establish a session. Whenever the client reconnects to the server, it can reestablish that same session to resume receiving push messages. #### Parameters Name | Type | Details :----|:-----|:------- version | String | The version of the Postvox protocol that the client understands. agent | String | The client's agent string. Contents are unspecified. [webhook] | URL | If specified, then messages will be pushed by making a POST request to this URL. May not be supported by all servers. #### Returns Name | Type | Details :----|:-----|:------- status | int | The status code of the result. See [Status codes](#status-codes). version | String | The version of the Postvox protocol that the server understands. agent | String | The server's agent string. Contents are unspecified. terms | String | A link to the server's terms of service. [newSessionId] | String | If a new session was created, then its ID is returned. Even if a client passes a `sessionId` in its request, it is not guaranteed that the server will be able to resume that session. Thus, clients must always check this parameter and -- if it is set -- reissue any `ROUTE` commands it may need. [error] | String | If `status` is not 200, a string describing the error. Request push notifications for stanzas published to a stream --------------------------------------------------------------- Socket form: SUBSCRIBE vox:[/] UNSUBSCRIBE vox:[/] HTTP form: POST /[]?source=&method=[SUBSCRIBE|UNSUBSCRIBE] Requests push notifications for any stanzas published to the given stream. The client must first create a session before requesting notifications. UNSUBSCRIBE does the opposite of SUBSCRIBE. #### Parameters Name | Type | Details :----|:-----|:------- sessionId | String | The session ID assigned by a previous call to [/session](#begin-or-resume-a-session). updatedAt | Timestamp (ms) | The timestamp of the request. For a given `sessionId` and stream URL, the server will accept only commands with a timestamp larger than the largest `updatedAt` received for the given URL so far. #### Returns Name | Type | Details :----|:-----|:------- status | int | The status code of the result. See [Status codes](#status-codes). [error] | String | If `status` is not 200, a string describing the error. Append a stanza to a stream ------------------------------ Socket form: POST vox:[/]?source= #### Parameters Name | Type | Details :----|:-----|:------- stanza | A [Stanza](#5-stanzas) | The stanza to append. #### Returns Name | Type | Details :----|:-----|:------- status | int | The status code of the result. See [Status codes](#status-codes). stanza | A [Stanza](#5-stanzas) | The stanza that was posted. Servers may choose to return a subset of the stanza that contains only the fields assigned by the server (for example, `seq`). [error] | String | If `status` is not 200, a string describing the error. #### Publishes The stanza will be published to any clients that have subscribed to the stream. Read a list of stanzas from a stream --------------------------------------- Socket form: GET vox:[/] HTTP form: GET /[]?source= #### Parameters Name | Type | Details :----|:-----|:------- [limit] | int | The maximum number of stanzas to return. Defaults to 40. [seqStart] | int | Return only stanzas with `seq` equal to or larger than this value. Defaults to 1. [seqLimit] | int | Return only stanzas with `seq` less than this value. Defaults to infinity. [reverse] | bool | Return stanzas in reverse `seq` order. [nick] | String | If set, then returns the messages that have this `nick` value. [stanzaUrl] | String | If set, then returns the stanza that has this `stanzaUrl` value. [thread] | String | If set, then returns the messages that have this `thread` value. Valid only for requests where `type` = "MESSAGE". [replyTo] | String | If set, then returns the messages that have this `replyTo` value. Valid only for requests where `type` = "MESSAGE". [opSeq] | String | If set, then returns the stanzas that have this `opSeq` value. #### Returns Name | Type | Details :----|:-----|:------- status | int | The status code of the result. See [Status codes](#status-codes). stanzas | Array of [Stanzas](#5-stanzas) | The requested stanzas. [error] | String | If `status` is not 200, a string describing the error. > TODO specify `auth` for private streams. Read a specific stanza from a stream --------------------------------------- Socket form: GET vox:[/]/ HTTP form: GET /[/]?source= Note that if a client wishes to retrieve the most recent version of a message, they should send a request for that message URL. #### Parameters *None* #### Returns Name | Type | Details :----|:-----|:------- status | int | The status code of the result. See [Status codes](#status-codes). stanza | A [Stanza](#5-stanzas) | The requested stanza. [error] | String | If `status` is not 200, a string describing the error. 5. Stanzas ============= Stanzas are the chunks of content that users read from and write to streams. - [Common fields](#common-fields) - [Stanza URLs](#stanza-urls) - [Message stanza](#message-stanza) - [UserProfile stanza](#userprofile-stanza) - [UserStatus stanza](#userstatus-stanza) - [Vote stanza](#vote-stanza) - [Settings stanza](#settings-stanza) - [Invite stanza](#invite-stanza) - [Envelope stanza](#envelope-stanza) Common fields ----------------- Every stanza has these fields. Name | Type | Details :----|:-----|:------- type | String | The type of the stanza. nick | String | The nickname of the author of the stanza. stream | String | The stream that the stanza belongs to. updatedAt | Timestamp (ms) | The (client-provided) timestamp of the stanza. seq | int | The sequence value of the stanza in its stream. Assigned by the interchange server. [op] | String | The operation to apply to the stream. One of "POST", "PUT", or "DELETE". Defaults to "POST". [opSeq] | int | The `seq` value of the stanza that is the target of a "PUT" or "DELETE" `op`. sig | String | The Base64 encoded signature of the author (see [Authentication](#2-authentication-and-encryption)). Stanza URLs -------------- Each stanza has a unique URL of the form `vox:[/]/`. For example: "vox:spacemaus/friends/3". Given a stanza, the URL can be constructed as `vox:/`. Message stanza ----------------- A MESSAGE stanza is a user-to-user communication. A message always exists in only one stream. A message may be cloned from an original. In this case, the message's `clone` field will be set. Clones are created when a stanza is published to multiple streams. For example, a stanza posted to the "spacemaus" stream with the text "@seadogg Hello!" can be cloned to the "seadogg" stream. Messages can be sent in reply to other messages. In this case, the messages' `replyTo` and `thread` fields should be set. The `replyTo` and `thread` fields should specify the URLs of the *original* messages, not their clones. Name | Type | Details :----|:-----|:------- type | String | `"MESSAGE"`. nick | String | The nickname of the author. E.g., `spacemaus`. stream | String | The stream that the stanza is in. The format is `[/"`. interchangeUrl | URL | The URL of the user's interchange server. pubkey | String | The user's public key. about | String | Details about the user. Probably a string in JSON format. updatedAt | Timestamp (ms) | The (client-provided) timestamp of the stanza. hubCreatedAt | Timestamp (ms) | The timestamp when this profile was first received by the Hub. hubSyncedAt | Timestamp (ms) | The timestamp that the profile was received by the Hub. syncedAt | Timestamp (ms) | The timestamp that the profile was received by the interchange server. sig | String | The Base64 encoded signature of the author (see [Authentication](#2-authentication-and-encryption)). hubSig | String | The Base64 encoded signature from the Hub (see [Authentication](#2-authentication-and-encryption)). seq | int | The sequence number assigned by the interchange server. [op] | String | **For UserProfiles, this should always be unset.** [opSeq] | int | **For UserProfiles, this should always be unset.** #### `sig` fields - about - interchangeUrl - nick - op - opSeq - pubkey - stream - type - updatedAt **NOTE**: When the user's `pubkey` has changed, the `UserProfile.sig` field notifying others of the change MUST be signed with the user's **previous** private key. **NOTE**: This stanza notifies others about updates to a user's profile details (e.g. `pubkey` and `interchangeUrl`), which should change rarely. There is a sibling stanza [UserStatus](#UserStatus-stanza) that updates a user's current status, which may change frequently. **NOTE**: This is an exact copy of the data registered at the Hub. Posting it to the Hub and to the user's interchange server notifies any of the user's followers of the update immediately, which is important when either the user's `pubkey` or `interchangeUrl` change. #### `hubSig` fields When the Hub receives a profile update, it signs these fields with its keys and stores the signature in `hubSig`. - about - hubCreatedAt - hubSyncedAt - interchangeUrl - nick - op - opSeq - pubkey - sig - stream - type - updatedAt UserStatus stanza -------------------- Name | Type | Details :----|:-----|:------- type | String | `"USER_STATUS"`. nick | String | The nickname of the user. stream | String | The stream that the status was posted to. E.g., "spacemaus/friends". statusText | String | The user-provided status text. isOnline | bool | Whether the user is online. updatedAt | Timestamp (ms) | The (client-provided) timestamp of the stanza. sig | String | The Base64 encoded signature of the author (see [Authentication](#2-authentication-and-encryption)). seq | int | The sequence number assigned by the interchange server. [op] | String | The operation to apply to the stream. One of "POST", "PUT", or "DELETE". Defaults to "POST". [opSeq] | int | The `seq` value of the stanza that is the target of a "PUT" or "DELETE" `op`. #### `sig` fields - isOnline - nick - op - opSeq - statusText - stream - type - updatedAt Vote stanza -------------- Name | Type | Details :----|:-----|:------- type | String | `"VOTE"`. nick | String | The nickname of the voter. stream | String | The stream that the vote was posted to. E.g., "spacemaus/friends". voteUrl | URL | The URL that is the subject of the vote. E.g., "vox:spacemaus/1234". tag | String | The aggregation tag. Clients may interpret this value however they like. Max 64 chars. score | int | The value of the vote. Clients may interpret this value however they like. updatedAt | Timestamp (ms) | The (client-provided) timestamp of the stanza. sig | String | The Base64 encoded signature of the author (see [Authentication](#2-authentication-and-encryption)). seq | int | The sequence number assigned by the interchange server. [op] | String | The operation to apply to the stream. One of "POST", "PUT", or "DELETE". Defaults to "POST". [opSeq] | int | The `seq` value of the stanza that is the target of a "PUT" or "DELETE" `op`. #### `sig` fields - nick - op - opSeq - score - stream - tag - type - updatedAt - voteUrl Settings stanza ------------------ Clients can post SETTINGS stanzas to a stream in order to control how the interchange server will respond to requests sent to that stream. Name | Type | Details :----|:-----|:------- type | String | `"SETTINGS"`. nick | String | The nickname of the user. stream | String | The stream that the settings are for. options | String | A JSON-encoded object. It is an encoded object so that future additions to the protocol do not alter the `sig` fields. options.streamPubkey | String | The public key that will be used to sign [Envelope stanzas](#envelope-stanza) sent by `"__private__"`. options.allowPublic | bool | Whether the interchange server will allow public stanzas. If false, then it will only accept [Envelope stanzas](#envelope-stanza) signed by `"__private__"`. sig | String | The Base64 encoded signature of the author (see [Authentication](#2-authentication-and-encryption)). updatedAt | Timestamp (ms) | The (client-provided) timestamp of the stanza. seq | int | The sequence number assigned by the interchange server. [op] | String | The operation to apply to the stream. One of "POST", "PUT", or "DELETE". Defaults to "POST". [opSeq] | int | The `seq` value of the stanza that is the target of a "PUT" or "DELETE" `op`. #### `sig` fields - nick - op - opSeq - options - stream - type - updatedAt Invite stanza ---------------- Clients can send INVITE stanzas to invite other users to private, encrypted streams. INVITE stanzas MUST always be enclosed in an [Envelope stanza](#envelope-stanza). Name | Type | Details :----|:-----|:------- type | String | `"INVITE"`. nick | String | The nickname of the inviter. stream | String | The stream that the invitation was posted to. E.g., "spacemaus/private". inviteTo | String | The name of the stream that the invitation is to. contentKey | String | The symmetric key used to encrypt and decrypt envelope contents in the `inviteTo` stream. streamPrivkey | String | The private key that is used to sign envelopes in the `inviteTo` stream. sig | String | The Base64 encoded signature of the author (see [Authentication](#2-authentication-and-encryption)). updatedAt | Timestamp (ms) | The (client-provided) timestamp of the stanza. seq | int | The sequence value of the stanza in its stream. Usually unset, since invites are sent in envelopes. [op] | String | The operation to apply to the stream. One of "POST", "PUT", or "DELETE". Defaults to "POST". [opSeq] | int | The `seq` value of the stanza that is the target of a "PUT" or "DELETE" `op`. #### `sig` fields - contentKey - inviteTo - nick - op - opSeq - stream - streamPrivkey - type - updatedAt Envelope stanza ------------------ Clients can use ENVELOPE stanzas to enclose private, encrypted stanzas. Name | Type | Details :----|:-----|:------- type | String | `"ENVELOPE"`. nick | String | The nickname of the author. This may be `"__private__"` if the contents of the envelope have been signed with a stream key instead of a user key. stream | String | The stream that the stanza belongs to. contents | String | The encrypted contents of the envelope. Generally, this will be a JSON-encoded stanza. contentKey | String | The symmetric key used to encrypt envelope contents. The key is itself encrypted with the public key of the intended recipient. In most cases, this field will only be set when the contents are an Invite stanza. updatedAt | Timestamp (ms) | The (client-provided) timestamp of the stanza. seq | int | The sequence value of the stanza in its stream. Assigned by the interchange server. [op] | String | The operation to apply to the stream. One of "POST", "PUT", or "DELETE". Defaults to "POST". [opSeq] | int | The `seq` value of the stanza that is the target of a "PUT" or "DELETE" `op`. sig | String | The Base64 encoded signature. **NOTE** If `nick` is `"__private__"`, then the private key used to sign an envelope is NOT the key of the author of the contents of the envelope. Instead, it is the private key received via an [Invite stanza](#invite-stanza). (See [Authentication](#2-authentication-and-encryption)). #### `sig` fields - contentKey - contents - nick - op - opSeq - stream - type - updatedAt