Swappable Privacy Backends

One of the biggest challenges to adopting new technology is the friction of changing standards and interfaces. Getting people - especially diverse groups in an ecosystem - to rewrite software is hard. In permissionless systems, it’s even harder since you may need to convince users to migrate individually. We’re seeing this challenge play out now with private payments and adoption by wallets. I’m writing down some early thoughts here to gather input from others thinking about similar problems.

A risk I see with the growing interest in TEEs is that they’ll become the default way to achieve technically enforced programmable privacy. If that happens, it might make it much harder for better cryptographic alternatives (like FHE or MPC) to gain adoption later, even once they’re mature and performant. To avoid this, it would be ideal if our software layers were designed so that the “backend” providing security could be swapped out easily.

I’m not yet sure what this entails—partly because I’m still working out what it means to design software for TEEs. I’d love input from others on how to think about this problem.

Memory Trace Obliviousness (MTO) and TEEs

Memory Trace Obliviousness (MTO) means that an adversary observing a program’s memory accesses cannot infer its secret inputs. Since program instructions live in memory, we assume the adversary can see which instructions and memory locations are accessed (though not the encrypted data itself).

There’s already extensive academic work on efficient MTO techniques and compilers that automatically transform ordinary programs into MTO ones. Oblivious Labs is working on productionizing such a compiler, and Elaine Shi gave an excellent talk on this topic at MEV-SBC. The compiler only needs the developer to specify which inputs are confidential—otherwise, it must conservatively treat everything as private.

MTO is necessary for TEEs (especially when assuming a physical adversary) and for certain forms of interactive MPC. Since thresholdized FHE is technically a form of MPC, I’m not sure whether MTO is universally required there—would appreciate input from others more familiar with this.

Available Instructions

The efficiency of MTO programs depends heavily on the instruction set available to the compiler or programmer. For instance, we could expose an oblivious_load instruction that implements an optimized ORAM for a specific memory region (example), reducing the need for software-level access indirection.

Other details of the backed could also come into play. For example, in the TEE setting, we may want the compiled program to split secrets up into shares in what is known as “software masking” as a means to obscure power channels. Depending on the hardware, there may be safe and unsafe sequences in which these shares can be processed (although ideally we encapsulate this complexity in the hardware so the compiler doesn’t need to worry about it).

Confidential Programs

So far we’ve only discussed hiding inputs, but sometimes we also want to hide the program logic itself (example). Conceptually, sensitive program logic can be treated as another “confidential input,” while the public program becomes a generic interpreter or universal circuit (e.g., a RISC-V processor).

For example:

  • A DEX smart contract might hide only the traded assets and amounts from an adversary running the TEE or participating in the MPC.
  • An “oblivious EVM” could go further, hiding even which contract logic is being executed (leaking only the maximum instruction count). This would, of course, be much slower.

Retrofitting MTO

It may not be too difficult to confidentially execute existing programs that weren’t designed for confidentiality. Consider blockchain pre-execution privacy: multiple users’ transactions must be processed privately, but once a block is finalized, its output must be public so others can verify it (either through re-execution or succinct proofs).

In this setting, TEEs could execute MTO-compiled versions of existing smart contracts (like Uniswap V3), assuming conservative confidentiality defaults or user-provided annotations. Since these modified contracts are functionally equivalent to the originals, the public chain could still treat the canonical contracts as authoritative—the state root would match.

Questions

  • What properties must a program satisfy to be securely implementable using interactive MPC, FHE, or other primitives (e.g., iO)?
  • Does it make sense to ask developers to label inputs as confidential, and later retarget compilers to different “secure backends”?
  • Can these primitives be combined easily—e.g., marking certain data to be TEE-protected only, and other data to remain confidential even if the TEE is compromised?
  • Are there ongoing projects targeting this “pluggable backend” abstraction? I know of Phantom Zone and Enclav3, but I’m unsure how applicable their work is.
  • Is the concept of MTO relevant to FHE at all?
  • What did I miss?
3 Likes

