ore_rs/lib.rs
1//! ore-rs is a library to encrypt numeric types in a way that allows the numeric ordering
2//! of the unencrypted values to be "revealed" during a query enabling range queries to be performed
3//! on encrypted data.
4//!
5//! This is an implementation of the BlockORE Encryption scheme developed by
6//! [Lewi-Wu in 2016](https://eprint.iacr.org/2016/612.pdf). It is used extensively in the
7//! [CipherStash](https://cipherstash.com) searchable encryption platform.
8//!
9//!
10//! # Usage
11//! This crate is [on crates.io](https://crates.io/crates/ore-rs) and can be
12//! used by adding `ore-rs` to your dependencies in your project's `Cargo.toml`.
13//! ```toml
14//! [dependencies]
15//! ore-rs = "0.1"
16//! ```
17//!
18//! ## Example: Encrypt a number with ORE.
19//!
20//! To encrypt a number you need to initalize an [`OreCipher`] as well as `use` the [`OreEncrypt`] trait
21//! which comes with implementations for `u32` and `u64`.
22//!
23//! To initalize the Cipher, you must decide on the scheme you want to use. There is only one ORE
24//! Scheme right now so that's easy but in the future more schemes will become available.
25//!
26//! An `OreCipher` also requires 2 keys (16-bytes each) and an 8-byte seed.
27//!
28//! ```rust
29//! use ore_rs::{
30//! OreCipher, // Main ORE Cipher trait
31//! OreEncrypt, // Traits for encrypting primitive types (e.g. u64)
32//! scheme::bit2::OreAes128ChaCha20 // Specific scheme we want to use
33//! };
34//! use hex_literal::hex;
35//!
36//! // Initalize an ORE Cipher with the OreAes128ChaCha20 scheme
37//! let k1: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
38//! let k2: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
39//! let ore: OreAes128ChaCha20 = OreCipher::init(&k1, &k2).unwrap();
40//!
41//! // Encryption takes a mutable reference to the cipher and returns a `Result`
42//! let a = 456u64.encrypt(&ore).unwrap();
43//! ```
44//!
45//! *Note that a cipher must be mutable as it manages internal state*.
46//!
47//!
48//! ## Example: Comparing 2 CipherTexts
49//!
50//! The result of an encryption is called a CipherText and is represented by the type
51//! [`CipherText<S, N>`] where `S` is the scheme used and `N` is the number of blocks is the size
52//! of the input type (in bytes) divided by 8. (e.g. for `u64` N=8).
53//!
54//! Comparisons can only be performed between ciphertexts of the same size and underlying scheme.
55//!
56//! ```rust
57//! # use ore_rs::{
58//! # CipherText,
59//! # OreCipher, // Main ORE Cipher trait
60//! # OreEncrypt, // Traits for encrypting primitive types (e.g. u64)
61//! # scheme::bit2::OreAes128ChaCha20 // Specific scheme we want to use
62//! # };
63//! # use hex_literal::hex;
64//! # let k1: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
65//! # let k2: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
66//! # let ore: OreAes128ChaCha20 = OreCipher::init(&k1, &k2).unwrap();
67//! let a = 456u64.encrypt(&ore).unwrap();
68//! let b = 1024u64.encrypt(&ore).unwrap();
69//!
70//! // This is fine
71//! let result = a > b; // false because 456 < 1024
72//! ```
73//!
74//! ```compile_fail
75//! # use ore_rs::{
76//! # CipherText,
77//! # OreCipher, // Main ORE Cipher trait
78//! # OreEncrypt, // Traits for encrypting primitive types (e.g. u64)
79//! # scheme::bit2::OreAes128ChaCha20 // Specific scheme we want to use
80//! # };
81//! # use hex_literal::hex;
82//! # let k1: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
83//! # let k2: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
84//! # let ore: OreAes128ChaCha20 = OreCipher::init(&k1, &k2).unwrap();
85//! // This isn't
86//! let a = 456u64.encrypt(&ore).unwrap();
87//! let b = 1024u32.encrypt(&ore).unwrap(); // note the u32
88//!
89//! let result = a > b; // compilation error
90//! ```
91//!
92//! ## Serializing/Deserializing
93//!
94//! *Note: this library doesn't use [Serde](https://crates.io/crates/serde) due to some complexities
95//! with GenericArray used in the [AES](https://crates.io/crates/aes) library. This may change in the future.*
96//!
97//! To serialize a [`CipherText<S, N>`] to a vector of bytes:
98//!
99//! ```rust
100//! # use ore_rs::{
101//! # CipherText,
102//! # OreCipher, // Main ORE Cipher trait
103//! # OreEncrypt, // Traits for encrypting primitive types (e.g. u64)
104//! # OreOutput,
105//! # scheme::bit2::OreAes128ChaCha20 // Specific scheme we want to use
106//! # };
107//! # use hex_literal::hex;
108//! # let k1: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
109//! # let k2: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
110//! # let ore: OreAes128ChaCha20 = OreCipher::init(&k1, &k2).unwrap();
111//! let a = 456u64.encrypt(&ore).unwrap();
112//! let bytes: Vec<u8> = a.to_bytes();
113//! ```
114//!
115//! To deserialize, you must specify the CipherText type (including number of blocks) you
116//! are deserializing into:
117//!
118//! ```rust
119//! # use ore_rs::{
120//! # CipherText,
121//! # OreCipher, // Main ORE Cipher trait
122//! # OreEncrypt, // Traits for encrypting primitive types (e.g. u64)
123//! # OreOutput,
124//! # scheme::bit2::OreAes128ChaCha20 // Specific scheme we want to use
125//! # };
126//! # use hex_literal::hex;
127//! # let k1: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
128//! # let k2: [u8; 16] = hex!("00010203 04050607 08090a0b 0c0d0e0f");
129//! # let ore: OreAes128ChaCha20 = OreCipher::init(&k1, &k2).unwrap();
130//! # let a = 456u64.encrypt(&ore).unwrap();
131//! # let bytes: Vec<u8> = a.to_bytes();
132//!
133//! let ct = CipherText::<OreAes128ChaCha20, 8>::from_bytes(&bytes).unwrap();
134//! # assert!(ct == a);
135//! ```
136
137#![deny(missing_docs)]
138
139#[cfg(feature = "chrono")]
140mod chrono;
141mod ciphertext;
142#[cfg(feature = "decimal")]
143mod decimal;
144mod encrypt;
145mod primitives;
146pub mod scheme;
147pub use crate::ciphertext::*;
148pub use crate::encrypt::OreEncrypt;
149use primitives::PrpError;
150use std::cmp::Ordering;
151use thiserror::Error;
152
153/// Fixed-size byte array used as the input to the BlockORE encryption
154/// routines. `N` is the number of plaintext bytes (one byte per ORE block).
155/// The fixed-N encryption path caps `N` at 15 due to the AES-as-PRF input
156/// packing in [`scheme::bit2`].
157pub type PlainText<const N: usize> = [u8; N];
158
159/// Errors returned by [`OreCipher`] initialisation and encryption.
160#[derive(Debug, Error)]
161pub enum OreError {
162 /// Cipher initialisation failed (e.g., key material rejected by an
163 /// underlying primitive).
164 #[error("Failed to initialize cipher")]
165 InitFailed,
166 /// A pseudo-random permutation primitive returned an error.
167 #[error(transparent)]
168 PrpError(#[from] PrpError),
169 /// The OS or seeded RNG failed to produce random bytes (used for the
170 /// per-ciphertext nonce).
171 #[error("Randomness Error")]
172 RandError(#[from] rand::Error),
173}
174
175/// A BlockORE cipher: a key-bound object that can encrypt fixed-N plaintexts
176/// to ciphertexts whose byte-wise comparison matches the plaintext order.
177///
178/// Implementations associate concrete block types for the Left and Right
179/// halves of a ciphertext (see [`Left`], [`Right`], [`CipherText`]) and
180/// expose [`init`](Self::init) for key setup, [`encrypt_left`](Self::encrypt_left)
181/// for query-side ciphertexts, [`encrypt`](Self::encrypt) for indexable
182/// ciphertexts, and [`compare_raw_slices`](Self::compare_raw_slices) for
183/// comparing serialised ciphertext bytes without round-tripping through
184/// typed values.
185pub trait OreCipher: Sized {
186 /// Block type for the Left half of a ciphertext (per-block PRF tag plus
187 /// permuted plaintext byte).
188 type LeftBlockType: CipherTextBlock;
189 /// Block type for the Right half of a ciphertext (per-block masked
190 /// truth-table row).
191 type RightBlockType: CipherTextBlock;
192
193 /// Initialise the cipher from two 16-byte keys: `k1` for the
194 /// per-block-tag PRF and `k2` for the per-block PRP seed PRF.
195 fn init(k1: &[u8; 16], k2: &[u8; 16]) -> Result<Self, OreError>;
196
197 /// Encrypt `input` and return only the Left half of the ciphertext.
198 /// Useful for query plaintexts compared against stored ciphertexts —
199 /// the Left alone is enough to drive the comparator.
200 fn encrypt_left<const N: usize>(&self, input: &PlainText<N>)
201 -> Result<Left<Self, N>, OreError>;
202
203 /// Encrypt `input` and return the full Left+Right ciphertext suitable
204 /// for storage and subsequent comparison against any other ciphertext.
205 fn encrypt<const N: usize>(
206 &self,
207 input: &PlainText<N>,
208 ) -> Result<CipherText<Self, N>, OreError>;
209
210 /// Compare two serialised ciphertexts (as produced by
211 /// [`OreOutput::to_bytes`]) byte-for-byte without deserialising. Returns
212 /// `None` if the slice lengths disagree (i.e., they encode ciphertexts
213 /// over different `N`).
214 fn compare_raw_slices(a: &[u8], b: &[u8]) -> Option<Ordering>;
215}
216
217#[cfg(test)]
218#[macro_use]
219extern crate quickcheck;