7.2 Verify-Before-Decrypt Dual Ed25519 Signatures
SignedHpkeBlobV1 · OriginSigV1 + CloudRelayBlobSigV1 (0.09ms)Why Chinwag enforces Sign-then-Encrypt-then-Dual-Sign (INV-VERIFY-BEFORE-DECRYPT-01) so untrusted network frames never reach the ML-KEM-768 decapsulator.
7.2 Verify-Before-Decrypt Dual Ed25519 Signatures
SignedHpkeBlobV1 · OriginSigV1 + CloudRelayBlobSigV1 (0.09ms)
Many encrypted protocols attempt key decapsulation first and only check sender authenticity after decrypting the inner payload. That ordering exposes the endpoint's lattice decapsulator (ML-KEM-768), heap allocator, and parser to unauthenticated network packets · creating a denial-of-service and chosen-ciphertext oracle surface.
Chinwag enforces a strict cryptographic invariant (INV-VERIFY-BEFORE-DECRYPT-01): no frame is ever passed to X25519MLKEM768::decap() or ChaCha20Poly1305::open() until both outer Ed25519 signatures (K1 Origin + K2 Relay/Domain) verify in strict non-malleable mode (0.09ms).
Sign-then-Encrypt-then-Dual-Sign Pipeline
SENDER WORKSTATION / CI RUNNER
1. Construct canonical 7-field payload (sender <SEAT>#<INSTANCE>, recipient, task, body, ts)
2. Sign plaintext canonical digest with K1 (Seat Ed25519 Key) -> Inner Authenticity Binding
3. Seal payload with X25519MLKEM768 + ChaCha20Poly1305 (RFC 9180 HPKE)
4. Sign outer header (Bytes 0..1252) || BLAKE3(ciphertext) with K1 -> OriginSigV1 (64 B)
5. Attach K2 Org/Relay Domain Attestation Signature -> CloudRelayBlobSigV1 (64 B)
│
▼ (Wire Transmission over iroh QUIC / Cloudflare Edge / .cwcard.png)
│
RECIPIENT WORKSTATION / AGENT DAEMON
1. Check 8-byte Magic ("CHWGHPKE") & monotonic sequence window (< 0.005ms)
2. Verify K1 OriginSigV1 + K2 CloudRelayBlobSigV1 via ed25519-dalek verify_strict (0.09ms)
├──► IF INVALID: Drop frame at socket boundary! Zero heap allocation, zero HPKE decap.
└──► IF VALID: Proceed to Step 3.
3. Decapsulate X25519MLKEM768 shared secret & open ChaCha20Poly1305 AEAD (0.41ms)
4. Verify inner 7-field sender <SEAT>#<INSTANCE> matches outer K1 certificate subject.
Why Dual Signatures (K1 + K2)?
| Signature Slot | Signer Key | What It Proves |
|---|---|---|
K1 (OriginSigV1) | Hardware/OS Keychain Ed25519 key of the sending human developer or agent seat (~/.chinwag/signing/<seat>_ed25519) | Proves that the specific authorized seat in .chinwag/allowed_signers authored and sealed this exact ciphertext. |
K2 (CloudRelayBlobSigV1) | Organization Enrollment Key or Edge Ingress Attestation Key | Proves tenant membership, rate-limit token validity, and ingress timestamp ordering so cross-tenant replay packets are rejected at the edge. |
Strict Non-Malleability (verify_strict)
All Ed25519 verifications in chinwag-crypto use ed25519_dalek::VerifyingKey::verify_strict (RFC 8032 cofactorless verification with small-order point rejection):
- Rejects small-order public keys (
8-torsionpoints) that could otherwise validate forged signatures. - Enforces canonical scalar encoding
S < Lto prevent signature malleability attacks on signed task receipts.
pub fn verify_before_decrypt(
frame: &SignedHpkeBlobV1,
allowed_signers: &AllowedSignersRing,
) -> Result<VerifiedCiphertextRef<'_>, CryptoError> {
let k1_vk = allowed_signers.lookup_strict(&frame.sender_key_id)?;
let k2_vk = allowed_signers.lookup_domain_strict(&frame.domain_key_id)?;
let transcript = frame.signing_transcript_blake3();
k1_vk.verify_strict(&transcript, &frame.origin_sig_k1)?;
k2_vk.verify_strict(&transcript, &frame.relay_sig_k2)?;
Ok(frame.as_verified_ciphertext())
}
Next Steps
- Next: Keyed
BLAKE3Blinded Commitments & The128-ByteZero-Leak Learning Flywheel - Ready for
0.09mscryptographic provenance on every agent action? Reserve Your Organization Credit Pack →
PQ-HPKE Wire Envelope
Complete byte-level specification of Chinwag's +1,408-byte hybrid post-quantum RFC 9180 HPKE envelope (X25519MLKEM768 + HKDF-SHA256 + ChaCha20Poly1305).
Blinded Commitments & 128B Flywheel
How Chinwag routes encrypted frames using blinded 32-byte BLAKE3 commitments (T_coalesce) and improves model routing with a 128-byte zero-code telemetry struct.