Every GeraClone reply is signed with an ed25519 key unique to that clone. This post explains why, how, and what you can do with the signatures.
The threat model. Voice-clone and avatar technology is good enough that a motivated attacker can produce a convincing fake message from "your dad's clone" asking "your daughter" for £500. If the clone platform does nothing, she has no way to verify. If the platform signs every reply, she does.
The architecture. At clone creation time we generate an ed25519 keypair. The public key is stored plaintext on the Clone row — we want everyone to be able to see it. The private key is wrapped by KMS (Encrypt(KMS_KEY_ID, privateKeyPem)) and only ciphertext is persisted. On every reply, the KeyVaultService unwraps the key for the request duration, signs the reply content, and discards the plaintext from memory.
The verification endpoint. Anyone can POST a signature to /api/v1/verify/:signature and receive back the clone ID, the public key, and the verified reply content — no authentication required. This is the family-trust anchor: if your grandmother receives a message supposedly from her son's clone, she can verify it came from the real clone.
What signatures do NOT guarantee: that the clone's advice is good, that the facts are correct, that the content is appropriate. They guarantee provenance — this message came from this clone — and nothing more.
Why ed25519. Fast (sub-ms signing, sub-ms verification), small signatures (64 bytes base64-encoded), widely supported (every language has a verifier in stdlib), no elliptic-curve-constants controversy.
Why KMS wrapping. An attacker who breaches our database sees only wrapped ciphertext — they still need to compromise our KMS to forge a signature. Defence in depth.
If you ever catch a forged-but-unsigned message claiming to be from a GeraClone clone, that's the attack vector: it doesn't carry a signature. Always demand one. We'll verify.