Programs written for Sunscreen’s Parasol compiler might be a good reference for what this could look like. This auction implementation, for example, explicitly specifies which parameters are encrypted values.

This recent paper is another example of an FHE-focused compiler, along with an intermediate representation github implementation.

I do quite like the idea of a Noir-like high-level language with an intermediate representation which can target any number of backends.

2 Likes
  • Is the concept of MTO relevant to FHE at all?

I think so. But a question for you: If a program is MTO can it be represented as a constant time circuit? Circuit library can have all arithmetic operations and many MUXs.

Conceptually, sensitive program logic can be treated as another “confidential input,” while the public program becomes a generic interpreter or universal circuit (e.g., a RISC-V processor).

We’ve something like this with Phantom - a risc-v FHE VM (still a WIP and first version will be vv slow ).

Shared backend seems kinda sweet. IMO the VM ( or the universal circuit ) seems to be the easiest abstraction from developer’s perspective. However, it always comes with a huge ( unnecessary ) overhead.

For optimal performance, we should stick with circuits. Perhaps we can have a shared IR ( maybe MTO can be applied on the IR, as an option ) from which we compile to different backends ( TEE, FHE, MPC, or …iO). The output result is a circuit. But, now, I can think of multiple ways I want developer to annotate their program to help output optimal FHE circuit while the same annotations, I suspect, are largely useless for MPC ( and other way around ).

1 Like

I’m not quite sure what you mean by “can”? My understanding is that you need to execute the whole circuit when it comes to FHE computation so all you need to do is not change the circuit based on the input data (i.e. the circuit should be generated only as a function of the program and not also of the input data). If that understanding is correct, then any program can be represented as a constant time circuit no?

Also, my understanding of phantom is that you guys compile to RISC-V assembly first and then go to circuits. That seems like a reasonable starting point. If we don’t need to hide everything (e.g. don’t need to hide the full program being executed), we would need this to come with labels indicating which instructions are handling confidential data.

I can think of multiple ways I want developer to annotate their program to help output optimal FHE circuit while the same annotations, I suspect, are largely useless for MPC

Can you give an example?

1 Like

I’m not quite sure what you mean by “can”?

Apologies this question was framed due to my lack of understanding of MTO. Yes you’re right any program can be made constant runtime as a circuit. I wondered whether being MTO implies constant time but, apparently, that is not the case ( right? ).

Also, my understanding of phantom is that you guys compile to RISC-V assembly first and then go to circuits.

No, phantom is an universal circuit. That means it takes (encrypted) program description and executes on (encrypted) inputs. The program description is not a circuit.

Can you give an example?

Two examples come to mind:

  1. Variable range: If developer can provide better hints of the range for any encrypted variable, then compiler can choose better scheme parameters. For example, if a float variable only requires small precision then scheme parameters can be smaller ( thus more performant ). I understand that this can be handled by providing new variables types of varying sizes that fit “most use-cases”.
  2. SIMD conversions: If same operation is applied to many variables at once, then the cost, using SIMD schemes, can be amortised across thousands of variables. Note that use of SIMD schemes does not rule out non-SIMD schemes. The same program can switch between the two types. If the developer understands this then they can write the program to leverage SIMD operations.

Point (1) is probably irrelevant for the purposes here. But I gave it anyways.

I still think a simple starting point is abstracting such complexities away from developers using standard libraries ( like rust::std ). The standard library provides types and certain APIs over types. The developer has to use them somehow to write their programs. Hopefully the types and their APIs are as close as possible to the ones we’re already familiar with ( in C or rust world ).

1 Like

I wondered whether being MTO implies constant time but, apparently, that is not the case ( right? )

I think it depends at what level of abstraction we are talking. (According to my understanding) The answer is generally no. MTO concerns itself mostly with the sequence of memory accesses but ignores information like how long it takes to execute instructions and how long those fetches take. In reality, cache hits and misses reveal information, and some instructions take many more cycles to execute than others. So you need both.
There is also a concept of differential MTO in which the sequence of memory accesses isn’t necessarily constant for all confidential inputs.