How to verify an Importable pack.
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.
1 · Quickstart
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.
2 · Verify by hand from the API
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:
- SHA-256 each leaf's content; the result must equal the published
sha256. - Build the Merkle tree exactly as §4 describes; the root must equal
merkleRoot. - Verify Ed25519:
verify(signature, lowercase-hex(root), publicKey).
3 · Pack response, field by field
- tradeId
- Stable identifier for the trade. Persisted forever; never reassigned.
- merkleRoot
- Lowercase hex SHA-256 (64 chars). Computed from the leaves as defined in §4.
- leaves[]
- The artefacts, in the order they were assembled by the hub. Each leaf is
{ sha256, label, contentUrl? }. Order matters; reordering changes the root. - signerId
- Stable identifier for the Ed25519 keypair that signed this pack. Look up the public key at
/api/v1/signers/{id}. - signature
- 64-byte hex Ed25519 signature over the canonical lowercase hex of
merkleRoot. See §5. - signedAt
- ISO-8601 UTC instant the hub signed the pack. Used to confirm the signer key was active (not retired) at the time of signing.
4 · The Merkle tree
- Each leaf is the artefact content-digest, domain-tagged:
leaf = sha256(0x00 || contentDigest), in assembly order. The order is part of the proof; do not sort. - If
nleaves is not a power of 2, pad to the next power of 2 withZERO_LEAF, whereZERO_LEAF = sha256("importable-zero-leaf-v1")(padding is tagged as a leaf like any other). - Each internal node is
sha256(0x01 || left || right)— concatenated raw bytes, left first, no separator. The0x00/0x01prefixes (RFC 6962 style) stop a leaf hash being reinterpreted as an internal node. - The root is the single hash at the top.
- A proof for leaf at index
iis 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 a0x01-tagged node and hash. The final value must equal the published root.
5 · Ed25519 signing
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.
6 · Signer registry + key rotation
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 signedAt is 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].
7 · FAQ
Why not blockchain?
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.
What if Importable disappears?
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.
Can I use this for non-Importable packs?
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.
What versions exist?
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.