Skip to main content

Confidentiality Model

OXN encrypts everything about what your contract does, while keeping public what your contract is and how it fits into consensus. This page is the reference for that boundary.

What is encrypted

Contract-facing data — the things your Solidity code produces, consumes, or reads back:

  • Contract storage. Every SSTORE. Encrypted at rest with keys held by the enclave. A node operator dumping the raw storage tree sees ciphertext.
  • Transaction calldata. The function selector, all arguments, and any raw bytes. Wrapped clients handle this transparently.
  • Return data. The abi.encode(...) output your function produces. Encrypted back to the caller's session key.
  • eth_call inputs and outputs. Both signed queries and encrypted view calls have their inputs and outputs encrypted end-to-end.
  • Event log contents. Both topics and data. See the caveat below about who can decrypt them.
  • Revert reasons. The error string in require(cond, "msg") and custom errors. Encrypted back to the caller — debugging a failed transaction from outside is impossible without the caller's session key.

What is not encrypted

Metadata the chain itself needs to route, order, and settle transactions:

  • Sender and recipient addresses (tx.from, tx.to). Always visible.
  • msg.value on the outer transaction. If value moves through a contract call, the value field is visible even though the contract logic that moved it is not.
  • Gas price, gas limit, gas used. Public on every block.
  • Contract bytecode. Every deployed contract's code is public and inspectable, exactly as on Ethereum.
  • Contract addresses and creation transactions. Public.
  • Block metadata — block number, timestamp, block hash, ordering of transactions inside a block.
  • Transaction status (success or reverted). The revert reason is encrypted; the success flag itself is not.

The cheat sheet

ThingEncrypted?Who can decrypt
Contract storageYesEnclave only
Transaction calldataYesEnclave (during execution), then discarded
Transaction return dataYesEnclave + caller's session key
eth_call inputs / outputsYesEnclave + caller's session key
Event topics + dataYesEnclave + the caller that emitted them
Revert reasonYesEnclave + caller's session key
msg.sender, tx.originNoEveryone
msg.value, gas fieldsNoEveryone
Block number, timestamp, hashNoEveryone
Contract address, bytecodeNoEveryone
Transaction hash, from / to / statusNoEveryone

Event logs: the gotcha every indexer team hits

Event topics and data are encrypted end-to-end with the same session key used for the encrypted call that produced them. This means:

  • The account that made the call can decrypt its own logs. If Alice calls contract.transfer(bob, 100) from a wrapped signer, Alice can read the resulting Transfer(Alice, bob, 100) event by listening on the same wrapped provider.
  • Nobody else can decrypt those logs — not Bob, not the contract owner, not a global indexer service, not you if you were monitoring Alice's account from outside.

If your architecture requires a third party (an indexer, a monitoring dashboard, a notification service) to observe contract events across all users, encrypted event logs will not work. Options:

  1. Emit a public event alongside a private one. Include the participants but not the amounts, for example. This sacrifices some confidentiality but restores observability.
  2. Use a public event-relay contract. The relayer emits plain events on behalf of the confidential contract, under access rules the confidential contract enforces.
  3. Have callers relay their own events off-chain. Each caller has the session key to decrypt and can post to whatever aggregator they trust.

Choose deliberately based on your app's threat model. See Best Practices for guidance.

How the encryption works

A short description of the mechanism so you can reason about failure modes. This is not a formal spec.

Setup. Each OXN enclave has a long-lived public key (rotated on a schedule). Clients fetch this key from the network on demand.

Encrypted call (transaction or eth_call).

  1. Client generates a fresh X25519 ephemeral keypair per call.
  2. Client derives a symmetric key from (ephemeral_secret, enclave_public) via X25519.
  3. Client encrypts plaintext calldata with Deoxys-II symmetric encryption under that key, along with a random nonce.
  4. Client wraps (ephemeral_public, nonce, ciphertext) in a CBOR envelope and submits it as tx.data.
  5. Enclave derives the same symmetric key from (enclave_secret, ephemeral_public), decrypts, executes.
  6. Return data is encrypted under the same session key back to the client.

Storage encryption. Each contract has a distinct storage key derived deterministically from a master key held by the enclave and the contract address. SSTORE transparently encrypts, SLOAD transparently decrypts. The Solidity author writes normal storage code — no explicit encryption calls are needed.

Ephemeral keys are single-use. After a call, the session key is discarded on both sides. This is why nobody can retroactively decrypt an old transaction's calldata even if the enclave's long-lived key later leaks.

Common misconceptions

"Confidentiality is opt-in per function." No. Contract storage encryption is on by default for every contract on OXN. Encrypted calldata is on whenever the client uses a wrapped provider. Contract authors do not opt in.

"msg.sender can be spoofed." No. msg.sender is checked at the consensus / transaction-signature layer, not at the encryption layer. It works exactly as on Ethereum. Signature verification protects it end-to-end.

"Encrypting my events is enough to keep them private." For the caller — yes. For anyone else — no. See the event logs gotcha above.

"I can hide which contract I'm calling." No. tx.to is always visible. Confidentiality hides what you asked the contract to do, not that you called it.

Next steps