use crate::zerokms::IndexKey;
use cipherstash_core::string::orderise_string;
use orderable_bytes::ToOrderableBytes;
use ore_rs::{scheme::bit2::OreAes128ChaCha20, OreCipher, OreEncrypt, OreOutput};
use zerokms_protocol::cipherstash_config::column;
use super::{
errors::EncryptionError,
indexer::{IndexerInit, Indexes, IndexesForQuery, QueryOp},
orderable::pad_orderable_to_8,
IndexTerm, Plaintext, QueryBuilder, StorageBuilder,
};
pub struct OreIndexer;
#[derive(Debug, Default)]
pub struct OreIndexerOptions;
impl IndexerInit for OreIndexer {
type Args = OreIndexerOptions;
type Error = EncryptionError;
fn try_init<A>(_opts: A) -> Result<Self, Self::Error>
where
Self::Args: TryFrom<A, Error = Self::Error>,
{
Ok(Self)
}
}
impl<'k> Indexes<'k, Plaintext> for OreIndexer {
fn index(
&self,
mut builder: StorageBuilder<'k, Plaintext>,
) -> Result<StorageBuilder<'k, Plaintext>, EncryptionError> {
let index_term = self.encrypt(builder.plaintext(), builder.index_key())?;
builder.add_index_term(index_term);
Ok(builder)
}
}
impl<C> IndexesForQuery<Plaintext, C> for OreIndexer {
fn query_index(
&self,
builder: QueryBuilder<Plaintext, C>,
_op: QueryOp,
) -> Result<IndexTerm, EncryptionError> {
let index_term = self.encrypt_for_query(builder.plaintext(), builder.index_key())?;
Ok(index_term)
}
}
impl Default for OreIndexer {
fn default() -> Self {
Self
}
}
impl TryFrom<&column::IndexType> for OreIndexerOptions {
type Error = EncryptionError;
fn try_from(value: &column::IndexType) -> Result<Self, Self::Error> {
match value {
column::IndexType::Ore => Ok(Default::default()),
_ => Err(EncryptionError::IndexingError(
"MatchIndexerOptions can only be created from a Match index configuration"
.to_string(),
)),
}
}
}
impl OreIndexer {
pub fn encrypt(
&self,
value: &Plaintext,
index_key: &IndexKey,
) -> Result<IndexTerm, EncryptionError> {
let mut k1: [u8; 16] = Default::default();
let mut k2: [u8; 16] = Default::default();
k1.copy_from_slice(&index_key.key()[0..16]);
k2.copy_from_slice(&index_key.key()[16..]);
let cipher: OreAes128ChaCha20 = OreCipher::init(&k1, &k2)?;
match value {
Plaintext::Text(Some(s)) => self.encrypt_string(s, &cipher),
Plaintext::BigInt(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::BigUInt(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::Boolean(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::Float(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::Int(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::NaiveDate(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::SmallInt(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::Decimal(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())),
Plaintext::Timestamp(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())),
Plaintext::Text(None)
| Plaintext::BigInt(None)
| Plaintext::BigUInt(None)
| Plaintext::Boolean(None)
| Plaintext::Decimal(None)
| Plaintext::Float(None)
| Plaintext::Int(None)
| Plaintext::NaiveDate(None)
| Plaintext::SmallInt(None)
| Plaintext::Timestamp(None) => Ok(IndexTerm::Null),
Plaintext::Json(_) => Err(EncryptionError::IndexingError(
"whole JSON documents are not supported by the ORE indexer".into(),
)),
}
}
pub fn encrypt_for_query(
&self,
value: &Plaintext,
index_key: &IndexKey,
) -> Result<IndexTerm, EncryptionError> {
let cipher = get_cipher(index_key)?;
match value {
Plaintext::Text(Some(s)) => self.encrypt_string(s, &cipher),
Plaintext::BigInt(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::BigUInt(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::Boolean(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::Float(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::Int(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::NaiveDate(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::SmallInt(Some(x)) => Ok(IndexTerm::OreFull(
pad_orderable_to_8(x.to_orderable_bytes().as_ref())
.encrypt(&cipher)?
.to_bytes(),
)),
Plaintext::Decimal(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())),
Plaintext::Timestamp(Some(x)) => Ok(IndexTerm::OreFull(x.encrypt(&cipher)?.to_bytes())),
Plaintext::Text(None)
| Plaintext::BigInt(None)
| Plaintext::BigUInt(None)
| Plaintext::Boolean(None)
| Plaintext::Decimal(None)
| Plaintext::Float(None)
| Plaintext::Int(None)
| Plaintext::NaiveDate(None)
| Plaintext::SmallInt(None)
| Plaintext::Timestamp(None) => Ok(IndexTerm::Null),
Plaintext::Json(_) => Err(EncryptionError::IndexingError(
"whole JSON documents are not supported by the ORE indexer".into(),
)),
}
}
fn encrypt_string(
&self,
input_str: &str,
cipher: &OreAes128ChaCha20,
) -> Result<IndexTerm, EncryptionError> {
use ore_rs::OreEncrypt;
let ciphertexts = orderise_string(input_str)?
.into_iter()
.map(|value| value.encrypt(cipher).map(|ct| ct.to_bytes()))
.collect::<Result<Vec<Vec<u8>>, _>>()?;
Ok(IndexTerm::OreArray(ciphertexts))
}
}
fn get_cipher(index_key: &IndexKey) -> Result<OreAes128ChaCha20, EncryptionError> {
let mut k1: [u8; 16] = Default::default();
let mut k2: [u8; 16] = Default::default();
k1.copy_from_slice(&index_key.key()[0..16]);
k2.copy_from_slice(&index_key.key()[16..]);
OreCipher::init(&k1, &k2).map_err(EncryptionError::from)
}
#[cfg(test)]
mod golden_vectors {
use super::*;
use chrono::NaiveDate;
const DETERMINISTIC_PREFIX_LEN: usize = 136;
fn key() -> IndexKey {
IndexKey::from([7u8; 32])
}
#[track_caller]
fn assert_deterministic_prefix(plaintext: Plaintext, expected_prefix_hex: &str) {
assert_eq!(
expected_prefix_hex.len(),
DETERMINISTIC_PREFIX_LEN * 2,
"fixture is not the expected prefix length",
);
let term = OreIndexer.encrypt(&plaintext, &key()).unwrap();
let bytes = match term {
IndexTerm::OreFull(b) => b,
other => panic!("expected IndexTerm::OreFull, got {other:?}"),
};
assert!(
bytes.len() >= DETERMINISTIC_PREFIX_LEN,
"ciphertext is shorter than the pre-PR deterministic prefix \
({} bytes, expected at least {}) — wire-format shape changed for {plaintext:?}",
bytes.len(),
DETERMINISTIC_PREFIX_LEN,
);
let actual_prefix_hex = hex::encode(&bytes[..DETERMINISTIC_PREFIX_LEN]);
assert_eq!(
actual_prefix_hex, expected_prefix_hex,
"ORE deterministic prefix diverges from origin/main for {plaintext:?}",
);
}
#[test]
fn bigint_wire_format_is_stable() {
assert_deterministic_prefix(
Plaintext::BigInt(Some(0)),
"d00d0d0d0d0d0d0ddaf1cba91cf962e083775b5237c06c06747140c08f50474471c10b31426c70f0\
405fc43b6ec3a1d65b77d5f6f4accab914a2da8ebdfba7f7fd24456ae9dd3fab5b84aadb93a43b89\
033f2a9c3a33305fafbf28974022357a1278dade28f538f20f2a218a1c145be0cc0d522b73ed052d\
39163d5fcbdb2227cecd7cd460cdefd4",
);
assert_deterministic_prefix(
Plaintext::BigInt(Some(42)),
"d00d0d0d0d0d0d16daf1cba91cf962e083775b5237c06c06747140c08f50474471c10b31426c70f0\
405fc43b6ec3a1d65b77d5f6f4accab914a2da8ebdfba7f7fd24456ae9dd3fab5b84aadb93a43b89\
033f2a9c3a33305fafbf28974022357a1278dade28f538f20f2a218a1c145be0cc0d522b73ed052d\
573c8f420a88893701ebcde6f5bb6101",
);
assert_deterministic_prefix(
Plaintext::BigInt(Some(-42)),
"d3228ef7485a3f789a88d0d95fd1c0b3f1790e02928fdd895c427411cb1288bfc18575b8ab6d8b59\
2d6875bf185afa1b242769643510b699adc553625ad5615af0cd34ad11f5e799f4e2607809d7db52\
c636cbe5837fd7c825642c485fdbc43014112a08408c71beb50119d58d3b5a09ea400ca8c9c8b98f\
c8f9430da4e4f04e19edc34f050376ce",
);
assert_deterministic_prefix(
Plaintext::BigInt(Some(i64::MIN)),
"cecececececececefc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d27f6b5fb9ca399b5f003190eab8031552\
cf4692d29f9451a0caa6a8885a60348f",
);
assert_deterministic_prefix(
Plaintext::BigInt(Some(i64::MAX)),
"e51cd055a35629704d773701cdbd62fd05175e913aa7c349deba38250f0c82b860b9782edab63bcb\
ba7a1325a5e3435a36a927dc6292c29f6021e3744d11be6e501b99ffcc309a397c9a0580cc234554\
0156953eb58b4c347ef5aabd19a6116bf6c2ed449be770a7c29fe8c750f62a37806759832dcb80f0\
2833467fb96b6a3cd2ee34cbf4e7e102",
);
}
#[test]
fn float_wire_format_is_stable() {
assert_deterministic_prefix(
Plaintext::Float(Some(0.0)),
"d00d0d0d0d0d0d0ddaf1cba91cf962e083775b5237c06c06747140c08f50474471c10b31426c70f0\
405fc43b6ec3a1d65b77d5f6f4accab914a2da8ebdfba7f7fd24456ae9dd3fab5b84aadb93a43b89\
033f2a9c3a33305fafbf28974022357a1278dade28f538f20f2a218a1c145be0cc0d522b73ed052d\
39163d5fcbdb2227cecd7cd460cdefd4",
);
assert_deterministic_prefix(
Plaintext::Float(Some(1.5)),
"0593dbdbdbdbdbdbbbd98421c5189a12fc5c39545de73168e3942466fd993ec48f554e421343631a\
5d9409a5a6ea0a78d5fc05a4e43a3eaabfba978c222edd3843bb320aa9ab2e883b0e1865898a85cc\
93c74512068e26af6ae406cd61e81c7f259c633bd621df1625312d5e0ad05672f19d79a3cfcdadf3\
a4904732daea75ce1eca3363d5be73c6",
);
assert_deterministic_prefix(
Plaintext::Float(Some(-1.5)),
"fb03c6e95da115fbb1ca1d4a741cf59b5eb87f38e59ac17cddb8dafbe0aa6b02b134c60c41e450ff\
016e9efbf87862fc57a5e75b586c3ed04baf962e54e865e9a3e9334e40042905462c2ea66e4e594d\
3d894db23927e27c615fef6ddd6e1ed62a976960d641eadfbe8e5d48dfe08656c4cc3a90d7dd5a3d\
e2422b8b5d9b078413f450fb6d113815",
);
}
#[test]
fn int_wire_format_is_stable() {
assert_deterministic_prefix(
Plaintext::Int(Some(0)),
"cecececed0a9a9a9fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dcdcb1a9700f4d32c6\
dc41fc34a88ccf77b52c5d4e1c4f7da240c568578dbeedd5264e55589c28595a43375cfa94bac3e6\
053991b72f39be2c921113c4a5c72a25",
);
assert_deterministic_prefix(
Plaintext::Int(Some(42)),
"cecececed0a9a933fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dcdcb1a9700f4d32c6\
dc41fc34a88ccf77b52c5d4e1c4f7da240c568578dbeedd5264e55589c28595a43375cfa94bac3e6\
6ef1707cb548a0de66186e891beb57da",
);
assert_deterministic_prefix(
Plaintext::Int(Some(-42)),
"cecececed34c5286fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc91b9cdcc1ed9d85f\
c879a34c46cabb5a150cf0371daad485609cd3f83ad405d4f5d21d0c9707d6f7abe3a69a6b873624\
5005c515601cc2256cec3d1bfd2d8e68",
);
assert_deterministic_prefix(
Plaintext::Int(Some(i32::MIN)),
"cecececececececefc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d27f6b5fb9ca399b5f003190eab8031552\
cf4692d29f9451a0caa6a8885a60348f",
);
assert_deterministic_prefix(
Plaintext::Int(Some(i32::MAX)),
"cecececee5b127cffc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc8628c164ef1d4d05\
e43b4541097afcd9a73e0b1e0dcc764a61dc87a1ca5ab2b44bdd1f1e76019f8d1ac199992ebbf7b4\
7dac70d91ece29a64730614e5941af0b",
);
}
#[test]
fn smallint_wire_format_is_stable() {
assert_deterministic_prefix(
Plaintext::SmallInt(Some(0)),
"cecececececed0b3fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d24c9d2d0eae98361347cf0cf5512aa634\
f8a54cd9f3cdcc80d4cb2aade70c9bf6",
);
assert_deterministic_prefix(
Plaintext::SmallInt(Some(42)),
"cecececececed0cefc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d24c9d2d0eae98361347cf0cf5512aa634\
5a6faec7e120919a2defe3216f7c22c0",
);
assert_deterministic_prefix(
Plaintext::SmallInt(Some(-42)),
"cecececececed304fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d287f297df5ffba135f4bf1663515607c7\
10b818c379efbaceb774e1b439070ba9",
);
}
#[test]
fn boolean_wire_format_is_stable() {
assert_deterministic_prefix(
Plaintext::Boolean(Some(false)),
"cecececececececefc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d27f6b5fb9ca399b5f003190eab8031552\
cf4692d29f9451a0caa6a8885a60348f",
);
assert_deterministic_prefix(
Plaintext::Boolean(Some(true)),
"cececececececee1fc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dc71281ff74c420c49\
2853a6ef321662fb7c7a3ec3b8e36d108e069671a8e086d27f6b5fb9ca399b5f003190eab8031552\
477f780ae4889b451ed2476529afeda6",
);
}
#[test]
fn naive_date_wire_format_is_stable() {
assert_deterministic_prefix(
Plaintext::NaiveDate(Some(NaiveDate::from_ymd_opt(2024, 1, 15).unwrap())),
"cecececed04e899bfc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dcdcb1a9700f4d32c6\
dc41fc34a88ccf77b3b672c5704695fe8e08cef5a45acc84f2b19a5a15496b5cb50de31976a89935\
ef1ea9fc4e12bc8e3dd88e1a6c288f37",
);
assert_deterministic_prefix(
Plaintext::NaiveDate(Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())),
"cecececed0b89efbfc6a65709ae5689bd6be674717d0b1e65b2fad12385216abdd9fefc3390261fe\
e1e223d4e971d7796d6edea734d9edb88eed524f40f5554b2e4d35f355d231dcdcb1a9700f4d32c6\
dc41fc34a88ccf7792dcf83723b71c2ac055b49569f690009812803e644e9ea79c82fec795ea3b5c\
af3f364db3ac5a2a701e64007d7d5a36",
);
}
}