Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.xandrlabs.ai/llms.txt

Use this file to discover all available pages before exploring further.

Every Knowledge Block has a single stable identifier: kbHash. It is not assigned by a registry, generated randomly, or tied to who published the KB. Instead, it is derived entirely from the KB’s content and lineage. This means you can compute kbHash yourself from any canonical envelope and verify whether it matches a published value — no trusted party required.

The derivation formula

kbHash is computed using Keccak-256 (the Ethereum-compatible hash function, not NIST SHA3-256) over the UTF-8 bytes of the domain tag "KB_V1" concatenated with the canonical JSON of the normalized hash-scoped envelope:
canonical_string := canonicalize(normalizeForHash(envelope))
kbHash           := keccak256(UTF8("KB_V1") + UTF8(canonical_string))
The output is hex-encoded with a 0x prefix — a 66-character string of the form 0x followed by 64 lowercase hex digits.

What is included in the hash

The normalizeForHash step projects the envelope down to a fixed set of keys. Only the following fields are included in the hash preimage:
FieldIncluded when
typeAlways
domainAlways
sourcesAlways (empty array [] if no parents)
artifactHashWhen present in the envelope
tierWhen present in the envelope
payloadAlways
derivationWhen present in the envelope

What is excluded from the hash

The following fields are never part of the kbHash preimage:
  • The top-level kbHash field itself (stripped before hashing)
  • curator and createdAt
  • signature
  • envelopeCid and other indexing or transport metadata
Signatures and timestamps are intentionally excluded. They can be stored alongside a KB without affecting its identity, which means the same KB can be signed by multiple curators and still have the same kbHash.
Never include kbHash or signature fields in the object you pass to kbHashFromEnvelope. If they are present, they will be ignored by normalizeForHash — but mixing hash-scoped and non-hash-scoped fields in your data model can cause subtle bugs.

Computing kbHash in code

Use kbHashFromEnvelope from @alx/protocol to derive the identifier from an envelope object:
import { kbHashFromEnvelope } from "@alx/protocol";

const envelope = {
  type: "practice",
  domain: "software.security",
  sources: [],
  artifactHash: "0xf3c9...7d11",
  tier: "open",
  payload: {
    type: "practice",
    rationale: "Rotate signing keys to limit exposure window.",
    contexts: [],
    failureModes: [],
  },
};

const kbHash = kbHashFromEnvelope(envelope);
// → "0x..." (0x-prefixed 64-character hex string)
kbHashFromEnvelope internally calls normalizeForHash to project and sort the envelope, then canonicalize to produce the deterministic JSON string, then hashes the result under the KB_V1 domain tag.
The sources array is sorted lexicographically by normalizeForHash before hashing. You can pass parents in any order — the resulting kbHash will always be the same as long as the set of parent values is identical. See Lineage for details.

artifactHash and cidV1

In addition to kbHash, the protocol defines two other identity-related values: artifactHash is a cryptographic commitment to the exact bytes of the off-chain artifact associated with the KB. It is computed as the keccak-256 hash of the artifact bytes and stored in the registry alongside kbHash. When you retrieve an artifact, you should hash its bytes and compare against the published artifactHash to confirm you have the correct content. cidV1 is a CIDv1 content identifier derived from SHA-256(canonical_bytes) using the raw codec (0x55). It is not the same as kbHash — it uses a different hash function and a multihash encoding. cidV1 is used when storing or referencing the canonical envelope on IPFS.
cidV1 = CIDv1(raw, SHA-256(canonical_bytes))

On-chain synonyms

The registry contract and indexing surfaces use different field names for the same value. All of the following refer to the same 32-byte value as kbHash:
NameWhere you encounter it
kbHashProtocol documentation and SDK
contentHashRegistry contract (Solidity)
kbIdRegistry events and indexer output
When you register a KB on-chain, the bytes32 field storing the identifier is named contentHash in the contract ABI. It holds the same bytes as kbHash.

Determinism guarantee

Identical inputs always produce identical kbHash. This is a core protocol invariant. It means:
  • You can verify any published KB by recomputing its kbHash from the envelope.
  • Independent implementations that conform to the protocol specification will produce the same kbHash for the same envelope.
  • Caching, deduplication, and cross-system references all rely on this property.
If you compute a kbHash that does not match a published value, either the envelope you have is different from the one that was published, or one of the implementations is not conformant.