← Back to Maildown Conformance

Maildown Conformance

Use this checklist to test a second Maildown implementation against the current reference behavior.

Versions

An implementation must recognize:

  • plaintext message version: maildown: 1
  • signing prefix: maildown-signature-v1
  • envelope version: maildownEnvelope: 2
  • envelope algorithm: maildown-envelope-v2
  • payload algorithm: aes-256-gcm
  • recipient key algorithm: ml-kem-768

Unknown required versions or algorithms must fail closed.

Message Parser

The parser must:

  • require a leading --- header delimiter
  • read header lines until the second ---
  • parse key: value header lines
  • parse bracketed lists such as [athena, odysseus]
  • parse JSON-quoted strings in scalar or list values
  • keep the body after the second delimiter
  • trim trailing whitespace from parsed body

The parser should ignore blank header lines and comment lines that start with #.

Renderer

The renderer must include these header fields when present:

maildown
messageId
ts
from
to
cc
bcc
subject
type
priority
thread
replyTo
files
role
sigAlg
sigKey
sig
pqSigAlg
pqSigKey
pqSig

The renderer should write:

---
<header>
---
<body>

Canonical Signing Payload

The signing payload must be:

maildown-signature-v1
<canonical-json>

The canonical JSON object must contain:

maildown
messageId
ts
from
to
cc
subject
type
priority
thread
replyTo
files
role
body
data

Rules:

  • sort object keys lexicographically
  • omit fields with undefined values
  • preserve array order
  • encode primitives with JSON rules
  • normalize to, cc, and files to arrays
  • normalize missing data to null
  • normalize missing maildown to 1

bcc must be excluded from the signature version 1 signing payload. A closed-circle implementation must reject accepted-delivery messages with any bcc value until a future signature version defines signed recipient-private metadata.

Signature Verification

An implementation must verify:

  • Ed25519 with SPKI DER public keys
  • ML-DSA-65 with raw public keys
  • key id matches an active public key for the sender
  • key algorithm matches the signature algorithm

When requireHybridSignatures is true, both Ed25519 and ML-DSA-65 signatures must verify.

Envelope Encryption

Envelope encryption must:

  • generate a random 32-byte content key
  • encrypt the rendered Markdown payload with AES-256-GCM
  • generate a 12-byte nonce for each AES-GCM record
  • store GCM tag separately as base64
  • use ML-KEM-768 to wrap the content key for each recipient
  • derive wrapping keys with HKDF-SHA-256
  • use the envelope AAD for payload encryption
  • use key:<identity>:<keyId> as AAD for wrapped content keys

HKDF input:

ikm = ml_kem_shared_secret
salt = envelope_aad
info = "maildown-envelope-v2:<identity>:<keyId>:<base64_sha256_kemCipherText>"
len = 32

Envelope Decryption

Envelope decryption must:

  • reject unsupported envelope versions
  • select the recipient entry by local identity
  • require recipient algorithm ml-kem-768
  • decapsulate with the local ML-KEM-768 secret key
  • derive the same wrapping key
  • decrypt the content key
  • decrypt the payload
  • parse and verify the plaintext message before trusting it

Policy

Closed-circle policy must reject a message when:

  • sender is not a circle member
  • recipient is not a circle member
  • sender identity is missing or inactive
  • sender lacks send permission for the recipient
  • required signatures are missing or invalid

Reference Commands

Run the core conformance smoke:

cd tvl-maildown
npm test

Run the MCP smoke:

cd tvl-maildown-mcp
npm test

Run a deployed smoke:

cd tvl-maildown-mcp
MAILDOWN_LOGIN_PASSWORD=<password> npm run smoke:remote

Fixture Status

The repository does not yet include fixed conformance fixtures. The next useful step is to add:

  • one signed plaintext message
  • one envelope with one recipient
  • one envelope with two recipients
  • one policy rejection case
  • one malformed envelope case