§
🌙 Toggle Dark Mode Home MoltGuard MoltProof MT Global · Regulated Markets MolTrust Sports MT Shopping MT Travel MT Skills MT Prediction MT Salesguard MT Music Integrity Dashboard VCOne Blog Developers Pricing Enterprise Partners Compliance About Publications Verify Us Status Contact API Docs
Credential Anchoring · v1 · September 2026 · CC BY 4.0

Credential Anchoring

Chain: Base mainnet Prefix: MolTrust/VC/v1 Hash: SHA-256 Specification URL: https://moltrust.ch/anchoring.html


The transaction calldata is a string

This is the part that costs people the most time, so it comes first. The anchoring transaction carries its payload as UTF-8 text, not as ABI-encoded arguments:

MolTrust/VC/v1/<merkle_root>

Decoding the transaction input with an ABI decoder finds no arguments and returns nothing, which reads as an empty anchor. An external auditor reached exactly that conclusion in September 2026. Read the input as bytes, decode as UTF-8, split on the last /.

The root is 64 lowercase hex characters. Everything before it is the version prefix and is there so a reader can tell what the number is without a lookup.

The leaf

One credential makes one leaf. The preimage is five fields joined by a pipe, in this order, with no padding and no whitespace:

leaf = SHA-256( credential_id | subject_did | credential_type | issued_at | proof_value )

issued_at is an ISO-8601 timestamp as stored. proof_value is the credential signature; an unsigned credential contributes an empty string rather than being skipped.

All five are needed. Leave any one out and the anchor stops binding the thing it is meant to bind — drop subject_did and the same anchor covers a credential about someone else.

The tree

Pairs are hashed left-then-right with SHA-256 over the concatenated raw bytes, not over their hex text:

parent = SHA-256( left_bytes || right_bytes )

A level with an odd number of nodes duplicates its last node to make the count even, and the duplicate is part of the level. This matters for verification: a proof for the last leaf of an odd level includes that duplicated sibling. An implementation that pads only while computing the root, and not while producing proofs, emits proofs that do not replay — we shipped that bug once and fixed it.

Replaying a proof

GET /identity/verify/<did> returns, for every credential, the anchor transaction, the root, and the proof:

"anchor": {
  "tx_hash": "0x0fc09fb8…",
  "merkle_root": "e3174fd3…",
  "merkle_proof": { "leaf": "53f9d163…", "path": [ { "hash": "…", "position": "left" }, … ] },
  "calldata_format": "utf8:MolTrust/VC/v1/<merkle_root>",
  "leaf_rule": "https://moltrust.ch/anchoring.html#leaf"
}

To check it:

  1. Recompute the leaf from the credential with the rule above and compare it with merkle_proof.leaf.
  2. Fold the path into the leaf: for each step, concatenate the raw bytes with the sibling on the side its position names, and hash.
  3. Compare the result with merkle_root.
  4. Fetch the transaction, decode its input as UTF-8, and check that the root in the calldata is the same root.

The proof travels with the credential rather than behind a second request. A verifier that has to fetch the proof from another endpoint cannot check us while that endpoint is unavailable, which is when checking matters most.

Two anchors, two tables

Credentials and interaction proof records are anchored by the same machinery and are not the same artefact. A credential anchor says we issued this credential about this subject at this time. An interaction proof record anchors an output an agent produced. An agent can hold anchored credentials and have no interaction proof records at all, and a count of zero for one says nothing about the other.

Which wallet pays

Anchoring is paid for from a dedicated address, never the wallet that receives payments. The anchor transaction and the payment rail share no key.


MolTrust / CryptoKRI GmbH, Zurich | hello@moltrust.ch | moltrust.ch Released under Creative Commons Attribution 4.0 International (CC BY 4.0)