AetherProof — the open-source receipt engine. Prototype of Signet.
Project description
AetherProof
The open-source receipt engine. Prototype of Signet.
Generate cryptographic receipts that prove an AI output is real and unmodified — no servers, no dependencies, verifiable forever offline.
pip install aetherproof
Run aetherproof with no arguments for the interactive menu:
.+######+. AETHERPROOF
## ## v0.2.1
# /\ # Cryptographic receipt engine
# / \ # Prototype of Signet · R0/L2
# / /\ \ #
# / / \ \ # github.com/pulkit6732/aetherproof
# /______\ \ # Verify(receipt, pk, log) = TRUE
# \______/ #
## ##
'######'
────────────────────────────────────────────────
Sign · Verify · Inspect · Log · Keygen
What it does
Every time an AI model produces output, AetherProof issues a tamper-proof signed receipt:
Receipt ID : ap_8694dae2
Model Root : abcdef0123456789... (SHA-256 Merkle of model weights)
Model Root Type : artifact_hash (bound to the weights) | name_only (claim only)
Output Hash : fedcba9876543210... (SHA-256 of the output)
Timestamp : 2026-06-11T15:30:00Z
Signature : ✓ Ed25519 VALID
The Model Root Type field is honest about what the root proves:
artifact_hash (you signed a real weights file), api_attested (a cloud API
returned the model id), or name_only (you typed a name). The receipt never
pretends a name is the weights — see Local vs Cloud
below.
One bit changes → signature fails instantly.
Quick start
Easy mode (for anyone)
aetherproof
Step-by-step wizard:
- What model did you use?
- Paste the output
- Receipt is signed and saved
Expert mode (for developers)
# Generate a receipt
aetherproof sign model.onnx output.txt
# Verify it (offline, forever)
aetherproof verify receipt.json
# Inspect all fields
aetherproof inspect receipt.json
# Test tamper detection
aetherproof tamper receipt.json
Local models vs Cloud models
This is the most important distinction in AetherProof. What can be proven depends on whether you can see the weights.
| Local / self-hosted (Llama, custom, on-prem) | Cloud API (GPT-4o, Claude, Grok, DeepSeek, Gemini…) | |
|---|---|---|
| Who holds the weights | You | The provider |
| Can you hash the weights? | Yes | No — ever |
model_root_type |
artifact_hash |
api_attested |
| Proves the exact model ran? | Yes | No (only the provider can attest that) |
| Proves input + output unaltered? | Yes | Yes |
| Proves which model the API claimed + when? | Yes | Yes |
Why a cloud receipt can't prove the weights — and why that's honest
model_weight_root requires reading the weights. With GPT-4o or Grok, the
weights live on the provider's servers — you never see them, so you cannot hash
them. No client-side tool can. Anyone claiming to "cryptographically prove the
cloud model ran" is overstating it, and a sharp auditor will reject that.
What a cloud receipt does prove is the part you are liable for: the exact input your system sent, the exact output it acted on, the model the API claimed, the time, and that none of it was altered or back-dated. That is your record-keeping obligation (SEC 17a-4, FRE 902(14)). The provider owns their infrastructure attestation; AetherProof owns the faithfulness of your record.
How the model name is captured (you don't type it)
For api_attested, the model identity comes from the API response, not from a
guess. Every major API returns the resolved id:
# OpenAI / DeepSeek / xAI (Grok) — OpenAI-compatible
resp = client.chat.completions.create(model="gpt-4o", messages=[...])
resp.model # "gpt-4o-2024-08-06" ← the RESOLVED id (dated snapshot)
resp.system_fingerprint # "fp_a7d06e42bc" ← backend config snapshot
resp.id, resp.created # call id + provider timestamp
# Anthropic (Claude)
resp = client.messages.create(model="claude-opus-4-8", ...)
resp.model # "claude-opus-4-8"
You asked for "gpt-4o"; the API answers "gpt-4o-2024-08-06" — the exact
snapshot that served you. You bind that returned string + system_fingerprint,
so the user can't fake it (it comes back inside the response you're hashing).
Signing a cloud call
from aetherproof.core.receipt import Receipt
from aetherproof.core.keystore import load_or_create_signer
from aetherproof.core.log import ReceiptLog
resp = client.chat.completions.create(model="gpt-4o", messages=[...])
r = Receipt.for_api_call(
provider="openai",
model_id=resp.model, # read FROM the response
prompt=user_prompt,
output_text=resp.choices[0].message.content,
response_metadata={
"system_fingerprint": resp.system_fingerprint,
"response_id": resp.id,
"created": resp.created,
},
)
# r.model_root_type == "api_attested"
signer = load_or_create_signer()
r.signature = signer.sign(r.signing_bytes())
ReceiptLog().append(r)
This works for any OpenAI-compatible or Anthropic-style provider — OpenAI,
Azure OpenAI, DeepSeek, xAI/Grok, Mistral, Together, Groq, etc. (tested across 17
model families in tests/security/test_cloud_models_matrix.py).
"But what if they have no provider attestation? Won't they get rejected?"
No — the honest answer passes the security review. When asked "prove Grok's weights ran," the answer is:
"No client-side tool can prove a closed cloud model's weights — only the provider can, and they don't expose it. What we prove is the part you're liable for: the exact input, the exact output, the model the API claimed, the time, and that none of it was altered. That's your record-keeping obligation."
In a real dispute (a wrongful denial, a bad summary), the fight is almost never
"was it GPT-4o or GPT-4-turbo" — it's "did the AI actually output this, or was
the record edited afterward?" AetherProof answers that completely, for any cloud
model, with zero provider cooperation. The model-identity gap upgrades to a real
hardware root only when the provider signs their infrastructure (AWS Bedrock +
Nitro, future) — at which point Signet imports that signature and the tier
graduates api_attested → hardware-rooted, with no change to the receipt format.
The invariant (what makes this real)
Verify(receipt, public_key, log) = TRUE
using ONLY those three inputs, forever, with zero dependency on
AetherProof servers, any vendor SDK, or any hardware driver.
This is not a claim. It's built in. Offline verification uses only:
- The receipt file
- The public key (PEM file)
- (Optional) transparency log entry
No network. No API calls. No special hardware. Works in 2026, 2035, 2050.
How offline verification actually works
You can prove a receipt is genuine with no internet and no AetherProof code at all — the math is open. Here is the exact process.
What you need (the three inputs)
- The receipt (
ap_xxxx.json) — the signed claim. - The public key (
ap_xxxx.pub, a PEM file) — shipped beside the receipt. - (Optional) the original output file — only if you also want to prove the output itself wasn't changed.
The steps
- Rebuild the signed message. The receipt is signed over a canonical, length-prefixed preimage of its fields (version, model root, model-root-type, input commitment, output hash, timestamp, log sequence, hardware evidence, log anchor). The encoding is injective, so no two distinct receipts can share a preimage (and thus a signature).
- Check the Ed25519 signature of that preimage against the public key. If it verifies, the receipt's contents are exactly what was signed — a single changed bit fails this check.
- (Optional) Re-hash the output file with SHA-256 (raw bytes, streamed) and
compare to the receipt's
output_hash. If they match, the output is unchanged.
The one-command way
# signature only
aetherproof verify ap_xxxx.json
# signature + confirm the output file still matches
aetherproof verify ap_xxxx.json --output original_output.txt
# scripting / CI: machine-readable, exits non-zero on any failure
aetherproof verify ap_xxxx.json --output original_output.txt --quiet
# -> {"valid": true, "signature_valid": true, "output_unmodified": true}
Exit code is 0 only when everything checks out, 1 on any tampering — so
aetherproof verify … && deploy is safe in a pipeline.
Verify without AetherProof (any Ed25519 library)
Because the format is open, anyone can verify with a standard crypto library —
no dependency on this tool. In Python with cryptography:
import json, hashlib
from cryptography.hazmat.primitives.serialization import load_pem_public_key
r = json.load(open("ap_xxxx.json"))
pub = load_pem_public_key(open("ap_xxxx.pub", "rb").read())
# rebuild the injective preimage: "<len>:<field>" for each field, in order
fields = [
r["receipt_version"], r["model_weight_root"], r["model_root_type"],
r["input_commitment"], r["output_hash"], str(r["timestamp_ms"]),
str(r["log_sequence"]),
json.dumps(r["hw_evidence"], sort_keys=True, separators=(",", ":")),
r["log_anchor"],
]
preimage = "".join(f"{len(f)}:{f}" for f in fields).encode("utf-8")
pub.verify(bytes.fromhex(r["signature"]), preimage) # raises if invalid
print("signature OK")
# optional: prove the output file is unchanged
digest = hashlib.sha256(open("original_output.txt", "rb").read()).hexdigest()
print("output unmodified:", digest == r["output_hash"])
That is the whole trust model: a public key and some SHA-256 + Ed25519 math you can run anywhere, forever.
Agent-chain context (receipt v1.2)
A receipt can optionally commit to namespaced runtime context — which agent action, run, or policy decision an output belongs to — inside the signature. This binds a receipt to the exact decision it was issued for, so a valid receipt can't be replayed in a different context.
from aetherproof.core.receipt import Receipt
r = Receipt(
model_weight_root="...",
output_hash="...",
signed_extensions={
"org.liminal.agent_chain/v0.1": {
"purpose": "generate",
"actor_id": "agent:planner",
"run_id": "run_42",
"policy_decision_id": "pol_7",
}
},
)
# r.receipt_version is now "1.2"; the SHA-256 commitment over the canonicalized
# extensions is folded into the signing preimage — tampering any field breaks it.
- Empty extensions → the receipt stays v1.1, byte-identical. No impact on existing receipts or verifiers.
- Non-empty → v1.2. Per-extension SHA-256 commitments (RFC 8785 JCS canonicalization) are aggregated and appended to the injective preimage, so a namespace can be disclosed or omitted without breaking the others.
This is the AetherProof side of the agent-chain context spec. Multi-hop pipeline aggregation (signing each hop, identifying a tampered hop) is Signet Layer 3 and builds on this primitive.
Architecture (god file)
AetherProof is Layer 2 of the Signet stack. Signet adds hardware roots, compliance packs, and a transparency network on top.
License
AGPL-3.0-or-later. Use it, deploy it, fork it, verify it — but if you run a modified version as a network service, you must release your changes under the same license. (Need a permissive/commercial license? That's what Signet is for.)
AetherProof is what you get when you need tamper-proof proof.
Signet is what you get when you need hardware roots, compliance packs, and a transparency log.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aetherproof-0.2.2.tar.gz.
File metadata
- Download URL: aetherproof-0.2.2.tar.gz
- Upload date:
- Size: 58.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa4c82ee3000a92140fbaeb2ed3df6b47bcbe8686997b7c1be6720a6573b543e
|
|
| MD5 |
9722237468ba446dd0d534b858ee90de
|
|
| BLAKE2b-256 |
35b279d0071d6f0420e5dff5b3a931c9aae732738c6970ee13a7a87c9451f223
|
File details
Details for the file aetherproof-0.2.2-py3-none-any.whl.
File metadata
- Download URL: aetherproof-0.2.2-py3-none-any.whl
- Upload date:
- Size: 48.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
778959941b0a4290b402b61e0f3bf2dbaa510ff9dd4bea86bf6de7b9e791114b
|
|
| MD5 |
948331a94b44b131f9c2bc1afa64a78e
|
|
| BLAKE2b-256 |
ca9a4ba9a09f73a04b7d0ed4d641f8f96d1d528f99d26f5d5065d141fc6a01c9
|