Content addressing

Every contract listed in a manifest carries a content address: sha256: plus the SHA-256 (Secure Hash Algorithm, 256-bit) digest of the contract's canonical form (SR-006). Reformatting a contract doesn't change its address. Editing it does.

Address format

Address string #

Carried in
artifacts[].address in the manifest
Pattern
^sha256:[0-9a-f]{64}$
Prefix
sha256:
Digest
64 hex characters, lowercase only
sha256:b11ac43d1cf8120534e96ec2d90c24a4c2d9b20364fe01e27632ee7ad63b7dd4

Algorithm

  1. Parse the contract file as JSON. The input to the next step is the parsed JSON value, not the file bytes.
  2. Serialize the parsed value in its canonical form per RFC 8785 (Request for Comments 8785, the JSON Canonicalization Scheme, JCS).
  3. Encode the canonical form as UTF-8 bytes.
  4. Compute the SHA-256 digest of those bytes.
  5. Write the digest as lowercase hex and prefix it with sha256:.

Canonical form

RFC 8785 canonical form, applied to the parsed JSON value:

AspectCanonical form
WhitespaceNone between tokens
Object member orderSorted by the UTF-16 code units of member names
NumbersECMAScript shortest round-trip form
StringsOnly the escapes RFC 8785 requires
Trailing newlineNone
Encoding for hashingUTF-8

What changes the address

Change to the fileAddress changes
Whitespace or indentationNo
Object member orderNo (members are sorted during canonicalization)
Any edit to content: a value, a member name, an added or removed member, array orderYes

Worked examples

Minimal contract #

File (any indentation)
{ "contractId": "button-primary", "component": "Button Primary" }
Canonical form (60 bytes)
{"component":"Button Primary","contractId":"button-primary"}

The address is sha256: followed by the lowercase hex SHA-256 digest of those 60 bytes. component sorts before contractId, so member order in the canonical form differs from the file.

Example contract file #

Contract
examples/button-primary.contract.json
Manifest
examples/manifest.json
path
button-primary.contract.json
address
sha256:b11ac43d1cf8120534e96ec2d90c24a4c2d9b20364fe01e27632ee7ad63b7dd4

The repository's test suite recomputes this address from the canonical form of the example contract file and checks it against the manifest.

Node.js recipe #

The repository's test suite computes addresses with Node's crypto module and the canonicalize npm package (version 2.x) for RFC 8785. doc is the parsed JSON value.

import { createHash } from 'node:crypto';
import canonicalize from 'canonicalize';

const address = (doc) =>
  'sha256:' + createHash('sha256').update(canonicalize(doc), 'utf8').digest('hex');

Mismatch handling

Recomputed address differs from the recorded address #

Applies to
Consumer
Outcome
The artifact is invalid
Severity
Hard failure. Never a warning.
Spec
SR-137

A consumer that recomputes an artifact's content address and finds it not equal to the recorded address treats that artifact as invalid and does not report it as conforming.

What the schema checks

The manifest schema checks only that address matches ^sha256:[0-9a-f]{64}$. It does not read the file at path and does not recompute the digest. Recomputation is a consumer obligation. This repository ships no checker tool for it.