Implementers' guide

How to build a conformant Label 309 implementation — the recommended layered architecture, the cross-language byte-identical contract, and the conformance test vectors that define interoperability.

Label 309 is a wire format and a set of cryptographic constructions, not a product. Any number of independent implementations — in TypeScript, Python, Rust, Go, or a native mobile runtime — can coexist, and a record produced by one MUST verify under another. This page is for the team building such an implementation. It describes the architecture that keeps the cryptographic surface auditable, the exact contract that makes two implementations interoperable, and the conformance suite that decides — mechanically — whether you have met it.

Two things make Label 309 interoperable across languages. The first is determinism: the constructions are pinned to public standards (RFC 8949 canonical CBOR, RFC 8032 Ed25519, RFC 7748 X25519, RFC 5869 HKDF, RFC 9106 Argon2id, RFC 9052 COSE), so the same inputs yield the same bytes everywhere. The second is the conformance suite: a set of byte-exact test vectors that an implementation either reproduces or does not. Conformance is a property you can check, not a claim you make.

The layered architecture

A conformant implementation SHOULD separate cryptographic primitives from application logic into distinct layers, each depending only on the one beneath it. The names below are roles, not package names; choose your own.

┌─────────────────────────────────────────────────────────┐
│  application                                            │
│  UI, routing, persistence, payments, background jobs    │
├─────────────────────────────────────────────────────────┤
│  SDK                                                    │
│  service client + standalone verifier + helpers         │
├─────────────────────────────────────────────────────────┤
│  wire-format library                                    │
│  schema · structural validator · canonical-CBOR codec   │
├─────────────────────────────────────────────────────────┤
│  cryptographic core                                     │
│  hashes · KDFs · signatures · KEM · AEAD · CBOR · COSE  │
│  no application or framework dependencies               │
└─────────────────────────────────────────────────────────┘

The boundaries are load-bearing, not cosmetic. Each layer has a single job and a short list of things it is forbidden to know about.

The cryptographic core

The bottom layer holds primitives only: hash functions, KDFs, signature and KEM operations, the AEAD content layer, canonical CBOR, COSE_Sign1, the sealed-PoE wrap/unwrap construction, Merkle roots and proofs, and the typed error classes they raise. It contains no domain logic, no HTTP, no database access, and no UI- or server-framework imports.

This layer MUST stay dependency-free of anything application- or server-bound, and MUST be browser-safe, for three concrete reasons:

  • It runs everywhere. Hashing a file, building an envelope, and — critically — the standalone verifier all run in browsers, in serverless workers, and on the command line as readily as on a server. A server-only dependency (a database driver, a logging framework bound to a runtime, a UI library) would break those targets and bloat every consumer that bundles the core.
  • It is the audit surface. A reviewer can read a primitives-only package end-to-end against the RFCs. The moment application code leaks in, the surface a security reviewer must hold in their head grows without bound.
  • It is what third parties embed. An independent verifier — someone who trusts no service, only the chain — pulls in this layer and nothing above it. Keeping it small and portable is what makes "verify it yourself" practical.

Concretely, the core MUST NOT import ORM or database drivers, UI frameworks, server-bound logging frameworks, or any application module. Randomness MUST come from the platform CSPRNG (Web Crypto getRandomValues, or an equivalent re-export), never from a Node-only source, so the same source runs unchanged in a browser.

Enforce the boundary in CI, not in code review

The zero-dependency rule decays the moment a convenient import slips in. An implementation SHOULD run a dependency-graph lint that walks every import in the core and the wire-format library and fails the build on any specifier outside a per-layer allow-list. Reviewers forget; the linter does not.

The wire-format library

The next layer up owns Label 309 itself: the record schema, the structural validator, and the canonical-CBOR encoder and decoder. It depends on the cryptographic core (for hashing, COSE, and the CBOR codec) and on nothing else application-bound. Its surface is small and pure:

  • encode — produce canonical-CBOR bytes for a validated record.
  • decode — the inverse.
  • validate — run the structural and semantic checks of the standard over a decoded record and return a typed result (see Verification).

This layer is where the rules of The record live as code: the closed key set, the chunk-reassembly discipline, the items-or-merkle invariant, the canonical-CBOR requirements. Like the core, it stays free of HTTP clients, database drivers, and framework imports.

The SDK and the application

The SDK wraps the lower layers into ergonomic helpers — a service client, envelope build/unlock helpers, and the standalone verifier, the function that decodes a record, checks its structure, verifies any record signatures against the on-chain key, and produces a verdict using only public data. The standalone verifier MUST work with no network access to any implementer-operated service; its only external input is a public blockchain explorer the verifier chooses. The SDK SHOULD also remain browser-safe.

The application layer — UI, routing, persistence, billing, background jobs — is greenfield and carries no interoperability obligations. Nothing in the standard constrains how you build it, only that it sits above the verified crypto surface rather than reaching into it.

The byte-identical contract

Interoperability is a property of bytes, not of intentions. Two implementations interoperate if and only if the primitives that have no freedom in their output produce the same bytes from the same inputs. This is the parity contract, and it is the heart of conformance.

The contract splits cleanly in two. Operations whose output is fully determined by their inputs MUST be byte-identical across implementations. Operations that consume randomness cannot be byte-equal call-to-call; for those the contract is cross-consumability — a value produced by one implementation MUST be consumable by any other (a ciphertext sealed in one language decrypts in another).

Byte-identical primitives

Every operation below is a pure function of its inputs and MUST emit byte-identical output in every conformant implementation:

