Skip to main content

esp_hal/
ecc.rs

1//! # Elliptic Curve Cryptography (ECC) Accelerator
2//!
3//! ## Overview
4//!
5//! Elliptic Curve Cryptography (ECC) is an approach to public-key cryptography
6//! based on the algebraic structure of elliptic curves. ECC allows smaller
7//! keys compared to RSA cryptography while providing equivalent security.
8//!
9//! ECC Accelerator can complete various calculation based on different
10//! elliptic curves, thus accelerating ECC algorithm and ECC-derived
11//! algorithms (such as ECDSA).
12
13use core::{marker::PhantomData, ptr::NonNull};
14
15use procmacros::BuilderLite;
16
17#[cfg(ecc_supports_enhanced_security)]
18use crate::efuse::ChipRevision;
19use crate::{
20    Blocking,
21    DriverMode,
22    interrupt::InterruptHandler,
23    pac::{self, ecc::mult_conf::KEY_LENGTH},
24    peripherals::{ECC, Interrupt},
25    private::Sealed,
26    system::{self, CryptoClockGuard, GenericPeripheralGuard},
27    work_queue::{Handle, Poll, Status, VTable, WorkQueue, WorkQueueDriver, WorkQueueFrontend},
28};
29
30/// This macro defines 4 other macros:
31/// - `doc_summary` that takes the first line of the documentation and returns it as a string
32/// - `result_type` that generates the return types for each operation
33/// - `operation` that generates the operation function
34/// - `backend_operation` that generates the backend operation function
35///
36/// These generated macros can then be fed to `for_each_ecc_working_mode!` to generate operations
37/// the device supports.
38macro_rules! define_operations {
39    ($($op:tt {
40        // The first line is used for summary, and it is prepended with `# ` on the driver method.
41        docs: [$first_line:literal $(, $lines:literal)*],
42        // The driver method name
43        function: $function:ident,
44        // Whether the operation is modular, i.e. whether it needs a modulus argument.
45        $(modular_arithmetic_method: $is_modular:literal,)?
46        // Whether the operation does point verification first.
47        $(verifies_point: $verifies_point:literal,)?
48        // Input parameters. This determines the name and order of the function arguments,
49        // as well as which memory block they will be written to. Depending on the value of
50        // cfg(ecc_separate_jacobian_point_memory), qx, qy and qz may be mapped to px, py and k.
51        inputs: [$($input:ident),*],
52        // What data does the output contain?
53        // - Scalar (and which memory block contains the scalar)
54        // - AffinePoint
55        // - JacobianPoint
56        returns: [
57            $(
58                // What data is computed may be device specific.
59                $(#[$returns_meta:meta])*
60                $returns:ident $({ const $c:ident: $t:tt = $v:expr })?
61            ),*
62        ]
63    }),*) => {
64        macro_rules! doc_summary {
65            $(
66                ($op) => { $first_line };
67            )*
68        }
69        macro_rules! result_type {
70            $(
71                ($op) => {
72                    #[doc = concat!("A marker type representing ", doc_summary!($op))]
73                    #[non_exhaustive]
74                    pub struct $op;
75
76                    impl crate::private::Sealed for $op {}
77
78                    impl EccOperation for $op {
79                        const WORK_MODE: WorkMode = WorkMode::$op;
80                        const VERIFIES_POINT: bool = $crate::if_set!($($verifies_point)?, false);
81                    }
82
83                    paste::paste! {
84                        $(
85                            $(#[$returns_meta])*
86                            impl [<OperationReturns $returns>] for $op {
87                                $(
88                                    const $c: $t = $v;
89                                )?
90                            }
91                        )*
92
93                        $(
94                            const _: bool = $verifies_point; // I just need this ignored.
95                            impl OperationVerifiesPoint for $op {}
96                        )?
97                    }
98                };
99            )*
100        }
101        macro_rules! driver_method {
102            $(
103                ($op) => {
104                    #[doc = concat!("# ", $first_line)]
105                    $(#[doc = $lines])*
106                    #[doc = r"
107
108# Errors
109
110[`OperationError::ParameterLengthMismatch`] if an input does not match the curve's size."]
111                    #[cfg_attr(ecc_has_curve_sm2, doc = "\n[`OperationError::UnsupportedCurve`] if SM2 is disabled by eFuse.")]
112                    #[inline]
113                    pub fn $function<'op>(
114                        &'op mut self,
115                        curve: EllipticCurve,
116                        $(#[cfg($is_modular)] modulus: EccModBase,)?
117                        $($input: &[u8],)*
118                    ) -> Result<EccResultHandle<'op, $op>, OperationError> {
119                        curve.check_parameters([$($input),*])?;
120
121                        paste::paste! {
122                            $(
123                                self.info().write_mem(self.info().[<$input _mem>](), $input);
124                            )*
125                        };
126
127                        #[cfg(ecc_has_modular_arithmetic)]
128                        let mod_base = $crate::if_set! {
129                            $(
130                                {
131                                    $crate::ignore!($is_modular);
132                                    modulus
133                                }
134                            )?,
135                            // else
136                            EccModBase::OrderOfCurve
137                        };
138
139                        Ok(self.run_operation::<$op>(
140                            curve,
141                            #[cfg(ecc_has_modular_arithmetic)] mod_base,
142                        ))
143                    }
144                };
145            )*
146        }
147
148        macro_rules! backend_operation {
149            $(
150                ($op) => {
151                    #[doc = concat!("Configures a new ", $first_line, " operation with the given inputs, to be executed on [`EccBackend`].")]
152                    ///
153                    /// Outputs need to be assigned separately before executing the operation.
154                    ///
155                    /// # Errors
156                    ///
157                    /// [`OperationError::ParameterLengthMismatch`] if an input does not match the curve's size.
158                    #[cfg_attr(ecc_has_curve_sm2, doc = "\n[`OperationError::UnsupportedCurve`] if SM2 is disabled by eFuse.")]
159                    pub fn $function<'op>(
160                        self,
161                        $(#[cfg($is_modular)] modulus: EccModBase,)?
162                        $($input: &'op [u8],)*
163                    ) -> Result<EccBackendOperation<'op, $op>, OperationError> {
164                        self.check_parameters([&$($input,)*])?;
165
166                        #[cfg(ecc_has_modular_arithmetic)]
167                        let mod_base = $crate::if_set! {
168                            $(
169                                {
170                                    $crate::ignore!($is_modular);
171                                    modulus
172                                }
173                            )?,
174                            // else
175                            EccModBase::OrderOfCurve
176                        };
177
178                        let work_item = EccWorkItem {
179                            curve: self,
180                            operation: WorkMode::$op,
181                            cancelled: false,
182                            #[cfg(ecc_has_modular_arithmetic)]
183                            mod_base,
184                            inputs: {
185                                let mut inputs = MemoryPointers::default();
186                                $(
187                                    paste::paste! {
188                                        inputs.[<set_ $input>](NonNull::from($input));
189                                    };
190                                )*
191                                inputs
192                            },
193                            point_verification_result: false,
194                            outputs: MemoryPointers::default(),
195                        };
196
197                        Ok(EccBackendOperation::new(work_item))
198                    }
199                };
200            )*
201        }
202    }
203}
204
205define_operations! {
206    AffinePointMultiplication {
207        docs: [
208            "Base Point Multiplication",
209            "",
210            "This operation performs `(Qx, Qy) = k * (Px, Py)`."
211        ],
212        function: affine_point_multiplication,
213        inputs: [k, px, py],
214        returns: [AffinePoint]
215    },
216
217    AffinePointVerification {
218        docs: [
219            "Base Point Verification",
220            "",
221            "This operation verifies whether Point (Px, Py) is on the selected elliptic curve."
222        ],
223        function: affine_point_verification,
224        verifies_point: true,
225        inputs: [px, py],
226        returns: []
227    },
228
229    AffinePointVerificationAndMultiplication {
230        docs: [
231            "Base Point Verification and Multiplication",
232            "",
233            "This operation verifies whether Point (Px, Py) is on the selected elliptic curve and performs `(Qx, Qy) = k * (Px, Py)`."
234        ],
235        function: affine_point_verification_multiplication,
236        verifies_point: true,
237        inputs: [k, px, py],
238        returns: [
239            AffinePoint,
240            #[cfg(ecc_separate_jacobian_point_memory)]
241            JacobianPoint
242        ]
243    },
244
245    AffinePointAddition {
246        docs: [
247            "Point Addition",
248            "",
249            "This operation performs `(Rx, Ry) = (Jx, Jy, Jz) = (Px, Py, 1) + (Qx, Qy, Qz)`."
250        ],
251        function: affine_point_addition,
252        inputs: [px, py, qx, qy, qz],
253        returns: [
254            AffinePoint,
255            #[cfg(ecc_separate_jacobian_point_memory)]
256            JacobianPoint
257        ]
258    },
259
260    JacobianPointMultiplication {
261        docs: [
262            "Jacobian Point Multiplication",
263            "",
264            "This operation performs `(Qx, Qy, Qz) = k * (Px, Py, 1)`."
265        ],
266        function: jacobian_point_multiplication,
267        inputs: [k, px, py],
268        returns: [
269            JacobianPoint
270        ]
271    },
272
273    JacobianPointVerification {
274        docs: [
275            "Jacobian Point Verification",
276            "",
277            "This operation verifies whether Point (Qx, Qy, Qz) is on the selected elliptic curve."
278        ],
279        function: jacobian_point_verification,
280        verifies_point: true,
281        inputs: [qx, qy, qz],
282        returns: [
283            JacobianPoint
284        ]
285    },
286
287    AffinePointVerificationAndJacobianPointMultiplication {
288        docs: [
289            "Base Point Verification + Jacobian Point Multiplication",
290            "",
291            "This operation first verifies whether Point (Px, Py) is on the selected elliptic curve. If yes, it performs `(Qx, Qy, Qz) = k * (Px, Py, 1)`."
292        ],
293        function: affine_point_verification_jacobian_multiplication,
294        verifies_point: true,
295        inputs: [k, px, py],
296        returns: [
297            JacobianPoint
298        ]
299    },
300
301    FiniteFieldDivision {
302        docs: [
303            "Finite Field Division",
304            "",
305            "This operation performs `R = Py * k^{−1} mod p`."
306        ],
307        function: finite_field_division,
308        inputs: [k, py],
309        returns: [
310            Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Py }
311        ]
312    },
313
314    ModularAddition {
315        docs: [
316            "Modular Addition",
317            "",
318            "This operation performs `R = Px + Py mod p`."
319        ],
320        function: modular_addition,
321        modular_arithmetic_method: true,
322        inputs: [px, py],
323        returns: [
324            Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Px }
325        ]
326    },
327
328    ModularSubtraction {
329        docs: [
330            "Modular Subtraction",
331            "",
332            "This operation performs `R = Px - Py mod p`."
333        ],
334        function: modular_subtraction,
335        modular_arithmetic_method: true,
336        inputs: [px, py],
337        returns: [
338            Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Px }
339        ]
340    },
341
342    ModularMultiplication {
343        docs: [
344            "Modular Multiplication",
345            "",
346            "This operation performs `R = Px * Py mod p`."
347        ],
348        function: modular_multiplication,
349        modular_arithmetic_method: true,
350        inputs: [px, py],
351        returns: [
352            Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Py }
353        ]
354    },
355
356    ModularDivision {
357        docs: [
358            "Modular Division",
359            "",
360            "This operation performs `R = Px * Py^{−1} mod p`."
361        ],
362        function: modular_division,
363        modular_arithmetic_method: true,
364        inputs: [px, py],
365        returns: [
366            Scalar { const LOCATION: ScalarResultLocation = ScalarResultLocation::Py }
367        ]
368    }
369}
370
371const MEM_BLOCK_SIZE: usize = property!("ecc.mem_block_size");
372
373/// The ECC Accelerator driver.
374///
375/// Unlike commonly used standards, this driver operates on **little-endian** data.
376pub struct Ecc<'d, Dm: DriverMode> {
377    _ecc: ECC<'d>,
378    phantom: PhantomData<Dm>,
379    _memory_guard: EccMemoryPowerGuard,
380    _guard: GenericPeripheralGuard<{ system::Peripheral::Ecc as u8 }>,
381    _clock_guard: CryptoClockGuard,
382}
383
384struct EccMemoryPowerGuard;
385
386impl EccMemoryPowerGuard {
387    fn new() -> Self {
388        #[cfg(soc_has_pcr)]
389        crate::peripherals::PCR::regs()
390            .ecc_pd_ctrl()
391            .modify(|_, w| {
392                w.ecc_mem_force_pd().clear_bit();
393                w.ecc_mem_force_pu().set_bit();
394                w.ecc_mem_pd().clear_bit()
395            });
396        Self
397    }
398}
399
400impl Drop for EccMemoryPowerGuard {
401    fn drop(&mut self) {
402        #[cfg(soc_has_pcr)]
403        crate::peripherals::PCR::regs()
404            .ecc_pd_ctrl()
405            .modify(|_, w| {
406                w.ecc_mem_force_pd().clear_bit();
407                w.ecc_mem_force_pu().clear_bit();
408                w.ecc_mem_pd().set_bit()
409            });
410    }
411}
412
413/// ECC peripheral configuration.
414#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, BuilderLite)]
415#[cfg_attr(feature = "defmt", derive(defmt::Format))]
416pub struct Config {
417    /// Force-enables the register clock.
418    force_enable_reg_clock: bool,
419
420    /// Force-enables the memory clock.
421    #[cfg(ecc_has_memory_clock_gate)]
422    force_enable_mem_clock: bool,
423
424    /// Enables constant time operation and minimized power consumption variation for
425    /// point-multiplication operations.
426    #[cfg_attr(
427        esp32h2,
428        doc = r"
429
430Only available on chip revision 1.2 and above."
431    )]
432    #[cfg(ecc_supports_enhanced_security)]
433    enhanced_security: bool,
434}
435
436/// ECC operation error.
437#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438pub enum OperationError {
439    /// The length of the arguments do not match the length required by the curve.
440    ParameterLengthMismatch,
441
442    /// Point verification failed.
443    PointNotOnCurve,
444
445    /// The selected curve is not supported.
446    UnsupportedCurve,
447}
448
449/// Modulus base.
450#[cfg(ecc_has_modular_arithmetic)]
451#[derive(Debug, Clone, Copy, PartialEq, Eq)]
452pub enum EccModBase {
453    /// The order of the curve.
454    OrderOfCurve = 0,
455
456    /// Prime modulus.
457    PrimeModulus = 1,
458}
459
460for_each_ecc_curve! {
461    (all $(( $id:literal, $name:ident, $bits:literal )),*) => {
462        /// Represents supported elliptic curves for cryptographic operations.
463        ///
464        /// The methods that represent operations require the `EccBackend` to be started before use.
465        #[derive(Clone, Copy, PartialEq, Eq, Debug)]
466        #[cfg_attr(feature = "defmt", derive(defmt::Format))]
467        pub enum EllipticCurve {
468            $(
469                #[doc = concat!("The ", stringify!($name), " elliptic curve, a ", $bits, "-bit curve.")]
470                $name,
471            )*
472        }
473        impl EllipticCurve {
474            /// Returns whether the curve is available for hardware operations.
475            #[cfg_attr(ecc_has_curve_sm2, doc = "\nSM2 is disabled when the `DIS_SM_CRYPT` eFuse is set.")]
476            #[instability::unstable]
477            pub fn is_supported(self) -> bool {
478                match self {
479                    #[cfg(ecc_has_curve_sm2)]
480                    Self::Sm2 => !crate::efuse::read_bit(crate::efuse::DIS_SM_CRYPT),
481                    _ => true,
482                }
483            }
484
485            /// Returns the size of the elliptic curve in bytes.
486            pub const fn size(self) -> usize {
487                match self {
488                    $(
489                        EllipticCurve::$name => $bits / 8,
490                    )*
491                }
492            }
493        }
494    };
495}
496
497for_each_ecc_working_mode! {
498    (all $(($wm_id:literal, $op:tt)),*) => {
499        impl EllipticCurve {
500            fn check_parameters<const N: usize>(&self, params: [&[u8]; N]) -> Result<(), OperationError> {
501                if !self.is_supported() {
502                    return Err(OperationError::UnsupportedCurve);
503                }
504
505                self.size_check(params)
506            }
507
508            fn size_check<const N: usize>(&self, params: [&[u8]; N]) -> Result<(), OperationError> {
509                let bytes = self.size();
510
511                if params.iter().any(|p| p.len() != bytes) {
512                    return Err(OperationError::ParameterLengthMismatch);
513                }
514
515                Ok(())
516            }
517
518            $(
519                // Macro defined by `define_operations`
520                backend_operation!($op);
521            )*
522        }
523    };
524}
525
526impl<'d> Ecc<'d, Blocking> {
527    /// Creates a new instance in [Blocking] mode.
528    pub fn new(ecc: ECC<'d>, config: Config) -> Self {
529        let clock_guard = CryptoClockGuard::new();
530        let this = Self {
531            _ecc: ecc,
532            phantom: PhantomData,
533            _memory_guard: EccMemoryPowerGuard::new(),
534            _guard: GenericPeripheralGuard::new(),
535            _clock_guard: clock_guard,
536        };
537
538        this.info().apply_config(&config);
539
540        this
541    }
542}
543
544impl crate::private::Sealed for Ecc<'_, Blocking> {}
545
546#[instability::unstable]
547impl crate::interrupt::InterruptConfigurable for Ecc<'_, Blocking> {
548    fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
549        self.set_interrupt_handler(handler);
550    }
551}
552
553struct Info {
554    regs: &'static pac::ecc::RegisterBlock,
555}
556
557impl Info {
558    fn reset(&self) {
559        self.regs.mult_conf().reset()
560    }
561
562    fn apply_config(&self, config: &Config) {
563        self.regs.mult_conf().modify(|_, w| {
564            w.clk_en().bit(config.force_enable_reg_clock);
565
566            #[cfg(ecc_has_memory_clock_gate)]
567            w.mem_clock_gate_force_on()
568                .bit(config.force_enable_mem_clock);
569
570            #[cfg(ecc_supports_enhanced_security)]
571            if !cfg!(esp32h2) || crate::soc::chip_revision_above(ChipRevision::from_combined(102)) {
572                w.security_mode().bit(config.enhanced_security);
573            }
574
575            w
576        });
577    }
578
579    fn check_point_verification_result(&self) -> Result<(), OperationError> {
580        if self
581            .regs
582            .mult_conf()
583            .read()
584            .verification_result()
585            .bit_is_set()
586        {
587            Ok(())
588        } else {
589            Err(OperationError::PointNotOnCurve)
590        }
591    }
592
593    #[inline]
594    fn write_mem(&self, mut word_ptr: *mut u32, data: &[u8]) {
595        // Note that at least the C2 requires writing this memory in words.
596
597        debug_assert!(data.len() <= MEM_BLOCK_SIZE);
598
599        #[cfg(ecc_zero_extend_writes)]
600        let end = word_ptr.wrapping_byte_add(MEM_BLOCK_SIZE);
601
602        let (chunks, remainder) = data.as_chunks::<4>();
603        debug_assert!(remainder.is_empty());
604
605        for word_bytes in chunks {
606            unsafe { word_ptr.write_volatile(u32::from_le_bytes(*word_bytes)) };
607            word_ptr = word_ptr.wrapping_add(1);
608        }
609
610        #[cfg(ecc_zero_extend_writes)]
611        while word_ptr < end {
612            unsafe { word_ptr.write_volatile(0) };
613            word_ptr = word_ptr.wrapping_add(1);
614        }
615    }
616
617    #[inline]
618    fn read_mem(&self, mut word_ptr: *const u32, out: &mut [u8]) {
619        let (chunks, _) = out.as_chunks_mut::<4>();
620        for word_bytes in chunks {
621            let word = unsafe { word_ptr.read_volatile() };
622            word_ptr = word_ptr.wrapping_add(1);
623            *word_bytes = word.to_le_bytes();
624        }
625    }
626
627    fn k_mem(&self) -> *mut u32 {
628        self.regs.k_mem(0).as_ptr()
629    }
630
631    fn px_mem(&self) -> *mut u32 {
632        self.regs.px_mem(0).as_ptr()
633    }
634
635    fn py_mem(&self) -> *mut u32 {
636        self.regs.py_mem(0).as_ptr()
637    }
638
639    fn qx_mem(&self) -> *mut u32 {
640        cfg_select! {
641            ecc_separate_jacobian_point_memory => self.regs.qx_mem(0).as_ptr(),
642            _ => self.regs.px_mem(0).as_ptr(),
643        }
644    }
645
646    fn qy_mem(&self) -> *mut u32 {
647        cfg_select! {
648            ecc_separate_jacobian_point_memory => self.regs.qy_mem(0).as_ptr(),
649            _ => self.regs.py_mem(0).as_ptr(),
650        }
651    }
652
653    fn qz_mem(&self) -> *mut u32 {
654        cfg_select! {
655            ecc_separate_jacobian_point_memory => self.regs.qz_mem(0).as_ptr(),
656            _ => self.regs.k_mem(0).as_ptr(),
657        }
658    }
659
660    fn read_point_result(&self, x: &mut [u8], y: &mut [u8]) {
661        self.read_mem(self.px_mem(), x);
662        self.read_mem(self.py_mem(), y);
663    }
664
665    fn read_jacobian_result(&self, qx: &mut [u8], qy: &mut [u8], qz: &mut [u8]) {
666        self.read_mem(self.qx_mem(), qx);
667        self.read_mem(self.qy_mem(), qy);
668        self.read_mem(self.qz_mem(), qz);
669    }
670
671    /// Clears all peripheral memory blocks.
672    #[cfg(clear_crypto_secrets)]
673    fn clear_secrets(&self) {
674        self.zero_mem(self.k_mem());
675        self.zero_mem(self.px_mem());
676        self.zero_mem(self.py_mem());
677
678        #[cfg(ecc_separate_jacobian_point_memory)]
679        {
680            self.zero_mem(self.qx_mem());
681            self.zero_mem(self.qy_mem());
682            self.zero_mem(self.qz_mem());
683        }
684    }
685
686    #[cfg(clear_crypto_secrets)]
687    fn zero_mem(&self, mut word_ptr: *mut u32) {
688        for _ in 0..(MEM_BLOCK_SIZE / 4) {
689            unsafe { word_ptr.write_volatile(0) };
690            word_ptr = word_ptr.wrapping_add(1);
691        }
692    }
693
694    fn is_busy(&self) -> bool {
695        self.regs.mult_conf().read().start().bit_is_set()
696    }
697
698    fn start_operation(
699        &self,
700        mode: WorkMode,
701        curve: EllipticCurve,
702        #[cfg(ecc_has_modular_arithmetic)] mod_base: EccModBase,
703    ) {
704        let curve_variant;
705        for_each_ecc_curve! {
706            (all $(($_id:tt, $name:ident, $_bits:tt)),*) => {
707                curve_variant = match curve {
708                    $(EllipticCurve::$name => KEY_LENGTH::$name,)*
709                }
710            };
711        };
712        self.regs.mult_conf().modify(|_, w| unsafe {
713            w.work_mode().bits(mode as u8);
714            w.key_length().variant(curve_variant);
715
716            #[cfg(ecc_has_modular_arithmetic)]
717            w.mod_base().bit(mod_base as u8 == 1);
718
719            w.start().set_bit()
720        });
721    }
722}
723
724// Broken into separate macro invocations per item, to make the "Expand macro" LSP output more
725// readable
726
727for_each_ecc_working_mode! {
728    (all $(( $id:literal, $mode:tt )),*) => {
729        #[derive(Clone, Copy)]
730        #[doc(hidden)]
731        /// Represents the operational modes for elliptic curve or modular arithmetic
732        /// computations.
733        pub enum WorkMode {
734            $(
735                $mode = $id,
736            )*
737        }
738    };
739}
740
741// Result type for each operation
742for_each_ecc_working_mode! {
743    (all $(( $id:literal, $mode:tt )),*) => {
744        $(
745            result_type!($mode);
746        )*
747    };
748}
749
750// The main driver implementation
751for_each_ecc_working_mode! {
752    (all $(( $id:literal, $mode:tt )),*) => {
753        impl<'d, Dm: DriverMode> Ecc<'d, Dm> {
754            fn info(&self) -> Info {
755                Info { regs: ECC::regs() }
756            }
757
758            fn run_operation<'op, O: EccOperation>(
759                &'op mut self,
760                curve: EllipticCurve,
761                #[cfg(ecc_has_modular_arithmetic)] mod_base: EccModBase,
762            ) -> EccResultHandle<'op, O> {
763                self.info().start_operation(
764                    O::WORK_MODE,
765                    curve,
766                    #[cfg(ecc_has_modular_arithmetic)] mod_base,
767                );
768
769                // wait for interrupt
770                while self.info().is_busy() {}
771
772                EccResultHandle::new(curve, self)
773            }
774
775            /// Applies the given configuration to the ECC peripheral.
776            pub fn apply_config(&mut self, config: &Config) {
777                self.info().apply_config(config);
778            }
779
780            /// Resets the ECC peripheral.
781            pub fn reset(&mut self) {
782                self.info().reset()
783            }
784
785            /// Registers an interrupt handler for the ECC peripheral.
786            ///
787            /// Replaces any previously registered interrupt handlers.
788            #[instability::unstable]
789            pub fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
790                for core in crate::system::Cpu::other() {
791                    crate::interrupt::disable(core, Interrupt::ECC);
792                }
793                crate::interrupt::bind_handler(Interrupt::ECC, handler);
794            }
795
796            $(
797                driver_method!($mode);
798            )*
799        }
800    };
801}
802
803/// Marks an ECC operation.
804pub trait EccOperation: Sealed {
805    /// Whether the operation verifies that the input point is on the curve.
806    const VERIFIES_POINT: bool;
807
808    /// Work mode
809    #[doc(hidden)]
810    const WORK_MODE: WorkMode;
811}
812
813/// Scalar result location.
814#[doc(hidden)]
815pub enum ScalarResultLocation {
816    /// The scalar value is stored in the `Px` memory location.
817    Px,
818    /// The scalar value is stored in the `Py` memory location.
819    Py,
820    /// The scalar value is stored in the `k` memory location.
821    K,
822}
823
824/// Marks operations that return a scalar value.
825pub trait OperationReturnsScalar: EccOperation {
826    /// Where the scalar value is stored.
827    #[doc(hidden)]
828    const LOCATION: ScalarResultLocation;
829}
830
831/// Marks operations that return a point in affine format.
832pub trait OperationReturnsAffinePoint: EccOperation {}
833
834/// Marks operations that return a point in Jacobian format.
835pub trait OperationReturnsJacobianPoint: EccOperation {}
836
837/// Marks operations that verify that the input point is on the curve.
838pub trait OperationVerifiesPoint: EccOperation {}
839
840/// The result of an ECC operation.
841///
842/// Used to read the result of an ECC operation. The available methods depend on
843/// the operation. An operation can compute multiple values, such as an affine point
844/// and a Jacobian point at the same time.
845#[must_use]
846pub struct EccResultHandle<'op, O>
847where
848    O: EccOperation,
849{
850    curve: EllipticCurve,
851    info: Info,
852    _marker: PhantomData<(&'op mut (), O)>,
853}
854
855impl<'op, O> EccResultHandle<'op, O>
856where
857    O: EccOperation,
858{
859    fn new<'d, Dm: DriverMode>(curve: EllipticCurve, driver: &'op mut Ecc<'d, Dm>) -> Self {
860        Self {
861            curve,
862            info: driver.info(),
863            _marker: PhantomData,
864        }
865    }
866
867    fn run_checks<const N: usize>(&self, params: [&[u8]; N]) -> Result<(), OperationError> {
868        self.curve.size_check(params)?;
869        if O::VERIFIES_POINT {
870            self.info.check_point_verification_result()?;
871        }
872        Ok(())
873    }
874
875    /// Returns whether the operation was successful.
876    ///
877    /// For operations that only perform point verification, this method returns whether the point
878    /// is on the curve. For operations that do not perform point verification, this method always
879    /// returns true.
880    pub fn success(&self) -> bool {
881        if O::VERIFIES_POINT {
882            self.info.check_point_verification_result().is_ok()
883        } else {
884            true
885        }
886    }
887
888    /// Retrieves the scalar result of the operation.
889    ///
890    /// # Errors
891    ///
892    /// [`OperationError`] when point verification failed, or when `out` is not the correct size.
893    pub fn read_scalar_result(&self, out: &mut [u8]) -> Result<(), OperationError>
894    where
895        O: OperationReturnsScalar,
896    {
897        self.run_checks([out])?;
898
899        match O::LOCATION {
900            ScalarResultLocation::Px => self.info.read_mem(self.info.px_mem(), out),
901            ScalarResultLocation::Py => self.info.read_mem(self.info.py_mem(), out),
902            ScalarResultLocation::K => self.info.read_mem(self.info.k_mem(), out),
903        }
904
905        Ok(())
906    }
907
908    /// Retrieves the affine point result of the operation.
909    ///
910    /// # Errors
911    ///
912    /// [`OperationError`] when point verification failed, or when `x` or `y` are not the correct
913    /// size.
914    pub fn read_affine_point_result(&self, x: &mut [u8], y: &mut [u8]) -> Result<(), OperationError>
915    where
916        O: OperationReturnsAffinePoint,
917    {
918        self.run_checks([x, y])?;
919        self.info.read_point_result(x, y);
920        Ok(())
921    }
922
923    /// Retrieves the Jacobian point result of the operation.
924    ///
925    /// # Errors
926    ///
927    /// [`OperationError`] when point verification failed, or when `x`, `y`, or `z` are not the
928    /// correct size.
929    pub fn read_jacobian_point_result(
930        &self,
931        x: &mut [u8],
932        y: &mut [u8],
933        z: &mut [u8],
934    ) -> Result<(), OperationError>
935    where
936        O: OperationReturnsJacobianPoint,
937    {
938        self.run_checks([x, y, z])?;
939        self.info.read_jacobian_result(x, y, z);
940        Ok(())
941    }
942}
943
944struct EccWorkItem {
945    curve: EllipticCurve,
946    operation: WorkMode,
947    cancelled: bool,
948    #[cfg(ecc_has_modular_arithmetic)]
949    mod_base: EccModBase,
950    inputs: MemoryPointers,
951    point_verification_result: bool,
952    outputs: MemoryPointers,
953}
954
955#[derive(Default)]
956struct MemoryPointers {
957    // All of these pointers point to slices with curve-appropriate lengths.
958    k: Option<NonNull<u8>>,
959    px: Option<NonNull<u8>>,
960    py: Option<NonNull<u8>>,
961    #[cfg(ecc_separate_jacobian_point_memory)]
962    qx: Option<NonNull<u8>>,
963    #[cfg(ecc_separate_jacobian_point_memory)]
964    qy: Option<NonNull<u8>>,
965    #[cfg(ecc_separate_jacobian_point_memory)]
966    qz: Option<NonNull<u8>>,
967}
968
969impl MemoryPointers {
970    fn set_scalar(&mut self, location: ScalarResultLocation, ptr: NonNull<[u8]>) {
971        match location {
972            ScalarResultLocation::Px => self.set_px(ptr),
973            ScalarResultLocation::Py => self.set_py(ptr),
974            ScalarResultLocation::K => self.set_k(ptr),
975        }
976    }
977
978    fn set_k(&mut self, ptr: NonNull<[u8]>) {
979        self.k = Some(ptr.cast());
980    }
981
982    fn set_px(&mut self, ptr: NonNull<[u8]>) {
983        self.px = Some(ptr.cast());
984    }
985
986    fn set_py(&mut self, ptr: NonNull<[u8]>) {
987        self.py = Some(ptr.cast());
988    }
989
990    fn set_qx(&mut self, ptr: NonNull<[u8]>) {
991        cfg_select! {
992            ecc_separate_jacobian_point_memory => {
993                self.qx = Some(ptr.cast());
994            }
995            _ => {
996                self.px = Some(ptr.cast());
997            }
998        }
999    }
1000
1001    fn set_qy(&mut self, ptr: NonNull<[u8]>) {
1002        cfg_select! {
1003            ecc_separate_jacobian_point_memory => {
1004                self.qy = Some(ptr.cast());
1005            }
1006            _ => {
1007                self.py = Some(ptr.cast());
1008            }
1009        }
1010    }
1011
1012    fn set_qz(&mut self, ptr: NonNull<[u8]>) {
1013        cfg_select! {
1014            ecc_separate_jacobian_point_memory => {
1015                self.qz = Some(ptr.cast());
1016            }
1017            _ => {
1018                self.k = Some(ptr.cast());
1019            }
1020        }
1021    }
1022}
1023
1024// Safety: MemoryPointers is safe to share between threads, in the context of a WorkQueue. The
1025// WorkQueue ensures that only a single location can access the data. All the internals, except
1026// for the pointers, are Sync. The pointers are safe to share because they point at data that the
1027// ECC driver ensures can be accessed safely and soundly.
1028unsafe impl Sync for MemoryPointers {}
1029// Safety: we will not hold on to the pointers when the work item leaves the queue.
1030unsafe impl Send for MemoryPointers {}
1031
1032static ECC_WORK_QUEUE: WorkQueue<EccWorkItem> = WorkQueue::new();
1033
1034const ECC_VTABLE: VTable<EccWorkItem> = VTable {
1035    post: |driver, item| {
1036        let driver = unsafe { EccBackend::from_raw(driver) };
1037
1038        // Ensure driver is initialized
1039        if let DriverState::Uninitialized(ecc) = &driver.driver {
1040            let mut ecc = Ecc::new(unsafe { ecc.clone_unchecked() }, driver.config);
1041            ecc.set_interrupt_handler(ecc_work_queue_handler);
1042            driver.driver = DriverState::Initialized(ecc);
1043        };
1044
1045        Some(driver.process(item))
1046    },
1047    poll: |driver, item| {
1048        let driver = unsafe { EccBackend::from_raw(driver) };
1049        driver.poll(item)
1050    },
1051    cancel: |driver, item| {
1052        let driver = unsafe { EccBackend::from_raw(driver) };
1053        driver.cancel(item);
1054    },
1055    stop: |driver| {
1056        let driver = unsafe { EccBackend::from_raw(driver) };
1057        driver.deinitialize()
1058    },
1059};
1060
1061enum DriverState<'d> {
1062    Uninitialized(ECC<'d>),
1063    Initialized(Ecc<'d, Blocking>),
1064}
1065
1066/// ECC processing backend.
1067///
1068/// Enables shared access to the device's ECC hardware using a work queue.
1069pub struct EccBackend<'d> {
1070    driver: DriverState<'d>,
1071    config: Config,
1072}
1073
1074impl<'d> EccBackend<'d> {
1075    /// Creates a new ECC backend.
1076    ///
1077    /// The backend must be started with [`Self::start`] before it can execute ECC operations.
1078    pub fn new(ecc: ECC<'d>, config: Config) -> Self {
1079        Self {
1080            driver: DriverState::Uninitialized(ecc),
1081            config,
1082        }
1083    }
1084
1085    /// Registers the ECC driver to process ECC operations.
1086    ///
1087    /// The driver stops operating when the returned object is dropped.
1088    pub fn start(&mut self) -> EccWorkQueueDriver<'_, 'd> {
1089        EccWorkQueueDriver {
1090            inner: WorkQueueDriver::new(self, ECC_VTABLE, &ECC_WORK_QUEUE),
1091        }
1092    }
1093
1094    // WorkQueue callbacks. They may run in any context.
1095
1096    unsafe fn from_raw<'any>(ptr: NonNull<()>) -> &'any mut Self {
1097        unsafe { ptr.cast::<EccBackend<'_>>().as_mut() }
1098    }
1099
1100    fn process(&mut self, item: &mut EccWorkItem) -> Poll {
1101        let DriverState::Initialized(driver) = &mut self.driver else {
1102            unreachable!()
1103        };
1104
1105        let bytes = item.curve.size();
1106
1107        macro_rules! set_input {
1108            ($input:ident, $input_mem:ident) => {
1109                if let Some($input) = item.inputs.$input {
1110                    driver.info().write_mem(driver.info().$input_mem(), unsafe {
1111                        core::slice::from_raw_parts($input.as_ptr(), bytes)
1112                    });
1113                }
1114            };
1115        }
1116
1117        set_input!(k, k_mem);
1118        set_input!(px, px_mem);
1119        set_input!(py, py_mem);
1120
1121        #[cfg(ecc_separate_jacobian_point_memory)]
1122        {
1123            set_input!(qx, qx_mem);
1124            set_input!(qy, qy_mem);
1125            set_input!(qz, qz_mem);
1126        }
1127
1128        driver.info().start_operation(
1129            item.operation,
1130            item.curve,
1131            #[cfg(ecc_has_modular_arithmetic)]
1132            item.mod_base,
1133        );
1134        Poll::Pending(false)
1135    }
1136
1137    fn poll(&mut self, item: &mut EccWorkItem) -> Poll {
1138        let DriverState::Initialized(driver) = &mut self.driver else {
1139            unreachable!()
1140        };
1141
1142        if driver.info().is_busy() {
1143            return Poll::Pending(false);
1144        }
1145        if item.cancelled {
1146            return Poll::Ready(Status::Cancelled);
1147        }
1148
1149        let bytes = item.curve.size();
1150
1151        macro_rules! read_output {
1152            ($output:ident, $output_mem:ident) => {
1153                if let Some($output) = item.outputs.$output {
1154                    driver.info().read_mem(driver.info().$output_mem(), unsafe {
1155                        core::slice::from_raw_parts_mut($output.as_ptr(), bytes)
1156                    });
1157                }
1158            };
1159        }
1160
1161        read_output!(k, k_mem);
1162        read_output!(px, px_mem);
1163        read_output!(py, py_mem);
1164
1165        #[cfg(ecc_separate_jacobian_point_memory)]
1166        {
1167            read_output!(qx, qx_mem);
1168            read_output!(qy, qy_mem);
1169            read_output!(qz, qz_mem);
1170        }
1171
1172        item.point_verification_result = driver.info().check_point_verification_result().is_ok();
1173
1174        #[cfg(clear_crypto_secrets)]
1175        driver.info().clear_secrets();
1176
1177        Poll::Ready(Status::Completed)
1178    }
1179
1180    fn cancel(&mut self, item: &mut EccWorkItem) {
1181        let DriverState::Initialized(driver) = &mut self.driver else {
1182            unreachable!()
1183        };
1184        driver.reset();
1185
1186        // The operands may have already been written to the peripheral.
1187        #[cfg(clear_crypto_secrets)]
1188        driver.info().clear_secrets();
1189
1190        item.cancelled = true;
1191    }
1192
1193    fn deinitialize(&mut self) {
1194        if let DriverState::Initialized(ref ecc) = self.driver {
1195            self.driver = DriverState::Uninitialized(unsafe { ecc._ecc.clone_unchecked() });
1196        }
1197    }
1198}
1199
1200/// An active work queue driver.
1201///
1202/// This object must be kept around, otherwise ECC operations will never complete.
1203pub struct EccWorkQueueDriver<'t, 'd> {
1204    inner: WorkQueueDriver<'t, EccBackend<'d>, EccWorkItem>,
1205}
1206
1207impl<'t, 'd> EccWorkQueueDriver<'t, 'd> {
1208    /// Finishes processing the current work queue item, then stops the driver.
1209    pub fn stop(self) -> impl Future<Output = ()> {
1210        self.inner.stop()
1211    }
1212}
1213
1214#[crate::ram]
1215#[crate::handler]
1216fn ecc_work_queue_handler() {
1217    if !ECC_WORK_QUEUE.process() {
1218        // The queue may indicate that it needs to be polled again. In this case, we do not clear
1219        // the interrupt bit, which causes the interrupt to be re-handled.
1220        cfg_select! {
1221            any(esp32c5, esp32c61) => {
1222                let reg = ECC::regs().int_clr();
1223            }
1224            _ => {
1225                let reg = ECC::regs().mult_int_clr();
1226            }
1227        }
1228        reg.write(|w| w.calc_done().clear_bit_by_one());
1229    }
1230}
1231
1232/// An ECC operation that can be enqueued on the work queue.
1233pub struct EccBackendOperation<'op, O: EccOperation> {
1234    frontend: WorkQueueFrontend<EccWorkItem>,
1235    _marker: PhantomData<(&'op mut (), O)>,
1236}
1237
1238impl<'op, O: EccOperation> EccBackendOperation<'op, O> {
1239    fn new(work_item: EccWorkItem) -> Self {
1240        Self {
1241            frontend: WorkQueueFrontend::new(work_item),
1242            _marker: PhantomData,
1243        }
1244    }
1245
1246    /// Designate a buffer for the scalar result of the operation.
1247    ///
1248    /// Once the operation is processed, the result can be retrieved from the designated buffer.
1249    ///
1250    /// # Errors
1251    ///
1252    /// [`OperationError::ParameterLengthMismatch`] when `out` is not the correct size.
1253    pub fn with_scalar_result(mut self, out: &'op mut [u8]) -> Result<Self, OperationError>
1254    where
1255        O: OperationReturnsScalar,
1256    {
1257        self.frontend.data().curve.size_check([out])?;
1258
1259        self.frontend
1260            .data_mut()
1261            .outputs
1262            .set_scalar(O::LOCATION, NonNull::from(out));
1263
1264        Ok(self)
1265    }
1266
1267    /// Designate buffers for the affine point result of the operation.
1268    ///
1269    /// Once the operation is processed, the result can be retrieved from the designated buffers.
1270    ///
1271    /// # Errors
1272    ///
1273    /// [`OperationError::ParameterLengthMismatch`] when `x` or `y` are not the correct size.
1274    pub fn with_affine_point_result(
1275        mut self,
1276        px: &'op mut [u8],
1277        py: &'op mut [u8],
1278    ) -> Result<Self, OperationError>
1279    where
1280        O: OperationReturnsAffinePoint,
1281    {
1282        self.frontend.data().curve.size_check([px, py])?;
1283
1284        self.frontend.data_mut().outputs.set_px(NonNull::from(px));
1285        self.frontend.data_mut().outputs.set_py(NonNull::from(py));
1286
1287        Ok(self)
1288    }
1289
1290    /// Designate buffers for the Jacobian point result of the operation.
1291    ///
1292    /// Once the operation is processed, the result can be retrieved from the designated buffers.
1293    ///
1294    /// # Errors
1295    ///
1296    /// [`OperationError::ParameterLengthMismatch`] when `x`, `y`, or `z` are not the correct size.
1297    pub fn with_jacobian_point_result(
1298        mut self,
1299        qx: &'op mut [u8],
1300        qy: &'op mut [u8],
1301        qz: &'op mut [u8],
1302    ) -> Result<Self, OperationError>
1303    where
1304        O: OperationReturnsJacobianPoint,
1305    {
1306        self.frontend.data().curve.size_check([qx, qy, qz])?;
1307
1308        self.frontend.data_mut().outputs.set_qx(NonNull::from(qx));
1309        self.frontend.data_mut().outputs.set_qy(NonNull::from(qy));
1310        self.frontend.data_mut().outputs.set_qz(NonNull::from(qz));
1311
1312        Ok(self)
1313    }
1314
1315    /// Returns whether the input point is on the curve.
1316    ///
1317    /// The operation must be processed before this method returns a meaningful value.
1318    pub fn point_on_curve(&self) -> bool
1319    where
1320        O: OperationVerifiesPoint,
1321    {
1322        self.frontend.data().point_verification_result
1323    }
1324
1325    /// Starts processing the operation.
1326    ///
1327    /// The returned [`EccHandle`] must be polled to completion before the operation is considered
1328    /// complete.
1329    pub fn process(&mut self) -> EccHandle<'_> {
1330        EccHandle(self.frontend.post(&ECC_WORK_QUEUE))
1331    }
1332}
1333
1334/// A handle for an in-progress operation.
1335#[must_use]
1336pub struct EccHandle<'t>(Handle<'t, EccWorkItem>);
1337
1338impl EccHandle<'_> {
1339    /// Polls the status of the work item.
1340    ///
1341    /// Returns whether the item has been processed.
1342    #[inline]
1343    pub fn poll(&mut self) -> bool {
1344        self.0.poll()
1345    }
1346
1347    /// Polls the work item to completion, by busy-looping.
1348    ///
1349    /// Returns immediately if `poll` returns `true`.
1350    #[inline]
1351    pub fn wait_blocking(self) -> Status {
1352        self.0.wait_blocking()
1353    }
1354
1355    /// Waits until the work item is completed.
1356    #[inline]
1357    pub fn wait(&mut self) -> impl Future<Output = Status> {
1358        self.0.wait()
1359    }
1360
1361    /// Cancels the work item and asynchronously waits until it is removed from the work queue.
1362    #[inline]
1363    pub fn cancel(&mut self) -> impl Future<Output = ()> {
1364        self.0.cancel()
1365    }
1366}