oram/lib.rs
1// Copyright (c) Meta Platforms, Inc. and affiliates.
2//
3// This source code is dual-licensed under either the MIT license found in the
4// LICENSE-MIT file in the root directory of this source tree or the Apache
5// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
6// of this source tree. You may select, at your option, one of the above-listed licenses.
7
8//! An implementation of Oblivious RAM (ORAM) for the secure enclave setting.
9//!
10//! ⚠️ **Warning**: This implementation has not been audited. Use at your own risk!
11//!
12//! # Overview
13//!
14//! This crate implements an oblivious RAM protocol (ORAM) for (secure) enclave applications.
15//!
16//! This crate assumes that ORAM clients are running inside a secure enclave architecture that provides memory encryption.
17//! It does not perform encryption-on-write and thus is **not** secure without memory encryption.
18//!
19//! # Design
20//!
21//! This crate implements the Path ORAM protocol, with oblivious
22//! client data structures based on the [Oblix paper](https://people.eecs.berkeley.edu/~raluca/oblix.pdf).
23//! See the [Path ORAM retrospective paper](http://elaineshi.com/docs/pathoram-retro.pdf)
24//! for a high-level introduction to ORAM and Path ORAM, and for more detailed references.
25//!
26//! # Example
27//!
28//! The below example reads a database from memory into an ORAM, thus permitting secret-dependent accesses.
29//!
30//! ```
31//! use oram::{Address, BlockSize, BlockValue, Oram, DefaultOram};
32//! # use oram::OramError;
33//!
34//! const BLOCK_SIZE: BlockSize = 64;
35//! const DB_SIZE: Address = 64;
36//! # const DATABASE: [[u8; BLOCK_SIZE as usize]; DB_SIZE as usize] =
37//! # [[0; BLOCK_SIZE as usize]; DB_SIZE as usize];
38//! let mut rng = rand::rngs::OsRng;
39//!
40//! // Initialize an ORAM to store 64 blocks of 64 bytes each.
41//! let mut oram = DefaultOram::<BlockValue<BLOCK_SIZE>>::new(DB_SIZE, &mut rng)?;
42//!
43//! // Read a database (here, an array of byte arrays) into the ORAM.
44//! for (i, bytes) in DATABASE.iter().enumerate() {
45//! oram.write(i as Address, BlockValue::new(*bytes), &mut rng)?;
46//! }
47//!
48//! // Now you can safely make secret-dependent accesses to your database.
49//! let secret = 42;
50//! let _ = oram.read(secret, &mut rng)?;
51//! # Ok::<(), OramError>(())
52//! ```
53//!
54//! # Advanced
55//!
56//! ORAMs can store arbitrary structs implementing `OramBlock`.
57//! We provide implementations of `OramBlock` for `u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`,
58//! and `BlockValue<const B: BlockSize>`.
59//!
60//! The `DefaultOram` used in the above example should have good performance in most use cases.
61//! But the underlying algorithms have several tunable parameters that impact performance.
62//! The following example instantiates the same ORAM struct as above, but using the `PathOram`
63//! interface which exposes these parameters.
64//!
65//! ```
66//! use oram::{Address, BlockSize, BlockValue, BucketSize,
67//! Oram, PathOram, StashSize, RecursionCutoff};
68//! use oram::path_oram::{DEFAULT_BLOCKS_PER_BUCKET, DEFAULT_RECURSION_CUTOFF,
69//! DEFAULT_POSITIONS_PER_BLOCK, DEFAULT_STASH_OVERFLOW_SIZE};
70//! # use oram::OramError;
71//! # let mut rng = rand::rngs::OsRng;
72//! # const BLOCK_SIZE: BlockSize = 64;
73//! # const DB_SIZE: Address = 64;
74//!
75//! const RECURSION_CUTOFF: RecursionCutoff = DEFAULT_RECURSION_CUTOFF;
76//! const BUCKET_SIZE: BucketSize = DEFAULT_BLOCKS_PER_BUCKET;
77//! const POSITIONS_PER_BLOCK: BlockSize = DEFAULT_POSITIONS_PER_BLOCK;
78//! const INITIAL_STASH_OVERFLOW_SIZE: StashSize = DEFAULT_STASH_OVERFLOW_SIZE;
79//!
80//! let mut oram = PathOram::<
81//! BlockValue<BLOCK_SIZE>,
82//! BUCKET_SIZE,
83//! POSITIONS_PER_BLOCK,
84//! >::new_with_parameters(DB_SIZE, &mut rng, INITIAL_STASH_OVERFLOW_SIZE, RECURSION_CUTOFF)?;
85//! # Ok::<(), OramError>(())
86//! ```
87//!
88//! See [`PathOram`] for an explanation of these parameters and their possible settings.
89
90#![warn(clippy::cargo, clippy::doc_markdown, missing_docs, rustdoc::all)]
91
92use std::num::TryFromIntError;
93
94use rand::{CryptoRng, RngCore};
95use subtle::ConditionallySelectable;
96use thiserror::Error;
97
98pub(crate) mod bucket;
99pub mod linear_time_oram;
100pub mod path_oram;
101pub(crate) mod position_map;
102pub(crate) mod stash;
103#[cfg(test)]
104mod test_utils;
105pub(crate) mod utils;
106
107pub use crate::bucket::BlockValue;
108pub use crate::path_oram::DefaultOram;
109pub use crate::path_oram::PathOram;
110
111/// The numeric type used to specify the size of an ORAM block in bytes.
112pub type BlockSize = usize;
113/// The numeric type used to specify the size of an ORAM in blocks, and to index into the ORAM.
114pub type Address = u64;
115/// The numeric type used to specify the size of an ORAM bucket in blocks.
116pub type BucketSize = usize;
117/// The numeric type used to specify the cutoff size
118/// below which `PathOram` uses a linear position map instead of a recursive one.
119pub type RecursionCutoff = u64;
120/// Numeric type used to represent the size of a Path ORAM stash in blocks.
121pub type StashSize = u64;
122
123/// A "trait alias" for ORAM blocks: the values read and written by ORAMs.
124pub trait OramBlock:
125 Copy + Clone + std::fmt::Debug + Default + PartialEq + ConditionallySelectable
126{
127}
128
129impl OramBlock for u8 {}
130impl OramBlock for u16 {}
131impl OramBlock for u32 {}
132impl OramBlock for u64 {}
133impl OramBlock for i8 {}
134impl OramBlock for i16 {}
135impl OramBlock for i32 {}
136impl OramBlock for i64 {}
137
138/// A list of error types which are produced during ORAM protocol execution.
139#[derive(Error, Debug)]
140pub enum OramError {
141 /// Errors arising from conversions between integer types.
142 #[error("Arithmetic error encountered.")]
143 IntegerConversionError(#[from] TryFromIntError),
144 /// Errors arising from attempting to make an ORAM access to an invalid address.
145 #[error("Attempted to access ORAM address {attempted}, which is larger than ORAM capacity {capacity}.")]
146 AddressOutOfBoundsError {
147 /// The invalid address that was accessed.
148 attempted: Address,
149 /// The capacity of the ORAM that was accessed.
150 capacity: Address,
151 },
152 /// Errors arising from invalid parameters or configuration.
153 #[error("Invalid configuration. {parameter_name} cannot have value {parameter_value}.")]
154 InvalidConfigurationError {
155 /// The misconfigured parameter.
156 parameter_name: String,
157 /// Its invalid value.
158 parameter_value: String,
159 },
160}
161
162/// Represents an oblivious RAM (ORAM) mapping addresses of type `Address` to values of type `V: OramBlock`.
163pub trait Oram
164where
165 Self: Sized,
166{
167 /// The type of elements stored in the ORAM.
168 type V: OramBlock;
169
170 /// Returns the capacity in blocks of this ORAM.
171 fn block_capacity(&self) -> Result<Address, OramError>;
172
173 /// Performs a (oblivious) ORAM access.
174 /// Returns the value `v` previously stored at `index`, and writes `callback(v)` to `index`.
175 ///
176 /// For updating a block in place, using `access` is expected to be about
177 /// twice as fast as performing a `read` followed by a `write`.
178 fn access<R: RngCore + CryptoRng, F: Fn(&Self::V) -> Self::V>(
179 &mut self,
180 index: Address,
181 callback: F,
182 rng: &mut R,
183 ) -> Result<Self::V, OramError>;
184
185 /// Obliviously reads the value stored at `index`.
186 fn read<R: RngCore + CryptoRng>(
187 &mut self,
188 index: Address,
189 rng: &mut R,
190 ) -> Result<Self::V, OramError> {
191 let callback = |x: &Self::V| *x;
192 self.access(index, callback, rng)
193 }
194
195 /// Obliviously writes the value stored at `index`. Returns the value previously stored at `index`.
196 fn write<R: RngCore + CryptoRng>(
197 &mut self,
198 index: Address,
199 new_value: Self::V,
200 rng: &mut R,
201 ) -> Result<Self::V, OramError> {
202 let callback = |_: &Self::V| new_value;
203 self.access(index, callback, rng)
204 }
205}