Three building blocks: SHA-256, a left-to-right Merkle tree, and Ed25519. If you can compute these three locally, you can audit every Importable trade without our help.
The @importable/verify client does every check locally — it trusts no Importable server.
Status:the package and its source publish with the Phase 9 verify network. Today the spec below is canonical and the seed pack (trade #4471) is already verifiable end-to-end — the snippet shows the shape it ships in.
npm install @importable/verify
import { createVerifyClient } from '@importable/verify';
const verify = createVerifyClient();
const result = await verify.pack('4471');
if (result.valid) {
console.log('Pack signed by', result.data.signerId);
console.log(result.data.artefactCount, 'artefacts');
} else {
console.error('Invalid:', result.reason);
}Targets Node 18+ and modern browsers · MIT · source publishes with the Phase 9 verify network.
If you would rather not install a client, three curl calls give you everything you need to verify a pack with your own crypto library.
curl https://importable.io/api/v1/pack/4471 curl https://importable.io/api/v1/hash/f64ded79989e618acfed260f988f1fc5591634b57aeb72fd53d3776568e21feb curl https://importable.io/api/v1/signers/hub-seed-v1
With the three responses in hand:
sha256.merkleRoot.verify(signature, lowercase-hex(root), publicKey).{ sha256, label, contentUrl? }. Order matters; reordering changes the root./api/v1/signers/{id}.merkleRoot. See §5.leaf = sha256(0x00 || contentDigest), in assembly order. The order is part of the proof; do not sort.n leaves is not a power of 2, pad to the next power of 2 with ZERO_LEAF, where ZERO_LEAF = sha256("importable-zero-leaf-v1") (padding is tagged as a leaf like any other).sha256(0x01 || left || right) — concatenated raw bytes, left first, no separator. The 0x00/0x01 prefixes (RFC 6962 style) stop a leaf hash being reinterpreted as an internal node.i is an ordered list of { sibling, position } records, leaf-level upward. To verify: start from the domain-tagged leaf, then at each step combine current with sibling in the indicated position as a 0x01-tagged node and hash. The final value must equal the published root.The hub signs the canonical lowercase hex representation of merkleRoot — not the raw bytes, not an uppercase form, not with a prefix. The exact UTF-8 bytes of the 64-character lowercase string.
We chose UTF-8 hex over raw bytes so a human (or an LLM) reading a verify URL can reproduce the exact signing input from the URL alone, with no transcoding step.
Verification uses verify(sig, message, pubkey) from any RFC 8032 conformant library. We use @noble/ed25519; node-forge and Sodium also work.
Each signer has a stable ID and a single Ed25519 public key. When a key is rotated, the signer record is amended with retiredAt; the new key gets a new signer ID (a versioned suffix is conventional, e.g. hub-master-2026 → hub-master-2027).
A verifier MUST treat a pack as invalid if signedAtis after the signer's retiredAt. The client library handles this for you.
The full registry is at /api/v1/signers/[id] for individual records, and rendered as a Person JSON-LD on /verify/keys/[id].
A signed Merkle tree gives the same guarantees a blockchain would (append-only, tamper-evident, externally verifiable) without the energy footprint, the wallet UX, or the trust assumption about consensus. The signer's reputation IS the consensus.
A pack exported as JSON (root + leaves + proof + signature + public key) is self-verifying forever, regardless of whether our API is reachable. Anyone holding the JSON can re-derive the proof with @importable/verify or by hand.
The spec is in the public domain — the npm client and this docs page are MIT. We would welcome other supply-chain operators adopting it as a shared standard; that is the explicit goal of /standard/v1.
The current version is v1. Any change to the on-the-wire format is a major bump and a new /standard/vN document. Historic packs continue to verify against their original version forever.