# ads-foundation

Authenticated data structures for Python. Merkle trees, hash-chained audit
trails, and a generic prover/verifier framework -- all with zero dependencies.

Based on the ADS4All framework from
[Alzahrani et al., ENASE 2026](https://doi.org/10.5220/0013620100003968).

```
pip install ads-foundation
```

## ProofTrail: tamper-evident audit trails

Track function calls with cryptographic receipts. Each call gets a Merkle
proof that can't be forged or altered after the fact.

```python
from ads_foundation import ProofTrailMonitor

monitor = ProofTrailMonitor()

@monitor.track_tool_call
def query_db(customer_id: str) -> dict:
    return {"balance": 15420.50}

with monitor.session(agent_id="support_bot") as session:
    result = query_db("cust_987")

    receipt = session.get_latest_receipt()
    assert session.verify_receipt(receipt)  # Merkle proof checks out
    print(result)  # {"balance": 15420.50} -- decorator is transparent
```

## Merkle trees

Build trees, generate inclusion proofs, verify them. Domain-separated hashing
follows the RFC 9162 (Certificate Transparency) pattern.

```python
from ads_foundation import build_merkle, generate_proof, merkle_verify_proof, root_hash

data = [b"tx1", b"tx2", b"tx3", b"tx4"]
tree = build_merkle(data)

proof = generate_proof(2, tree, len(data))     # prove tx3 is in the tree
assert merkle_verify_proof(b"tx3", proof)       # valid
assert not merkle_verify_proof(b"fake", proof)  # invalid
```

## Authenticated data structures

The core abstraction: separate a data structure into a **prover** (has the
data) and a **verifier** (has only a hash). The verifier can check operations
without seeing the data.

```python
from ads_foundation import Auth, prover_context, verifier_context, shallow_hash
from ads_foundation.bst import BST, insert_auth

# Build a tree
tree = BST.insert(5, BST.insert(3, BST.insert(7, None)))
root = shallow_hash(tree)

# Prover: perform an insert, produce a proof
with prover_context() as p:
    auth_tree = Auth.prover(root, tree)
    auth_tree = insert_auth(10, auth_tree, p)
proof = p.get_proof()
new_root = auth_tree.digest()

# Verifier: replay the insert using only the hash + proof
with verifier_context(proof) as v:
    auth_tree_v = Auth.verifier(root)
    auth_tree_v = insert_auth(10, auth_tree_v, v)
assert auth_tree_v.digest() == new_root  # same result, no data needed
```

## What's in the box

| Module | What it does |
|---|---|
| `hash` | SHA-256 utilities, `shallow_hash()` via singledispatch |
| `merkle` | Balanced binary Merkle tree with proofs |
| `core` | `Auth[T]`, `ProofStream`, prover/verifier context managers |
| `bst` | Authenticated binary search tree |
| `prooftrail` | Decorator-based audit trail with Merkle receipts |
| `errors` | `ADSError`, `ProofError`, `VerificationError` |

## Design choices

- **Zero dependencies.** Only stdlib. Optional `httpx` for cloud submission.
- **Domain separation.** Leaf and node hashes use distinct tags (RFC 9162).
- **Constant-time comparisons.** All hash checks use `hmac.compare_digest`.
- **Type-safe.** Ships `py.typed`; full mypy compatibility.

## Extending

Any type with an `__ads_hash__` method works with the framework:

```python
from dataclasses import dataclass
from ads_foundation import sha256, combine_hashes, shallow_hash

@dataclass(frozen=True)
class MyRecord:
    data: int

    def __ads_hash__(self) -> bytes:
        return combine_hashes(sha256(b"MyRecord"), shallow_hash(self.data))
```

## License

MIT
