verify · docs

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:

  1. SHA-256 each leaf's content; the result must equal the published sha256.
  2. Build the Merkle tree exactly as §4 describes; the root must equal merkleRoot.
  3. 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 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).
  • Each internal node is 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.
  • The root is the single hash at the top.
  • A proof for leaf at index 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.

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-2026hub-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.

Try the test pack/standard/v1Machine-readable spec