PrimitivePinned toOutput that must match
Seed → Ed25519 / X25519 keypairHKDF-SHA-256 with the registered info constantsderived public and private keys
HKDF-SHA-256RFC 5869output key material for fixed input
HMAC-SHA-256 slot-set MACRFC 2104slots_mac tag bytes for a fixed CEK and slot set
Argon2id (passphrase KDF)RFC 9106derived key for fixed (m, t, p, salt, len, password)
SHA-256FIPS 180-4digest
BLAKE2b-256RFC 7693digest
Canonical CBOR encodeRFC 8949 §4.2.1encoded bytes for fixed input
COSE_Sign1 encodeRFC 9052structure bytes for fixed header, payload, signature
Ed25519 sign / verifyRFC 8032 (strict)signature; verdict
X25519 ECDHRFC 7748shared secret for fixed scalars
Sealed-PoE wrap / unwrapSealed PoEper-slot bytes and MAC when ephemerals and CEK are injected
Merkle root + inclusion proofsRFC 9162 §2.1.1root and per-leaf proofs over an ordered leaf list

Two points deserve emphasis. Ed25519 is strict: a conformant verifier MUST apply the canonical-S and rejected-low-order-point rules of RFC 8032 §5.1.7, so two implementations agree not only on signatures they accept but on signatures they reject. Argon2id crosses ecosystem boundaries: different languages reach for different Argon2 libraries, but every conformant library implements RFC 9106 and MUST produce identical output for identical parameters — the parameter set, not the library, is the contract.

Randomness-consuming operations

Key generation, sealed-PoE wrapping under fresh per-slot ephemerals, and envelope encryption all draw fresh randomness, so their output differs every call and cannot be byte-pinned. The contract for these is cross-consumability: output produced by one implementation MUST be consumable by every other. A record sealed in one language MUST decrypt in another; a keypair minted in one MUST verify and encrypt-to in another. Conformance suites pin these with deterministic test hooks that inject the ephemerals — making the wrap reproducible — and with round-trip fixtures that encrypt in one language and decrypt in the other.

Conformance and test vectors

The normative test vectors are the interoperability contract. An implementation is conformant if and only if it reproduces every pinned byte string in the conformance suite from the same inputs — and emits the correct typed error code for every negative fixture. There is no partial credit and no appeal: if a comparison fails, the implementation is wrong, never the vector.

The vectors live in the standard's conformance suite, organized by primitive class: record fixtures, sealed-PoE wrap/unwrap, COSE_Sign1 signatures, HKDF, seed derivation, Argon2id, and canonical CBOR. Each pins lowercase-hex inputs and the expected outputs. To use them: feed the inputs into your implementation, compare each named output byte-for-byte, and fix your code on any mismatch.

Three obligations every implementation must meet

Reproduce the positive vectors. For every record fixture, both halves of encode(record) == expected_cbor AND the round-trip encode(decode(expected_cbor)) == expected_cbor MUST hold. The round-trip generalizes beyond the fixtures: for arbitrary well-formed input, encode(decode(x)) == x. A decoder that loses or reorders information, or an encoder that is not canonical, breaks this and fails conformance.

Emit the right rejection codes. The negative fixtures pair a deliberately malformed record with the exact typed error code a structural validator MUST raise. Reproducing the bytes of valid records is half the contract; rejecting invalid ones with the correct code is the other half. A validator that rejects a bad record for the wrong reason — or accepts it — is non-conformant. The negative fixtures are the single source of truth for cross-language rejection parity: the same malformed input MUST raise the same code in every implementation. The full catalogue of codes and their meanings is on Verification.

Match the registries. Algorithm identifiers are named strings drawn from the registries on Algorithm registries. An unrecognized identifier MUST surface the precise unsupported-algorithm code, never a silent acceptance or a panic.

Fix the implementation, never the vector

The vectors are pinned to upstream RFCs and to the deterministic constructions of this standard. When a comparison fails, the bug is in the implementation under test. Editing a vector to make a suite pass converts a real interoperability failure into a latent one that surfaces only when a record crosses implementations on chain — the worst possible time to discover it.

Run parity on every change

An implementation that ships more than one language — or wants to prove interoperability with another — SHOULD run a single continuous-integration job that builds every package, runs each language's test suite against the shared fixtures, enforces the dependency-graph lint, and checks that the fixture set is identical on both sides. A fixture added on one side but not the other fails the gate: the two implementations have silently diverged, and the build catches it before a real record does. The fixtures are the canonical source; each language holds a byte-identical mirror, and the gate asserts the mirror is complete and exact.

Naming and wire conventions

A few conventions keep an implementation legible and the wire format stable:

  • Wire field names are snake_caseleaf_count, cose_sign1, slots_mac. This holds across languages: even where a language idiomatically uses camelCase for its in-memory API, the encoded record uses snake_case keys, because the keys are part of the canonical bytes a signature covers.
  • Identifiers are registry strings, not enums baked into code. Hashes, AEADs, KEMs, KDFs, and signatures all reference named identifiers; adding an algorithm (a post-quantum KEM, say) is an additive registry entry, never a wire-format break.
  • Cross-language method names mirror semantically. A function in one language has a same-named counterpart in another (encode_canonical_cborencodeCanonicalCbor), so a reader fluent in either can map one surface onto the other and reason about parity by inspection.
  • Bootstrap the crypto layers first. Stand up the cryptographic core and the wire-format library against the vectors and get the parity gate green before writing a line of application code. The standalone verifier is the smallest application-adjacent surface and the next thing to build; everything else sits on top of a crypto layer you have already proven correct.
  • The record — the wire format the validator and encoder implement.
  • Algorithm registries — the named identifiers an implementation resolves.
  • Verification — the validation pipeline, the standalone verifier, and the error-code catalogue.