Skip to main content

prism_numerics/
ring.rs

1//! `RingAxis` declaration + parametric GF(2)-over-N-bytes impl + shape.
2//!
3//! Per [Wiki ADR-031][09-adr-031] the numerics sub-crate exposes
4//! `RingAxis` as the canonical Layer-3 surface for finite-ring
5//! arithmetic. The reference impl [`Gf2NumericAxisN`] is generic over
6//! byte-width: addition is bitwise XOR, multiplication is bitwise AND
7//! (each bit treated as an independent GF(2) element).
8//!
9//! [09-adr-031]: https://github.com/UOR-Foundation/UOR-Framework/wiki/09-Architecture-Decisions
10
11#![allow(missing_docs)]
12
13use uor_foundation::enforcement::{GroundedShape, ShapeViolation};
14use uor_foundation::pipeline::{ConstrainedTypeShape, ConstraintRef, IntoBindingValue, TermValue};
15use uor_foundation_sdk::axis;
16
17use crate::{check_output, split_pair};
18
19axis! {
20    /// Wiki ADR-031 finite-ring arithmetic axis.
21    ///
22    /// Addition and multiplication mod a fixed finite ring. The
23    /// reference impl `Gf2NumericAxisN<BYTES>` is GF(2) per byte
24    /// (bitwise XOR / AND).
25    pub trait RingAxis: AxisExtension {
26        /// ADR-017 content address.
27        const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/RingAxis";
28        /// Operand byte-width (overridden per impl).
29        const MAX_OUTPUT_BYTES: usize = 32;
30        /// Ring addition. Input `a || b` (`2N` bytes).
31        ///
32        /// # Errors
33        ///
34        /// Returns `ShapeViolation` on input/output arity mismatch.
35        fn add(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
36        /// Ring multiplication. Input `a || b` (`2N` bytes).
37        ///
38        /// # Errors
39        ///
40        /// Returns `ShapeViolation` on input/output arity mismatch.
41        fn mul(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
42    }
43}
44
45/// `Gf2NumericAxisN<BYTES>` admits **any** operand byte-width `BYTES ≥ 1`:
46/// the GF(2) bitwise kernels (XOR / AND) write directly into the caller's
47/// `out` buffer with no fixed-width scratch, so there is no upper ceiling
48/// on the width — the operand scales arbitrarily (§ 11.10 category 3).
49/// The only floor is non-emptiness: a ring element needs at least one byte.
50fn width_violation() -> ShapeViolation {
51    ShapeViolation {
52        shape_iri: "https://uor.foundation/axis/RingAxis",
53        constraint_iri: "https://uor.foundation/axis/RingAxis/widthPositive",
54        property_iri: "https://uor.foundation/axis/operandByteWidth",
55        expected_range: "https://uor.foundation/axis/RingAxis/PositiveByteWidth",
56        min_count: 1,
57        max_count: u32::MAX,
58        kind: uor_foundation::ViolationKind::ValueCheck,
59    }
60}
61
62/// GF(2) arithmetic over `N`-byte operands — bitwise XOR / AND.
63///
64/// Each byte position is independently a GF(2)-element under bitwise
65/// XOR (addition) and AND (multiplication). Per-byte
66/// distributivity / commutativity / GF(2) field properties hold.
67#[derive(Debug, Clone, Copy)]
68pub struct Gf2NumericAxisN<const BYTES: usize>;
69
70impl<const BYTES: usize> Default for Gf2NumericAxisN<BYTES> {
71    fn default() -> Self {
72        Self
73    }
74}
75
76impl<const BYTES: usize> RingAxis for Gf2NumericAxisN<BYTES> {
77    const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/RingAxis/Gf2";
78    const MAX_OUTPUT_BYTES: usize = BYTES;
79
80    fn add(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
81        if BYTES == 0 {
82            return Err(width_violation());
83        }
84        let (a, b) = split_pair(input, BYTES)?;
85        check_output(out, BYTES)?;
86        for i in 0..BYTES {
87            out[i] = a[i] ^ b[i];
88        }
89        Ok(BYTES)
90    }
91
92    fn mul(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
93        if BYTES == 0 {
94            return Err(width_violation());
95        }
96        let (a, b) = split_pair(input, BYTES)?;
97        check_output(out, BYTES)?;
98        for i in 0..BYTES {
99            out[i] = a[i] & b[i];
100        }
101        Ok(BYTES)
102    }
103}
104
105// ADR-052 generic-form companion.
106axis_extension_impl_for_ring_axis!(@generic Gf2NumericAxisN<BYTES>, [const BYTES: usize]);
107
108/// 256-bit GF(2) ring (canonical 32-byte width).
109pub type Gf2NumericAxis = Gf2NumericAxisN<32>;
110/// 128-bit GF(2) ring.
111pub type Gf2NumericAxis128 = Gf2NumericAxisN<16>;
112/// 512-bit GF(2) ring.
113pub type Gf2NumericAxis512 = Gf2NumericAxisN<64>;
114
115// ---- Gf2RingShape: ConstrainedTypeShape carrier ----
116
117/// Parametric ConstrainedTypeShape for an `N`-byte GF(2) ring element.
118#[derive(Debug, Clone, Copy)]
119pub struct Gf2RingShape<const BYTES: usize>;
120
121impl<const BYTES: usize> Default for Gf2RingShape<BYTES> {
122    fn default() -> Self {
123        Self
124    }
125}
126
127impl<const BYTES: usize> ConstrainedTypeShape for Gf2RingShape<BYTES> {
128    const IRI: &'static str = "https://uor.foundation/type/ConstrainedType";
129    const SITE_COUNT: usize = BYTES;
130    const CONSTRAINTS: &'static [ConstraintRef] = &[];
131    #[allow(clippy::cast_possible_truncation)]
132    const CYCLE_SIZE: u64 = 256u64.saturating_pow(BYTES as u32);
133}
134
135impl<const BYTES: usize> uor_foundation::pipeline::__sdk_seal::Sealed for Gf2RingShape<BYTES> {}
136impl<const BYTES: usize> GroundedShape for Gf2RingShape<BYTES> {}
137impl<'a, const BYTES: usize> IntoBindingValue<'a> for Gf2RingShape<BYTES> {
138    fn as_binding_value<const INLINE_BYTES: usize>(&self) -> TermValue<'a, INLINE_BYTES> {
139        TermValue::empty()
140    }
141}