Sealed Transactions
This document traces the complete flow of sealed (encrypted) transactions from client submission through decryption and execution.
Overview
Section titled “Overview”The chain implements Time-Lock Encryption (TLE) using BLS12-381 threshold cryptography. No single validator can decrypt transactions - they must cooperate to reach threshold.
Client seals tx --> Gossip --> Mempool --> Leader selects -->DecryptionRequest --> Validators send shares --> Combine shares -->Decrypt --> Execute --> Finalize blockTwo Encryption Schemes
Section titled “Two Encryption Schemes”| Scheme | Status | Use Case |
|---|---|---|
| TLE (BLS12-381) | Preferred | Threshold encryption requiring t-of-n cooperation |
| X25519 | Deprecated | Per-message ECDH + ChaCha20-Poly1305 |
This document focuses on the TLE scheme.
1. Transaction Sealing (Client Side)
Section titled “1. Transaction Sealing (Client Side)”The client encrypts using the collective BLS public key:
pub fn seal( plaintext: &[u8], collective_pk: &G2Affine, epoch: u64,) -> Result<Self, TleSealError>Process
Section titled “Process”- Pad plaintext to 32-byte block boundary
- For each 32-byte block:
- Encrypt using
tle::encrypt::<MinSig>(collective_pk, target, block) - Target = domain separator + epoch bytes
- Encrypt using
- Concatenate ciphertext blocks
- Compute Blake3 commitment hash for FIFO ordering
Ciphertext Format
Section titled “Ciphertext Format”Each 32-byte plaintext block produces 112 bytes of ciphertext:
Block = U (G1, 48 bytes) || V (32 bytes) || W (32 bytes)Wire Format
Section titled “Wire Format”TleSealedTransaction { epoch: u64, // Encryption epoch (matches DKG) ciphertext_bytes: Vec<u8>, // blocks * 112 bytes block_count: u32, // Number of ciphertext blocks plaintext_len: u32, // Original plaintext length commitment: Hash, // Blake3(ciphertext) for ordering}Domain Separator
Section titled “Domain Separator”const TLE_TARGET_PREFIX: &[u8] = b"chain-tle-v1";2. Submission and Gossip
Section titled “2. Submission and Gossip”Gossip Message Types
Section titled “Gossip Message Types”pub enum TxGossipMessage { Plaintext(SignedTransaction), // variant 0 Sealed(SealedTransaction), // variant 1 (X25519, deprecated) DecryptShare(DecryptionShare), // variant 2 (X25519) TleSealed(TleSealedTransaction), // variant 3 TleDecryptShare(TleDecryptionShare), // variant 4 TleDecryptionRequest(DecryptionRequest),// variant 5}Mempool Storage
Section titled “Mempool Storage”// Configurationmax_sealed_txs: 10_000,max_sealed_per_block: 500,Sealed transactions are stored separately from regular transactions. No decryption is attempted at this stage.
3. Block Production and Decryption Request
Section titled “3. Block Production and Decryption Request”The Application Actor maintains:
struct Actor { tle_collector: Option<TleDecryptionCollector>, tle_gossip_tx: Option<mpsc::Sender<TxGossipMessage>>, supervisor: Option<ViewSupervisor>, // ...}When Leader Builds a Block
Section titled “When Leader Builds a Block”- Select sealed transactions from mempool (FIFO order)
- Create
TleDecryptionCollectorwith selected transactions - Broadcast
DecryptionRequest:DecryptionRequest {height: BlockHeight,epoch: u64,tx_commitments: Vec<Hash>,} - Wait for validator shares with timeout
4. Validator Share Generation
Section titled “4. Validator Share Generation”When a validator receives a DecryptionRequest:
Process
Section titled “Process”- Retrieve DKG secret key share for the epoch
- For each transaction commitment:
TleDecryptionShare::sign(share_sk: &Scalar,validator_index: u16,tx_commitment: Hash,epoch: u64,)
- Sign the epoch target:
ops::sign_message::<MinSig>(share_sk, Some(TLE_TARGET_PREFIX), &target)
- Broadcast share via gossip
Share Format
Section titled “Share Format”TleDecryptionShare { validator_index: u16, // Which validator signature_bytes: [u8; 48], // G1 point (partial BLS signature) tx_commitment: Hash, // Which transaction epoch: u64, // Epoch binding}5. Share Collection and Combination
Section titled “5. Share Collection and Combination”TleDecryptionCollector
Section titled “TleDecryptionCollector”impl TleDecryptionCollector { // Initialize with sealed transactions pub fn new(sealed_txs: Vec<TleSealedTransaction>) -> Self;
// Accept incoming shares pub fn add_share(&mut self, share: TleDecryptionShare) -> bool;
// Check if threshold reached (f+1) pub fn has_enough_shares(&self, threshold: usize) -> bool;
// Core decryption pub fn decrypt_all(&self, threshold: usize) -> Result<Vec<Vec<u8>>, ...>;}Threshold Signature Recovery
Section titled “Threshold Signature Recovery”pub fn combine_partial_signatures( threshold: usize, partial_sigs: &[(u16, G1Affine)], // (validator_index, signature)) -> Result<G1Affine, CombineError>Uses Lagrange interpolation:
ops::threshold_signature_recover::<MinSig, _>(threshold, &partial_sigs)6. Transaction Decryption
Section titled “6. Transaction Decryption”pub fn unseal( &self, threshold_sig: &G1Affine, expected_epoch: u64,) -> Result<Vec<u8>, TleUnsealError>Process
Section titled “Process”- Validate epoch binding (prevents cross-epoch replay)
- For each ciphertext block:
- Extract U (G1), V (32 bytes), W (32 bytes)
- Call
tle::decrypt::<MinSig>(threshold_sig, &ciphertext) - Recover plaintext block
- Concatenate blocks
- Truncate to original
plaintext_len - Return plaintext bytes
Convenience Function
Section titled “Convenience Function”pub fn decrypt_with_shares( sealed: &TleSealedTransaction, shares: &[TleDecryptionShare], threshold: usize, expected_epoch: u64,) -> Result<Vec<u8>, ...>Single call that combines shares and unseals.
7. Transaction Execution
Section titled “7. Transaction Execution”The decrypted plaintext is deserialized:
let signed_tx: SignedTransaction = borsh::from_slice(&plaintext)?;Then normal execution proceeds:
- Signature Verification: ed25519 public key check
- Nonce Check: Verify nonce matches expected for (authorizer, nonce_space)
- Balance Check: Ensure payer has sufficient balance for max_fee
- Gas Accounting: Charge base gas + access list prefetch gas
- VM Execution: Run contract bytecode if call_data present
- State Update: Apply changes to balances, nonces, storage
Result
Section titled “Result”pub struct ExecutionOutcome { pub status: ExecutionStatus, // Success | Revert | Rejected | Trap pub gas_used: u64, pub logs: Vec<Log>, pub return_data: Option<Vec<u8>>,}Security Properties
Section titled “Security Properties”| Property | Mechanism |
|---|---|
| Threshold Security | Requires t-of-n validators to decrypt (no single validator can read) |
| Epoch Binding | Encryption target includes epoch, prevents cross-epoch replay |
| FIFO Ordering | Commitment hash enables ordering verification without decryption |
| Tamper Resistance | Ciphertext commitment hash detects modifications |
| Replay Protection | Epoch + commitment hash combination |
Sequence Diagram
Section titled “Sequence Diagram”User Node Mempool Leader Validators | | | | | | seal(tx, pk) | | | | |----------------->| | | | | | gossip(TleSealed) | | | |-------------->| | | | | | store | | | | | | | | | | | select() | | | |<-------------| | | | | | | | | | | DecryptionRequest | | | |--------------->| | | | | | | | | | TleDecryptShare | | | |<---------------| | | | | | | | | | combine_shares | | | | |----+ | | | | | | | | | | |<---+ | | | | | | | | | | unseal() | | | | |----+ | | | | | | | | | | |<---+ | | | | | | | | | | execute() | | | | |----+ | | | | | | | | | | |<---+ | | | | | | | | | | finalize block | |<-----------------|---------------|--------------| | | tx_included | | | |Related Documentation
Section titled “Related Documentation”- DKG Flow - Distributed key generation for threshold keys
- Finalization Flow - Block finalization process