> ## Documentation Index
> Fetch the complete documentation index at: https://finance.qwedai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Compliance and auditing

> How QWED-Finance generates verification receipts with tamper-evident keyed signatures and audit trails for SOX, MiFID II, and banking compliance.

QWED-Finance can attach a **tamper-evident receipt** to a verification, supporting audit and regulatory workflows.

> "If an AI makes a mistake, the algorithm isn't sued—the bank is. Your `input_hash` and keyed `signature` provide **tamper evidence**."

## Verification receipts

`CrossGuard`, `UCPIntegration`, and the built-in `OpenResponsesIntegration` tools create receipts automatically. Custom Open Responses tools get a receipt only if their `verification_fn` creates one. Direct calls to the other guards (for example `ComplianceGuard.verify_aml_flag`) return a result object without a receipt; create one yourself with `ReceiptGenerator` when you need an audit record:

```python theme={null}
from qwed_finance import ReceiptGenerator, VerificationEngine

receipt = ReceiptGenerator.create_receipt(
    guard_name="ComplianceGuard.verify_aml_flag",
    engine=VerificationEngine.Z3,
    llm_output="Transaction approved",
    verified=False,
    violations=["AML_CTR_THRESHOLD"]
)

print(receipt.to_json())
```

### Receipt fields

Every field below is part of the signed payload.

| Field | Description | Example |
| - | - | - |
| `receipt_id` | Unique identifier | `"a1b2c3d4-..."` |
| `timestamp` | ISO 8601 UTC with microseconds | `"2026-01-18T14:30:00.123456+00:00"` |
| `input_hash` | SHA-256 of LLM output | `"7f83b1657..."` |
| `input_preview` | First 100 chars of the input | `"Transaction approved"` |
| `guard_name` | Guard that ran | `"ComplianceGuard.verify_aml_flag"` |
| `engine_used` | Verification engine | `"Z3"` |
| `status` | Outcome: `verified`, `rejected`, `insufficient_data`, `error` | `"verified"` |
| `verified` | Pass/fail | `true/false` |
| `computed_value` | Deterministically computed value | `"flag_required=True"` |
| `llm_value` | LLM output when ≤ 50 chars, else `null` | `"Transaction approved"` |
| `difference` | `computed_value` vs expected | `null` |
| `proof_steps` | Symbolic derivation | `["amount=15000", "threshold=10000", "15000>=10000"]` |
| `formula_used` | Formula applied | `"llm_flagged == flag_required"` |
| `violations` | Rule violations | `[]` |
| `metadata` | Extra context | `{"rule": "AML_CTR"}` |

### Cryptographic signature

Since v3.0.0, signatures are keyed HMAC-SHA256 over the **full** canonical receipt —
every field in the table above, as sorted compact JSON. The verifier
holds the key; there is no default key. The earlier unkeyed
`get_signature()` (no argument) was removed in v3.0.0.

```python theme={null}
import hmac
import secrets

# Generate a key once per verifier and keep it outside the receipt
signing_key = secrets.token_bytes(32)

signature = receipt.get_signature(signing_key)
# "b4d30b8e..."

# Verify the receipt has not been modified (constant-time comparison)
same = hmac.compare_digest(receipt.get_signature(signing_key), signature)
```

