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