Tx Lifecycle

This page describes how Seismic transactions work from end-to-end

Key management

  • The network manages a its keys through the seismic-enclave-serverarrow-up-right crate. Reth (and summit) can make RPC calls to this server. One such call is to get the network keys

  • Enclave can boot in either --genesis-node OR --peers <ip> mode. The former generates its own root key and will share it with peers after they pass validation. The latter will loop through its peer IPs until it receives the root key from one of them

  • Enclave then derives a few different keys from that root key. Most importantly, it derives the encryption secret key. This is the key used to decrypt Seismic transaction calldata

  • When seismic-retharrow-up-right boots, it requests the derived keys from the enclave and keeps them in memory

  • We exposed a new RPC method, seismic_getTeePublicKey. Its response is the public key of the aforementioned secret key that decrypts Seismic transactions. Calling this endpoint is the first step that a client takes to build a Seismic transaction

  • Clients also generate their own secret key, which can be either ephemeral (default) or long-lived, if they prefer to manage this themselves

  • Their secret key & the network public key combine to create an AES key, which is used to encrypt calldata

Encryption & Metadata

  • The client will include their encryption public key in the transaction, along with a four other parameters to tighten the security of encryption. These are:

    • a 12 byte encryptionNonce

    • recentBlockHash, which must be within 100 blocks of the latest block

    • an expiresAtBlock number, after which this transaction is invalid

    • optionally, a boolean signedRead. If set, will only allow the transaction to be used for signed reads. Otherwise it will be rejected by the transaction pool validation

  • The is one other field in seismic transaction metadata, an integer called messageVersion. This field is a hack to allow browser extension wallets (e.g. Metamask) to support the Seismic transaction type. Message version 0, the default, means a standard Seismic transaction as described above. Message version 2 corresponds to a transaction sent as EIP712 typed data, which browser extension wallets can sign. There is no message version 1, but we reserved this for implementing seismic transactions via eth_personalSign

  • These 6 seismic-specific fields are called SeismicElements: client encryption pubkey, the 4 security fields above, message version) are combined with the EOA address (sender), chainId, nonce, to address, and value. The collection of all 11 of these fields are referred to as the Seismic transaction metadata (TxSeismicMetadata) in seismic-alloy-consensusarrow-up-right

  • To encrypt calldata, we use AES with AEAD. The AEAD is an RLP encoding of the TxSeismicMetadata. The nonce for this encryption is the same 12-byte encryption nonce as we included in the metadata

  • The encrypted calldata is then passed to a Seismic transaction in the data/input field. Seismic transactions work just like Legacy Ethereum transactions, except:

    • they have tx type of 74 or 0x4a

    • they have these SeismicElements attached as well

Decryption

  • Transactions sent with type 0x4a but incomplete Seismic elements are rejected from the tx pool, as are transactions not marked as 0x4a but do contain Seismic elements

  • Nodes will decrypt Seismic transactions by:

    • decoding the transaction

    • recovering the signer

    • assembling the metadata

    • encoding it as AEAD via RLP

    • generating the AES key by combining the network's secret key with the transaction's encryption public key

  • If transaction decryption fails, the transaction is removed from the transaction pool

  • If it succeeds, it passes to the revm transaction environment as plaintext

  • Calls to get transaction information after they have landed on chain should return input as the encrypted calldata, and not the plaintext

  • A note on calldata encoding: s-types are encoded the same exact way as their public analogues. The consequence of this brings up an interesting artifact of the Seismic protocol (and maybe a security concern?): solidity has no clue whether functions are called with Seismic transactions or not. And as a result, it's totally allowed to altar shielded state with vanilla Ethereum transactions. It's also allowed to do the reverse, by altaring public state with Seismic transactions

Shielded storage

  • Inside solidity, we've defined 2 new opcodes: CLOAD and CSTORE. These are the shielded analogues to SLOAD and SSTORE. We know to use CLOAD/CSTORE iff the underlying type is an s-type

  • Importantly, we maintain only 1 state tree, as you can see in seismic-triearrow-up-right (a fork of alloy-trie). We modified the state tree by adding an extra boolean flag to leaves, is_private. This is wrapped inside the struct called FlaggedStorage, which replaces StorageValue (a plain U256). We use this flag to validate CLOAD/CSTORE calls, and to know whether we can expose this piece of storage, via e.g. eth_getStorageAt

  • When we see a CLOAD opcode inside seismic-revmarrow-up-right, we load the storage slot and validate that it either has is_private set to true or is an uninitialized slot. For CSTORE, we write the storage value with is_private = true. We throw errors when trying to CLOAD/CSTORE with FlaggedStorage that has is_private = false (“Invalid public storage access”). Similarly we throw errors when trying to SLOAD/SSTORE on FlaggedStorage that has is_private = true (“Invalid private storage access”). These errors are thrown inside seismic-revm.

  • Unlike SSTORE/SLOAD, the gas cost of CLOAD/CSTORE does not change based on the length of the word stored. This is to prevent attackers from deducing information about shielded storage values

Notes

  • Outside of SeismicElements and the encrypted calldata, Seismic transactions have the same fields as legacy transactions. In particular, it uses the same gas parameters: gasPrice and gasLimit

  • Seismic transactions cannot be used to deploy bytecode via Create calls. Instead we only allow Seismic transactions to be sent to a specific address. We did this to make metadata validation easier, and also we can’t see a good reason to deploy encrypted bytecode

Last updated