Mutating any field after signing — `computed_value`, `violations`,
`status`, `proof_steps`, anything in the table — produces a different
signature. This is tamper evidence for holders of the key: it does not
establish issuer identity, third-party verifiability, or
non-repudiation. Public-key attestation (ES256) is planned separately —
see [qwed-finance#37](https://github.com/QWED-AI/qwed-finance/issues/37).

***

## Audit log

Aggregate receipts for regulatory reporting:

```python theme={null}
from qwed_finance import AuditLog

log = AuditLog()

# Log verifications
log.log(receipt1)
log.log(receipt2)

# Get summary
summary = log.summary()
# {
#     "total_verifications": 100,
#     "passed": 95,
#     "failed": 5,
#     "pass_rate": "95.0%",
#     "by_guard": {"ComplianceGuard": 40, "QueryGuard": 60}
# }

# Export for regulators
json_export = log.export_json()
```

### Query failed verifications

```python theme={null}
# Get all failures for investigation
failures = log.get_failures()

for receipt in failures:
    print(f"Failed: {receipt.guard_name}")
    print(f"  Input: {receipt.input_preview}")
    print(f"  Violations: {receipt.violations}")
```

***

## Regulatory alignment

| Regulation | QWED Feature |
| - | - |
| **RBI FREE-AI** | Audit trail with receipts |
| **BSA/FinCEN CTR** | AML threshold verification |
| **OFAC** | Sanctions screening |
| **SOC 2** | Immutable verification logs |
| **ISO 27001** | Input hashing & signatures |

***

## Adversarial defense

We test against "jailbroken" LLMs:

```python theme={null}
# tests/adversarial/test_sql_jailbreaks.py

def test_delete_in_subquery():
    """DELETE hidden in subquery should be caught"""
    sql = "SELECT * FROM (DELETE FROM users WHERE id=1 RETURNING *) AS deleted"
    result = guard.verify_readonly_safety(sql)
    assert result.safe == False  # ✅ Caught

def test_mixed_case_drop():
    """DrOp TaBlE should be caught"""
    result = guard.verify_readonly_safety("DrOp TaBlE users")
    assert result.safe == False  # ✅ Caught
```

### Test suites

| Suite | Tests | Coverage |
| - | - | - |
| `test_sql_jailbreaks.py` | 20+ | SQL injection, UNION, comments |
| `test_math_compliance_jailbreaks.py` | 15+ | Float precision, AML boundaries |

***

## For compliance officers

When a regulator asks: *"How do you verify AI decisions?"*

Show them:

1. **Input Hash** — Proof of what the LLM said
2. **Timestamp** — When verification occurred
3. **Engine Signature** — Which solver verified (Z3/SymPy)
4. **Proof Steps** — Symbolic derivation of truth
5. **Receipt Signature** — Keyed tamper evidence (HMAC)

Receipts carry no embedded `signature` field; `get_signature(signing_key)`
computes it on demand from the full receipt. Export both artifacts under
unique paths — one pair per receipt — so no export overwrites an earlier
audit record:

```python theme={null}
from pathlib import Path

signature = receipt.get_signature(signing_key)
base = f"receipt-{receipt.receipt_id}"

try:
    Path(f"{base}.json").write_text(receipt.to_json(), encoding="utf-8")
    Path(f"{base}.sig").write_text(signature, encoding="utf-8")
except OSError as exc:
    raise RuntimeError(f"receipt export failed, pair incomplete: {exc}") from exc
```

The exported `receipt-abc-123.json` contains the full receipt:

```json theme={null}
{
  "receipt_id": "abc-123",
  "timestamp": "2026-01-18T14:30:00.123456+00:00",
  "input_hash": "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069",
  "input_preview": "Transaction approved",
  "guard_name": "ComplianceGuard.verify_aml_flag",
  "engine_used": "Z3",
  "status": "verified",
  "verified": true,
  "computed_value": "flag_required=True",
  "llm_value": "Transaction approved",
  "difference": null,
  "proof_steps": [
    "amount = 15000",
    "threshold = 10000",
    "15000 >= 10000 → flag_required = True",
    "llm_flagged = True",
    "llm_flagged == flag_required → COMPLIANT"
  ],
  "formula_used": "llm_flagged == flag_required",
  "violations": [],
  "metadata": {}
}
```

To verify an export later, re-canonicalize each parsed JSON exactly as
`get_signature(signing_key)` does and compare it with the stored
signature. Every receipt name found in the directory is checked, a
`.json` or `.sig` file without its partner counts as a failure, and one
failure never stops the rest.

The auditor needs the verifier's HMAC key. It is never stored with the
receipts, so provision it to the auditor through a secret store or an
environment variable:

```python theme={null}
import hashlib
import hmac
import json
import os
from pathlib import Path

# The verifier's HMAC key, provisioned to the auditor out of band
signing_key = bytes.fromhex(os.environ["QWED_RECEIPT_KEY_HEX"])

# Collect receipt names from both file types, so an orphaned file is caught
directory = Path(".")
names = sorted(
    {p.stem for p in directory.glob("receipt-*.json")}
    | {p.stem for p in directory.glob("receipt-*.sig")}
)
if not names:
    raise SystemExit("no exported receipts to audit")

results = {}
failures = []
for base in names:  # "receipt-<receipt_id>"
    try:
        receipt_json = (directory / f"{base}.json").read_text(encoding="utf-8")
        stored = (directory / f"{base}.sig").read_text(encoding="utf-8")
        canonical = json.dumps(
            json.loads(receipt_json),
            sort_keys=True,
            separators=(",", ":"),
            allow_nan=False,
        )
        results[base] = hmac.compare_digest(
            hmac.new(signing_key, canonical.encode("utf-8"), hashlib.sha256).hexdigest(),
            stored,
        )
    except (OSError, ValueError, TypeError) as exc:
        # unreadable/invalid UTF-8, malformed or NaN JSON, non-ASCII signature
        results[base] = False
        failures.append(f"{base}: {exc}")
    else:
        if not results[base]:
            failures.append(f"{base}: signature mismatch")

for failure in failures:
    print(f"verification failed: {failure}")

audited_ok = all(results.values())
if not audited_ok:
    raise SystemExit(1)  # fail the audit job on any missing, unreadable, or tampered receipt
```

***

## Related pages

* **Previous:** [The 11 guards](/finance/guards) — Deep dive into each verification guard
* **Next:** [QWED UCP: transaction verification for AI commerce](https://docs.qwedai.com/ucp/overview) — Connect finance verification to AI-driven commerce flows
* **See Also:** [QWED Open Responses: verified tool calls for AI agents](https://docs.qwedai.com/open-responses/overview) — Add runtime verification to agent tool calls

***

<Info>
  Source code and adversarial tests: [github.com/QWED-AI/qwed-finance](https://github.com/QWED-AI/qwed-finance)
</Info>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.