Docs
/
Reference

Envelope format

The exact wire format of a sealed message, field by field, and the validation an incoming envelope must pass before anything is displayed.

An envelope is UTF-8 JSON, hex-encoded into the envelope parameter of send. It is opaque to the contract, which only checks its length.

Structure

Envelope, cleartext header v: version · kv: recipient key version · sv: sender key version · ep: ephemeral public key r · sealed to the recipient n · nonce 24 bytes, random per message c · ciphertext XSalsa20-Poly1305 over the padded payload s · sealed to the sender n · nonce distinct from the recipient nonce c · ciphertext same plaintext, so sent mail stays readable ONLY THE HEADER IS READABLE. BOTH BOXES CARRY THE SAME PADDED PLAINTEXT. Both boxes share one ephemeral secret, which is discarded immediately after sealing.
Figure 8. The header is deliberately minimal. Everything meaningful is inside the sealed boxes.

Fields

FieldTypeMeaning
vnumberEnvelope version. Currently 2. A client refuses anything it does not know.
kvnumberWhich recipient key version this was sealed to, so an old key can be re-derived.
svnumberWhich sender key version the self-copy uses.
epbase64Ephemeral public key for this message only.
r.n, r.cbase64Nonce and ciphertext for the recipient.
s.n, s.cbase64Nonce and ciphertext for the sender's own copy.

Sealed payload

{
  subject:  "...",        // at most 512 characters
  body:     "...",        // at most 64 KB
  sentAt:   1730000000,   // client clock, untrusted
  from:     "0x...",      // must match the event's from topic
  to:       "0x...",      // must match the event's to topic
  chainId:  56,           // binds to one network
  mailbox:  "0x...",      // binds to one MailBox deployment
  skv:      1             // sender key version
}

The last four fields are what make a copied envelope useless. A client compares them against the event it actually observed and rejects a mismatch, so an envelope lifted from the log cannot be re-emitted as somebody else's message or replayed onto a different deployment.

Padding

Before sealing, the JSON is length-prefixed with four bytes and padded with zeroes up to a multiple of 256.

target = ceil((len + 4) / 256) * 256

On opening, the prefix gives the true length and the padding is discarded. This collapses messages into size buckets so an observer learns less from the ciphertext length. It reduces the leak rather than removing it: a 40 KB message is still visibly larger than a one-liner.

Opening: the validation order

Order matters. Each step assumes the previous one passed.

  1. Version. Unknown v is refused outright.
  2. Screen the ephemeral key. Small-order points, non-canonical encodings, and a zero shared secret are all rejected. Skipping this is what made an earlier revision vulnerable from the receiving side.
  3. Pick the role. Open r if you are the recipient, s if you are the sender. Roles are never tried interchangeably.
  4. Open and authenticate. A failed Poly1305 tag ends processing.
  5. Unpad and parse. Malformed JSON is discarded.
  6. Validate the payload. Types and length bounds on every field.
  7. Compare routing. from, to, chainId and mailbox must match the observed event.
Decryption is not authentication

A valid tag proves the ciphertext was not altered in transit. It proves nothing about who composed it. Every field inside the payload is untrusted input and is escaped before display.

Return value

open() returns the payload alongside the version metadata, so a client can surface a mismatch rather than hiding it:

{
  payload,          // the validated contents
  keyVersion,       // the key that actually opened it
  claimedVersion,   // the version the envelope claimed
  cipher            // which box was opened, r or s
}

Version history

VersionStatusChange
1SupersededNo routing commitment, so envelopes could be replayed and re-attributed.
2CurrentRouting commitment, padding, dual boxes, key versioning, peer-key screening.
3PlannedXChaCha20-Poly1305. Clients keep opening version 2 indefinitely.

bMail is an independent, community-built project. It is not affiliated with, endorsed by, or operated by Binance or BNB Chain.