1#[derive(Clone, Copy, Debug)]
16pub struct FieldElement<const LIMBS: usize> {
17 pub limbs: [u64; LIMBS],
19}
20
21impl<const LIMBS: usize> PartialEq for FieldElement<LIMBS> {
22 fn eq(&self, other: &Self) -> bool {
23 let mut acc = 0u64;
24 for i in 0..LIMBS {
25 acc |= self.limbs[i] ^ other.limbs[i];
26 }
27 acc == 0
28 }
29}
30
31impl<const LIMBS: usize> Eq for FieldElement<LIMBS> {}
32
33impl<const LIMBS: usize> FieldElement<LIMBS> {
34 pub const ZERO: Self = Self { limbs: [0u64; LIMBS] };
36
37 pub const fn one() -> Self {
39 let mut limbs = [0u64; LIMBS];
40 limbs[0] = 1;
41 Self { limbs }
42 }
43
44 pub fn is_zero(&self) -> bool {
47 let mut acc = 0u64;
48 for i in 0..LIMBS {
49 acc |= self.limbs[i];
50 }
51 acc == 0
52 }
53
54 pub fn from_bytes_be(bytes: &[u8]) -> Self {
56 let mut limbs = [0u64; LIMBS];
57 let byte_len = LIMBS * 8;
58 for i in 0..byte_len.min(bytes.len()) {
59 let byte_idx = bytes.len() - 1 - i;
60 let limb_idx = i / 8;
61 let shift = (i % 8) * 8;
62 limbs[limb_idx] |= (bytes[byte_idx] as u64) << shift;
63 }
64 Self { limbs }
65 }
66
67 pub fn to_bytes_be(&self) -> Vec<u8> {
69 let byte_len = LIMBS * 8;
70 let mut out = vec![0u8; byte_len];
71 for i in 0..byte_len {
72 let limb_idx = i / 8;
73 let shift = (i % 8) * 8;
74 out[byte_len - 1 - i] = (self.limbs[limb_idx] >> shift) as u8;
75 }
76 out
77 }
78
79 pub fn from_bytes_le(bytes: &[u8]) -> Self {
82 let mut limbs = [0u64; LIMBS];
83 let byte_len = LIMBS * 8;
84 for i in 0..byte_len.min(bytes.len()) {
85 let limb_idx = i / 8;
88 let shift = (i % 8) * 8;
89 limbs[limb_idx] |= (bytes[i] as u64) << shift;
90 }
91 Self { limbs }
92 }
93
94 pub fn to_bytes_le(&self) -> Vec<u8> {
96 let byte_len = LIMBS * 8;
97 let mut out = vec![0u8; byte_len];
98 for i in 0..byte_len {
99 let limb_idx = i / 8;
100 let shift = (i % 8) * 8;
101 out[i] = (self.limbs[limb_idx] >> shift) as u8;
102 }
103 out
104 }
105}
106
107pub const P256_P: [u64; 4] = [
113 0xFFFF_FFFF_FFFF_FFFF,
114 0x0000_0000_FFFF_FFFF,
115 0x0000_0000_0000_0000,
116 0xFFFF_FFFF_0000_0001,
117];
118
119pub const P256_N: [u64; 4] = [
121 0xF3B9_CAC2_FC63_2551,
122 0xBCE6_FAAD_A717_9E84,
123 0xFFFF_FFFF_FFFF_FFFF,
124 0xFFFF_FFFF_0000_0000,
125];
126
127pub const P384_P: [u64; 6] = [
129 0x0000_0000_FFFF_FFFF,
130 0xFFFF_FFFF_0000_0000,
131 0xFFFF_FFFF_FFFF_FFFE,
132 0xFFFF_FFFF_FFFF_FFFF,
133 0xFFFF_FFFF_FFFF_FFFF,
134 0xFFFF_FFFF_FFFF_FFFF,
135];
136
137pub const P384_N: [u64; 6] = [
141 0xECEC_196A_CCC5_2973,
142 0x581A_0DB2_48B0_A77A,
143 0xC763_4D81_F437_2DDF,
144 0xFFFF_FFFF_FFFF_FFFF,
145 0xFFFF_FFFF_FFFF_FFFF,
146 0xFFFF_FFFF_FFFF_FFFF,
147];
148
149pub const CURVE25519_P: [u64; 4] = [
156 0xFFFF_FFFF_FFFF_FFED, 0xFFFF_FFFF_FFFF_FFFF,
158 0xFFFF_FFFF_FFFF_FFFF,
159 0x7FFF_FFFF_FFFF_FFFF, ];
161
162pub const CURVE448_P: [u64; 7] = [
172 0xFFFF_FFFF_FFFF_FFFF,
173 0xFFFF_FFFF_FFFF_FFFF,
174 0xFFFF_FFFF_FFFF_FFFF,
175 0xFFFF_FFFE_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF,
177 0xFFFF_FFFF_FFFF_FFFF,
178 0xFFFF_FFFF_FFFF_FFFF,
179];
180
181pub fn field_add<const LIMBS: usize>(
188 a: &FieldElement<LIMBS>,
189 b: &FieldElement<LIMBS>,
190 p: &[u64; LIMBS],
191) -> FieldElement<LIMBS> {
192 let mut result = [0u64; LIMBS];
193 let mut carry: u64 = 0;
194
195 for i in 0..LIMBS {
196 let sum = (a.limbs[i] as u128) + (b.limbs[i] as u128) + (carry as u128);
197 result[i] = sum as u64;
198 carry = (sum >> 64) as u64;
199 }
200
201 let mut borrow: u64 = 0;
203 let mut sub = [0u64; LIMBS];
204 for i in 0..LIMBS {
205 let diff = (result[i] as u128)
206 .wrapping_sub(p[i] as u128)
207 .wrapping_sub(borrow as u128);
208 sub[i] = diff as u64;
209 borrow = ((diff >> 64) as u64) & 1;
210 }
211
212 let need_sub = carry | (1 - borrow);
216 let mask = core::hint::black_box(0u64.wrapping_sub(need_sub));
217 let inv_mask = !mask;
218
219 let mut out = FieldElement { limbs: [0u64; LIMBS] };
220 for i in 0..LIMBS {
221 out.limbs[i] = (sub[i] & mask) | (result[i] & inv_mask);
222 }
223 out
224}
225
226pub fn field_sub<const LIMBS: usize>(
228 a: &FieldElement<LIMBS>,
229 b: &FieldElement<LIMBS>,
230 p: &[u64; LIMBS],
231) -> FieldElement<LIMBS> {
232 let mut result = [0u64; LIMBS];
233 let mut borrow: u64 = 0;
234
235 for i in 0..LIMBS {
236 let diff = (a.limbs[i] as u128)
237 .wrapping_sub(b.limbs[i] as u128)
238 .wrapping_sub(borrow as u128);
239 result[i] = diff as u64;
240 borrow = ((diff >> 64) as u64) & 1;
241 }
242
243 let mut carry: u64 = 0;
245 let mut added = [0u64; LIMBS];
246 for i in 0..LIMBS {
247 let sum = (result[i] as u128) + (p[i] as u128) + (carry as u128);
248 added[i] = sum as u64;
249 carry = (sum >> 64) as u64;
250 }
251
252 let mask = core::hint::black_box(0u64.wrapping_sub(borrow));
255 let inv_mask = !mask;
256
257 let mut out = FieldElement { limbs: [0u64; LIMBS] };
258 for i in 0..LIMBS {
259 out.limbs[i] = (added[i] & mask) | (result[i] & inv_mask);
260 }
261 out
262}
263
264pub fn field_neg<const LIMBS: usize>(a: &FieldElement<LIMBS>, p: &[u64; LIMBS]) -> FieldElement<LIMBS> {
266 field_sub(&FieldElement::<LIMBS>::ZERO, a, p)
267}
268
269pub fn field_mul<const LIMBS: usize>(
278 a: &FieldElement<LIMBS>,
279 b: &FieldElement<LIMBS>,
280 p: &[u64; LIMBS],
281) -> FieldElement<LIMBS> {
282 let mut product = [0u64; 18];
286 for i in 0..LIMBS {
287 let mut carry: u64 = 0;
288 for j in 0..LIMBS {
289 let uv = (a.limbs[i] as u128) * (b.limbs[j] as u128) + (product[i + j] as u128) + (carry as u128);
290 product[i + j] = uv as u64;
291 carry = (uv >> 64) as u64;
292 }
293 product[i + LIMBS] = carry;
294 }
295
296 reduce_wide::<LIMBS>(&product, p)
297}
298
299pub fn field_sqr<const LIMBS: usize>(a: &FieldElement<LIMBS>, p: &[u64; LIMBS]) -> FieldElement<LIMBS> {
301 field_mul(a, a, p)
302}
303
304fn reduce_wide<const LIMBS: usize>(product: &[u64; 18], p: &[u64; LIMBS]) -> FieldElement<LIMBS> {
307 let double = 2 * LIMBS;
308 let total_bits = double * 64;
309
310 let mut remainder = FieldElement { limbs: [0u64; LIMBS] };
311
312 for i in (0..total_bits).rev() {
313 let mut carry = 0u64;
315 for j in 0..LIMBS {
316 let new_carry = remainder.limbs[j] >> 63;
317 remainder.limbs[j] = (remainder.limbs[j] << 1) | carry;
318 carry = new_carry;
319 }
320
321 let word_idx = i / 64;
323 let bit_idx = i % 64;
324 let bit = (product[word_idx] >> bit_idx) & 1;
325 remainder.limbs[0] |= bit;
326
327 let mut trial_borrow: u64 = 0;
332 let mut trial = [0u64; LIMBS];
333 for j in 0..LIMBS {
334 let diff = (remainder.limbs[j] as u128)
335 .wrapping_sub(p[j] as u128)
336 .wrapping_sub(trial_borrow as u128);
337 trial[j] = diff as u64;
338 trial_borrow = ((diff >> 64) as u64) & 1;
339 }
340 let need_sub = carry | (1u64.wrapping_sub(trial_borrow));
349 let mask = core::hint::black_box(0u64.wrapping_sub(need_sub));
350 let inv_mask = !mask;
351 for j in 0..LIMBS {
352 remainder.limbs[j] = (trial[j] & mask) | (remainder.limbs[j] & inv_mask);
353 }
354 }
355
356 remainder
357}
358
359pub fn field_inv<const LIMBS: usize>(a: &FieldElement<LIMBS>, p: &[u64; LIMBS]) -> FieldElement<LIMBS> {
362 let mut pm2 = [0u64; LIMBS];
363 let mut borrow: u64 = 0;
365 for i in 0..LIMBS {
366 let sub_val = if i == 0 { 2u64 } else { 0u64 };
367 let diff = (p[i] as u128)
368 .wrapping_sub(sub_val as u128)
369 .wrapping_sub(borrow as u128);
370 pm2[i] = diff as u64;
371 borrow = ((diff >> 64) as u64) & 1;
372 }
373
374 field_pow(a, &pm2, p)
375}
376
377pub fn field_pow<const LIMBS: usize>(
380 base: &FieldElement<LIMBS>,
381 exp: &[u64; LIMBS],
382 p: &[u64; LIMBS],
383) -> FieldElement<LIMBS> {
384 let mut result = FieldElement::<LIMBS>::one();
385
386 for i in (0..LIMBS).rev() {
388 for bit in (0..64).rev() {
389 result = field_sqr(&result, p);
390 let b = (exp[i] >> bit) & 1;
391 let product = field_mul(&result, base, p);
392 let mask = 0u64.wrapping_sub(b);
394 let inv = !mask;
395 for j in 0..LIMBS {
396 result.limbs[j] = (product.limbs[j] & mask) | (result.limbs[j] & inv);
397 }
398 }
399 }
400
401 result
402}
403
404pub fn field_sqrt_p3mod4<const LIMBS: usize>(a: &FieldElement<LIMBS>, p: &[u64; LIMBS]) -> FieldElement<LIMBS> {
423 let mut exp = [0u64; LIMBS];
427 let mut carry: u128 = 1;
428 for i in 0..LIMBS {
429 let sum = p[i] as u128 + carry;
430 exp[i] = sum as u64;
431 carry = sum >> 64;
432 }
433 debug_assert_eq!(carry, 0, "p + 1 overflowed the limb array");
437
438 let mut prev_lo: u64 = 0;
442 for i in (0..LIMBS).rev() {
443 let new_prev = exp[i] & 0x3;
444 exp[i] = (exp[i] >> 2) | (prev_lo << 62);
445 prev_lo = new_prev;
446 }
447
448 field_pow(a, &exp, p)
449}
450
451pub fn scalar_add<const LIMBS: usize>(
458 a: &FieldElement<LIMBS>,
459 b: &FieldElement<LIMBS>,
460 n: &[u64; LIMBS],
461) -> FieldElement<LIMBS> {
462 field_add(a, b, n)
463}
464
465pub fn scalar_mul<const LIMBS: usize>(
467 a: &FieldElement<LIMBS>,
468 b: &FieldElement<LIMBS>,
469 n: &[u64; LIMBS],
470) -> FieldElement<LIMBS> {
471 field_mul(a, b, n)
472}
473
474pub fn scalar_inv<const LIMBS: usize>(a: &FieldElement<LIMBS>, n: &[u64; LIMBS]) -> FieldElement<LIMBS> {
476 field_inv(a, n)
477}
478
479pub fn scalar_is_valid<const LIMBS: usize>(a: &FieldElement<LIMBS>, n: &[u64; LIMBS]) -> bool {
481 let mut borrow: u64 = 0;
483 for i in 0..LIMBS {
484 let diff = (a.limbs[i] as u128)
485 .wrapping_sub(n[i] as u128)
486 .wrapping_sub(borrow as u128);
487 borrow = ((diff >> 64) as u64) & 1;
488 }
489 borrow == 1
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 fn hex_to_bytes(hex: &str) -> Vec<u8> {
497 (0..hex.len())
498 .step_by(2)
499 .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
500 .collect()
501 }
502
503 #[test]
504 fn test_p256_field_add_sub() {
505 let a = FieldElement::<4>::from_bytes_be(&[1]);
506 let b = FieldElement::<4>::from_bytes_be(&[2]);
507 let sum = field_add(&a, &b, &P256_P);
508 assert_eq!(sum.limbs[0], 3);
509
510 let diff = field_sub(&sum, &b, &P256_P);
511 assert_eq!(diff.limbs[0], 1);
512 }
513
514 #[test]
515 fn test_p256_field_mul() {
516 let a = FieldElement::<4>::from_bytes_be(&[3]);
517 let b = FieldElement::<4>::from_bytes_be(&[7]);
518 let prod = field_mul(&a, &b, &P256_P);
519 assert_eq!(prod.limbs[0], 21);
520 }
521
522 #[test]
523 fn test_p256_mul_large() {
524 let mut pm1 = FieldElement::<4> { limbs: P256_P };
526 pm1.limbs[0] -= 1;
527 let result = field_mul(&pm1, &pm1, &P256_P);
528 assert_eq!(result, FieldElement::<4>::one(), "(p-1)^2 should be 1");
529 }
530
531 #[test]
532 fn test_p256_pow_small() {
533 let two = FieldElement::<4>::from_bytes_be(&[2]);
535 let exp = [10u64, 0, 0, 0];
536 let result = field_pow(&two, &exp, &P256_P);
537 assert_eq!(result.limbs[0], 1024, "2^10 should be 1024");
538 assert_eq!(result.limbs[1], 0);
539 assert_eq!(result.limbs[2], 0);
540 assert_eq!(result.limbs[3], 0);
541 }
542
543 #[test]
544 fn test_p256_field_mul_known() {
545 let gx = FieldElement::<4>::from_bytes_be(&hex_to_bytes(
551 "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
552 ));
553 let gy = FieldElement::<4>::from_bytes_be(&hex_to_bytes(
554 "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
555 ));
556 let product = field_mul(&gx, &gy, &P256_P);
557 let product_hex: String = product.to_bytes_be().iter().map(|b| format!("{:02x}", b)).collect();
558 eprintln!("Gx * Gy mod p = {}", product_hex);
559 let gy_inv = field_inv(&gy, &P256_P);
561 let should_be_gx = field_mul(&product, &gy_inv, &P256_P);
562 assert_eq!(should_be_gx, gx, "field_mul or field_inv inconsistency");
563 }
564
565 #[test]
566 fn test_p256_field_inv() {
567 let a = FieldElement::<4>::from_bytes_be(&[42]);
568 let a_inv = field_inv(&a, &P256_P);
569 let product = field_mul(&a, &a_inv, &P256_P);
570 assert_eq!(product, FieldElement::<4>::one());
571 }
572
573 #[test]
574 fn test_p256_field_wrap() {
575 let mut pm1 = FieldElement::<4> { limbs: P256_P };
577 pm1.limbs[0] -= 1;
578 let one = FieldElement::<4>::one();
579 let result = field_add(&pm1, &one, &P256_P);
580 assert!(result.is_zero());
581 }
582
583 #[test]
584 fn test_bytes_roundtrip() {
585 let bytes = hex_to_bytes("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296");
586 let fe = FieldElement::<4>::from_bytes_be(&bytes);
587 let out = fe.to_bytes_be();
588 assert_eq!(out, bytes);
589 }
590
591 #[test]
592 fn test_scalar_inv_p256() {
593 let a = FieldElement::<4>::from_bytes_be(&[7]);
594 let a_inv = scalar_inv(&a, &P256_N);
595 let product = scalar_mul(&a, &a_inv, &P256_N);
596 assert_eq!(product, FieldElement::<4>::one());
597 }
598
599 #[test]
602 fn test_field_sqrt_p3mod4_p256() {
603 let four = FieldElement::<4>::from_bytes_be(&[4]);
605 let y = field_sqrt_p3mod4(&four, &P256_P);
606 let y2 = field_sqr(&y, &P256_P);
607 assert_eq!(y2, four);
608
609 let twenty_five = FieldElement::<4>::from_bytes_be(&[25]);
611 let y = field_sqrt_p3mod4(&twenty_five, &P256_P);
612 let y2 = field_sqr(&y, &P256_P);
613 assert_eq!(y2, twenty_five);
614 }
615
616 #[test]
617 fn test_field_sqrt_p3mod4_p384() {
618 let four = FieldElement::<6>::from_bytes_be(&[4]);
619 let y = field_sqrt_p3mod4(&four, &P384_P);
620 let y2 = field_sqr(&y, &P384_P);
621 assert_eq!(y2, four);
622 }
623
624 #[test]
637 fn test_field_sqrt_p3mod4_non_residue_is_caught_by_squaring() {
638 let three = FieldElement::<4>::from_bytes_be(&[3]);
652 let y = field_sqrt_p3mod4(&three, &P256_P);
653 let y2 = field_sqr(&y, &P256_P);
654 assert_ne!(
655 y2, three,
656 "3 is known non-residue on P-256; sqrt should not satisfy y^2 == 3"
657 );
658 }
659}