Skip to content

Federated AEF Anchoring

Federated anchoring lets multiple customers cross-anchor evidence under one shared Merkle root, then prove that their own record existed at time T without revealing any other tenant's content.

Threat model

  • Every participant keeps its own record_hash.
  • The shared manifest stores a blinded commitment_hash, not raw evidence.
  • The Merkle root is the only value sent to OpenTimestamps calendars.
  • Every participant receives a proof path that resolves only their commitment into the shared root.

This gives each tenant a public timestamp proof without exposing sibling records or the full tenant population.

Manifest shape

Each manifest member contains:

  • participant_id
  • record_hash
  • scope
  • nonce
  • commitment_hash
  • proof

The commitment is deterministic:

{
  "nonce": "...",
  "participant_id": "...",
  "record_hash": "...",
  "scope": "..."
}

That JSON object is canonicalized with sorted keys, compact separators, and ensure_ascii=false, then hashed with SHA-256.

CLI flow

axiom federated-build --input ./members.json --output ./federated-manifest.json
axiom federated-anchor --manifest ./federated-manifest.json --output ./anchored-manifest.json
axiom federated-verify \
  --manifest ./anchored-manifest.json \
  --participant-id tenant-alpha \
  --record-hash aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
  --scope prod

Python flow

from axiom_sdk import (
    anchor_federated_manifest,
    build_federated_anchor_manifest,
    lookup_federated_anchor_member,
    verify_federated_anchor_member,
)

manifest = build_federated_anchor_manifest(
    [
        {"participant_id": "tenant-alpha", "record_hash": "a" * 64, "scope": "prod"},
        {"participant_id": "tenant-bravo", "record_hash": "b" * 64, "scope": "prod"},
    ]
)
anchored_manifest = anchor_federated_manifest(manifest)
member = lookup_federated_anchor_member(
    anchored_manifest,
    participant_id="tenant-alpha",
    record_hash="a" * 64,
    scope="prod",
)
result = verify_federated_anchor_member(member, merkle_root=anchored_manifest.merkle_root)
assert result.ok is True

Operational constraints

  • record_hash must already be a 64-character lowercase SHA-256 hex digest.
  • nonce must be a non-trivial even-length hex string. If omitted, the SDK generates one.
  • Duplicate (participant_id, record_hash, scope, nonce) entries are rejected.
  • The current implementation anchors the shared Merkle root through OpenTimestamps. It does not yet run a hosted federation coordinator.