openvm_ecc_sw_macros/
lib.rs

1extern crate proc_macro;
2
3use openvm_macros_common::MacroArgs;
4use proc_macro::TokenStream;
5use quote::format_ident;
6use syn::{
7    parse::{Parse, ParseStream},
8    parse_macro_input, ExprPath, LitStr, Token,
9};
10
11/// This macro generates the code to setup the elliptic curve for a given modular type. Also it
12/// places the curve parameters into a special static variable to be later extracted from the ELF
13/// and used by the VM. Usage:
14/// ```
15/// sw_declare! {
16///     Secp256k1Point { mod_type = Secp256k1Coord, b = CURVE_B },
17/// }
18/// ```
19/// This creates a struct `Secp256k1Point` representing an affine point on the short Weierstrass
20/// curve `y^2 = x^3 + a*x + b` with coordinate type `mod_type` (a type generated by
21/// `moduli_declare!`). The curve coefficients `a` (optional, defaults to zero) and `b` are
22/// constants of the coordinate type.
23///
24/// The generated point type stores raw affine coordinates. Deserializing these points does not
25/// check that they are on the curve. Validate points from untrusted input before using group
26/// operations, scalar multiplication, or MSM.
27///
28/// For this macro to work, you must import the `elliptic_curve` crate and the `openvm_ecc_guest`
29/// crate.
30#[proc_macro]
31pub fn sw_declare(input: TokenStream) -> TokenStream {
32    let MacroArgs { items } = parse_macro_input!(input as MacroArgs);
33
34    let mut output = Vec::new();
35
36    let span = proc_macro::Span::call_site();
37
38    for item in items.into_iter() {
39        let struct_name_str = item.name.to_string();
40        let struct_name = syn::Ident::new(&struct_name_str, span.into());
41        let mut intmod_type: Option<syn::Path> = None;
42        let mut const_a: Option<syn::Expr> = None;
43        let mut const_b: Option<syn::Expr> = None;
44        for param in item.params {
45            match param.name.to_string().as_str() {
46                // Note that mod_type must have NUM_LIMBS divisible by 4
47                "mod_type" => {
48                    if let syn::Expr::Path(ExprPath { path, .. }) = param.value {
49                        intmod_type = Some(path)
50                    } else {
51                        return syn::Error::new_spanned(param.value, "Expected a type")
52                            .to_compile_error()
53                            .into();
54                    }
55                }
56                "a" => {
57                    // We currently leave it to the compiler to check if the expression is actually
58                    // a constant
59                    const_a = Some(param.value);
60                }
61                "b" => {
62                    // We currently leave it to the compiler to check if the expression is actually
63                    // a constant
64                    const_b = Some(param.value);
65                }
66                _ => {
67                    panic!("Unknown parameter {}", param.name);
68                }
69            }
70        }
71
72        let intmod_type = intmod_type.expect("mod_type parameter is required");
73        // const_a is optional, default to 0
74        let const_a = const_a
75            .unwrap_or(syn::parse_quote!(<#intmod_type as openvm_algebra_guest::IntMod>::ZERO));
76        let const_b = const_b.expect("constant b coefficient is required");
77
78        macro_rules! create_extern_func {
79            ($name:ident) => {
80                let $name = syn::Ident::new(
81                    &format!("{}_{}", stringify!($name), struct_name_str),
82                    span.into(),
83                );
84            };
85        }
86        create_extern_func!(sw_add_ne_extern_func);
87        create_extern_func!(sw_double_extern_func);
88        create_extern_func!(sw_setup_extern_func);
89
90        let group_ops_mod_name = format_ident!("{}_ops", struct_name_str.to_lowercase());
91
92        let result = TokenStream::from(quote::quote_spanned! { span.into() =>
93            extern "C" {
94                fn #sw_add_ne_extern_func(rd: usize, rs1: usize, rs2: usize);
95                fn #sw_double_extern_func(rd: usize, rs1: usize);
96                fn #sw_setup_extern_func(uninit: *mut core::ffi::c_void, p1: *const u8, p2: *const u8);
97            }
98
99            #[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
100            #[repr(C)]
101            pub struct #struct_name {
102                x: #intmod_type,
103                y: #intmod_type,
104            }
105            #[allow(non_upper_case_globals)]
106
107            impl #struct_name {
108                const fn identity() -> Self {
109                    Self {
110                        x: <#intmod_type as openvm_algebra_guest::IntMod>::ZERO,
111                        y: <#intmod_type as openvm_algebra_guest::IntMod>::ZERO,
112                    }
113                }
114                // Below are wrapper functions for the intrinsic instructions.
115                // Should not be called directly.
116                #[inline(always)]
117                unsafe fn add_ne<const CHECK_SETUP: bool>(p1: &#struct_name, p2: &#struct_name) -> #struct_name {
118                    #[cfg(not(target_os = "zkvm"))]
119                    {
120                        use openvm_algebra_guest::DivUnsafe;
121                        let lambda = (&p2.y - &p1.y).div_unsafe(&p2.x - &p1.x);
122                        let x3 = &lambda * &lambda - &p1.x - &p2.x;
123                        let y3 = &lambda * &(&p1.x - &x3) - &p1.y;
124                        #struct_name { x: x3, y: y3 }
125                    }
126                    #[cfg(target_os = "zkvm")]
127                    {
128                        if CHECK_SETUP {
129                            Self::set_up_once();
130                        }
131                        let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit();
132                        #sw_add_ne_extern_func(
133                            uninit.as_mut_ptr() as usize,
134                            p1 as *const #struct_name as usize,
135                            p2 as *const #struct_name as usize
136                        );
137                        uninit.assume_init()
138                    }
139                }
140
141                #[inline(always)]
142                unsafe fn add_ne_assign<const CHECK_SETUP: bool>(&mut self, p2: &#struct_name) {
143                    #[cfg(not(target_os = "zkvm"))]
144                    {
145                        use openvm_algebra_guest::DivUnsafe;
146                        let lambda = (&p2.y - &self.y).div_unsafe(&p2.x - &self.x);
147                        let x3 = &lambda * &lambda - &self.x - &p2.x;
148                        let y3 = &lambda * &(&self.x - &x3) - &self.y;
149                        self.x = x3;
150                        self.y = y3;
151                    }
152                    #[cfg(target_os = "zkvm")]
153                    {
154                        if CHECK_SETUP {
155                            Self::set_up_once();
156                        }
157                        #sw_add_ne_extern_func(
158                            self as *mut #struct_name as usize,
159                            self as *const #struct_name as usize,
160                            p2 as *const #struct_name as usize
161                        );
162                    }
163                }
164
165                /// Assumes that `p` is not identity.
166                #[inline(always)]
167                unsafe fn double_impl<const CHECK_SETUP: bool>(p: &#struct_name) -> #struct_name {
168                    #[cfg(not(target_os = "zkvm"))]
169                    {
170                        use openvm_algebra_guest::DivUnsafe;
171                        let curve_a: #intmod_type = #const_a;
172                        let two = #intmod_type::from_u8(2);
173                        let lambda = (&p.x * &p.x * #intmod_type::from_u8(3) + &curve_a).div_unsafe(&p.y * &two);
174                        let x3 = &lambda * &lambda - &p.x * &two;
175                        let y3 = &lambda * &(&p.x - &x3) - &p.y;
176                        #struct_name { x: x3, y: y3 }
177                    }
178                    #[cfg(target_os = "zkvm")]
179                    {
180                        if CHECK_SETUP {
181                            Self::set_up_once();
182                        }
183                        let mut uninit: core::mem::MaybeUninit<#struct_name> = core::mem::MaybeUninit::uninit();
184                        #sw_double_extern_func(
185                            uninit.as_mut_ptr() as usize,
186                            p as *const #struct_name as usize,
187                        );
188                        uninit.assume_init()
189                    }
190                }
191
192                // Helper function to call the setup instruction on first use
193                #[inline(always)]
194                #[cfg(target_os = "zkvm")]
195                fn set_up_once() {
196                    static is_setup: ::openvm_ecc_guest::once_cell::race::OnceBool = ::openvm_ecc_guest::once_cell::race::OnceBool::new();
197
198                    is_setup.get_or_init(|| {
199                        // p1 is (x1, y1), and x1 must be the modulus.
200                        // y1 can be anything for SetupEcAdd, but must equal `a` for SetupEcDouble
201                        let modulus_bytes = <<Self as openvm_ecc_guest::weierstrass::WeierstrassPoint>::Coordinate as openvm_algebra_guest::IntMod>::MODULUS;
202                        let mut one = [0u8; <<Self as openvm_ecc_guest::weierstrass::WeierstrassPoint>::Coordinate as openvm_algebra_guest::IntMod>::NUM_LIMBS];
203                        one[0] = 1;
204                        let curve_a_bytes = openvm_algebra_guest::IntMod::as_le_bytes(&<#struct_name as openvm_ecc_guest::weierstrass::WeierstrassPoint>::CURVE_A);
205                        // p1 should be (p, a)
206                        let p1 = [modulus_bytes.as_ref(), curve_a_bytes.as_ref()].concat();
207                        // (EcAdd only) p2 is (x2, y2), and x1 - x2 has to be non-zero to avoid division over zero in add.
208                        let p2 = [one.as_ref(), one.as_ref()].concat();
209                        let mut uninit: core::mem::MaybeUninit<[Self; 2]> = core::mem::MaybeUninit::uninit();
210
211                        unsafe { #sw_setup_extern_func(uninit.as_mut_ptr() as *mut core::ffi::c_void, p1.as_ptr(), p2.as_ptr()); }
212                        <#intmod_type as openvm_algebra_guest::IntMod>::set_up_once();
213                        true
214                    });
215                }
216
217                #[inline(always)]
218                #[cfg(not(target_os = "zkvm"))]
219                fn set_up_once() {
220                    // No-op for non-ZKVM targets
221                }
222
223                #[inline(always)]
224                fn is_identity_impl<const CHECK_SETUP: bool>(&self) -> bool {
225                    use openvm_algebra_guest::IntMod;
226                    // Safety: Self::set_up_once() ensures IntMod::set_up_once() has been called.
227                    unsafe {
228                        self.x.eq_impl::<CHECK_SETUP>(&#intmod_type::ZERO) && self.y.eq_impl::<CHECK_SETUP>(&#intmod_type::ZERO)
229                    }
230                }
231            }
232
233            impl ::openvm_ecc_guest::weierstrass::WeierstrassPoint for #struct_name {
234                const CURVE_A: #intmod_type = #const_a;
235                const CURVE_B: #intmod_type = #const_b;
236                const IDENTITY: Self = Self::identity();
237                type Coordinate = #intmod_type;
238
239                /// SAFETY: assumes that #intmod_type has a memory representation
240                /// such that with repr(C), two coordinates are packed contiguously.
241                #[inline(always)]
242                fn as_le_bytes(&self) -> &[u8] {
243                    unsafe { &*core::ptr::slice_from_raw_parts(self as *const Self as *const u8, <#intmod_type as openvm_algebra_guest::IntMod>::NUM_LIMBS * 2) }
244                }
245
246                #[inline(always)]
247                unsafe fn from_xy_unchecked(x: Self::Coordinate, y: Self::Coordinate) -> Self {
248                    Self { x, y }
249                }
250
251                #[inline(always)]
252                fn x(&self) -> &Self::Coordinate {
253                    &self.x
254                }
255
256                #[inline(always)]
257                fn y(&self) -> &Self::Coordinate {
258                    &self.y
259                }
260
261                #[inline(always)]
262                fn x_mut(&mut self) -> &mut Self::Coordinate {
263                    &mut self.x
264                }
265
266                #[inline(always)]
267                fn y_mut(&mut self) -> &mut Self::Coordinate {
268                    &mut self.y
269                }
270
271                #[inline(always)]
272                fn into_coords(self) -> (Self::Coordinate, Self::Coordinate) {
273                    (self.x, self.y)
274                }
275
276                #[inline(always)]
277                fn set_up_once() {
278                    Self::set_up_once();
279                }
280
281                #[inline]
282                fn add_assign_impl<const CHECK_SETUP: bool>(&mut self, p2: &Self) {
283                    use openvm_algebra_guest::IntMod;
284
285                    if CHECK_SETUP {
286                        // Call setup here so we skip it below
287                        #intmod_type::set_up_once();
288                    }
289
290                    if self.is_identity_impl::<CHECK_SETUP>() {
291                        *self = p2.clone();
292                    } else if p2.is_identity_impl::<CHECK_SETUP>() {
293                        // do nothing
294                    } else if unsafe { self.x.eq_impl::<false>(&p2.x) } { // Safety: we called IntMod setup above
295                        let sum_ys = unsafe { self.y.add_ref::<false>(&p2.y) };
296                        // Safety: we called IntMod setup above
297                        if unsafe { IntMod::eq_impl::<false>(&sum_ys, &<#intmod_type as IntMod>::ZERO) } {
298                            *self = Self::identity();
299                        } else {
300                            unsafe {
301                                self.double_assign_nonidentity::<CHECK_SETUP>();
302                            }
303                        }
304                    } else {
305                        unsafe {
306                            self.add_ne_assign_nonidentity::<CHECK_SETUP>(p2);
307                        }
308                    }
309                }
310
311                #[inline(always)]
312                fn double_assign_impl<const CHECK_SETUP: bool>(&mut self) {
313                    if !self.is_identity_impl::<CHECK_SETUP>() {
314                        unsafe {
315                            self.double_assign_nonidentity::<CHECK_SETUP>();
316                        }
317                    }
318                }
319
320                #[inline(always)]
321                unsafe fn add_ne_nonidentity<const CHECK_SETUP: bool>(&self, p2: &Self) -> Self {
322                    Self::add_ne::<CHECK_SETUP>(self, p2)
323                }
324
325                #[inline(always)]
326                unsafe fn add_ne_assign_nonidentity<const CHECK_SETUP: bool>(&mut self, p2: &Self) {
327                    Self::add_ne_assign::<CHECK_SETUP>(self, p2);
328                }
329
330                #[inline(always)]
331                unsafe fn sub_ne_nonidentity<const CHECK_SETUP: bool>(&self, p2: &Self) -> Self {
332                    Self::add_ne::<CHECK_SETUP>(self, &p2.clone().neg())
333                }
334
335                #[inline(always)]
336                unsafe fn sub_ne_assign_nonidentity<const CHECK_SETUP: bool>(&mut self, p2: &Self) {
337                    Self::add_ne_assign::<CHECK_SETUP>(self, &p2.clone().neg());
338                }
339
340                #[inline(always)]
341                unsafe fn double_nonidentity<const CHECK_SETUP: bool>(&self) -> Self {
342                    Self::double_impl::<CHECK_SETUP>(self)
343                }
344
345                #[inline(always)]
346                unsafe fn double_assign_nonidentity<const CHECK_SETUP: bool>(&mut self) {
347                    #[cfg(not(target_os = "zkvm"))]
348                    {
349                        *self = Self::double_impl::<CHECK_SETUP>(self);
350                    }
351                    #[cfg(target_os = "zkvm")]
352                    {
353                        if CHECK_SETUP {
354                            Self::set_up_once();
355                        }
356                        #sw_double_extern_func(
357                            self as *mut #struct_name as usize,
358                            self as *const #struct_name as usize
359                        );
360                    }
361                }
362            }
363
364            impl core::ops::Neg for #struct_name {
365                type Output = Self;
366
367                fn neg(self) -> Self::Output {
368                    #struct_name {
369                        x: self.x,
370                        y: -self.y,
371                    }
372                }
373            }
374
375            impl core::ops::Neg for &#struct_name {
376                type Output = #struct_name;
377
378                fn neg(self) -> #struct_name {
379                    #struct_name {
380                        x: self.x.clone(),
381                        y: core::ops::Neg::neg(&self.y),
382                    }
383                }
384            }
385
386            mod #group_ops_mod_name {
387                use ::openvm_ecc_guest::{weierstrass::{WeierstrassPoint, FromCompressed}, impl_sw_group_ops, algebra::IntMod};
388                use super::*;
389
390                impl_sw_group_ops!(#struct_name, #intmod_type);
391
392                impl FromCompressed<#intmod_type> for #struct_name {
393                    fn decompress(x: #intmod_type, rec_id: &u8) -> Option<Self> {
394                        use openvm_algebra_guest::Sqrt;
395                        let y_squared = &x * &x * &x + &<#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::CURVE_A * &x + &<#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::CURVE_B;
396                        let y = y_squared.sqrt();
397                        match y {
398                            None => None,
399                            Some(y) => {
400                                let correct_y = if y.as_le_bytes()[0] & 1 == *rec_id & 1 {
401                                    y
402                                } else {
403                                    -y
404                                };
405                                // If y = 0 then negating y doesn't change its parity
406                                if correct_y.as_le_bytes()[0] & 1 != *rec_id & 1 {
407                                    return None;
408                                }
409                                // In order for sqrt() to return Some, we are guaranteed that y * y == y_squared, which already proves (x, correct_y) is on the curve
410                                unsafe { Some(<#struct_name as ::openvm_ecc_guest::weierstrass::WeierstrassPoint>::from_xy_unchecked(x, correct_y)) }
411                            }
412                        }
413                    }
414                }
415            }
416        });
417        output.push(result);
418    }
419
420    TokenStream::from_iter(output)
421}
422
423struct SwDefine {
424    items: Vec<String>,
425}
426
427impl Parse for SwDefine {
428    fn parse(input: ParseStream) -> syn::Result<Self> {
429        let items = input.parse_terminated(<LitStr as Parse>::parse, Token![,])?;
430        Ok(Self {
431            items: items.into_iter().map(|e| e.value()).collect(),
432        })
433    }
434}
435
436#[proc_macro]
437pub fn sw_init(input: TokenStream) -> TokenStream {
438    let SwDefine { items } = parse_macro_input!(input as SwDefine);
439
440    let mut externs = Vec::new();
441
442    let span = proc_macro::Span::call_site();
443
444    for (ec_idx, struct_id) in items.into_iter().enumerate() {
445        // Unique identifier shared by sw_define! and sw_init! used for naming the extern funcs.
446        // Currently it's just the struct type name.
447        let add_ne_extern_func =
448            syn::Ident::new(&format!("sw_add_ne_extern_func_{struct_id}"), span.into());
449        let double_extern_func =
450            syn::Ident::new(&format!("sw_double_extern_func_{struct_id}"), span.into());
451        let setup_extern_func =
452            syn::Ident::new(&format!("sw_setup_extern_func_{struct_id}"), span.into());
453
454        externs.push(quote::quote_spanned! { span.into() =>
455            #[no_mangle]
456            extern "C" fn #add_ne_extern_func(rd: usize, rs1: usize, rs2: usize) {
457                openvm::platform::custom_insn_r!(
458                    opcode = OPCODE,
459                    funct3 = SW_FUNCT3 as usize,
460                    funct7 = SwBaseFunct7::SwAddNe as usize + #ec_idx
461                        * (SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS as usize),
462                    rd = In rd,
463                    rs1 = In rs1,
464                    rs2 = In rs2
465                );
466            }
467
468            #[no_mangle]
469            extern "C" fn #double_extern_func(rd: usize, rs1: usize) {
470                openvm::platform::custom_insn_r!(
471                    opcode = OPCODE,
472                    funct3 = SW_FUNCT3 as usize,
473                    funct7 = SwBaseFunct7::SwDouble as usize + #ec_idx
474                        * (SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS as usize),
475                    rd = In rd,
476                    rs1 = In rs1,
477                    rs2 = Const "x0"
478                );
479            }
480
481            #[no_mangle]
482            extern "C" fn #setup_extern_func(uninit: *mut core::ffi::c_void, p1: *const u8, p2: *const u8) {
483                #[cfg(target_os = "zkvm")]
484                {
485                    openvm::platform::custom_insn_r!(
486                        opcode = ::openvm_ecc_guest::OPCODE,
487                        funct3 = ::openvm_ecc_guest::SW_FUNCT3 as usize,
488                        funct7 = ::openvm_ecc_guest::SwBaseFunct7::SwSetup as usize
489                            + #ec_idx
490                                * (::openvm_ecc_guest::SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS as usize),
491                        rd = In uninit,
492                        rs1 = In p1,
493                        rs2 = In p2
494                    );
495                    openvm::platform::custom_insn_r!(
496                        opcode = ::openvm_ecc_guest::OPCODE,
497                        funct3 = ::openvm_ecc_guest::SW_FUNCT3 as usize,
498                        funct7 = ::openvm_ecc_guest::SwBaseFunct7::SwSetup as usize
499                            + #ec_idx
500                                * (::openvm_ecc_guest::SwBaseFunct7::SHORT_WEIERSTRASS_MAX_KINDS as usize),
501                        rd = In uninit,
502                        rs1 = In p1,
503                        rs2 = Const "x0" // will be parsed as 0 and therefore transpiled to SETUP_EC_DOUBLE
504                    );
505
506
507                }
508            }
509        });
510    }
511
512    TokenStream::from(quote::quote_spanned! { span.into() =>
513        #[allow(non_snake_case)]
514        #[cfg(target_os = "zkvm")]
515        mod openvm_intrinsics_ffi_2 {
516            use ::openvm_ecc_guest::{OPCODE, SW_FUNCT3, SwBaseFunct7};
517
518            #(#externs)*
519        }
520    })
521}