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