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:
- A commitment — SHA-256 of the plaintext.
- A ciphertext — payload encrypted under a random per-transaction AES-256-GCM key.
- 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
| Party | Holds | Can decrypt? |
|---|---|---|
| Sender bank | Ephemeral key before sealing | Yes, until seal completes |
| Receiver bank | Out-of-band key from sender (optional) | Yes, if sender transmits |
| WarpCore operator | Ciphertext, envelope metadata | No. Key is forgotten. |
| Individual regulator | One share | No. Below threshold. |
| Regulator quorum (T of N) | T shares | Yes. Can reconstruct and decrypt. |
| Public | Commitment, ciphertext, share count | No. |
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:
- Generate 32 random bytes K from
OsRng. - Compute
ciphertext = AES-256-GCM-seal(K, nonce = OsRng[12], plaintext, aad). The HSM helper prepends the 12-byte nonce to the ciphertext. - Split K via
shamir_split(K, T, N)to produce N shares. - Compute
commitment = SHA-256(plaintext). - Zero K from memory.
- 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:
- If
shares.len() < envelope.threshold, returnInsufficientShares. - Reconstruct
K = shamir_reconstruct(shares). - Decode
ciphertext_b64→ciphertext; decodeaad_b64→aad. - Decrypt
plaintext = AES-256-GCM-open(K, ciphertext, aad). If AEAD auth fails, returnDecryption. - Compute
commitment' = SHA-256(plaintext). - If
commitment' != envelope.commitment_hex, returnCommitmentMismatch. (This is defence in depth; AEAD auth should catch any tampering first, but the commitment is cheap and independent.) - 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/v1region=...(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:
- Bank computes the plaintext.
- Bank generates a ZK proof that
amountis within the jurisdictional travel-rule / CTR bounds usinghalo2_prover::prove(AmountInRange, witness). - Bank seals the envelope with
seal(). - Bank attaches the proof reference via
attach_proof(env, proof_id). - Bank submits
(envelope, proof)to WarpCore. - 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:
| Trigger | Result |
|---|---|
| Insufficient shares | InsufficientShares { have, need } |
| Tampered ciphertext | Decryption error (AEAD auth failure) |
| Tampered commitment | Decryption error OR CommitmentMismatch |
| Tampered AAD | Decryption error |
| Invalid threshold (T < 2) | InvalidThreshold at seal time |
| N < T | InvalidThreshold at seal time |
| ZK proof verification fails | Transaction rejected before settlement |
| Regulator HSM outage | Affected shares unavailable; decrypt blocked |
11. Test vectors
| Test | Asserts |
|---|---|
happy_path_seal_and_open | Basic seal+open roundtrip |
open_with_different_quorum_also_works | Any T of N works (Shamir property) |
fewer_than_threshold_shares_rejected | T-1 shares insufficient |
single_share_cannot_decrypt | One share is information-theoretically useless |
tampered_ciphertext_fails_auth | AES-GCM integrity enforcement |
seal_rejects_threshold_below_2 | T < 2 refused |
seal_rejects_total_below_threshold | N < T refused |
two_seals_of_same_plaintext_differ | Fresh key per seal (no deterministic ciphertext) |
envelope_roundtrips_through_json | Serialisation is lossless |
attach_proof_sets_ref | Proof slot works |
large_payload_roundtrip | 64 KiB payload works |
aad_is_bound_to_ciphertext | AAD 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.rs—ZkEnvelope,KeyDistribution,seal,open,attach_proof, inline hex and base64tests/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
| Version | Planned |
|---|---|
| v1 (this doc) | Seal / open / Shamir escrow / ZK proof slot |
| v1.1 | HTTP endpoints /v1/privacy/seal and /v1/privacy/open |
| v1.2 | Regulator HSM connector with automatic share distribution |
| v2 | Multi-level escrow: per-region quorums plus a global quorum |
| v2.1 | Verifiable reveal logs — regulators publish a hash when they unlock |
| v3 | Post-quantum threshold signatures on the envelope |
Review: Bank privacy officers, FATF FIU working group, regulators