This is a personal proof-of-concept project. It is not intended for production use. Please refrain from using it.
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