Legacy compatibility · sd1 runtimes

Legacy sd1: Wasm first. JavaScript around it. Ruby beside it.

This page documents the older JSON/Base64 sd1 compatibility stack: WebAssembly is its portable execution core, JavaScript is its primary ergonomic API, and Ruby is its complete second-language implementation.

Not the SDB1 runtime guideEvery token, API, fixture, and compatibility claim below concerns the text-oriented sd1 format. New raw-byte integrations should start with SDB1.
shipped in this repository packaging next profile design

Order and responsibility

Three layers, no duplicated meaning.

Each runtime has a job that can be tested independently.

02
JS

JavaScript API

primary human interface

  • sign, inspect, and verify envelopes
  • Wasm, Web Crypto, or pure JS engines
  • serializers and incremental decoder
  • browser and Node-compatible ES modules
reference ES modules shipped
03
Rb

Ruby gem

full second language

  • idiomatic binary-string API
  • zero runtime gem dependencies
  • same envelope and ParcelStream bytes
  • readable Minitest contract suite
gem source · Ruby 3.2+

Legacy sd1 playground · interoperability

Make four engines prove one legacy sd1 token.

The expected sd1 token is a checked-in fixture generated and verified by Ruby. This browser recomputes it through three independent web paths.

Fixed inputs

public test vector
Payload
hello
Secret
interop-secret
Key ID
key-1
Purpose
example/message/1
Issued
1700000000

Ready to compare.

W
WebAssembly coreHMAC + token through JS APIwaiting…
—
C
Web Cryptonative browser cryptographywaiting…
—
JS
Pure JavaScriptdependency-free fallbackwaiting…
—
Rb
Ruby fixturecanonical cross-runtime expectationchecked in
✓

Host the legacy sd1 Wasm core

The browser wrapper loads the committed module and turns its pointer-based ABI into safe Uint8Array operations. The core never decides purposes, parses application objects, or discovers keys.

import {
  createWasmHmacSigner,
  loadSignedDataWasm
} from "./signed-data-wasm.mjs";
import { sign, utf8 } from "./signed-data.mjs";

const core = await loadSignedDataWasm();
const signer = createWasmHmacSigner(core, secret, {
  keyId: "messages-2026-08"
});

const token = await sign(utf8("hello"), {
  signer,
  purpose: "example/message/1"
});
Why raw pointers?The ABI uses caller-owned linear-memory offsets, so it does not require a JavaScript object runtime. A Ruby Wasm host, embedded runtime, or another language can call the same small functions.

Use JavaScript as the friendly legacy sd1 API

JavaScript owns envelope ergonomics and chooses the execution engine. The same sign and verify calls work with Wasm, Web Crypto, pure JavaScript, or a future asymmetric signer.

const hint = peek(token); // untrusted routing data
const signer = trustedKeyring.get(hint.keyId);

const envelope = await verify(token, {
  signer,
  purpose: "example/message/1"
});

use(envelope.payload); // trusted now

Use Ruby as the second legacy sd1 language

The gem is a peer implementation of the same contract. It should feel like Ruby, but it must never invent Ruby-only wire behavior.

require "signed_data"

signer = SignedData::HMACSigner.new(
  ENV.fetch("SIGNED_DATA_SECRET"),
  key_id: "messages-2026-08"
)

envelope = SignedData.verify(
  token,
  signer: signer,
  purpose: "example/message/1"
)

use(envelope.payload)

Packaging status

LayerNowNext clean step
Wasmreproducible source, WAT, and browser-ready artifactstandalone release artifact with ABI changelog
JavaScriptdependency-free ES modules used by this siteversioned npm package with browser/Node exports
Rubygem source and stable idiomatic APIrelease after the multi-runtime contract settles

The repository does not pretend an npm or gem publication happened when it did not. The checked-in modules are usable today; package registries are distribution work, not protocol work.

Legacy sd1 compatibility proof

npm ci
npm run build:wasm
bundle exec rake test:browser
bundle exec rake test:interop
bundle exec rake test:all

Tests cover published SHA/HMAC examples, long messages that grow Wasm memory, token scanning, every ParcelStream split point, and exact equality with Ruby fixtures.

Next: learn what incremental parcels do—and do not—mean →