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::{
15    AxisExtension, ConstrainedTypeShape, ConstraintRef, IntoBindingValue,
16};
17use uor_foundation_sdk::axis;
18
19use crate::{check_output, split_pair};
20
21axis! {
22    /// Wiki ADR-031 finite-ring arithmetic axis.
23    ///
24    /// Addition and multiplication mod a fixed finite ring. The
25    /// reference impl `Gf2NumericAxisN<BYTES>` is GF(2) per byte
26    /// (bitwise XOR / AND).
27    pub trait RingAxis: AxisExtension {
28        /// ADR-017 content address.
29        const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/RingAxis";
30        /// Operand byte-width (overridden per impl).
31        const MAX_OUTPUT_BYTES: usize = 32;
32        /// Ring addition. Input `a || b` (`2N` bytes).
33        ///
34        /// # Errors
35        ///
36        /// Returns `ShapeViolation` on input/output arity mismatch.
37        fn add(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
38        /// Ring multiplication. Input `a || b` (`2N` bytes).
39        ///
40        /// # Errors
41        ///
42        /// Returns `ShapeViolation` on input/output arity mismatch.
43        fn mul(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
44    }
45}
46
47/// Maximum operand byte-width any `Gf2NumericAxisN<BYTES>`
48/// instantiation supports. GF(2) bitwise ops have no accumulator
49/// cost, but we cap the type-system surface at 128 bytes (1024 bits)
50/// to keep error-path metadata cohesive.
51pub const MAX_GF2_BYTES: usize = 128;
52
53fn width_violation() -> ShapeViolation {
54    ShapeViolation {
55        shape_iri: "https://uor.foundation/axis/RingAxis",
56        constraint_iri: "https://uor.foundation/axis/RingAxis/widthInRange",
57        property_iri: "https://uor.foundation/axis/operandByteWidth",
58        expected_range: "https://uor.foundation/axis/RingAxis/MaxGf2Bytes",
59        min_count: 1,
60        #[allow(clippy::cast_possible_truncation)]
61        max_count: MAX_GF2_BYTES as u32,
62        kind: uor_foundation::ViolationKind::ValueCheck,
63    }
64}
65
66/// GF(2) arithmetic over `N`-byte operands — bitwise XOR / AND.
67///
68/// Each byte position is independently a GF(2)-element under bitwise
69/// XOR (addition) and AND (multiplication). Per-byte
70/// distributivity / commutativity / GF(2) field properties hold.
71#[derive(Debug, Clone, Copy)]
72pub struct Gf2NumericAxisN<const BYTES: usize>;
73
74impl<const BYTES: usize> Default for Gf2NumericAxisN<BYTES> {
75    fn default() -> Self {
76        Self
77    }
78}
79
80impl<const BYTES: usize> RingAxis for Gf2NumericAxisN<BYTES> {
81    const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/RingAxis/Gf2";
82    const MAX_OUTPUT_BYTES: usize = BYTES;
83
84    fn add(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
85        if BYTES == 0 || BYTES > MAX_GF2_BYTES {
86            return Err(width_violation());
87        }
88        let (a, b) = split_pair(input, BYTES)?;
89        check_output(out, BYTES)?;
90        for i in 0..BYTES {
91            out[i] = a[i] ^ b[i];
92        }
93        Ok(BYTES)
94    }
95
96    fn mul(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
97        if BYTES == 0 || BYTES > MAX_GF2_BYTES {
98            return Err(width_violation());
99        }
100        let (a, b) = split_pair(input, BYTES)?;
101        check_output(out, BYTES)?;
102        for i in 0..BYTES {
103            out[i] = a[i] & b[i];
104        }
105        Ok(BYTES)
106    }
107}
108
109// ADR-052 generic-form companion.
110axis_extension_impl_for_ring_axis!(@generic Gf2NumericAxisN<BYTES>, [const BYTES: usize]);
111
112/// 256-bit GF(2) ring (canonical 32-byte width).
113pub type Gf2NumericAxis = Gf2NumericAxisN<32>;
114/// 128-bit GF(2) ring.
115pub type Gf2NumericAxis128 = Gf2NumericAxisN<16>;
116/// 512-bit GF(2) ring.
117pub type Gf2NumericAxis512 = Gf2NumericAxisN<64>;
118
119// ---- Gf2RingShape: ConstrainedTypeShape carrier ----
120
121/// Parametric ConstrainedTypeShape for an `N`-byte GF(2) ring element.
122#[derive(Debug, Clone, Copy)]
123pub struct Gf2RingShape<const BYTES: usize>;
124
125impl<const BYTES: usize> Default for Gf2RingShape<BYTES> {
126    fn default() -> Self {
127        Self
128    }
129}
130
131impl<const BYTES: usize> ConstrainedTypeShape for Gf2RingShape<BYTES> {
132    const IRI: &'static str = "https://uor.foundation/type/ConstrainedType";
133    const SITE_COUNT: usize = BYTES;
134    const CONSTRAINTS: &'static [ConstraintRef] = &[];
135    #[allow(clippy::cast_possible_truncation)]
136    const CYCLE_SIZE: u64 = 256u64.saturating_pow(BYTES as u32);
137}
138
139impl<const BYTES: usize> uor_foundation::pipeline::__sdk_seal::Sealed for Gf2RingShape<BYTES> {}
140impl<const BYTES: usize> GroundedShape for Gf2RingShape<BYTES> {}
141impl<const BYTES: usize> IntoBindingValue for Gf2RingShape<BYTES> {
142    const MAX_BYTES: usize = BYTES;
143
144    fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
145        Ok(0)
146    }
147}