Host the portable core
Use the Wasm signer and digest through the friendly JavaScript envelope API.
Read pattern ↓Legacy compatibility · sd1 integration patterns
sd1 envelope. Keep your architecture.These practical patterns use the older JSON/Base64 sd1 envelope and its multi-runtime APIs, add one capability at a time, and show where SignedData stops.
sd1 tokens and APIs. New raw-byte integrations should start with SDB1.Use the Wasm signer and digest through the friendly JavaScript envelope API.
Read pattern ↓Bind form policy to a purpose and short lifetime while planning legacy migration separately.
Read pattern ↓Inspect locally, select a key from trusted configuration, and verify before displaying authenticated claims.
Read pattern ↓Handle arbitrary read boundaries while keeping framing completeness separate from trust.
Read pattern ↓Use an opaque key ID as a routing hint, then require the chosen signer's identity to match exactly.
Read pattern ↓Sign the complete token first, fragment it outside the envelope, reconstruct it, then verify.
Read pattern ↓Implement three explicit methods and keep code selection in trusted application policy.
Read pattern ↓Assign compact type numbers without asking SignedData to know a mailbox, actor, or message schema.
Read pattern ↓sd1 Wasm core through JavaScriptLet Wasm own deterministic byte-heavy operations and JavaScript own the readable envelope policy. Switching to Web Crypto or the pure-JavaScript fallback does not change the sign/verify API.
const core = await loadSignedDataWasm();
const signer = createWasmHmacSigner(core, secret, {
keyId: "messages-2026-08"
});
const token = await sign(payload, {
signer,
purpose: "example/message/1"
});
Ruby services can create or verify the same token with the gem. Use the live interoperability lab to watch all paths reproduce one fixed vector.
Use a purpose that names the action, not merely “form.” Include a short expiry when the signed value authorizes submission.
purpose: "account/contact-form/1"
expires_at: Time.now.to_i + 15.minutes.to_i
Verify with that exact purpose before parsing or applying the payload. The current SignedForm gem has a legacy compatibility token of its own. Keep that verifier isolated; migration belongs in the form profile, not this envelope.
The incremental ParcelStream decoder can consume a socket-sized chunk and emit only complete parcels. A complete parcel is framed, not necessarily authenticated.
const decoder = new ParcelStreamDecoder();
for await (const chunk of source) {
for (const parcel of decoder.feed(chunk)) {
stage(parcel);
}
}
decoder.finish();
For an sd1 payload, verify the whole envelope before acting on staged values. If the transport authenticates each chunk separately, that transport defines early-trust policy. The interactive stream guide lets you change chunk sizes and trust modes.
sd1 browser verificationA browser may inspect the header to decide which already-trusted public key should verify a token. The incoming algorithm or key ID must never cause arbitrary code or remote key material to be trusted automatically.
A Caravan-shaped message could assign 1 to sender bytes, 2 to recipient bytes, and 3 to a body. Those meanings belong to a Caravan schema, never to SignedData.
parcels = [
[1, sender_address_bytes],
[2, recipient_address_bytes],
[3, body_bytes]
]
Unknown types remain skippable. Duplicate types and ordering remain intact. An application can explicitly declare that a parcel contains another ParcelStream.
sd1 key rotationGive each active key an opaque, stable ID. A key ring can use peek only to choose a candidate, then pass that signer into strict verification.
hint = SignedData.peek(token).key_id
signer = trusted_keyring.fetch(hint)
SignedData.verify(
token,
signer: signer,
purpose: "example/message/1"
)
Keep retired verification keys for the lifetime of tokens they signed. Stop signing with an old key before removing verification support.
The sd1 token is the authenticated object. Torrenting, erasure codes, fountain codes, QR chunks, and relay frames operate outside it.
Relays can remain “dumb”: they need no signer, serializer, mailbox schema, or application runtime.
sd1A serializer has a versioned ID plus dump and load. Keep it boring and explicit.
class LinesSerializer
def id = "example/lines/1"
def dump(lines) = lines.join("\n").b
def load(bytes) = bytes.split("\n")
end
SignedData authenticates the ID and SHA-256 identity of the serialized bytes. The caller still chooses the serializer; an incoming format string never loads a Ruby constant.