openvm_algebra_moduli_macros/
lib.rs

1extern crate alloc;
2extern crate proc_macro;
3
4use std::sync::atomic::AtomicUsize;
5
6use num_bigint::BigUint;
7use num_prime::nt_funcs::is_prime;
8use openvm_macros_common::{string_to_bytes, MacroArgs};
9use proc_macro::TokenStream;
10use quote::format_ident;
11use syn::{
12    parse::{Parse, ParseStream},
13    parse_macro_input, LitStr, Token,
14};
15
16static MOD_IDX: AtomicUsize = AtomicUsize::new(0);
17
18/// This macro generates the code to setup the modulus for a given prime. Also it places the moduli
19/// into a special static variable to be later extracted from the ELF and used by the VM. Usage:
20/// ```
21/// moduli_declare! {
22///     Bls12381 { modulus = "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" },
23///     Bn254 { modulus = "21888242871839275222246405745257275088696311157297823662689037894645226208583" },
24/// }
25/// ```
26/// This creates two structs, `Bls12381` and `Bn254`, each representing the modular arithmetic class
27/// (implementing `Add`, `Sub` and so on).
28#[proc_macro]
29pub fn moduli_declare(input: TokenStream) -> TokenStream {
30    let MacroArgs { items } = parse_macro_input!(input as MacroArgs);
31
32    let mut output = Vec::new();
33
34    let span = proc_macro::Span::call_site();
35
36    for item in items {
37        let struct_name = item.name.to_string();
38        let struct_name = syn::Ident::new(&struct_name, span.into());
39        let mut modulus: Option<String> = None;
40        for param in item.params {
41            match param.name.to_string().as_str() {
42                "modulus" => {
43                    if let syn::Expr::Lit(syn::ExprLit {
44                        lit: syn::Lit::Str(value),
45                        ..
46                    }) = param.value
47                    {
48                        modulus = Some(value.value());
49                    } else {
50                        return syn::Error::new_spanned(
51                            param.value,
52                            "Expected a string literal for macro argument `modulus`",
53                        )
54                        .to_compile_error()
55                        .into();
56                    }
57                }
58                _ => {
59                    panic!("Unknown parameter {}", param.name);
60                }
61            }
62        }
63
64        // Parsing the parameters is over at this point
65
66        let mod_idx = MOD_IDX.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
67
68        let modulus = modulus.expect("modulus parameter is required");
69        let modulus_bytes = string_to_bytes(&modulus);
70        let mut limbs = modulus_bytes.len();
71        let mut block_size = 32;
72
73        if limbs <= 32 {
74            limbs = 32;
75        } else if limbs <= 48 {
76            limbs = 48;
77            block_size = 16;
78        } else {
79            panic!("limbs must be at most 48");
80        }
81
82        let modulus_bytes = modulus_bytes
83            .into_iter()
84            .chain(vec![0u8; limbs])
85            .take(limbs)
86            .collect::<Vec<_>>();
87
88        let modulus_hex = modulus_bytes
89            .iter()
90            .rev()
91            .map(|x| format!("{x:02x}"))
92            .collect::<Vec<_>>()
93            .join("");
94        macro_rules! create_extern_func {
95            ($name:ident) => {
96                let $name = syn::Ident::new(
97                    &format!("{}_{}", stringify!($name), modulus_hex),
98                    span.into(),
99                );
100            };
101        }
102        create_extern_func!(add_extern_func);
103        create_extern_func!(sub_extern_func);
104        create_extern_func!(mul_extern_func);
105        create_extern_func!(div_extern_func);
106        create_extern_func!(is_eq_extern_func);
107        create_extern_func!(hint_sqrt_extern_func);
108        create_extern_func!(hint_non_qr_extern_func);
109        create_extern_func!(moduli_setup_extern_func);
110
111        let block_size = proc_macro::Literal::usize_unsuffixed(block_size);
112        let block_size = syn::Lit::new(block_size.to_string().parse::<_>().unwrap());
113
114        let module_name = format_ident!("algebra_impl_{}", mod_idx);
115
116        let result = TokenStream::from(quote::quote_spanned! { span.into() =>
117            /// An element of the ring of integers modulo a positive integer.
118            /// The element is internally represented as a fixed size array of bytes.
119            ///
120            /// ## Caution
121            /// It is not guaranteed that the integer representation is less than the modulus.
122            /// After any arithmetic operation, the honest host should normalize the result
123            /// to its canonical representation less than the modulus, but guest execution does not
124            /// require it.
125            ///
126            /// See [`assert_reduced`](openvm_algebra_guest::IntMod::assert_reduced) and
127            /// [`is_reduced`](openvm_algebra_guest::IntMod::is_reduced).
128            #[derive(Clone, Eq, serde::Serialize, serde::Deserialize)]
129            #[repr(C, align(#block_size))]
130            pub struct #struct_name(#[serde(with = "openvm_algebra_guest::BigArray")] [u8; #limbs]);
131
132            extern "C" {
133                fn #add_extern_func(rd: usize, rs1: usize, rs2: usize);
134                fn #sub_extern_func(rd: usize, rs1: usize, rs2: usize);
135                fn #mul_extern_func(rd: usize, rs1: usize, rs2: usize);
136                fn #div_extern_func(rd: usize, rs1: usize, rs2: usize);
137                fn #is_eq_extern_func(rs1: usize, rs2: usize) -> bool;
138                fn #hint_sqrt_extern_func(rs1: usize);
139                fn #hint_non_qr_extern_func();
140                fn #moduli_setup_extern_func();
141            }
142
143            impl #struct_name {
144                #[inline(always)]
145                const fn from_const_u8(val: u8) -> Self {
146                    let mut bytes = [0; #limbs];
147                    bytes[0] = val;
148                    Self(bytes)
149                }
150
151                /// Constructor from little-endian bytes. Does not enforce the integer value of `bytes`
152                /// must be less than the modulus.
153                pub const fn from_const_bytes(bytes: [u8; #limbs]) -> Self {
154                    Self(bytes)
155                }
156
157                #[inline(always)]
158                fn add_assign_impl(&mut self, other: &Self) {
159                    #[cfg(not(target_os = "zkvm"))]
160                    {
161                        *self = Self::from_biguint(
162                            (self.as_biguint() + other.as_biguint()) % Self::modulus_biguint(),
163                        );
164                    }
165                    #[cfg(target_os = "zkvm")]
166                    {
167                        Self::set_up_once();
168                        unsafe {
169                            #add_extern_func(
170                                self as *mut Self as usize,
171                                self as *const Self as usize,
172                                other as *const Self as usize,
173                            );
174                        }
175                    }
176                }
177
178                #[inline(always)]
179                fn sub_assign_impl(&mut self, other: &Self) {
180                    #[cfg(not(target_os = "zkvm"))]
181                    {
182                        let modulus = Self::modulus_biguint();
183                        *self = Self::from_biguint(
184                            (self.as_biguint() + modulus.clone() - other.as_biguint()) % modulus,
185                        );
186                    }
187                    #[cfg(target_os = "zkvm")]
188                    {
189                        Self::set_up_once();
190                        unsafe {
191                            #sub_extern_func(
192                                self as *mut Self as usize,
193                                self as *const Self as usize,
194                                other as *const Self as usize,
195                            );
196                        }
197                    }
198                }
199
200                #[inline(always)]
201                fn mul_assign_impl(&mut self, other: &Self) {
202                    #[cfg(not(target_os = "zkvm"))]
203                    {
204                        *self = Self::from_biguint(
205                            (self.as_biguint() * other.as_biguint()) % Self::modulus_biguint(),
206                        );
207                    }
208                    #[cfg(target_os = "zkvm")]
209                    {
210                        Self::set_up_once();
211                        unsafe {
212                            #mul_extern_func(
213                                self as *mut Self as usize,
214                                self as *const Self as usize,
215                                other as *const Self as usize,
216                            );
217                        }
218                    }
219                }
220
221                #[inline(always)]
222                fn div_assign_unsafe_impl(&mut self, other: &Self) {
223                    #[cfg(not(target_os = "zkvm"))]
224                    {
225                        let modulus = Self::modulus_biguint();
226                        let inv = other.as_biguint().modinv(&modulus).unwrap();
227                        *self = Self::from_biguint((self.as_biguint() * inv) % modulus);
228                    }
229                    #[cfg(target_os = "zkvm")]
230                    {
231                        Self::set_up_once();
232                        unsafe {
233                            #div_extern_func(
234                                self as *mut Self as usize,
235                                self as *const Self as usize,
236                                other as *const Self as usize,
237                            );
238                        }
239                    }
240                }
241
242                /// # Safety
243                /// - `dst_ptr` must be a raw pointer to `&mut Self`. It will be written to only at the very end.
244                #[inline(always)]
245                unsafe fn add_refs_impl<const CHECK_SETUP: bool>(&self, other: &Self, dst_ptr: *mut Self) {
246                    #[cfg(not(target_os = "zkvm"))]
247                    {
248                        let mut res = self.clone();
249                        res += other;
250                        // BEWARE order of operations: when dst_ptr = other as pointers
251                        let dst = unsafe { &mut *dst_ptr };
252                        *dst = res;
253                    }
254                    #[cfg(target_os = "zkvm")]
255                    {
256                        if CHECK_SETUP {
257                            Self::set_up_once();
258                        }
259                        #add_extern_func(
260                            dst_ptr as usize,
261                            self as *const #struct_name as usize,
262                            other as *const #struct_name as usize,
263                        );
264                    }
265                }
266
267                /// SAFETY: `dst_ptr` must be a raw pointer to `&mut Self`.
268                /// It will be written to only at the very end .
269                #[inline(always)]
270                unsafe fn sub_refs_impl(&self, other: &Self, dst_ptr: *mut Self) {
271                    #[cfg(not(target_os = "zkvm"))]
272                    {
273                        let mut res = self.clone();
274                        res -= other;
275                        // BEWARE order of operations: when dst_ptr = other as pointers
276                        let dst = unsafe { &mut *dst_ptr };
277                        *dst = res;
278                    }
279                    #[cfg(target_os = "zkvm")]
280                    {
281                        Self::set_up_once();
282                        unsafe {
283                            #sub_extern_func(
284                                dst_ptr as usize,
285                                self as *const #struct_name as usize,
286                                other as *const #struct_name as usize,
287                            );
288                        }
289                    }
290                }
291
292                /// SAFETY: `dst_ptr` must be a raw pointer to `&mut Self`.
293                /// It will be written to only at the very end .
294                #[inline(always)]
295                unsafe fn mul_refs_impl(&self, other: &Self, dst_ptr: *mut Self) {
296                    #[cfg(not(target_os = "zkvm"))]
297                    {
298                        let mut res = self.clone();
299                        res *= other;
300                        // BEWARE order of operations: when dst_ptr = other as pointers
301                        let dst = unsafe { &mut *dst_ptr };
302                        *dst = res;
303                    }
304                    #[cfg(target_os = "zkvm")]
305                    {
306                        Self::set_up_once();
307                        unsafe {
308                            #mul_extern_func(
309                                dst_ptr as usize,
310                                self as *const #struct_name as usize,
311                                other as *const #struct_name as usize,
312                            );
313                        }
314                    }
315                }
316
317                #[inline(always)]
318                fn div_unsafe_refs_impl(&self, other: &Self) -> Self {
319                    #[cfg(not(target_os = "zkvm"))]
320                    {
321                        let modulus = Self::modulus_biguint();
322                        let inv = other.as_biguint().modinv(&modulus).unwrap();
323                        Self::from_biguint((self.as_biguint() * inv) % modulus)
324                    }
325                    #[cfg(target_os = "zkvm")]
326                    {
327                        Self::set_up_once();
328                        let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit();
329                        unsafe {
330                            #div_extern_func(
331                                uninit.as_mut_ptr() as usize,
332                                self as *const #struct_name as usize,
333                                other as *const #struct_name as usize,
334                            );
335                        }
336                        unsafe { uninit.assume_init() }
337                    }
338                }
339
340                #[inline(always)]
341                unsafe fn eq_impl<const CHECK_SETUP: bool>(&self, other: &Self) -> bool {
342                    #[cfg(not(target_os = "zkvm"))]
343                    {
344                        self.as_le_bytes() == other.as_le_bytes()
345                    }
346                    #[cfg(target_os = "zkvm")]
347                    {
348                        if CHECK_SETUP {
349                            Self::set_up_once();
350                        }
351                        #is_eq_extern_func(self as *const #struct_name as usize, other as *const #struct_name as usize)
352                    }
353                }
354
355                // Helper function to call the setup instruction on first use
356                #[inline(always)]
357                #[cfg(target_os = "zkvm")]
358                fn set_up_once() {
359                    static is_setup: ::openvm_algebra_guest::once_cell::race::OnceBool = ::openvm_algebra_guest::once_cell::race::OnceBool::new();
360                    is_setup.get_or_init(|| {
361                        unsafe { #moduli_setup_extern_func(); }
362                        true
363                    });
364                }
365                #[inline(always)]
366                #[cfg(not(target_os = "zkvm"))]
367                fn set_up_once() {
368                    // No-op for non-ZKVM targets
369                }
370            }
371
372            // Put trait implementations in a private module to avoid conflicts
373            mod #module_name {
374                use openvm_algebra_guest::IntMod;
375
376                use super::#struct_name;
377
378                impl IntMod for #struct_name {
379                    type Repr = [u8; #limbs];
380                    type SelfRef<'a> = &'a Self;
381
382                    const MODULUS: Self::Repr = [#(#modulus_bytes),*];
383
384                    const ZERO: Self = Self([0; #limbs]);
385
386                    const NUM_LIMBS: usize = #limbs;
387
388                    const ONE: Self = Self::from_const_u8(1);
389
390                    fn from_repr(repr: Self::Repr) -> Self {
391                        Self(repr)
392                    }
393
394                    fn from_le_bytes(bytes: &[u8]) -> Option<Self> {
395                        if bytes.len() != #limbs {
396                            return None;
397                        }
398                        let elt = Self::from_le_bytes_unchecked(bytes);
399                        if elt.is_reduced() {
400                            Some(elt)
401                        } else {
402                            None
403                        }
404                    }
405
406                    fn from_be_bytes(bytes: &[u8]) -> Option<Self> {
407                        if bytes.len() != #limbs {
408                            return None;
409                        }
410                        let elt = Self::from_be_bytes_unchecked(bytes);
411                        if elt.is_reduced() {
412                            Some(elt)
413                        } else {
414                            None
415                        }
416                    }
417
418                    fn from_le_bytes_unchecked(bytes: &[u8]) -> Self {
419                        let mut arr = [0u8; #limbs];
420                        arr.copy_from_slice(bytes);
421                        Self(arr)
422                    }
423
424                    fn from_be_bytes_unchecked(bytes: &[u8]) -> Self {
425                        let mut arr = [0u8; #limbs];
426                        for (a, b) in arr.iter_mut().zip(bytes.iter().rev()) {
427                            *a = *b;
428                        }
429                        Self(arr)
430                    }
431
432                    fn from_u8(val: u8) -> Self {
433                        Self::from_const_u8(val)
434                    }
435
436                    fn from_u32(val: u32) -> Self {
437                        let mut bytes = [0; #limbs];
438                        bytes[..4].copy_from_slice(&val.to_le_bytes());
439                        Self(bytes)
440                    }
441
442                    fn from_u64(val: u64) -> Self {
443                        let mut bytes = [0; #limbs];
444                        bytes[..8].copy_from_slice(&val.to_le_bytes());
445                        Self(bytes)
446                    }
447
448                    #[inline(always)]
449                    fn as_le_bytes(&self) -> &[u8] {
450                        &(self.0)
451                    }
452
453                    #[inline(always)]
454                    fn to_be_bytes(&self) -> [u8; #limbs] {
455                        core::array::from_fn(|i| self.0[#limbs - 1 - i])
456                    }
457
458                    #[cfg(not(target_os = "zkvm"))]
459                    fn modulus_biguint() -> num_bigint::BigUint {
460                        num_bigint::BigUint::from_bytes_le(&Self::MODULUS)
461                    }
462
463                    #[cfg(not(target_os = "zkvm"))]
464                    fn from_biguint(biguint: num_bigint::BigUint) -> Self {
465                        Self(openvm::utils::biguint_to_limbs(&biguint))
466                    }
467
468                    #[cfg(not(target_os = "zkvm"))]
469                    fn as_biguint(&self) -> num_bigint::BigUint {
470                        num_bigint::BigUint::from_bytes_le(self.as_le_bytes())
471                    }
472
473                    #[inline(always)]
474                    fn neg_assign(&mut self) {
475                        unsafe {
476                            // SAFETY: we borrow self as &Self and as *mut Self but
477                            // the latter will only be written to at the very end.
478                            (#struct_name::ZERO).sub_refs_impl(self, self as *const Self as *mut Self);
479                        }
480                    }
481
482                    #[inline(always)]
483                    fn double_assign(&mut self) {
484                        unsafe {
485                            // SAFETY: we borrow self as &Self and as *mut Self but
486                            // the latter will only be written to at the very end.
487                            self.add_refs_impl::<true>(self, self as *const Self as *mut Self);
488                        }
489                    }
490
491                    #[inline(always)]
492                    fn square_assign(&mut self) {
493                        unsafe {
494                            // SAFETY: we borrow self as &Self and as *mut Self but
495                            // the latter will only be written to at the very end.
496                            self.mul_refs_impl(self, self as *const Self as *mut Self);
497                        }
498                    }
499
500                    #[inline(always)]
501                    fn double(&self) -> Self {
502                        self + self
503                    }
504
505                    #[inline(always)]
506                    fn square(&self) -> Self {
507                        self * self
508                    }
509
510                    #[inline(always)]
511                    fn cube(&self) -> Self {
512                        &self.square() * self
513                    }
514
515                    /// If `self` is not in its canonical form, the proof will fail to verify.
516                    /// This means guest execution will never terminate (either successfully or
517                    /// unsuccessfully) if `self` is not in its canonical form.
518                    // is_eq_mod enforces `self` is less than `modulus`
519                    fn assert_reduced(&self) {
520                        // This must not be optimized out
521                        let _ = core::hint::black_box(PartialEq::eq(self, self));
522                    }
523
524                    fn is_reduced(&self) -> bool {
525                        // limbs are little endian
526                        for (x_limb, p_limb) in self.0.iter().rev().zip(Self::MODULUS.iter().rev()) {
527                            if x_limb < p_limb {
528                                return true;
529                            } else if x_limb > p_limb {
530                                return false;
531                            }
532                        }
533                        // At this point, all limbs are equal
534                        false
535                    }
536
537                    #[inline(always)]
538                    fn set_up_once() {
539                        Self::set_up_once();
540                    }
541
542                    #[inline(always)]
543                    unsafe fn eq_impl<const CHECK_SETUP: bool>(&self, other: &Self) -> bool {
544                        Self::eq_impl::<CHECK_SETUP>(self, other)
545                    }
546
547                    #[inline(always)]
548                    unsafe fn add_ref<const CHECK_SETUP: bool>(&self, other: &Self) -> Self {
549                        let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit();
550                        self.add_refs_impl::<CHECK_SETUP>(other, uninit.as_mut_ptr());
551                        uninit.assume_init()
552                    }
553                }
554
555                impl<'a> core::ops::AddAssign<&'a #struct_name> for #struct_name {
556                    #[inline(always)]
557                    fn add_assign(&mut self, other: &'a #struct_name) {
558                        self.add_assign_impl(other);
559                    }
560                }
561
562                impl core::ops::AddAssign for #struct_name {
563                    #[inline(always)]
564                    fn add_assign(&mut self, other: Self) {
565                        self.add_assign_impl(&other);
566                    }
567                }
568
569                impl core::ops::Add for #struct_name {
570                    type Output = Self;
571                    #[inline(always)]
572                    fn add(mut self, other: Self) -> Self::Output {
573                        self += other;
574                        self
575                    }
576                }
577
578                impl<'a> core::ops::Add<&'a #struct_name> for #struct_name {
579                    type Output = Self;
580                    #[inline(always)]
581                    fn add(mut self, other: &'a #struct_name) -> Self::Output {
582                        self += other;
583                        self
584                    }
585                }
586
587                impl<'a> core::ops::Add<&'a #struct_name> for &#struct_name {
588                    type Output = #struct_name;
589                    #[inline(always)]
590                    fn add(self, other: &'a #struct_name) -> Self::Output {
591                        // Safety: ensure setup
592                        unsafe { self.add_ref::<true>(other) }
593                    }
594                }
595
596                impl<'a> core::ops::SubAssign<&'a #struct_name> for #struct_name {
597                    #[inline(always)]
598                    fn sub_assign(&mut self, other: &'a #struct_name) {
599                        self.sub_assign_impl(other);
600                    }
601                }
602
603                impl core::ops::SubAssign for #struct_name {
604                    #[inline(always)]
605                    fn sub_assign(&mut self, other: Self) {
606                        self.sub_assign_impl(&other);
607                    }
608                }
609
610                impl core::ops::Sub for #struct_name {
611                    type Output = Self;
612                    #[inline(always)]
613                    fn sub(mut self, other: Self) -> Self::Output {
614                        self -= other;
615                        self
616                    }
617                }
618
619                impl<'a> core::ops::Sub<&'a #struct_name> for #struct_name {
620                    type Output = Self;
621                    #[inline(always)]
622                    fn sub(mut self, other: &'a #struct_name) -> Self::Output {
623                        self -= other;
624                        self
625                    }
626                }
627
628                impl<'a> core::ops::Sub<&'a #struct_name> for &'a #struct_name {
629                    type Output = #struct_name;
630                    #[inline(always)]
631                    fn sub(self, other: &'a #struct_name) -> Self::Output {
632                        let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit();
633                        unsafe {
634                            self.sub_refs_impl(other, uninit.as_mut_ptr());
635                            uninit.assume_init()
636                        }
637                    }
638                }
639
640                impl<'a> core::ops::MulAssign<&'a #struct_name> for #struct_name {
641                    #[inline(always)]
642                    fn mul_assign(&mut self, other: &'a #struct_name) {
643                        self.mul_assign_impl(other);
644                    }
645                }
646
647                impl core::ops::MulAssign for #struct_name {
648                    #[inline(always)]
649                    fn mul_assign(&mut self, other: Self) {
650                        self.mul_assign_impl(&other);
651                    }
652                }
653
654                impl core::ops::Mul for #struct_name {
655                    type Output = Self;
656                    #[inline(always)]
657                    fn mul(mut self, other: Self) -> Self::Output {
658                        self *= other;
659                        self
660                    }
661                }
662
663                impl<'a> core::ops::Mul<&'a #struct_name> for #struct_name {
664                    type Output = Self;
665                    #[inline(always)]
666                    fn mul(mut self, other: &'a #struct_name) -> Self::Output {
667                        self *= other;
668                        self
669                    }
670                }
671
672                impl<'a> core::ops::Mul<&'a #struct_name> for &#struct_name {
673                    type Output = #struct_name;
674                    #[inline(always)]
675                    fn mul(self, other: &'a #struct_name) -> Self::Output {
676                        let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit();
677                        unsafe {
678                            self.mul_refs_impl(other, uninit.as_mut_ptr());
679                            uninit.assume_init()
680                        }
681                    }
682                }
683
684                impl<'a> openvm_algebra_guest::DivAssignUnsafe<&'a #struct_name> for #struct_name {
685                    /// Undefined behaviour when denominator is not coprime to N
686                    #[inline(always)]
687                    fn div_assign_unsafe(&mut self, other: &'a #struct_name) {
688                        self.div_assign_unsafe_impl(other);
689                    }
690                }
691
692                impl openvm_algebra_guest::DivAssignUnsafe for #struct_name {
693                    /// Undefined behaviour when denominator is not coprime to N
694                    #[inline(always)]
695                    fn div_assign_unsafe(&mut self, other: Self) {
696                        self.div_assign_unsafe_impl(&other);
697                    }
698                }
699
700                impl openvm_algebra_guest::DivUnsafe for #struct_name {
701                    type Output = Self;
702                    /// Undefined behaviour when denominator is not coprime to N
703                    #[inline(always)]
704                    fn div_unsafe(mut self, other: Self) -> Self::Output {
705                        self.div_assign_unsafe_impl(&other);
706                        self
707                    }
708                }
709
710                impl<'a> openvm_algebra_guest::DivUnsafe<&'a #struct_name> for #struct_name {
711                    type Output = Self;
712                    /// Undefined behaviour when denominator is not coprime to N
713                    #[inline(always)]
714                    fn div_unsafe(mut self, other: &'a #struct_name) -> Self::Output {
715                        self.div_assign_unsafe_impl(other);
716                        self
717                    }
718                }
719
720                impl<'a> openvm_algebra_guest::DivUnsafe<&'a #struct_name> for &#struct_name {
721                    type Output = #struct_name;
722                    /// Undefined behaviour when denominator is not coprime to N
723                    #[inline(always)]
724                    fn div_unsafe(self, other: &'a #struct_name) -> Self::Output {
725                        self.div_unsafe_refs_impl(other)
726                    }
727                }
728
729                impl PartialEq for #struct_name {
730                    #[inline(always)]
731                    fn eq(&self, other: &Self) -> bool {
732                        // Safety: must check setup
733                        unsafe { self.eq_impl::<true>(other) }
734                    }
735                }
736
737                impl<'a> core::iter::Sum<&'a #struct_name> for #struct_name {
738                    fn sum<I: Iterator<Item = &'a #struct_name>>(iter: I) -> Self {
739                        iter.fold(Self::ZERO, |acc, x| &acc + x)
740                    }
741                }
742
743                impl core::iter::Sum for #struct_name {
744                    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
745                        iter.fold(Self::ZERO, |acc, x| &acc + &x)
746                    }
747                }
748
749                impl<'a> core::iter::Product<&'a #struct_name> for #struct_name {
750                    fn product<I: Iterator<Item = &'a #struct_name>>(iter: I) -> Self {
751                        iter.fold(Self::ONE, |acc, x| &acc * x)
752                    }
753                }
754
755                impl core::iter::Product for #struct_name {
756                    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
757                        iter.fold(Self::ONE, |acc, x| &acc * &x)
758                    }
759                }
760
761                impl core::ops::Neg for #struct_name {
762                    type Output = #struct_name;
763                    #[inline(always)]
764                    fn neg(self) -> Self::Output {
765                        #struct_name::ZERO - &self
766                    }
767                }
768
769                impl<'a> core::ops::Neg for &'a #struct_name {
770                    type Output = #struct_name;
771                    #[inline(always)]
772                    fn neg(self) -> Self::Output {
773                        #struct_name::ZERO - self
774                    }
775                }
776
777                impl core::fmt::Debug for #struct_name {
778                    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
779                        write!(f, "{:?}", self.as_le_bytes())
780                    }
781                }
782            }
783
784            impl openvm_algebra_guest::Reduce for #struct_name {
785                fn reduce_le_bytes(bytes: &[u8]) -> Self {
786                    debug_assert!(
787                        bytes.len() % #limbs == 0,
788                        "reduce_le_bytes: input length {} is not a multiple of modulus byte size {}",
789                        bytes.len(),
790                        #limbs,
791                    );
792                    let mut res = <Self as openvm_algebra_guest::IntMod>::ZERO;
793                    // base should be 2 ^ #limbs which exceeds what Self can represent
794                    let mut base = <Self as openvm_algebra_guest::IntMod>::from_le_bytes_unchecked(&[255u8; #limbs]);
795                    base += <Self as openvm_algebra_guest::IntMod>::ONE;
796                    for chunk in bytes.chunks(#limbs).rev() {
797                        let mut padded = [0u8; #limbs];
798                        padded[..chunk.len()].copy_from_slice(chunk);
799                        res = res * &base + <Self as openvm_algebra_guest::IntMod>::from_repr(padded);
800                    }
801                    openvm_algebra_guest::IntMod::assert_reduced(&res);
802                    res
803                }
804            }
805        });
806
807        output.push(result);
808
809        let modulus_biguint = BigUint::from_bytes_le(&modulus_bytes);
810        let modulus_is_prime = is_prime(&modulus_biguint, None);
811
812        if modulus_is_prime.probably() {
813            // implement Field and Sqrt traits for prime moduli
814            let field_and_sqrt_impl = TokenStream::from(quote::quote_spanned! { span.into() =>
815                impl ::openvm_algebra_guest::Field for #struct_name {
816                    const ZERO: Self = <Self as ::openvm_algebra_guest::IntMod>::ZERO;
817                    const ONE: Self = <Self as ::openvm_algebra_guest::IntMod>::ONE;
818
819                    type SelfRef<'a> = &'a Self;
820
821                    fn double_assign(&mut self) {
822                        ::openvm_algebra_guest::IntMod::double_assign(self);
823                    }
824
825                    fn square_assign(&mut self) {
826                        ::openvm_algebra_guest::IntMod::square_assign(self);
827                    }
828
829                }
830
831                impl openvm_algebra_guest::Sqrt for #struct_name {
832                    // Returns a sqrt of self if it exists, otherwise None.
833                    // Note that we use a hint-based approach to prove whether the square root exists.
834                    // This approach works for prime moduli, but not necessarily for composite moduli,
835                    // which is why we have the sqrt method in the Field trait, not the IntMod trait.
836                    fn sqrt(&self) -> Option<Self> {
837                        match self.honest_host_sqrt() {
838                            // self is a square
839                            Some(Some(sqrt)) => Some(sqrt),
840                            // self is not a square
841                            Some(None) => None,
842                            // host is dishonest
843                            None => {
844                                // host is dishonest, enter infinite loop
845                                loop {
846                                    openvm::io::println("ERROR: Square root hint is invalid. Entering infinite loop.");
847                                }
848                            }
849                        }
850                    }
851                }
852
853                impl #struct_name {
854                    // Returns None if the hint is incorrect (i.e. the host is dishonest)
855                    // Returns Some(None) if the hint proves that self is not a quadratic residue
856                    // Otherwise, returns Some(Some(sqrt)) where sqrt is a square root of self
857                    fn honest_host_sqrt(&self) -> Option<Option<Self>> {
858                        // Zero is always a perfect square; bypass the host hint to prevent
859                        // a dishonest host from claiming 0 is not a QR (the verification
860                        // check `0*0 == 0*non_qr` would trivially pass for sqrt=0).
861                        if self == &<Self as ::openvm_algebra_guest::IntMod>::ZERO {
862                            return Some(Some(<Self as ::openvm_algebra_guest::IntMod>::ZERO));
863                        }
864
865                        let (is_square, sqrt) = self.hint_sqrt_impl()?;
866
867                        if is_square {
868                            // ensure sqrt < modulus
869                            <Self as ::openvm_algebra_guest::IntMod>::assert_reduced(&sqrt);
870
871                            if &(&sqrt * &sqrt) == self {
872                                Some(Some(sqrt))
873                            } else {
874                                None
875                            }
876                        } else {
877                            // ensure sqrt < modulus
878                            <Self as ::openvm_algebra_guest::IntMod>::assert_reduced(&sqrt);
879
880                            if &sqrt * &sqrt == self * Self::get_non_qr() {
881                                Some(None)
882                            } else {
883                                None
884                            }
885                        }
886                    }
887
888
889                    // Returns None if the hint is malformed.
890                    // Otherwise, returns Some((is_square, sqrt)) where sqrt is a square root of self if is_square is true,
891                    // and a square root of self * non_qr if is_square is false.
892                    fn hint_sqrt_impl(&self) -> Option<(bool, Self)> {
893                        #[cfg(not(target_os = "zkvm"))]
894                        {
895                            unimplemented!();
896                        }
897                        #[cfg(target_os = "zkvm")]
898                        {
899                            use ::openvm_algebra_guest::{openvm_custom_insn, openvm_rv32im_guest}; // needed for hint_store_u32! and hint_buffer_chunked
900
901                            let is_square = core::mem::MaybeUninit::<u32>::uninit();
902                            let mut sqrt = core::mem::MaybeUninit::<#struct_name>::uninit();
903                            unsafe {
904                                #hint_sqrt_extern_func(self as *const #struct_name as usize);
905                                let is_square_ptr = is_square.as_ptr() as *const u32;
906                                openvm_rv32im_guest::hint_store_u32!(is_square_ptr);
907                                openvm_rv32im_guest::hint_buffer_chunked(sqrt.as_mut_ptr() as *mut u8, <#struct_name as ::openvm_algebra_guest::IntMod>::NUM_LIMBS / 4 as usize);
908                                let is_square = is_square.assume_init();
909                                if is_square == 0 || is_square == 1 {
910                                    Some((is_square == 1, sqrt.assume_init()))
911                                } else {
912                                    None
913                                }
914                            }
915                        }
916                    }
917
918                    // Generate a non quadratic residue by using a hint
919                    fn init_non_qr() -> alloc::boxed::Box<#struct_name> {
920                        #[cfg(not(target_os = "zkvm"))]
921                        {
922                            unimplemented!();
923                        }
924                        #[cfg(target_os = "zkvm")]
925                        {
926                            use ::openvm_algebra_guest::{openvm_custom_insn, openvm_rv32im_guest}; // needed for hint_buffer_chunked
927
928                            let mut non_qr_uninit = core::mem::MaybeUninit::<Self>::uninit();
929                            let mut non_qr;
930                            unsafe {
931                                #hint_non_qr_extern_func();
932                                let ptr = non_qr_uninit.as_mut_ptr() as *mut u8;
933                                openvm_rv32im_guest::hint_buffer_chunked(ptr, <Self as ::openvm_algebra_guest::IntMod>::NUM_LIMBS / 4 as usize);
934                                non_qr = non_qr_uninit.assume_init();
935                            }
936                            // ensure non_qr < modulus
937                            <Self as ::openvm_algebra_guest::IntMod>::assert_reduced(&non_qr);
938
939                            use ::openvm_algebra_guest::{DivUnsafe, ExpBytes};
940                            // construct exp = (p-1)/2 as an integer by first constraining exp = (p-1)/2 (mod p) and then exp < p
941                            let exp = -<Self as ::openvm_algebra_guest::IntMod>::ONE.div_unsafe(Self::from_const_u8(2));
942                            <Self as ::openvm_algebra_guest::IntMod>::assert_reduced(&exp);
943
944                            if non_qr.exp_bytes(true, &<Self as ::openvm_algebra_guest::IntMod>::to_be_bytes(&exp)) != -<Self as ::openvm_algebra_guest::IntMod>::ONE
945                            {
946                                // non_qr is not a non quadratic residue, so host is dishonest
947                                loop {
948                                    openvm::io::println("ERROR: Non quadratic residue hint is invalid. Entering infinite loop.");
949                                }
950                            }
951
952                            alloc::boxed::Box::new(non_qr)
953                        }
954                    }
955
956                    // This function is public for use in tests
957                    pub fn get_non_qr() -> &'static #struct_name {
958                        static non_qr: ::openvm_algebra_guest::once_cell::race::OnceBox<#struct_name> = ::openvm_algebra_guest::once_cell::race::OnceBox::new();
959                        &non_qr.get_or_init(Self::init_non_qr)
960                    }
961                }
962            });
963
964            output.push(field_and_sqrt_impl);
965        }
966    }
967
968    TokenStream::from_iter(output)
969}
970
971struct ModuliDefine {
972    items: Vec<LitStr>,
973}
974
975impl Parse for ModuliDefine {
976    fn parse(input: ParseStream) -> syn::Result<Self> {
977        let items = input.parse_terminated(<LitStr as Parse>::parse, Token![,])?;
978        Ok(Self {
979            items: items.into_iter().collect(),
980        })
981    }
982}
983
984#[proc_macro]
985pub fn moduli_init(input: TokenStream) -> TokenStream {
986    let ModuliDefine { items } = parse_macro_input!(input as ModuliDefine);
987
988    let mut externs = Vec::new();
989
990    // List of all modular limbs in one (that is, with a compile-time known size) array.
991    let mut two_modular_limbs_flattened_list = Vec::<u8>::new();
992    // List of "bars" between adjacent modular limbs sublists.
993    let mut limb_list_borders = vec![0usize];
994
995    let span = proc_macro::Span::call_site();
996
997    let mut max_block_size = 4;
998
999    for (mod_idx, item) in items.into_iter().enumerate() {
1000        let modulus = item.value();
1001        let modulus_bytes = string_to_bytes(&modulus);
1002        let mut limbs = modulus_bytes.len();
1003        let mut block_size = 32;
1004
1005        if limbs <= 32 {
1006            limbs = 32;
1007        } else if limbs <= 48 {
1008            limbs = 48;
1009            block_size = 16;
1010        } else {
1011            panic!("limbs must be at most 48");
1012        }
1013
1014        max_block_size = max_block_size.max(block_size);
1015
1016        let block_size = proc_macro::Literal::usize_unsuffixed(block_size);
1017        let block_size = syn::Lit::new(block_size.to_string().parse::<_>().unwrap());
1018
1019        let modulus_bytes = modulus_bytes
1020            .into_iter()
1021            .chain(vec![0u8; limbs])
1022            .take(limbs)
1023            .collect::<Vec<_>>();
1024
1025        // We need two copies of modular limbs for Fp2 setup.
1026        let doubled_modulus = [modulus_bytes.clone(), modulus_bytes.clone()].concat();
1027        two_modular_limbs_flattened_list.extend(doubled_modulus);
1028        limb_list_borders.push(two_modular_limbs_flattened_list.len());
1029
1030        let modulus_hex = modulus_bytes
1031            .iter()
1032            .rev()
1033            .map(|x| format!("{x:02x}"))
1034            .collect::<Vec<_>>()
1035            .join("");
1036
1037        let setup_extern_func = syn::Ident::new(
1038            &format!("moduli_setup_extern_func_{modulus_hex}"),
1039            span.into(),
1040        );
1041
1042        for op_type in ["add", "sub", "mul", "div"] {
1043            let func_name =
1044                syn::Ident::new(&format!("{op_type}_extern_func_{modulus_hex}"), span.into());
1045            let mut chars = op_type.chars().collect::<Vec<_>>();
1046            chars[0] = chars[0].to_ascii_uppercase();
1047            let local_opcode = syn::Ident::new(
1048                &format!("{}Mod", chars.iter().collect::<String>()),
1049                span.into(),
1050            );
1051            externs.push(quote::quote_spanned! { span.into() =>
1052                #[no_mangle]
1053                extern "C" fn #func_name(rd: usize, rs1: usize, rs2: usize) {
1054                    openvm::platform::custom_insn_r!(
1055                        opcode = ::openvm_algebra_guest::OPCODE,
1056                        funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3 as usize,
1057                        funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::#local_opcode as usize + #mod_idx * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize),
1058                        rd = In rd,
1059                        rs1 = In rs1,
1060                        rs2 = In rs2
1061                    )
1062                }
1063            });
1064        }
1065
1066        let is_eq_extern_func =
1067            syn::Ident::new(&format!("is_eq_extern_func_{modulus_hex}"), span.into());
1068        externs.push(quote::quote_spanned! { span.into() =>
1069            #[no_mangle]
1070            extern "C" fn #is_eq_extern_func(rs1: usize, rs2: usize) -> bool {
1071                let mut x: u32;
1072                openvm::platform::custom_insn_r!(
1073                    opcode = ::openvm_algebra_guest::OPCODE,
1074                    funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3 as usize,
1075                    funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::IsEqMod as usize + #mod_idx * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize),
1076                    rd = Out x,
1077                    rs1 = In rs1,
1078                    rs2 = In rs2
1079                );
1080                x != 0
1081            }
1082        });
1083
1084        let hint_non_qr_extern_func = syn::Ident::new(
1085            &format!("hint_non_qr_extern_func_{modulus_hex}"),
1086            span.into(),
1087        );
1088        externs.push(quote::quote_spanned! { span.into() =>
1089            #[no_mangle]
1090            extern "C" fn #hint_non_qr_extern_func() {
1091                openvm::platform::custom_insn_r!(
1092                    opcode = ::openvm_algebra_guest::OPCODE,
1093                    funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3 as usize,
1094                    funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::HintNonQr as usize + #mod_idx * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize),
1095                    rd = Const "x0",
1096                    rs1 = Const "x0",
1097                    rs2 = Const "x0"
1098                );
1099            }
1100
1101
1102        });
1103
1104        // This function will be defined regardless of whether the modulus is prime or not,
1105        // but it will be called only if the modulus is prime.
1106        let hint_sqrt_extern_func =
1107            syn::Ident::new(&format!("hint_sqrt_extern_func_{modulus_hex}"), span.into());
1108        externs.push(quote::quote_spanned! { span.into() =>
1109            #[no_mangle]
1110            extern "C" fn #hint_sqrt_extern_func(rs1: usize) {
1111                openvm::platform::custom_insn_r!(
1112                    opcode = ::openvm_algebra_guest::OPCODE,
1113                    funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3 as usize,
1114                    funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::HintSqrt as usize + #mod_idx * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize),
1115                    rd = Const "x0",
1116                    rs1 = In rs1,
1117                    rs2 = Const "x0"
1118                );
1119            }
1120        });
1121
1122        externs.push(quote::quote_spanned! { span.into() =>
1123            #[no_mangle]
1124            extern "C" fn #setup_extern_func() {
1125                #[cfg(target_os = "zkvm")]
1126                {
1127                    // To avoid importing #struct_name, we create a placeholder struct with the same size and alignment.
1128                    #[repr(C, align(#block_size))]
1129                    struct AlignedPlaceholder([u8; #limbs]);
1130
1131                    const MODULUS_BYTES: AlignedPlaceholder = AlignedPlaceholder([#(#modulus_bytes),*]);
1132
1133                    // We are going to use the numeric representation of the `rs2` register to distinguish the chip to setup.
1134                    // The transpiler will transform this instruction, based on whether `rs2` is `x0`, `x1` or `x2`, into a `SETUP_ADDSUB`, `SETUP_MULDIV` or `SETUP_ISEQ` instruction.
1135                    let mut uninit: core::mem::MaybeUninit<AlignedPlaceholder> = core::mem::MaybeUninit::uninit();
1136                    openvm::platform::custom_insn_r!(
1137                        opcode = ::openvm_algebra_guest::OPCODE,
1138                        funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3,
1139                        funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::SetupMod as usize
1140                            + #mod_idx
1141                                * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize),
1142                        rd = In uninit.as_mut_ptr(),
1143                        rs1 = In MODULUS_BYTES.0.as_ptr(),
1144                        rs2 = Const "x0" // will be parsed as 0 and therefore transpiled to SETUP_ADDMOD
1145                    );
1146                    openvm::platform::custom_insn_r!(
1147                        opcode = ::openvm_algebra_guest::OPCODE,
1148                        funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3,
1149                        funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::SetupMod as usize
1150                            + #mod_idx
1151                                * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize),
1152                        rd = In uninit.as_mut_ptr(),
1153                        rs1 = In MODULUS_BYTES.0.as_ptr(),
1154                        rs2 = Const "x1" // will be parsed as 1 and therefore transpiled to SETUP_MULDIV
1155                    );
1156                    unsafe {
1157                        // This should not be x0:
1158                        let mut tmp = uninit.as_mut_ptr() as usize;
1159                        openvm::platform::custom_insn_r!(
1160                            opcode = ::openvm_algebra_guest::OPCODE,
1161                            funct3 = ::openvm_algebra_guest::MODULAR_ARITHMETIC_FUNCT3 as usize,
1162                            funct7 = ::openvm_algebra_guest::ModArithBaseFunct7::SetupMod as usize
1163                                + #mod_idx
1164                                    * (::openvm_algebra_guest::ModArithBaseFunct7::MODULAR_ARITHMETIC_MAX_KINDS as usize),
1165                            rd = InOut tmp,
1166                            rs1 = In MODULUS_BYTES.0.as_ptr(),
1167                            rs2 = Const "x2" // will be parsed as 2 and therefore transpiled to SETUP_ISEQ
1168                        );
1169                        // rd = inout(reg) is necessary because this instruction will write to `rd` register
1170                    }
1171                }
1172            }
1173        });
1174    }
1175
1176    let max_block_size = proc_macro::Literal::usize_unsuffixed(max_block_size);
1177    let max_block_size = syn::Lit::new(max_block_size.to_string().parse::<_>().unwrap());
1178
1179    let total_limbs_cnt = two_modular_limbs_flattened_list.len();
1180    let cnt_limbs_list_len = limb_list_borders.len();
1181    TokenStream::from(quote::quote_spanned! { span.into() =>
1182        #[allow(non_snake_case)]
1183        #[cfg(target_os = "zkvm")]
1184        mod openvm_intrinsics_ffi {
1185            #(#externs)*
1186        }
1187        #[allow(non_snake_case, non_upper_case_globals)]
1188        pub mod openvm_intrinsics_meta_do_not_type_this_by_yourself {
1189            #[repr(C, align(#max_block_size))]
1190            pub struct Aligned<T>(pub T);
1191
1192            pub const two_modular_limbs_list: Aligned<[u8; #total_limbs_cnt]> = Aligned([#(#two_modular_limbs_flattened_list),*]);
1193            pub const limb_list_borders: [usize; #cnt_limbs_list_len] = [#(#limb_list_borders),*];
1194        }
1195    })
1196}