Chronicle

GDPR Erasure

Crypto-erasure — destroying per-subject encryption keys to make data irrecoverable.

Chronicle implements GDPR Article 17 (right to erasure) through crypto-erasure: each data subject's sensitive fields are encrypted with a per-subject AES-256-GCM key. On erasure request, the key is destroyed — making the encrypted payload irrecoverable — while the hash chain remains structurally valid.

Enable crypto-erasure

c, err := chronicle.New(
    chronicle.WithStore(adapter),
    chronicle.WithCryptoErasure(true),
)

Or via the extension:

ext := extension.New(
    extension.WithStore(s),
    extension.WithCryptoErasure(true),
)

Record events with a subject ID

When recording events, set SubjectID to associate the event with a data subject:

c.Info(ctx, "profile_update", "user", "user-42").
    Category("account").
    SubjectID("user-42").
    Meta("email", "alice@example.com").
    Record()

Perform erasure

Via the erasure service

import "github.com/xraph/chronicle/erasure"

svc := erasure.NewService(store, keyStore)

result, err := svc.Erase(ctx, &erasure.Input{
    SubjectID:   "user-42",
    Reason:      "GDPR Article 17",
    RequestedBy: "dpo@company.com",
}, appID, tenantID)

fmt.Printf("keyDestroyed=%v eventsAffected=%d\n",
    result.KeyDestroyed, result.EventsAffected)

Via the HTTP API

POST /v1/erasures

Request body:

{
  "subject_id": "user-42",
  "reason": "GDPR Article 17",
  "requested_by": "dpo@company.com"
}

Response: erasure.Result JSON.

What erasure does

  1. Looks up all events with SubjectID = "user-42" in the given app+tenant scope.
  2. For each event, sets Erased = true, ErasedAt, and ErasureID.
  3. Destroys the per-subject encryption key from the KeyStore.
  4. Saves an erasure.Erasure record to the store.
  5. Returns an erasure.Result with KeyDestroyed = true and EventsAffected.

After erasure, attempting ks.Get("user-42") returns chronicle.ErrErasureKeyNotFound. The ciphertext in the store cannot be decrypted.

Hash chain integrity after erasure

Hashes are computed over event metadata — not the encrypted payload. Destroying the key does not change any hash value, so VerifyChain still returns Valid = true for streams that contain erased events.

erasure types

erasure.Input

FieldTypeDescription
SubjectIDstringThe data subject to erase
ReasonstringErasure justification (e.g. "GDPR Article 17")
RequestedBystringIdentity of the requestor

erasure.Result

FieldTypeDescription
IDid.IDTypeID of the erasure record
SubjectIDstringThe erased subject
EventsAffectedint64Number of events marked as erased
KeyDestroyedboolWhether the encryption key was destroyed

erasure.Erasure (stored record)

FieldTypeDescription
IDid.IDerasure_ TypeID
SubjectIDstringThe erased subject
ReasonstringErasure reason
RequestedBystringRequestor identity
EventsAffectedint64Events affected
KeyDestroyedboolKey destruction status
AppID / TenantIDstringScope

Crypto primitives

The crypto package provides Encryptor (AES-256-GCM with random nonces) and KeyStore:

import "github.com/xraph/chronicle/crypto"

enc := crypto.NewEncryptor()
ks  := crypto.NewInMemoryKeyStore()

// Get or create the key for a subject
key, keyID, err := ks.GetOrCreate("user-42")

// Encrypt
ciphertext, err := enc.Encrypt(key, []byte("sensitive data"))

// Decrypt
plaintext, err := enc.Decrypt(key, ciphertext)

// Erase: destroy the key
err = ks.Delete("user-42")
// Any subsequent Decrypt with this key is impossible

For production, replace InMemoryKeyStore with a backend that delegates to AWS KMS, HashiCorp Vault, or similar.

HTTP endpoints

MethodPathDescription
POST/v1/erasuresRequest erasure for a subject
GET/v1/erasuresList erasure records
GET/v1/erasures/:idGet a specific erasure record

On this page