Aira

Merkle Settlement

Periodic Merkle anchoring of action receipts. Every notarized receipt eventually gets sealed into exactly one settlement; the settlement's Merkle root is the cryptographic commitment that the batch existed at a specific moment in time.

What it is

A settlement is a sealed batch of action receipts with a single SHA-256 Merkle root over their payload hashes. The Merkle root is signed with Aira's gateway key and (best-effort) anchored to an RFC 3161 trusted timestamp authority.

After a settlement is sealed:

  • Every settled receipt has its settlement_uuid and merkle_proof populated.
  • A regulator can prove "this single receipt was in this settlement" in O(log n) using the inline merkle_proof — no need to hold the whole receipt list.
  • The settlement's Merkle root + signature is the cryptographic commitment that the batch existed at the moment it was sealed.

This is the F8 piece from the LangChain RFC #35691 thread: APS-style periodic anchoring without external blockchain dependence.


Settlement vs Compliance Bundle

Aira has two batch-receipt primitives. They look similar but serve different jobs:

Compliance bundleSettlement
TriggerUser, ad-hocAdmin / scheduled
ScopeUser-chosen date range, framework-mappedAll unsettled receipts at the moment of seal
MembershipA receipt can be in many bundlesA receipt is in exactly one settlement
OutputSelf-contained JSON for a regulator (downloadable)Inline merkle_proof on each receipt row
Use case"Q1 2026 evidence packet for the EU AI Act audit""Cryptographic commitment that yesterday's receipts existed by 06:00 UTC"

Both reuse the same RFC 6962 Merkle primitive. Use compliance bundles when you need an export. Use settlements when you need automatic, append-only anchoring with per-receipt inclusion proofs.


Seal a settlement

Admin endpoint

curl -X POST https://api.airaproof.com/api/v1/settlements \
  -H "Authorization: Bearer aira_live_xxx"

Returns the new settlement, or null (still 201) if there were no unsettled receipts:

{
  "id": "s-...",
  "org_uuid": "...",
  "period_start": "2026-04-10T00:00:00Z",
  "period_end": "2026-04-10T23:59:59Z",
  "receipt_count": 1247,
  "merkle_root": "fc2a8b...",
  "settlement_hash": "sha256:...",
  "signature": "ed25519:...",
  "signing_key_id": "aira-signing-key-v1",
  "timestamp_token": "MIIB...",
  "sealed_at": "2026-04-11T00:00:00Z",
  "request_id": "..."
}

Scheduled

In production, run the seal on a schedule via Celery beat:

# app/tasks/settlement_task.py

from celery.schedules import crontab
from app.celery_app import app as celery_app
from app.core.database import async_session
from app.services import settlement_service
from app.models.organization import Organization

@celery_app.task
async def settle_all_orgs():
    async with async_session() as db:
        orgs = (await db.execute(select(Organization))).scalars().all()
        for org in orgs:
            await settlement_service.create_settlement(db, org.id)
        await db.commit()


celery_app.conf.beat_schedule = {
    "hourly-settlement": {
        "task": "app.tasks.settlement_task.settle_all_orgs",
        "schedule": crontab(minute=0),
    },
}

Whatever cadence you pick (every hour, every N receipts, end-of-day), each settlement seals only the receipts that have not yet been sealed. Re-running on an empty set is a no-op that returns null.


Inclusion proofs

Every settled receipt carries its own Merkle inclusion proof on action_receipts.merkle_proof. The shape mirrors app.services.merkle.MerkleProof:

{
  "leaf_hash": "abc123...",
  "index": 42,
  "leaf_count": 1247,
  "siblings": ["...", "...", "..."]
}

To fetch a receipt's proof in standalone form:

curl https://api.airaproof.com/api/v1/settlements/inclusion-proof/<receipt_uuid> \
  -H "Authorization: Bearer aira_live_xxx"
{
  "settlement_uuid": "s-...",
  "receipt_uuid": "r-...",
  "merkle_root": "fc2a8b...",
  "leaf_hash": "abc123...",
  "index": 42,
  "leaf_count": 1247,
  "siblings": ["...", "...", "..."],
  "request_id": "..."
}

Verify an inclusion proof offline

import hashlib

LEAF_PREFIX = b"\x00"
NODE_PREFIX = b"\x01"

def hash_node(left: bytes, right: bytes) -> bytes:
    return hashlib.sha256(NODE_PREFIX + left + right).digest()


def verify_inclusion(proof: dict, root_hex: str) -> bool:
    """Walk the siblings from leaf to root and confirm we land on the
    published Merkle root. Pure function — no Aira dependency."""
    cursor = bytes.fromhex(proof["leaf_hash"])
    idx = proof["index"]

    for sibling_hex in proof["siblings"]:
        sibling = bytes.fromhex(sibling_hex)
        if idx % 2 == 0:
            cursor = hash_node(cursor, sibling)
        else:
            cursor = hash_node(sibling, cursor)
        idx //= 2

    return cursor.hex() == root_hex


# Example
import json
with open("inclusion-proof.json") as f:
    proof = json.load(f)

assert verify_inclusion(proof, proof["merkle_root"]), "Proof does not verify"
print("Receipt", proof["receipt_uuid"], "is provably in settlement", proof["settlement_uuid"])

The verifier is a pure function. A regulator can paste it into their own script and confirm the receipt was sealed into the settlement, without ever calling Aira.


Webhook

Aira fires receipts.settlement_sealed whenever a new settlement is created:

{
  "event": "receipts.settlement_sealed",
  "settlement_uuid": "s-...",
  "org_uuid": "...",
  "receipt_count": 1247,
  "merkle_root": "fc2a8b...",
  "sealed_at": "2026-04-11T00:00:00Z"
}

Subscribe via Dashboard → Webhooks → Add endpoint → receipts.settlement_sealed and pipe it into your alerting / archive pipeline.


What this neutralizes

  • APS's settlement pattern — Aira ships periodic Merkle anchoring out of the box, with per-receipt inclusion proofs and a public verification path.
  • The implicit "all your receipts live in our database" risk. Even if Aira's row-level data is lost or tampered with, the published Merkle roots are append-only commitments to the receipts that existed at each settlement window.

Combined with the public verify-action endpoint and the JWKS-published signing key, every receipt has at least three independently verifiable trust statements:

  1. The Ed25519 signature verifies against the published gateway public key (per-receipt).
  2. The policy-evaluator signature verifies against the published evaluator public key (per-evaluation, multi-party).
  3. The Merkle inclusion proof verifies against the published settlement root (per-batch).

A regulator can confirm any one of these without trusting the other two.

On this page