WarpCore / Documentation

ZK Envelope

SOURCE · docs/specs/zk-envelope-v1.md · synced from the engineering repo

Zero-Knowledge Payment Envelope with Regulator Escrow — v1

Status: Draft for regulator + bank privacy review Crate: warpcore-zkenv Document version: 2026-04-11

1. Abstract

Every permissioned blockchain claiming to do cross-border banking has the same contradiction: banks want payment data private, regulators need it accessible. This document defines a cryptographic envelope that resolves the contradiction without trusting the operator.

Every WarpCore payment carries on-chain:

  1. A commitment — SHA-256 of the plaintext.
  2. A ciphertext — payload encrypted under a random per-transaction AES-256-GCM key.
  3. A key escrow — the AES key split via Shamir’s Secret Sharing across N regulator slots with threshold T. WarpCore forgets the key after sealing; fewer than T regulators cannot decrypt.

The plaintext is recoverable only by a quorum of regulators. WarpCore itself cannot unilaterally decrypt, even with full database access and full source code review. This is cryptographically enforced, not policy-enforced.

2. What this defeats

  • Ripple / XRPL. Payment data is fully public on-chain.
  • Stellar. Payment data is fully public on-chain.
  • Corda. Private but stored at a notary the bank must trust. Regulator access requires subpoena.
  • Partior. Closed consortium; non-members cannot audit at all.
  • Fnality. GBP-only; non-members cannot audit at all.
  • SWIFT gpi. Opaque operator-held records; regulator access via subpoena.

The WarpCore envelope gives the regulator cryptographic access guarantees as a property of the protocol — no subpoena, no trusted third party, no legal review — while still giving the bank complete privacy from the operator and from the public.

3. Trust model

PartyHoldsCan decrypt?
Sender bankEphemeral key before sealingYes, until seal completes
Receiver bankOut-of-band key from sender (optional)Yes, if sender transmits
WarpCore operatorCiphertext, envelope metadataNo. Key is forgotten.
Individual regulatorOne shareNo. Below threshold.
Regulator quorum (T of N)T sharesYes. Can reconstruct and decrypt.
PublicCommitment, ciphertext, share countNo.

The key property: after seal() returns, the AES key is zeroed from memory. A post-hoc compromise of the WarpCore process yields ciphertexts and commitments only. To decrypt any single transaction, an attacker must compromise T independent regulator HSMs simultaneously.

4. Envelope format

pub struct ZkEnvelope {
    pub envelope_id: Uuid,
    pub schema_version: String,       // "zkenv/v1"
    pub commitment_hex: String,       // SHA-256(plaintext), 64 hex chars
    pub ciphertext_b64: String,       // base64(nonce || AES-GCM ct || tag)
    pub aad_b64: String,              // base64 of the AEAD additional data
    pub threshold: u8,                // Shamir T: min shares needed
    pub total_shares: u8,             // Shamir N: total shares distributed
    pub sealed_at: DateTime<Utc>,
    pub compliance_proof_ref: Option<Uuid>,  // points at a ZK compliance proof
}

5. Sealing protocol

seal(plaintext, aad, T, N) -> (envelope, key_distribution)

Algorithm:

  1. Generate 32 random bytes K from OsRng.
  2. Compute ciphertext = AES-256-GCM-seal(K, nonce = OsRng[12], plaintext, aad). The HSM helper prepends the 12-byte nonce to the ciphertext.
  3. Split K via shamir_split(K, T, N) to produce N shares.
  4. Compute commitment = SHA-256(plaintext).
  5. Zero K from memory.
  6. Return (ZkEnvelope { commitment, ciphertext, aad, T, N, ... }, KeyDistribution { shares, threshold: T }).

The caller distributes the shares to regulator HSMs out of band. Once seal() returns, K exists only as N Shamir shares. Fewer than T cannot reconstruct it (information-theoretically — Shamir over GF(256) has no computational assumption).

6. Opening protocol

open(envelope, shares) -> plaintext | Err

Algorithm:

  1. If shares.len() < envelope.threshold, return InsufficientShares.
  2. Reconstruct K = shamir_reconstruct(shares).
  3. Decode ciphertext_b64ciphertext; decode aad_b64aad.
  4. Decrypt plaintext = AES-256-GCM-open(K, ciphertext, aad). If AEAD auth fails, return Decryption.
  5. Compute commitment' = SHA-256(plaintext).
  6. If commitment' != envelope.commitment_hex, return CommitmentMismatch. (This is defence in depth; AEAD auth should catch any tampering first, but the commitment is cheap and independent.)
  7. Return plaintext.

7. Threshold semantics

  • T = 1 is rejected at seal time. A single regulator compromise would decrypt every envelope, which defeats the purpose.
  • T = 2, N = 3 is the minimum practical configuration. Requires two regulators to cooperate; tolerant of one share loss.
  • T = 3, N = 5 is the recommended production default. Tolerant of two share losses; requires three-regulator cooperation. Matches the structure of most FATF-style mutual-legal-assistance frameworks.
  • T = 5, N = 7 for maximum-sensitivity transactions (CBDC, large interbank bridges).

8. AEAD additional data (AAD)

The AAD field is cryptographically bound to the ciphertext by AES-GCM. Any modification of the AAD after sealing causes decryption to fail with an authentication error.

Recommended AAD content:

  • uetr=... (Unique End-to-End Transaction Reference)
  • payment_id=... (WarpCore internal ID)
  • schema_version=zkenv/v1
  • region=... (for jurisdiction-scoped audit)

The AAD is public (stored alongside the ciphertext). Do NOT put sensitive data in it. Its purpose is to bind the ciphertext to a specific transaction context so the envelope cannot be replay-attached to a different transaction.

