7. Zero-Knowledge Cryptography

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 SlotSigner KeyWhat 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 KeyProves 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-torsion points) that could otherwise validate forged signatures.
  • Enforces canonical scalar encoding S < L to 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