Optimized Oblivious Accesses to Bitcoin and other UTXO-based Blockchains
$T^3$ is a Bitcoin full node that answers light (SPV) clients from inside a trusted execution environment, over ORAM, so the node that hosts it never learns which addresses a client asked about. The trick: split every ORAM access in two — reads on one tree, evictions on another — and swap the trees at every new block.
1Purdue University · 2National University of Colombia · 3Seoul National University
(2,3 most of this work was done while the author was at Purdue University)
In three numbers
All three come straight from the paper's evaluation: a tree of $N=2^{24}$ ORAM blocks of 544 bytes, over the 58,156,895 unspent outputs of Bitcoin blocks 0 to 551,731.
versus 1.70 ms for a standard ORAM access on the same tree. Path ORAM: 2.43 ms vs 5.89 ms. (Table 1)
Read-once accesses never write, so threads share the tree without locks. Path ORAM drops from 2.43 ms to 0.73 ms. (Table 2)
with Circuit ORAM ($Z=2$); ≈51 GB with Path ORAM ($Z=4$). Integrity needs only a ≈44 MB header chain. (§5.2)
All latencies were measured in SGX simulation mode on an Intel Xeon Silver 4116 (not SGX-enabled). The paper reports that a smaller tree run in hardware mode on an SGX-enabled Core i7 showed no noticeable difference from simulation.
The problem
A phone cannot store the Bitcoin blockchain (230 GB unindexed as of October 2018), so it runs a simplified payment verification (SPV) client. Under BIP37 it loads its addresses into a Bloom filter and hands the filter to a full node, which returns matching transactions with Merkle proofs. Gervais et al. showed that a malicious node can recover several of the client's addresses from that filter with high probability, and many more once it sees two filters from the same client.
Cryptographic private information retrieval hides the query but does not scale to millions of users. ORAM inside a TEE is fast enough, but plain tree-based ORAM is sequential: every access reads a path and rewrites it (eviction), so two clients cannot be served at once. And a TEE like Intel SGX has little protected memory, so the position map has to be recursive.
$T^3$ starts from two observations. First, each tree-based ORAM access is a read-path followed by an eviction, and the two can happen on different copies of the tree. Second, Bitcoin produces a block roughly every 10 minutes, and a rational SPV client has no reason to ask for the same outputs twice before the next block. So one tree only serves reads, in parallel and without write-back. A second tree absorbs every eviction and every block update. At each new block the updated tree becomes the new read tree.
Interactive · Figures 1, 4 and 5 of the paper
A client asks for the outputs of one address, A. Step through the read protocol (blue) and then a new Bitcoin block arriving (amber). Turn on the irrational client to see the one case the design gives up, and how long it lasts.
The trees here are toy trees of 8 leaves so the paths are visible; the evaluated trees hold $2^{20}$ to $2^{24}$ blocks. Leaf numbers are illustrative. In the prototype all enclaves are threads of one enclave (§5.2).
Interactive · Table 1 of the paper
Pick a tree size. Growing $N$ shrinks each block (fewer outputs per block), and smaller blocks mean a smaller stash to scan with cmov, so every access gets cheaper. The read-once access skips eviction entirely. Average of 10,000 accesses.
Only the five measured tree sizes are selectable. Ratios in the readouts are computed here from Table 1; they do not appear in the paper.
Interactive · Table 2 of the paper
A read-once access writes nothing back to the tree. Each thread keeps its own stash and only reads the shared position map, so there is no memory region two threads can write at once (Claim 5). Slide to add read threads on the $N=2^{24}$, 544-byte tree.
| Threads | $T^3$ (Path ORAM) | $T^3$ (Circuit ORAM) |
|---|---|---|
| 1 | 2.43 ms | 0.64 ms |
| 2 | 1.40 ms | 0.58 ms |
| 3 | 0.90 ms | 0.43 ms |
| 4 | 0.73 ms | 0.35 ms |
Interactive · Figures 2, 3 and 6 of the paper
The client only knows its addresses (public-key hashes, $pkh$). Inside the enclave a PRF with a secret key $k_b$ maps each $pkh$ to a block ID, $\mathsf{bid} \leftarrow \mathsf{OBlockMap}(pkh, k_b)$. Only the enclave knows the mapping. Each block has room for a fixed number of outputs per address. How many is enough?
Type any string as a stand-in for $pkh$. This page draws a random key $k_b$ (your "enclave") and uses HMAC-SHA-256 as the PRF into a toy tree of $N=16$ blocks.
Figures 7 and 8 of the paper
For the Bloom-filter baseline, a "request" is the full node scanning one block and building Merkle proofs. For $T^3$ and BITE it is one ORAM access. BITE was re-implemented from its description with non-recursive Path ORAM and 32 kB blocks ($N=2^{17}$). An "improved BITE" uses recursive Path ORAM with $T^3$'s parameters.
Reproduced from the paper's figure files. The underlying data series are not in the paper's source repository, so this page does not read values off these plots.
Guarantees
| Goal | What it means | How it is achieved |
|---|---|---|
| Privacy | The host learns nothing about which addresses a client queries. | Attested TLS-style channel into the enclave. ORAM hides access patterns to untrusted memory. cmov-based oblivious code inside the enclave. |
| Validity | A malicious host cannot feed the enclave invalid outputs. | The enclave checks proof of work and the Merkle root against its own header chain. A Merkle hash tree authenticates every ORAM block fetched from untrusted memory. |
| Completeness | Clients get (most of) their unspent outputs. | Address-to-block mappings that serve 92–96% of clients, trading storage for performance. |
| Efficiency | Bursty concurrent requests, little downtime. | Concurrent read-once accesses on the read tree. Updates on the other tree. Reads queue only while queued evictions are replayed. |
| # | Claim | Argument |
|---|---|---|
| 1 | The managing enclave leaks nothing user-related. | Secure channel after attestation. Each address maps to a fixed number of ORAM blocks. The only thing revealed is the number of blocks updated, which is public. |
| 2 | Read-once accesses on the read tree leak nothing. | Each path should be read once per block interval and is reshuffled before the next. A repeat query by the same client is the only leak (see the walkthrough above). |
| 3 | Writes on the original tree leak nothing. | They are standard ORAM accesses, implemented side-channel-resistant as in ZeroTrace and Obliviate. |
| 4 | Data brought into the TEE is correct. | Blocks are verified by proof of work and the header chain. ORAM data is verified with a Merkle hash tree. |
| 5 | Multiple threads cause no synchronisation issues. | Threads only read the shared position map. Each writes only to its own stash. |
| 6 | In-enclave memory interactions are side-channel-resistant. | ORAM hides accesses to untrusted memory. Oblivious operations inside. Every operation takes the same branch sequence. |
Out of scope: speculative-execution attacks, which the paper leaves to Intel's patches. Denial of service by a client that floods requests is also out of scope; the paper suggests fees or a cuckoo filter of unspent addresses.
| System | Concurrency | Recursive construction | Side-channel protection |
|---|---|---|---|
| ConcurORAM | ✓ | ✗ | — (not a goal) |
| Obliviate | ✗ | ✗ | ✓ |
| ZeroTrace | ✗ | ✓ | ✓ |
| BITE Oblivious Database | ✗ | ✗ | ✓ |
| $T^3$ | ✓ | ✓ | ✓ |
Measured
C++ on the Intel SGX SDK v2.1.3. The ORAM controller is built on ZeroTrace, and libjson-rpc-cpp talks to bitcoind from inside the enclave. Recursive Path ORAM ($Z=4$) and recursive Circuit ORAM ($Z=2$), single address into single block, up to 2 outputs per address. Intel Xeon Silver 4116 @ 2.10 GHz, 128 GB RAM, simulation mode.
| $N$ | Block size | Path · read-once | Path · standard | Circuit · read-once | Circuit · standard |
|---|
| UTXO snapshot | blocks 0 – 551,731 |
| Unspent transaction outputs | 58,156,895 |
| ORAM storage blow-up | ≈4× Circuit · 6–8× Path |
| Both ORAM trees, Circuit ORAM $Z=2$ | ≈26 GB |
| Both ORAM trees, Path ORAM $Z=4$ | ≈51 GB |
| Bitcoin header chain (untrusted, integrity-checked) | ≈44 MB |
Reference
@article{le2020tale,
title = {A Tale of Two Trees: One Writes, and Other Reads.
{O}ptimized Oblivious Accesses to {B}itcoin and other
{UTXO}-based Blockchains},
author = {Le, Duc V. and Tengana Hurtado, Lizzy and Ahmad, Adil and
Minaei, Mohsen and Lee, Byoungyoung and Kate, Aniket},
journal = {Proceedings on Privacy Enhancing Technologies},
year = {2020}
}
Volume, issue and pages are omitted because the source PDF leaves them as placeholders. The year is from the author's publication list.
Source code. The paper states that the prototype implementation is available at github.com/TEE-3/T3. It builds on the ZeroTrace source code, which its authors shared with the paper's authors.