Visual wire reference

Four segments. Exact bytes. No hidden negotiation.

This page explains the legacy sd1 compatibility contract. New raw-byte integrations should use the draft SDB1 profile documented in WIRE_PROTOCOL.md.

Compact form

sd1.<base64url header>.<base64url payload>.<base64url signature>
SegmentEncodingRole
sd1literal ASCIIProtocol and version marker
headerunpadded base64url of UTF-8 JSONKey-selection labels and authenticated context
payloadunpadded base64urlArbitrary bytes; may be empty
signatureunpadded base64urlRaw bytes returned by the chosen signer

The signer receives the exact ASCII bytes of:

sd1.<header segment>.<payload segment>

A verifier must not decode and reserialize the JSON before checking the signature. Different languages may order valid JSON keys differently and still verify the exact token they produced.

Trust sequence

  1. Parse the four strict wire segments.
  2. Optionally inspect untrusted routing hints.
  3. Select a signer through trusted application policy.
  4. Require its algorithm and key ID to match the header.
  5. Verify the exact original signing input.
  6. If the token carries a purpose, require the caller's complete expected purpose and reject omission or mismatch; then apply expiry policy.
  7. Only now trust or deserialize the payload.
Self-description is not self-trustAn incoming algorithm, key ID, serializer name, or schema is data. It does not authorize code loading, network key discovery, or codec selection by itself.

Included HMAC profile

The Wasm, JavaScript, and Ruby implementations include hmac-sha256. Signature bytes are the 32-byte HMAC-SHA256 digest over the exact signing input. Wasm and Ruby provide constant-time byte comparison; the native browser path delegates verification to Web Crypto.

The envelope itself stays algorithm-agnostic. P-256 or Ed25519 can satisfy the signer contract without changing sd1.

Optional ParcelStream profile

Format ID: signed-data/parcel-stream/1

FieldWidthEncoding
type2 bytesunsigned integer, network byte order
length4 bytesunsigned integer, network byte order
valuelengthopaque bytes

Parcels repeat until clean end-of-input. There is no stream header, count, or terminator. Duplicate types, ordering, and empty values are preserved. Truncated headers and values are invalid.

When signed through the serializer API, description.serialization carries the format ID and sha256: content ID of the exact serialized payload.

The optional incremental decoder can identify complete parcels across arbitrary chunk boundaries. That is a framing feature, not partial sd1 verification. See the partial-stream guide.

Portable vectors

Two committed fixtures prevent documentation drift:

  • test/fixtures/v1_hmac_sha256.json fixes a complete Ruby-produced token that Node and the browser runtime reproduce exactly.
  • test/fixtures/parcel_stream_v1.json fixes parcels, encoded bytes, and SHA-256 content identity.

The browser contract suite uses the same files as Ruby. A pretty example cannot silently become a different protocol.

Deliberate non-goals

SignedData does not provide encryption, replay storage, identity discovery, routing, mailboxes, torrents, fragmentation, generic object serialization, or automatic algorithm and serializer negotiation.

Those boundaries let a form library, a browser, a Caravan-like mailbox, or a recoverable peer fabric share the envelope without becoming the same project.

Build a legacy sd1 token → or start a new integration with SDB1 →