Metadata-Version: 2.4
Name: deleteceipt
Version: 0.1.0
Summary: Cryptographic proof-of-deletion receipts — HMAC-SHA256 signed, hash-chained audit log
Project-URL: Homepage, https://github.com/computeportal/deleteceipt
Project-URL: Repository, https://github.com/computeportal/deleteceipt
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: audit,cryptography,deletion,gdpr,privacy,right-to-be-forgotten
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: cryptography>=42.0; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: ecdsa
Requires-Dist: cryptography>=42.0; extra == 'ecdsa'
Provides-Extra: mongo
Requires-Dist: motor>=3.0; extra == 'mongo'
Requires-Dist: pymongo>=4.0; extra == 'mongo'
Description-Content-Type: text/markdown

# deleteceipt

[![PyPI](https://img.shields.io/pypi/v/deleteceipt?label=PyPI)](https://pypi.org/project/deleteceipt/)
[![Python](https://img.shields.io/pypi/pyversions/deleteceipt)](https://pypi.org/project/deleteceipt/)
[![License](https://img.shields.io/pypi/l/deleteceipt)](LICENSE)
[![CI](https://github.com/computeportal/deleteceipt/actions/workflows/ci.yml/badge.svg)](https://github.com/computeportal/deleteceipt/actions/workflows/ci.yml)

**Cryptographic proof that a file was deleted — not just a promise.**

When users exercise their right to erasure (GDPR Article 17, CCPA, etc.),
they receive a deletion confirmation email. That email is a promise, not
proof: it is unilaterally generated by the same party that did the deleting
and cannot be independently verified. `deleteceipt` replaces the promise with
a cryptographically signed receipt — a JSON document whose signature can be
verified by any third party holding only a public key, without trusting the
server that issued it.

---

## Why pre-deletion hashing?

The non-obvious insight at the heart of this library: **the file hash must be
committed before deletion, not after.**

A hash computed after the file is gone is meaningless — the server could hash
anything (an empty string, a zero byte) and produce a validly-signed receipt
with no connection to what the user actually uploaded. `deleteceipt` separates
the commitment (SHA-256 at upload time, stored in the database) from the
deletion (discarding bytes later). By the time the receipt is issued, the hash
is already on the ledger and cannot be changed retroactively without breaking
the hash-chain audit log.

See [docs/architecture.md](docs/architecture.md) for the full explanation and
Mermaid diagrams.

---

## Quick start

```bash
pip install deleteceipt           # HMAC-SHA256 core (zero dependencies)
pip install deleteceipt[ecdsa]    # + ECDSA/P-256 signing
pip install deleteceipt[mongo]    # + MongoDB/Motor backends
pip install deleteceipt[ecdsa,mongo]  # everything
```

### 10-line HMAC example

```python
from datetime import datetime, timezone
from deleteceipt import compute_file_hash, issue_receipt, verify_receipt

# 1. At upload time — commit the hash before processing begins
file_bytes = open("document.pdf", "rb").read()
file_hash = compute_file_hash(file_bytes)

# 2. At deletion time — issue the signed receipt
now = datetime.now(timezone.utc)
receipt = issue_receipt(
    job_id="job-12345",
    file_hash=file_hash,
    uploaded_at=now,
    processing_completed_at=now,
    deleted_at=now,
    signing_key="your-secret-hmac-key",
    files_deleted=[{"path": "document.pdf", "size_bytes": len(file_bytes), "role": "input"}],
)

# 3. Verify — anyone with the key can check
assert verify_receipt(receipt, "your-secret-hmac-key")
```

### ECDSA example (self-contained verification)

```python
from deleteceipt import generate_keypair, issue_receipt_ecdsa, verify_receipt_ecdsa

private_pem, public_pem = generate_keypair()

receipt = issue_receipt_ecdsa(
    job_id="job-12345",
    file_hash=file_hash,
    uploaded_at=uploaded_at,
    processing_completed_at=processing_completed_at,
    deleted_at=deleted_at,
    private_key_pem=private_pem,
)

# The receipt embeds the public key — no external material needed
assert verify_receipt_ecdsa(receipt)

# Or verify with an explicit public key (e.g., downloaded from server)
assert verify_receipt_ecdsa(receipt, public_key_pem=public_pem)
```

---

## HMAC vs ECDSA

| Property | HMAC-SHA256 | ECDSA / P-256 |
|----------|-------------|---------------|
| Dependencies | stdlib only | `cryptography` |
| Key type | Shared secret | Private / public keypair |
| Who can verify | Anyone with the secret key | Anyone with the public key |
| Server must share | Secret key (risky) | Public key only (safe) |
| Self-contained receipt | No | Yes (`signing_public_key_pem` embedded) |
| Suitable for | Internal systems | Public / third-party verification |
| Signature size | ~44 bytes (base64) | ~96–104 bytes (DER base64) |

Use **HMAC** when the verifier is the same party as the issuer (e.g., your
own backend verifying its own receipts). Use **ECDSA** when you need users or
regulators to verify receipts independently without possessing any secret key.

---

## CLI

```bash
# Verify an HMAC receipt
deleteceipt verify receipt.json --key "your-hmac-key"

# Verify an ECDSA receipt (uses embedded public key)
deleteceipt verify-ecdsa receipt.json

# Verify with an explicit public key file
deleteceipt verify-ecdsa receipt.json --key server_pub.pem

# Generate a new P-256 keypair
deleteceipt keygen

# Inspect a receipt (no verification, human-readable table)
deleteceipt inspect receipt.json
```

Example `deleteceipt inspect` output:

```
+------------------------------+------------------------------------------------------------------+
| job_id                       | job-12345                                                        |
+------------------------------+------------------------------------------------------------------+
| file_hash_sha256             | b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576...             |
+------------------------------+------------------------------------------------------------------+
| uploaded_at                  | 2026-03-01T10:00:00+00:00                                        |
+------------------------------+------------------------------------------------------------------+
| processing_completed_at      | 2026-03-01T10:00:05+00:00                                        |
+------------------------------+------------------------------------------------------------------+
| deleted_at                   | 2026-03-01T10:00:06+00:00                                        |
+------------------------------+------------------------------------------------------------------+
```

---

## Audit log

```python
from deleteceipt import AuditLog

log = AuditLog()  # InMemoryBackend by default
log.append_event("upload", job_id="job-1", metadata={"filename": "doc.pdf", "size_bytes": 4096})
log.append_event("processing_complete", job_id="job-1", metadata={})
log.append_event("deleted", job_id="job-1", metadata={"files_count": 2})

result = log.verify_chain()
# {"valid": True, "broken_at_seq": None, "total_entries": 3}
```

For async workloads (FastAPI, asyncio):

```python
from deleteceipt import AsyncAuditLog

log = AsyncAuditLog()
await log.append_event("upload", job_id="job-1", metadata={"filename": "doc.pdf"})
result = await log.verify_chain()
```

For MongoDB production deployments:

```python
from pymongo import MongoClient
from deleteceipt.audit import AuditLog, MongoBackend

col = MongoClient(url)["mydb"]["audit_log"]
log = AuditLog(backend=MongoBackend(col))
```

For async MongoDB (requires `pip install deleteceipt[mongo]`):

```python
import motor.motor_asyncio
from deleteceipt.audit import AsyncAuditLog, AsyncMongoBackend

col = motor.motor_asyncio.AsyncIOMotorClient(url)["mydb"]["audit_log"]
log = AsyncAuditLog(backend=AsyncMongoBackend(col))
```

---

## Examples

- [`examples/fastapi_integration.py`](examples/fastapi_integration.py) — full
  upload/delete/receipt flow with FastAPI
- [`examples/web_demo.html`](examples/web_demo.html) — browser UI demo (open
  after starting the FastAPI server with `uvicorn fastapi_integration:app --reload`)

## Documentation

- [`docs/architecture.md`](docs/architecture.md) — Mermaid lifecycle diagram
  and deep explanation of why pre-deletion hashing matters
- [`docs/legal-foundation.md`](docs/legal-foundation.md) — The Google Spain
  case, GDPR Article 17, global erasure laws, and the gap between compliance
  and cryptographic proof
- [`docs/why-deletion-is-hard.md`](docs/why-deletion-is-hard.md) — Filesystems,
  soft deletes, WAL logs, backups, caches, and derived data: why "deleted" is
  rarely what it seems
- [`docs/deletion-first-architecture.md`](docs/deletion-first-architecture.md) —
  tmpfs, TTL-by-default, data minimization, and the one-job-one-directory pattern
- [`docs/cryptographic-assurance.md`](docs/cryptographic-assurance.md) — SHA-256
  pre-deletion commitment, HMAC vs ECDSA, hash chains, and the honest limits of
  what signatures prove

---

## License

Apache 2.0. See [LICENSE](LICENSE).

## Patent notice

See [NOTICE](NOTICE).

`deleteceipt` is an open-source implementation of the cryptographic deletion
receipt mechanism described in USPTO Provisional Application No. 64/019,899,
"System and Method for Generating Cryptographically Signed Deletion Receipts
in a Document Processing Service," filed March 28, 2026, by Yoonil Choi.
