cipherstash-client 0.42.0

The official CipherStash SDK
Documentation
use cllw_ore::{OpeCllw8VariableV1, OreCllw8VariableV1};
use serde::{Deserialize, Serialize};

/// The (optional) orderable term carried by a SteVec entry — the ONLY term
/// kind an entry can carry. Exact matching for every node kind is the
/// value-inclusive selector's job (see
/// [`super::priv_state::value_selector`]), so no per-value MAC term (`hm`)
/// exists on entries.
///
/// A single tagged-plaintext orderable ciphertext in one of the two SteVec
/// modes — OPE in Compat mode (serialized under `op`), ORE in Standard mode
/// (serialized under `oc`). Numeric and string plaintexts share the same
/// orderable variant; domain separation is enforced on the **plaintext** bit
/// stream (see [`super::priv_state::ste_plaintext_term::OrderableTerm`]), not
/// by tagging the ciphertext after the fact.
///
/// The mode is the variant itself — there is no longer a per-mode wrapper
/// enum, because with `hm` retired each mode carries exactly one term kind.
/// Deserialization is `#[serde(untagged)]`: an `op` key selects `Compat`, an
/// `oc` key selects `Standard`, and the retired MAC key (`hm`) matches
/// neither, so a legacy payload carrying it fails to deserialize
/// (re-encryption is required — this is a breaking wire change).
#[derive(Debug, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum EncryptedSteVecTerm {
    /// Compat mode: a CLLW OPE ciphertext for the single orderable domain
    /// (Number ∪ String). Numeric and string plaintexts produce ciphertexts in
    /// disjoint ranges because each plaintext is tagged at the bit-stream level
    /// before OPE (see
    /// [`super::priv_state::ste_plaintext_term::OrderableTerm`]). Lex byte
    /// comparison of the ciphertext is correct under CLLW OPE.
    Compat {
        #[serde(with = "hex::serde", rename = "op")]
        op: OpeCllw8VariableV1,
    },
    /// Standard mode: a CLLW ORE ciphertext for the single orderable domain
    /// (Number ∪ String). Numeric and string plaintexts produce ciphertexts in
    /// disjoint ranges because each plaintext is tagged at the bit-stream level
    /// before ORE (see
    /// [`super::priv_state::ste_plaintext_term::OrderableTerm`]). The derived
    /// `PartialOrd` uses `OreCllw8VariableV1`'s ORE-aware comparison
    /// (first-differing-byte `y + 1 == x`), not plain lex order.
    Standard {
        #[serde(with = "hex::serde", rename = "oc")]
        oc: OreCllw8VariableV1,
    },
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn compat_ope_serializes_as_op() {
        let term = EncryptedSteVecTerm::Compat {
            op: OpeCllw8VariableV1::from_bytes(vec![1, 2, 3]),
        };
        assert_eq!(
            serde_json::to_value(&term).unwrap(),
            json!({"op": "010203"})
        );
    }

    #[test]
    fn compat_ope_roundtrips_through_serde() {
        let term = EncryptedSteVecTerm::Compat {
            op: OpeCllw8VariableV1::from_bytes(vec![0xde, 0xad]),
        };
        let s = serde_json::to_string(&term).unwrap();
        let back: EncryptedSteVecTerm = serde_json::from_str(&s).unwrap();
        assert_eq!(back, term);
    }

    #[test]
    fn standard_ore_serializes_as_oc() {
        let term = EncryptedSteVecTerm::Standard {
            oc: OreCllw8VariableV1::from(vec![0x42, 0x43]),
        };
        assert_eq!(serde_json::to_value(&term).unwrap(), json!({"oc": "4243"}));
    }

    #[test]
    fn standard_ore_roundtrips_through_serde() {
        let term = EncryptedSteVecTerm::Standard {
            oc: OreCllw8VariableV1::from(vec![0x99]),
        };
        let s = serde_json::to_string(&term).unwrap();
        let back: EncryptedSteVecTerm = serde_json::from_str(&s).unwrap();
        assert_eq!(back, term);
    }

    #[test]
    fn legacy_hm_key_does_not_deserialize_as_a_term() {
        // Entries no longer carry MAC terms; a payload carrying the retired
        // "hm" key must not silently parse into either mode's term.
        assert!(serde_json::from_value::<EncryptedSteVecTerm>(json!({"hm": "abcd"})).is_err());
    }
}