1#![allow(missing_docs)]
40
41use uor_foundation::enforcement::{GroundedShape, ShapeViolation};
42use uor_foundation::pipeline::{
43 AxisExtension, ConstrainedTypeShape, ConstraintRef, IntoBindingValue,
44};
45use uor_foundation_sdk::axis;
46
47use crate::{check_output, split_pair};
48
49axis! {
50 pub trait FieldAxis: AxisExtension {
55 const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/FieldAxis";
57 const MAX_OUTPUT_BYTES: usize = 32;
59 fn add(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
65 fn sub(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
71 fn mul(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation>;
77 }
78}
79
80const WIDTH: usize = 32;
81
82const P: [u8; WIDTH] = [
84 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
85 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfc, 0x2f,
86];
87
88fn cmp_ge(a: &[u8; WIDTH], b: &[u8; WIDTH]) -> bool {
89 for i in 0..WIDTH {
90 if a[i] != b[i] {
91 return a[i] > b[i];
92 }
93 }
94 true
95}
96
97fn sub_assign(target: &mut [u8; WIDTH], rhs: &[u8; WIDTH]) {
98 let mut borrow: i16 = 0;
99 for i in (0..WIDTH).rev() {
100 let diff = i16::from(target[i]) - i16::from(rhs[i]) - borrow;
101 if diff < 0 {
102 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
103 {
104 target[i] = (diff + 256) as u8;
105 }
106 borrow = 1;
107 } else {
108 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
109 {
110 target[i] = diff as u8;
111 }
112 borrow = 0;
113 }
114 }
115}
116
117fn add_with_carry(a: &[u8; WIDTH], b: &[u8; WIDTH]) -> ([u8; WIDTH], u8) {
118 let mut out = [0u8; WIDTH];
119 let mut carry: u16 = 0;
120 for i in (0..WIDTH).rev() {
121 let sum = u16::from(a[i]) + u16::from(b[i]) + carry;
122 #[allow(clippy::cast_possible_truncation)]
123 {
124 out[i] = (sum & 0xff) as u8;
125 }
126 carry = sum >> 8;
127 }
128 #[allow(clippy::cast_possible_truncation)]
129 (out, carry as u8)
130}
131
132fn reduce_to_field(value: [u8; WIDTH], had_carry: bool) -> [u8; WIDTH] {
133 let mut v = value;
134 if had_carry {
135 sub_assign(&mut v, &P);
136 }
137 while cmp_ge(&v, &P) {
138 sub_assign(&mut v, &P);
139 }
140 v
141}
142
143fn mod_mul(a: &[u8; WIDTH], b: &[u8; WIDTH]) -> [u8; WIDTH] {
144 let mut acc = [0u32; 2 * WIDTH];
145 for i in (0..WIDTH).rev() {
146 for j in (0..WIDTH).rev() {
147 let prod = u32::from(a[i]) * u32::from(b[j]);
148 let pos = i + j + 1;
149 let sum = acc[pos] + (prod & 0xff);
150 acc[pos] = sum & 0xff;
151 let mut carry = (sum >> 8) + (prod >> 8);
152 let mut k = pos;
153 while carry > 0 && k > 0 {
154 k -= 1;
155 let next = acc[k] + carry;
156 acc[k] = next & 0xff;
157 carry = next >> 8;
158 }
159 }
160 }
161 let mut bytes = [0u8; 2 * WIDTH];
162 for i in 0..2 * WIDTH {
163 #[allow(clippy::cast_possible_truncation)]
164 {
165 bytes[i] = (acc[i] & 0xff) as u8;
166 }
167 }
168 for shift_bytes in (0..=WIDTH).rev() {
169 loop {
170 let mut higher_than_p = false;
171 for i in 0..WIDTH {
172 let lhs = bytes[shift_bytes + i];
173 let rhs = P[i];
174 if lhs != rhs {
175 higher_than_p = lhs > rhs;
176 break;
177 } else if i == WIDTH - 1 {
178 higher_than_p = true;
179 }
180 }
181 let mut upper_zero = true;
182 for byte in bytes.iter().take(shift_bytes) {
183 if *byte != 0 {
184 upper_zero = false;
185 break;
186 }
187 }
188 if !upper_zero || !higher_than_p {
189 break;
190 }
191 let mut borrow: i16 = 0;
192 for i in (0..WIDTH).rev() {
193 let diff = i16::from(bytes[shift_bytes + i]) - i16::from(P[i]) - borrow;
194 if diff < 0 {
195 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
196 {
197 bytes[shift_bytes + i] = (diff + 256) as u8;
198 }
199 borrow = 1;
200 } else {
201 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
202 {
203 bytes[shift_bytes + i] = diff as u8;
204 }
205 borrow = 0;
206 }
207 }
208 }
209 }
210 let mut out = [0u8; WIDTH];
211 out.copy_from_slice(&bytes[WIDTH..]);
212 out
213}
214
215fn read32(slice: &[u8]) -> [u8; WIDTH] {
216 let mut out = [0u8; WIDTH];
217 out.copy_from_slice(&slice[..WIDTH]);
218 out
219}
220
221#[derive(Debug, Clone, Copy, Default)]
223pub struct PrimeFieldNumericSecp256k1;
224
225impl FieldAxis for PrimeFieldNumericSecp256k1 {
226 const AXIS_ADDRESS: &'static str = "https://uor.foundation/axis/FieldAxis/Secp256k1Base";
227 const MAX_OUTPUT_BYTES: usize = WIDTH;
228
229 fn add(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
230 let (a, b) = split_pair(input, WIDTH)?;
231 check_output(out, WIDTH)?;
232 let a = read32(a);
233 let b = read32(b);
234 let (sum, carry) = add_with_carry(&a, &b);
235 let result = reduce_to_field(sum, carry != 0);
236 out[..WIDTH].copy_from_slice(&result);
237 Ok(WIDTH)
238 }
239
240 fn sub(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
241 let (a, b) = split_pair(input, WIDTH)?;
242 check_output(out, WIDTH)?;
243 let a = read32(a);
244 let b = read32(b);
245 let mut p_minus_b = P;
246 sub_assign(&mut p_minus_b, &b);
247 let (sum, carry) = add_with_carry(&a, &p_minus_b);
248 let result = reduce_to_field(sum, carry != 0);
249 out[..WIDTH].copy_from_slice(&result);
250 Ok(WIDTH)
251 }
252
253 fn mul(input: &[u8], out: &mut [u8]) -> Result<usize, ShapeViolation> {
254 let (a, b) = split_pair(input, WIDTH)?;
255 check_output(out, WIDTH)?;
256 let a = read32(a);
257 let b = read32(b);
258 let result = mod_mul(&a, &b);
259 out[..WIDTH].copy_from_slice(&result);
260 Ok(WIDTH)
261 }
262}
263
264axis_extension_impl_for_field_axis!(PrimeFieldNumericSecp256k1);
265
266#[derive(Debug, Clone, Copy)]
275pub struct FieldElementShape<const BYTES: usize>;
276
277impl<const BYTES: usize> Default for FieldElementShape<BYTES> {
278 fn default() -> Self {
279 Self
280 }
281}
282
283impl<const BYTES: usize> ConstrainedTypeShape for FieldElementShape<BYTES> {
284 const IRI: &'static str = "https://uor.foundation/type/ConstrainedType";
285 const SITE_COUNT: usize = BYTES;
286 const CONSTRAINTS: &'static [ConstraintRef] = &[];
287 #[allow(clippy::cast_possible_truncation)]
288 const CYCLE_SIZE: u64 = 256u64.saturating_pow(BYTES as u32);
289}
290
291impl<const BYTES: usize> uor_foundation::pipeline::__sdk_seal::Sealed for FieldElementShape<BYTES> {}
292impl<const BYTES: usize> GroundedShape for FieldElementShape<BYTES> {}
293impl<const BYTES: usize> IntoBindingValue for FieldElementShape<BYTES> {
294 const MAX_BYTES: usize = BYTES;
295
296 fn into_binding_bytes(&self, _out: &mut [u8]) -> Result<usize, ShapeViolation> {
297 Ok(0)
298 }
299}