9. ZK proof slot

The envelope carries an optional compliance_proof_ref that points at a ZK proof produced by the existing warpcore-compliance::zkp circuits. The proof demonstrates a property of the plaintext (e.g., amount ∈ [min, max] via the amount_in_range circuit) without revealing the plaintext.

Workflow:

  1. Bank computes the plaintext.
  2. Bank generates a ZK proof that amount is within the jurisdictional travel-rule / CTR bounds using halo2_prover::prove(AmountInRange, witness).
  3. Bank seals the envelope with seal().
  4. Bank attaches the proof reference via attach_proof(env, proof_id).
  5. Bank submits (envelope, proof) to WarpCore.
  6. WarpCore verifies the proof using halo2_prover::verify(proof, public_inputs).

If verification fails, WarpCore rejects the transaction without ever seeing the plaintext. The plaintext only becomes accessible to a regulator quorum, and even then only when legally required.

The ZK circuit library already exists:

  • sanctions_cleared — proves a hash is NOT in the OFAC/UN/EU Merkle tree without revealing the name.
  • kyc_verification — proves KYC passed without revealing entity.
  • amount_in_range — proves amount ∈ [min, max] without revealing it.
  • travel_rule — proves FATF R.16 obligation satisfied.
  • transfer_limit — proves amount < jurisdictional limit.
  • whitelist_membership — proves destination is in an approved Merkle tree.
  • cbdc_authority — proves CBDC mint/burn was authorised.
  • freeze_authority — proves a freeze order is valid.

10. Failure modes

Covered in docs/specs/failure-modes-v1.md, summarised here:

TriggerResult
Insufficient sharesInsufficientShares { have, need }
Tampered ciphertextDecryption error (AEAD auth failure)
Tampered commitmentDecryption error OR CommitmentMismatch
Tampered AADDecryption error
Invalid threshold (T < 2)InvalidThreshold at seal time
N < TInvalidThreshold at seal time
ZK proof verification failsTransaction rejected before settlement
Regulator HSM outageAffected shares unavailable; decrypt blocked

11. Test vectors

TestAsserts
happy_path_seal_and_openBasic seal+open roundtrip
open_with_different_quorum_also_worksAny T of N works (Shamir property)
fewer_than_threshold_shares_rejectedT-1 shares insufficient
single_share_cannot_decryptOne share is information-theoretically useless
tampered_ciphertext_fails_authAES-GCM integrity enforcement
seal_rejects_threshold_below_2T < 2 refused
seal_rejects_total_below_thresholdN < T refused
two_seals_of_same_plaintext_differFresh key per seal (no deterministic ciphertext)
envelope_roundtrips_through_jsonSerialisation is lossless
attach_proof_sets_refProof slot works
large_payload_roundtrip64 KiB payload works
aad_is_bound_to_ciphertextAAD mutation breaks decryption

12 tests, all passing.

12. HTTP API (future — not in this crate)

POST /v1/privacy/seal
Authorization: Bearer <JWT>
Content-Type: application/json

{
  "plaintext_b64": "<base64>",
  "aad": { "uetr": "...", "payment_id": "..." },
  "threshold": 3,
  "total_shares": 5,
  "proof_id": null
}

Response:

{
  "envelope": { ... },
  "shares": [
    { "index": 1, "regulator_slot": "us-fincen", "share_b64": "..." },
    { "index": 2, "regulator_slot": "eu-ecb",    "share_b64": "..." },
    ...
  ]
}

The response is returned once to the caller, which MUST distribute shares to regulators and drop the response before logging. The API server never stores the shares.

POST /v1/privacy/open
Authorization: Bearer <JWT, role=Regulator>
Content-Type: application/json

{
  "envelope_id": "<UUID>",
  "shares": [
    { "index": 1, "share_b64": "..." },
    { "index": 2, "share_b64": "..." },
    ...
  ]
}

Response:

{
  "plaintext_b64": "<base64>",
  "commitment_verified": true
}

Both endpoints are for v1.1. This spec documents the envelope primitive; the API surface ships separately.

13. Reference implementation

  • Crate: warpcore-zkenv
  • Key files:
    • src/lib.rsZkEnvelope, KeyDistribution, seal, open, attach_proof, inline hex and base64
    • tests/envelope.rs — 12 passing integration tests
  • Key dependencies:
    • warpcore-hsm::split_secret / reconstruct_secret (Shamir over GF(256))
    • warpcore-hsm::encrypt_aes256gcm / decrypt_aes256gcm (AES-256-GCM)
    • sha2::Sha256 (commitment)
    • rand::rngs::OsRng (ephemeral key + nonce)

14. Security properties

  • Computational confidentiality (AES-256 against the ciphertext alone)
  • Information-theoretic threshold resilience (Shamir below T reveals nothing)
  • Integrity (AES-GCM tag)
  • Domain binding (AAD binds envelope to transaction context)
  • Tamper evidence (commitment SHA-256)
  • Forward privacy (per-tx ephemeral keys; key rotation is implicit)
  • No operator trust (WarpCore forgets the key after sealing)

15. Roadmap

VersionPlanned
v1 (this doc)Seal / open / Shamir escrow / ZK proof slot
v1.1HTTP endpoints /v1/privacy/seal and /v1/privacy/open
v1.2Regulator HSM connector with automatic share distribution
v2Multi-level escrow: per-region quorums plus a global quorum
v2.1Verifiable reveal logs — regulators publish a hash when they unlock
v3Post-quantum threshold signatures on the envelope

Review: Bank privacy officers, FATF FIU working group, regulators