Summary
Add a new wire-format magic RPGV (Verified) for documents that are both encrypted to and signed by the same identity — the user's own key. Replaces the current layered approach (sign blob → encrypt blob) with a single flat header fully authenticated by AEAD.
These are personal vault documents: only you can decrypt them, and only you could have produced them.
Wire format
[4B "RPGV"][1B ver=0x01][1B variant=0x01]
[32B ephemeral_x25519_pubkey]
[12B nonce]
[64B ed25519_signature] ← sig over raw plaintext
[ciphertext+tag]
Total header: 114 bytes. All header bytes passed as AEAD AAD — the signature is authenticated by the MAC.
Encrypt path
- Ed25519-sign raw plaintext with own Ed25519 privkey → 64-byte signature
- Generate ephemeral X25519 keypair
ECDH(ephemeral_priv, own_x25519_pub) → shared secret
HKDF-SHA256(shared_secret, salt=nonce) → AES-256-GCM key
- Construct full 114-byte header (including sig)
- AES-GCM encrypt plaintext, AAD = full header
Decrypt + verify path
- Parse header: extract ephemeral pubkey, nonce, sig
ECDH(own_x25519_priv, ephemeral_pub) → shared secret
- HKDF → AES key; AES-GCM decrypt (tag verifies AAD/header integrity)
- Ed25519 verify sig over plaintext using own Ed25519 pubkey
- Report: decrypted + signature valid / invalid
Surface changes
GUI: Operations → Encrypt → Asymmetric + Sign... produces RPGV instead of the current nested RPGA(binary-signed-blob). No separate recipient picker — always uses own identity (own X25519 + own Ed25519).
CLI: rpg --encrypt --sign produces RPGV.
Armor header: -----BEGIN RUSTPG VERIFIED MESSAGE-----
detect_kind(): add CipherKind::Verified arm for RPGV magic.
BufferKind: add ArmoredCipherVerified variant; status bar shows Ciphertext (verified).
Future extension (not in scope)
The current format does not embed the owner's public key or fingerprint. For multi-identity setups, a v2 variant could include an 8-byte key fingerprint in the header to identify which identity opens the file. Noted in TODO.md.
## Summary
Add a new wire-format magic `RPGV` (Verified) for documents that are both encrypted to and signed by the same identity — the user's own key. Replaces the current layered approach (sign blob → encrypt blob) with a single flat header fully authenticated by AEAD.
These are personal vault documents: only you can decrypt them, and only you could have produced them.
## Wire format
```
[4B "RPGV"][1B ver=0x01][1B variant=0x01]
[32B ephemeral_x25519_pubkey]
[12B nonce]
[64B ed25519_signature] ← sig over raw plaintext
[ciphertext+tag]
```
Total header: **114 bytes.** All header bytes passed as AEAD AAD — the signature is authenticated by the MAC.
## Encrypt path
1. Ed25519-sign raw plaintext with own Ed25519 privkey → 64-byte signature
2. Generate ephemeral X25519 keypair
3. `ECDH(ephemeral_priv, own_x25519_pub)` → shared secret
4. `HKDF-SHA256(shared_secret, salt=nonce)` → AES-256-GCM key
5. Construct full 114-byte header (including sig)
6. AES-GCM encrypt plaintext, AAD = full header
## Decrypt + verify path
1. Parse header: extract ephemeral pubkey, nonce, sig
2. `ECDH(own_x25519_priv, ephemeral_pub)` → shared secret
3. HKDF → AES key; AES-GCM decrypt (tag verifies AAD/header integrity)
4. Ed25519 verify sig over plaintext using own Ed25519 pubkey
5. Report: decrypted + signature valid / invalid
## Surface changes
**GUI**: `Operations → Encrypt → Asymmetric + Sign...` produces RPGV instead of the current nested RPGA(binary-signed-blob). No separate recipient picker — always uses own identity (own X25519 + own Ed25519).
**CLI**: `rpg --encrypt --sign` produces RPGV.
**Armor header**: `-----BEGIN RUSTPG VERIFIED MESSAGE-----`
**`detect_kind()`**: add `CipherKind::Verified` arm for `RPGV` magic.
**`BufferKind`**: add `ArmoredCipherVerified` variant; status bar shows `Ciphertext (verified)`.
## Future extension (not in scope)
The current format does not embed the owner's public key or fingerprint. For multi-identity setups, a v2 variant could include an 8-byte key fingerprint in the header to identify which identity opens the file. Noted in TODO.md.