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