Skip to main content

prism_fhe/
fhe.rs

1//! `FheAxis` declaration + parametric one-time-pad reference impl +
2//! shape carrier.
3
4#![allow(missing_docs)]
5
6use uor_foundation::enforcement::{GroundedShape, ShapeViolation};
7use uor_foundation::pipeline::{ConstrainedTypeShape, ConstraintRef, IntoBindingValue, TermValue};
8use uor_foundation_sdk::axis;
9
10axis! {
11    /// Wiki ADR-031 homomorphic-encryption axis.
12    ///
13    /// Reference kernel `add_ciphertexts` is the additive operation
14    /// over a fixed `BLOCK_BYTES`-byte ciphertext block; the scheme's
15    /// correctness predicate is `Dec(Enc(a) ⊕ Enc(b)) = a + b` (XOR
16    /// for the one-time-pad reference impl, real homomorphism for
17    /// production FHE schemes).
18    pub trait FheAxis: AxisExtension {
19        const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/FheAxis";
20        /// Ciphertext block width (overridden per impl).
21        const MAX_OUTPUT_BYTES: usize = 32;
22        /// Homomorphic addition of two ciphertext blocks.
23        /// Input = `c_a || c_b` (`2 * BLOCK_BYTES`); output = `c_a ⊕_FHE c_b`.
24        ///
25        /// # Errors
26        ///
27        /// Returns `ShapeViolation` on malformed ciphertext encoding.
28        fn add_ciphertexts(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
29    }
30}
31
32/// Maximum ciphertext block width any [`OneTimePadFhe`] instantiation
33/// supports. The XOR kernel is byte-loop, so the cap is more about
34/// admission-error metadata cohesion than implementation cost.
35pub const MAX_FHE_BLOCK_BYTES: usize = 256;
36
37fn shape_violation(constraint: &'static str) -> ShapeViolation {
38    ShapeViolation {
39        shape_iri: "https://uor.foundation/axis/FheAxis",
40        constraint_iri: constraint,
41        property_iri: "https://uor.foundation/axis/inputBytes",
42        expected_range: "https://uor.foundation/axis/FheBlockShape",
43        min_count: 0,
44        max_count: 0,
45        kind: uor_foundation::ViolationKind::ValueCheck,
46    }
47}
48
49/// Parametric one-time-pad "FHE" — additive over ciphertexts under XOR.
50///
51/// Reference impl suitable for conformance testing the axis dispatch
52/// path; not a cryptographic FHE scheme. `BLOCK_BYTES` is the
53/// ciphertext block width. Production FHE schemes (TFHE, BGV, CKKS
54/// per ADR-031's roster) are application-level integrations that
55/// satisfy the same `FheAxis` contract with cryptographically secure
56/// schemes.
57#[derive(Debug, Clone, Copy)]
58pub struct OneTimePadFhe<const BLOCK_BYTES: usize>;
59
60impl<const BLOCK_BYTES: usize> Default for OneTimePadFhe<BLOCK_BYTES> {
61    fn default() -> Self {
62        Self
63    }
64}
65
66impl<const BLOCK_BYTES: usize> FheAxis for OneTimePadFhe<BLOCK_BYTES> {
67    const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/FheAxis/OneTimePadReference";
68    const MAX_OUTPUT_BYTES: usize = BLOCK_BYTES;
69
70    fn add_ciphertexts(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
71        if BLOCK_BYTES == 0 || BLOCK_BYTES > MAX_FHE_BLOCK_BYTES {
72            return Err(shape_violation(
73                "https://uor.foundation/axis/FheAxis/blockBytesInRange",
74            ));
75        }
76        if input.len() != 2 * BLOCK_BYTES {
77            return Err(shape_violation(
78                "https://uor.foundation/axis/FheAxis/inputBlockPair",
79            ));
80        }
81        if out.len() < BLOCK_BYTES {
82            return Err(shape_violation(
83                "https://uor.foundation/axis/FheAxis/outputBlock",
84            ));
85        }
86        for i in 0..BLOCK_BYTES {
87            out[i] = input[i] ^ input[BLOCK_BYTES + i];
88        }
89        Ok(BLOCK_BYTES)
90    }
91}
92
93// ADR-052 generic-form companion.
94axis_extension_impl_for_fhe_axis!(@generic OneTimePadFhe<BLOCK_BYTES>, [const BLOCK_BYTES: usize]);
95
96/// 32-byte one-time-pad FHE (canonical block width).
97pub type OneTimePadFheAxis = OneTimePadFhe<32>;
98/// 16-byte one-time-pad FHE.
99pub type OneTimePadFhe16 = OneTimePadFhe<16>;
100/// 64-byte one-time-pad FHE.
101pub type OneTimePadFhe64 = OneTimePadFhe<64>;
102/// 128-byte one-time-pad FHE.
103pub type OneTimePadFhe128 = OneTimePadFhe<128>;
104
105// ---- CiphertextShape: ConstrainedTypeShape carrier ----
106
107/// Parametric ConstrainedTypeShape for an `N`-byte ciphertext block.
108///
109/// Per ADR-031's `Ciphertext<Plaintext, Scheme>` shape commitment —
110/// reduced to byte-width parametricity here since the
111/// scheme-and-plaintext type-level pair would require richer
112/// const-generic machinery. The byte width carries the structural
113/// commitment; downstream schemes wrap this shape in a newtype
114/// associating the plaintext type's IRI per ADR-017.
115#[derive(Debug, Clone, Copy)]
116pub struct CiphertextShape<const BYTES: usize>;
117
118impl<const BYTES: usize> Default for CiphertextShape<BYTES> {
119    fn default() -> Self {
120        Self
121    }
122}
123
124impl<const BYTES: usize> ConstrainedTypeShape for CiphertextShape<BYTES> {
125    const IRI: &'static str = "https://uor.foundation/type/ConstrainedType";
126    const SITE_COUNT: usize = BYTES;
127    const CONSTRAINTS: &'static [ConstraintRef] = &[];
128    #[allow(clippy::cast_possible_truncation)]
129    const CYCLE_SIZE: u64 = 256u64.saturating_pow(BYTES as u32);
130}
131
132impl<const BYTES: usize> uor_foundation::pipeline::__sdk_seal::Sealed for CiphertextShape<BYTES> {}
133impl<const BYTES: usize> GroundedShape for CiphertextShape<BYTES> {}
134impl<'a, const BYTES: usize> IntoBindingValue<'a> for CiphertextShape<BYTES> {
135    fn as_binding_value<const INLINE_BYTES: usize>(&self) -> TermValue<'a, INLINE_BYTES> {
136        TermValue::empty()
137    }
138}