Skip to content

Sealed Transactions

This document traces the complete flow of sealed (encrypted) transactions from client submission through decryption and execution.

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 block
SchemeStatusUse Case
TLE (BLS12-381)PreferredThreshold encryption requiring t-of-n cooperation
X25519DeprecatedPer-message ECDH + ChaCha20-Poly1305

This document focuses on the TLE scheme.


The client encrypts using the collective BLS public key:

pub fn seal(
plaintext: &[u8],
collective_pk: &G2Affine,
epoch: u64,
) -> Result<Self, TleSealError>
  1. Pad plaintext to 32-byte block boundary
  2. For each 32-byte block:
    • Encrypt using tle::encrypt::<MinSig>(collective_pk, target, block)
    • Target = domain separator + epoch bytes
  3. Concatenate ciphertext blocks
  4. Compute Blake3 commitment hash for FIFO ordering

Each 32-byte plaintext block produces 112 bytes of ciphertext:

Block = U (G1, 48 bytes) || V (32 bytes) || W (32 bytes)
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
}
const TLE_TARGET_PREFIX: &[u8] = b"chain-tle-v1";

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
}
// Configuration
max_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>,
// ...
}
  1. Select sealed transactions from mempool (FIFO order)
  2. Create TleDecryptionCollector with selected transactions
  3. Broadcast DecryptionRequest:
    DecryptionRequest {
    height: BlockHeight,
    epoch: u64,
    tx_commitments: Vec<Hash>,
    }
  4. Wait for validator shares with timeout

When a validator receives a DecryptionRequest:

  1. Retrieve DKG secret key share for the epoch
  2. For each transaction commitment:
    TleDecryptionShare::sign(
    share_sk: &Scalar,
    validator_index: u16,
    tx_commitment: Hash,
    epoch: u64,
    )
  3. Sign the epoch target:
    ops::sign_message::<MinSig>(share_sk, Some(TLE_TARGET_PREFIX), &target)
  4. Broadcast share via gossip
TleDecryptionShare {
validator_index: u16, // Which validator
signature_bytes: [u8; 48], // G1 point (partial BLS signature)
tx_commitment: Hash, // Which transaction
epoch: u64, // Epoch binding
}

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>>, ...>;
}
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)

pub fn unseal(
&self,
threshold_sig: &G1Affine,
expected_epoch: u64,
) -> Result<Vec<u8>, TleUnsealError>
  1. Validate epoch binding (prevents cross-epoch replay)
  2. For each ciphertext block:
    • Extract U (G1), V (32 bytes), W (32 bytes)
    • Call tle::decrypt::<MinSig>(threshold_sig, &ciphertext)
    • Recover plaintext block
  3. Concatenate blocks
  4. Truncate to original plaintext_len
  5. Return plaintext bytes
pub fn decrypt_with_shares(
sealed: &TleSealedTransaction,
shares: &[TleDecryptionShare],
threshold: usize,
expected_epoch: u64,
) -> Result<Vec<u8>, ...>

Single call that combines shares and unseals.


The decrypted plaintext is deserialized:

let signed_tx: SignedTransaction = borsh::from_slice(&plaintext)?;

Then normal execution proceeds:

  1. Signature Verification: ed25519 public key check
  2. Nonce Check: Verify nonce matches expected for (authorizer, nonce_space)
  3. Balance Check: Ensure payer has sufficient balance for max_fee
  4. Gas Accounting: Charge base gas + access list prefetch gas
  5. VM Execution: Run contract bytecode if call_data present
  6. State Update: Apply changes to balances, nonces, storage
pub struct ExecutionOutcome {
pub status: ExecutionStatus, // Success | Revert | Rejected | Trap
pub gas_used: u64,
pub logs: Vec<Log>,
pub return_data: Option<Vec<u8>>,
}

PropertyMechanism
Threshold SecurityRequires t-of-n validators to decrypt (no single validator can read)
Epoch BindingEncryption target includes epoch, prevents cross-epoch replay
FIFO OrderingCommitment hash enables ordering verification without decryption
Tamper ResistanceCiphertext commitment hash detects modifications
Replay ProtectionEpoch + commitment hash combination

User Node Mempool Leader Validators
| | | | |
| seal(tx, pk) | | | |
|----------------->| | | |
| | gossip(TleSealed) | |
| |-------------->| | |
| | | store | |
| | | | |
| | | | select() |
| | |<-------------| |
| | | | |
| | | | DecryptionRequest
| | | |--------------->|
| | | | |
| | | | TleDecryptShare
| | | |<---------------|
| | | | |
| | | | combine_shares |
| | | |----+ |
| | | | | |
| | | |<---+ |
| | | | |
| | | | unseal() |
| | | |----+ |
| | | | | |
| | | |<---+ |
| | | | |
| | | | execute() |
| | | |----+ |
| | | | | |
| | | |<---+ |
| | | | |
| | | | finalize block |
|<-----------------|---------------|--------------| |
| tx_included | | | |