atsamd-hal 0.15.1

HAL and Peripheral access API for ATSAMD11, ATSAMD21, ATSAMD51, ATSAME51, ATSAME53 and ATSAME54 microcontrollers
Documentation
#![warn(missing_docs)]
//! # Public Key Cryptography Controller (PUKCC)
//!
//! This module provides both low and high level abstractions for dealing with
//! a PUKCC peripheral.
//!
//! PUKCC consists of a set of functions (called services) hidden within a
//! reserved region of memory. These functions usually make use of a separate
//! piece of RAM to operate called CryptoRAM.
//!
//! [`c_abi`] module contains raw structs and callable C-like function
//! definitions. [`Pukcc`] wraps this low-level access API and exposes it in a
//! safe manner.
//!
//! ## WARNING!
//! This module has not been evaluated for correctness nor suitability for any
//! use-case. Subtle implementation details may have catastrophic implications
//! for the security of your cryptosystem, and users are advised to engage a
//! cryptographer before making use of this module.
#![allow(clippy::just_underscores_and_digits)]
pub mod c_abi;
pub mod curves;

use core::iter::{once, repeat};

use crate::pac::MCLK;
use c_abi::{u2, u4, CryptoRamSlice, Service};
use curves::Curve;

use rand_core::{CryptoRng, RngCore};

/// This macro linearly copies provided iterable slices/arrays to CryptoRAM and
/// assigns slices to provided declared local variables from outer scope
macro_rules! copy_to_cryptoram {
        (
            $crypto_ram:expr,
            $(
                ($name:ident, $data:expr)
            ),+
        ) =>
        {
            {
            (&[])
                .iter()
                .cloned()
            $(
                .chain($data)
            )+
            .zip($crypto_ram.iter_mut())
            .for_each(|(data_iter, cr_iter)| *cr_iter = data_iter);

            let mut _offset = 0;
            $(
                let len = $data.size_hint().1.unwrap_or_else(|| panic!("provided iterator has no size hint"));
                $name = &$crypto_ram[_offset.._offset + len];
                _offset += len;
            )+
            }
        }
}

/// Struct representing a PUKCC peripheral.
///
/// It provides an access to cryptographic algorithms in a safe, high-level
/// manner
pub struct Pukcc {
    __: (),
}

impl Pukcc {
    /// Constructor.
    ///
    /// Waits for a CryptoRAM readiness, enables a synchronous PUKCC clock and
    /// performs a self test. In case a self test fails it returns an error
    pub fn enable(mclk: &mut MCLK) -> Result<Self, SelfTestFailure> {
        unsafe {
            c_abi::wait_for_crypto_ram_clear_process();
        }
        mclk.ahbmask.modify(|_, w| w.pukcc_().set_bit());
        let pukcc = Self { __: () };
        pukcc.self_test().map(|_| pukcc)
    }

    /// Self test service.
    ///
    /// Clears up a CryptoRAM and does the checksum. If a checksum and a version
    /// matches one defined in a HAL, it means that a self test passed
    /// successfully.
    ///
    /// While using a high-level API, user should not need to use this service
    /// explicitly.
    pub fn self_test(&self) -> Result<(), SelfTestFailure> {
        const PUKCL_VERSION: u4 = 0x04070100;
        const CHECKNUM_1: u4 = 0x6E70DDD2;
        const CHECKNUM_2: u4 = 0x25C8D64F;
        let mut pukcl_params = c_abi::PukclParams::default();
        unsafe {
            c_abi::SelfTest::call(&mut pukcl_params);
        }
        let header = pukcl_params.header;
        let service_params = unsafe { pukcl_params.params.SelfTest };
        match header.u2Status.into() {
            PukclReturnCode::Ok => {}
            _ => return Err(SelfTestFailure(service_params)),
        };
        if service_params.u4Version != PUKCL_VERSION {
            return Err(SelfTestFailure(service_params));
        }
        if service_params.u4CheckNum1 != CHECKNUM_1 {
            return Err(SelfTestFailure(service_params));
        }
        if service_params.u4CheckNum2 != CHECKNUM_2 {
            return Err(SelfTestFailure(service_params));
        }

        Ok(())
    }

    /// Service generating an ECDSA signature.
    ///
    /// GF(p) service. GF(2^n) variant is not implemented -- use low-level API.
    ///
    /// Input parameters:
    /// - `hash`: `&[u8]` of length [`Curve::SCALAR_LENGTH`]
    ///     - Hash of a message that is supposed to be signed.
    /// - `private_key`: `&[u8]` of length [`Curve::SCALAR_LENGTH`]
    ///     - Private key used for signing. Poorly generated `private_key` might
    ///       have negative security implications.
    /// - `k_buffer`: `&mut [u8]` of length [`Curve::SCALAR_LENGTH`]
    ///     - Mutable buffer that is being populated by an entropy source and
    ///       then used for signing.
    /// - `k_entropy_source`: `&mut (impl RngCore + CryptoRng)`
    ///     - Generic source of cryptographically secure randomness.
    ///
    /// Output parameters:
    /// - `signature`: `&mut [u8]` of length `2 * `[`Curve::MOD_LENGTH`]
    ///     - Mutable slice that signature will be copied to from CryptoRAM
    ///       after generation is finished. First [`Curve::MOD_LENGTH`] bytes
    ///       contain `R` part of a signature. Last [`Curve::MOD_LENGTH`] bytes
    ///       contain `S` part of a signature.
    ///
    /// Return value:
    /// - `Result::Ok`
    ///     - Signature was generated successfully
    /// - `Result::Err`
    ///     - Possible failure scenarios are encapsulated in a
    ///       [`EcdsaSignFailure`] enum type
    ///
    /// Note: Provided [`Curve`] needs to be sound. Otherwise, point
    /// multiplication can become reversible (lack of _trapdoor function_
    /// property) and an attacker might be able to reverse engineer a
    /// `private_key` from a `signature`.
    pub fn zp_ecdsa_sign_with_entropy<C: Curve>(
        &self,
        signature: &mut [u8],
        hash: &[u8],
        private_key: &[u8],
        k_buffer: &mut [u8],
        k_entropy_source: &mut (impl RngCore + CryptoRng),
    ) -> Result<(), EcdsaSignFailure> {
        k_entropy_source.fill_bytes(k_buffer);
        self.zp_ecdsa_sign::<C>(signature, hash, private_key, k_buffer)
    }

    /// Service generating an ECDSA signature.
    ///
    /// GF(p) service. GF(2^n) variant is not implemented -- use low-level API.
    ///
    /// Input parameters:
    /// - `hash`: `&[u8]` of length [`Curve::SCALAR_LENGTH`]
    ///     - Hash of a message that is supposed to be signed.
    /// - `private_key`: `&[u8]` of length [`Curve::SCALAR_LENGTH`]
    ///     - Private key used for signing. Poorly generated `private_key` might
    ///       have negative security implications.
    /// - `k`: `&[u8]` of length [`Curve::SCALAR_LENGTH`]
    ///     - A random number used for signature generation. It is heavily
    ///       encouraged to use cryptographically-secure random number
    ///       generators. One should never use the same `k` more than once.
    ///       Private key can be extracted from signatures generated with a
    ///       poorly randomized / the same `k` value.
    ///
    /// Exact same set of input parameters (hash, private_key and k) produces
    /// exactly the same signature.
    ///
    /// Output parameters:
    /// - `signature`: `&mut [u8]` of length `2 * `[`Curve::MOD_LENGTH`]
    ///     - Mutable slice that signature will be copied to from CryptoRAM
    ///       after generation is finished. First [`Curve::MOD_LENGTH`] bytes
    ///       contain `R` part of a signature. Last [`Curve::MOD_LENGTH`] bytes
    ///       contain `S` part of a signature.
    ///
    /// Return value:
    /// - `Result::Ok`
    ///     - Signature was generated successfully
    /// - `Result::Err`
    ///     - Possible failure scenarios are encapsulated in a
    ///       [`EcdsaSignFailure`] enum type
    ///
    /// Note: Provided [`Curve`] needs to be sound. Otherwise, point
    /// multiplication can become reversible (lack of _trapdoor function_
    /// property) and an attacker might be able to reverse engineer a
    /// `private_key` from a `signature`.
    ///
    /// # Safety
    ///
    /// `k` value must be cryptographically secure.
    pub unsafe fn zp_ecdsa_sign_with_raw_k<C: Curve>(
        &self,
        signature: &mut [u8],
        hash: &[u8],
        private_key: &[u8],
        k: &[u8],
    ) -> Result<(), EcdsaSignFailure> {
        self.zp_ecdsa_sign::<C>(signature, hash, private_key, k)
    }

    fn zp_ecdsa_sign<C: Curve>(
        &self,
        signature: &mut [u8],
        hash: &[u8],
        private_key: &[u8],
        k: &[u8],
    ) -> Result<(), EcdsaSignFailure> {
        C::verify_curve().map_err(EcdsaSignFailure::InvalidCurve)?;

        if signature.len() != (2 * C::MOD_LENGTH).into() {
            return Err(EcdsaSignFailure::WrongInputParameterLength {
                faulty_slice: "signature",
                expected_length: (2 * C::MOD_LENGTH).into(),
                actual_length: signature.len(),
            });
        }
        if hash.len() != (C::SCALAR_LENGTH).into() {
            return Err(EcdsaSignFailure::WrongInputParameterLength {
                faulty_slice: "hash",
                expected_length: (C::SCALAR_LENGTH).into(),
                actual_length: hash.len(),
            });
        }
        if private_key.len() != (C::SCALAR_LENGTH).into() {
            return Err(EcdsaSignFailure::WrongInputParameterLength {
                faulty_slice: "private_key",
                expected_length: (C::SCALAR_LENGTH).into(),
                actual_length: private_key.len(),
            });
        }
        if k.len() != (C::SCALAR_LENGTH).into() {
            return Err(EcdsaSignFailure::WrongInputParameterLength {
                faulty_slice: "k",
                expected_length: (C::SCALAR_LENGTH).into(),
                actual_length: k.len(),
            });
        }
        let (
            modulo_p,
            a_curve,
            base_point_a_x,
            base_point_a_y,
            base_point_a_z,
            order_point,
            cns,
            hash_cr,
            private_key_cr,
            k_cr,
            workspace,
            mut __,
        );
        let mut crypto_ram = unsafe { c_abi::CryptoRam::new() };
        // 32-byte padding with zeroes on a MSB side of every parameter is required by
        // PUKCC algorithms. Little endianness requires padding *after* a parameter
        // as MSB is placed on high addresses.

        // 32-byte zero padding for curve parameters should be included in original
        // slices.
        copy_to_cryptoram! {
            crypto_ram,
            (modulo_p, C::MODULO_P.iter().cloned().rev()),
            (a_curve, C::A_CURVE.iter().cloned().rev()),
            (base_point_a_x, C::BASE_POINT_A_X.iter().cloned().rev()),
            (base_point_a_y, C::BASE_POINT_A_Y.iter().cloned().rev()),
            (base_point_a_z, C::BASE_POINT_A_Z.iter().cloned().rev()),
            (order_point, C::ORDER_POINT.iter().cloned().rev()),
            (cns, C::CNS.iter().cloned().rev()),
            (hash_cr, hash.iter().cloned().rev()),
            (__, repeat(0).take(4)),
            (private_key_cr, private_key.iter().cloned().rev()),
            (__, repeat(0).take(4)),
            (k_cr, k.iter().cloned().rev()),
            (__, repeat(0).take(4)),
            // Workspace is just marked with a zero length iterator just to get its address.
            // As it is placed at the end, idea is that algorithm will use whatever amount
            // of memory it needs
            (workspace, 0..0)
        };
        let mut pukcl_params = c_abi::PukclParams::default();
        unsafe {
            let mut service_params = &mut pukcl_params.params.ZpEcDsaGenerateFast;
            service_params.nu1ModBase = modulo_p.pukcc_base();
            service_params.nu1CnsBase = cns.pukcc_base();
            service_params.u2ModLength = C::MOD_LENGTH as u16;
            service_params.nu1ScalarNumber = k_cr.pukcc_base();
            service_params.nu1OrderPointBase = order_point.pukcc_base();
            service_params.nu1PrivateKey = private_key_cr.pukcc_base();
            service_params.nu1HashBase = hash_cr.pukcc_base();
            service_params.u2ScalarLength = C::SCALAR_LENGTH;
            service_params.nu1PointABase = base_point_a_x.pukcc_base();
            service_params.nu1ABase = a_curve.pukcc_base();
            service_params.nu1Workspace = workspace.pukcc_base();
        }

        unsafe { c_abi::ZpEcDsaGenerateFast::call(&mut pukcl_params) };

        match pukcl_params.header.u2Status.into() {
            PukclReturnCode::Ok => {}
            error_code => return Err(EcdsaSignFailure::ServiceFailure(error_code)),
        };

        // Generated signature R part is written to base point X coordinate memory.
        // Generated signature S part is written to base point Y coordinate memory.
        // Base point Z coordinate should be zero.

        if !base_point_a_z.iter().all(|&el| el == 0) {
            return Err(EcdsaSignFailure::BasePointZCoordinateIsNotZero);
        }

        // Copying signature back from the CryptoRAM while ignoring irrelevant padding.
        signature
            .iter_mut()
            .zip(
                base_point_a_x
                    .iter()
                    .rev()
                    .skip(4)
                    .chain(base_point_a_y.iter().rev().skip(4)),
            )
            .for_each(|(target_iter, source_iter)| *target_iter = *source_iter);

        Ok(())
    }

    /// Service verifying an ECDSA signature.
    ///
    /// GF(p) service. GF(2^n) variant is not implemented -- use low-level API.
    ///
    /// Input parameters:
    /// - `signature`: `&[u8]` of length `2 * `[`Curve::SCALAR_LENGTH`]
    ///     - Signature that is being verified
    /// - `hash`: `&[u8]` of length [`Curve::SCALAR_LENGTH`]
    ///     - Hash of a message that is signed.
    /// - `public_key`: `&[u8]` of length [`Curve::SCALAR_LENGTH`]
    ///     - Public key used for a signature verification.
    ///
    /// Return value:
    /// - `Result::Ok`
    ///     - Signature is valid against chosen `hash` and `public_key`
    /// - `Result::Err`
    ///     - Possible failure scenarios are encapsulated in a
    ///       [`EcdsaSignatureVerificationFailure`] enum type
    ///
    /// In case of an invalid signature the returned error type will be
    /// [`EcdsaSignatureVerificationFailure::ServiceFailure`]`(`
    /// [`Warning`][`PukclReturnCode::Warning`]`(`
    /// [`WrongSignature`][`PukclReturnCodeWarning::WrongSignature`]`))`
    pub fn zp_ecdsa_verify_signature<C: Curve>(
        &self,
        signature: &[u8],
        hash: &[u8],
        public_key: &[u8],
    ) -> Result<(), EcdsaSignatureVerificationFailure> {
        C::verify_curve().map_err(EcdsaSignatureVerificationFailure::InvalidCurve)?;

        let (
            modulo_p,
            a_curve,
            base_point_a_x,
            order_point,
            cns,
            signature_cr,
            hash_cr,
            public_key_cr,
            workspace,
            mut __,
        );
        if signature.len() != (2 * C::SCALAR_LENGTH).into() {
            return Err(
                EcdsaSignatureVerificationFailure::WrongInputParameterLength {
                    faulty_slice: "signature",
                    expected_length: (2 * C::SCALAR_LENGTH).into(),
                    actual_length: signature.len(),
                },
            );
        }
        if hash.len() != (C::SCALAR_LENGTH).into() {
            return Err(
                EcdsaSignatureVerificationFailure::WrongInputParameterLength {
                    faulty_slice: "hash",
                    expected_length: (C::SCALAR_LENGTH).into(),
                    actual_length: hash.len(),
                },
            );
        }
        if public_key.len() != (2 * C::MOD_LENGTH).into() {
            return Err(
                EcdsaSignatureVerificationFailure::WrongInputParameterLength {
                    faulty_slice: "public_key",
                    expected_length: (2 * C::MOD_LENGTH).into(),
                    actual_length: public_key.len(),
                },
            );
        }
        let mut crypto_ram = unsafe { c_abi::CryptoRam::new() };
        // 32-byte padding with zeroes on a MSB side of every parameter is required by
        // PUKCC algorithms. Little endianness requires padding *after* a parameter
        // as MSB is placed on high addresses.

        // 32-byte zero padding for curve parameters should be included in original
        // slices.
        copy_to_cryptoram! {
            crypto_ram,
            (modulo_p, C::MODULO_P.iter().cloned().rev()),
            (a_curve, C::A_CURVE.iter().cloned().rev()),
            (base_point_a_x, C::BASE_POINT_A_X.iter().cloned().rev()),
            (__, C::BASE_POINT_A_Y.iter().cloned().rev()),
            (__, C::BASE_POINT_A_Z.iter().cloned().rev()),
            (order_point, C::ORDER_POINT.iter().cloned().rev()),
            (cns, C::CNS.iter().cloned().rev()),
            // Signature has to be split into two parts + padding must be added
            // Signature layout:
            //   [ R: (little endian) ][ 0_u32 ]..
            (signature_cr, signature.iter().cloned().take(C::SCALAR_LENGTH.into()).rev()),
            (__, repeat(0).take(4)),
            // ..[ S: (little endian) ][ 0_u32 ]
            (__, signature.iter().cloned().skip(C::SCALAR_LENGTH.into()).take(C::SCALAR_LENGTH.into()).rev()),
            (__, repeat(0).take(4)),
            (hash_cr, hash.iter().cloned().rev()),
            (__, repeat(0).take(4)),
            // Public key has to be represented as a point + padding must be added
            // Public key layout:
            //   [ X coordinate: (little endian) ][ 0_u32 ]..
            (public_key_cr, public_key.iter().cloned().take(C::MOD_LENGTH.into()).rev()),
            (__, repeat(0).take(4)),
            // ..[ Y coordinate: (little endian) ][ 0_u32 ]
            (__, public_key.iter().cloned().skip(C::MOD_LENGTH.into()).take(C::MOD_LENGTH.into()).rev()),
            (__, repeat(0).take(4)),
            // ..[ Z coordinate: (little endian) ][ 0_u32 ] == 1
            (__, once(1).chain(repeat(0).take((C::MOD_LENGTH - 1).into()))),
            (__, repeat(0).take(4)),
            // Workspace is just marked with a zero length iterator just to get its address.
            // As it is placed at the end, idea is that algorithm will use whatever amount
            // of memory it needs
            (workspace, 0..0)
        };
        let mut pukcl_params = c_abi::PukclParams::default();
        unsafe {
            let mut service_params = &mut pukcl_params.params.ZpEcDsaVerifyFast;
            service_params.nu1ModBase = modulo_p.pukcc_base();
            service_params.nu1CnsBase = cns.pukcc_base();
            service_params.u2ModLength = C::MOD_LENGTH;
            service_params.nu1OrderPointBase = order_point.pukcc_base();
            service_params.nu1PointSignature = signature_cr.pukcc_base();
            service_params.nu1HashBase = hash_cr.pukcc_base();
            service_params.u2ScalarLength = C::SCALAR_LENGTH;
            service_params.nu1PointABase = base_point_a_x.pukcc_base();
            service_params.nu1PointPublicKeyGen = public_key_cr.pukcc_base();
            service_params.nu1ABase = a_curve.pukcc_base();
            service_params.nu1Workspace = workspace.pukcc_base();
        }

        unsafe { c_abi::ZpEcDsaVerifyFast::call(&mut pukcl_params) };

        match pukcl_params.header.u2Status.into() {
            PukclReturnCode::Ok => Ok(()),
            error_code => Err(EcdsaSignatureVerificationFailure::ServiceFailure(
                error_code,
            )),
        }
    }

    /// Service performing a modular exponentiation.
    ///
    /// ```text
    /// result = pow(input, exponent) % modulus
    /// ```
    ///
    /// Input parameters:
    /// - `input`: `&[u8]`
    ///     - Requirements:
    ///         - `len(input) <= len(modulus)`
    ///     - Message, hash, any slice of data that will undergo modular
    ///       exponentiation
    /// - `exponent`: `&[u8]`
    ///     - Requirements:
    ///         - `len(exponent) <= len(modulus)`
    /// - `modulus`: `&[u8]`
    ///     - Requirements:
    ///         - `len(modulus) % 4`
    ///         - `12 <= len(modulus) < ?`
    ///     - Note: Maximum size depends on few factors like CryptoRAM and
    ///       workspace window size. Consult the table with data layout down
    ///       below.
    /// - `mode`: [`ExpModMode`]
    ///     - Mode of operation: use regular or fast variant of the underlying
    ///       algorithm
    ///     - This parameter does not influence the end result of a computation
    /// - `window_size`: [`ExpModWindowSize`]
    ///     - Enum describing 4 predefined workspace sizes (from smallest to
    ///       biggest) in CryptoRAM.
    ///     - Bigger the workspace size - faster the algorithm can operate -
    ///       greater limitations on input parameters are put (as they occupy
    ///       CryptoRAM address space as well). Consult the table with data
    ///       layout down below.
    ///     - This parameter does not influence the end result of a computation
    /// - `buffer`: `&'a mut [u8]`
    ///     - Requirements:
    ///         - `len(buffer) >= len(modulus) + 5`
    ///     - Buffer used internally for CNS calculation. Piece of it is used
    ///       also for a return value.
    ///
    /// Return value:
    /// - `Result::Ok(&'a [u8])`
    ///     - Length: `len(modulus)`
    ///     - A result of modular exponentiation
    /// - `Result::Err`
    ///     - Possible failure scenarios are encapsulated in a [`ExpModFailure`]
    ///       enum type
    ///
    /// Failing to meet the requirements for any input parameter will end up
    /// with an error being returned.
    ///
    /// CryptoRAM is `4KiB` (`0x1000` bytes) in size. Data layout in CryptoRAM
    /// looks as follows and its size cannot go over the threshold of `4KiB`.
    ///
    /// ```text
    /// - modulus: len(modulus) + 4
    /// - cns (reduction constant): len(modulus) + 8
    /// - output/input (after/before calculation): len(modulus) + 16
    /// - exponent: len(exponent) + 4 (+ padding to be % 4)
    /// - workspace: (depending on `window_size`)
    ///     - ExpModWindowSize::One => 3 * (modulus.len() + 4) + 8
    ///     - ExpModWindowSize::Two => 4 * (modulus.len() + 4) + 8
    ///     - ExpModWindowSize::Three => 6 * (modulus.len() + 4) + 8
    ///     - ExpModWindowSize::Four => 10 * (modulus.len() + 4) + 8
    /// ```
    ///
    /// # RSA
    ///
    /// This function can be used to perform RSA related computation like
    /// encryption, decryption, signature generation and validation.
    /// - To **encrypt** `input` value, split _public key_ into _public_
    ///   `exponent` and `modulus` and pass them into the function. Return value
    ///   is going to be a cipher of an `input`.
    /// - To **decrypt** `input` value, split _private key_ into _private_
    ///   `exponent` and `modulus` and pass them into the function. Return value
    ///   is going to be a decrypted `input`.
    /// - To **generate a signature**, pass the _hash_ of a message being signed
    ///   as an `input` into the function and use a _private key_. Return value
    ///   is going to be a signature (encrypted _hash_).
    /// - To **validate a signature**, pass it as an `input` into the function
    ///   and use the _public key_. Decrypted signature is an expected hash of a
    ///   message. Calculate the hash of your message and compare it with an
    ///   expected value. If they are the same, validation can be considered
    ///   successful.
    ///
    /// All RSA variants up to **RSA4096** (included) will fit into CryptoRAM
    /// and therefore are supported.
    pub fn modular_exponentiation<'a>(
        &self,
        input: &[u8],
        exponent: &[u8],
        modulus: &[u8],
        mode: ExpModMode,
        window_size: ExpModWindowSize,
        buffer: &'a mut [u8],
    ) -> Result<&'a [u8], ExpModFailure> {
        const PUKCL_EXPMOD_EXPINPUKCCRAM: u16 = 0x02;

        // Modulus validation
        if modulus.len() % 4 != 0 {
            return Err(ExpModFailure::WrongInputParameterAlignment {
                faulty_slice: "modulus",
            });
        }
        // Modulus size must be at least 12 bytes (43.3.5.2.3 of DS60001507F datasheet)
        const MINIMUM_MODULUS_LEN: usize = 12;
        if modulus.len() < MINIMUM_MODULUS_LEN {
            return Err(ExpModFailure::WrongInputParameterLength {
                faulty_slice: "modulus",
                actual_length: modulus.len(),
                expected_length: ExpectedLengthError::AtLeast(MINIMUM_MODULUS_LEN),
            });
        }
        // Input validation
        // Note: Only length is checked but in theory value itself being larger than
        // modulus is probably wrong as well
        if input.len() > modulus.len() {
            return Err(ExpModFailure::WrongInputParameterLength {
                faulty_slice: "input",
                actual_length: input.len(),
                expected_length: ExpectedLengthError::AtMost(modulus.len()),
            });
        }
        // Exponent validation
        // Note: Only length is checked but in theory value itself being larger than
        // modulus is probably wrong as well
        if exponent.len() > modulus.len() {
            return Err(ExpModFailure::WrongInputParameterLength {
                faulty_slice: "exponent",
                actual_length: exponent.len(),
                expected_length: ExpectedLengthError::AtMost(modulus.len()),
            });
        }

        let (modulus_cr, cns_cr, output, workspace, exponent_cr, mut __);

        let cns = self.zp_calculate_cns(buffer, modulus)?;
        let padding_for_cns = padding_for_len(cns.len());
        // Sanity check in case someone changes `zp_calculate_cns` implementation
        assert!(cns.len() + padding_for_cns == modulus.len() + 8);
        let padding_for_exponent = padding_for_len(exponent.len());

        let mut crypto_ram = unsafe { c_abi::CryptoRam::new() };
        // 32-byte padding with zeroes on a MSB side of every parameter is required by
        // PUKCC algorithms (unless said otherwise). Little endianness requires padding
        // *after* a parameter as MSB is placed on high addresses.
        copy_to_cryptoram! {
            crypto_ram,
            (modulus_cr, modulus.iter().cloned().rev()),
            (__, repeat(0).take(4)),
            (cns_cr, cns.iter().cloned().rev()),
            (__, repeat(0).take(padding_for_cns)),
            // 1. `input` is replaced with an outcome of the computation
            // 2. `input` is padded to match `len(modulus)`
            (output, input.iter().cloned().rev().chain(repeat(0).take(modulus.len() - input.len()))),
            // `input` area is used for computation and requires additional 4 0_u32 on MSB side
            (__, repeat(0).take(16)),
            // Exponent is required to have 0_u32 on LSB side
            // `exponent_cr` is only used to get a pointer during `pukcl_params` initialization
            (exponent_cr, repeat(0).take(4)),
            (__, exponent.iter().cloned().rev()),
            (__, repeat(0).take(padding_for_exponent)),
            // Workspace is just marked with a zero length slice just to get its address. As
            // it is placed at the end, idea is that algorithm will use whatever amount of
            // memory it needs
            (workspace, 0..0)
        };
        // Table 43-52; 43.3.5.2.5 in DS60001507F
        let workspace_len: usize = match window_size {
            ExpModWindowSize::One => 3 * (modulus.len() + 4) + 8,
            ExpModWindowSize::Two => 4 * (modulus.len() + 4) + 8,
            ExpModWindowSize::Three => 6 * (modulus.len() + 4) + 8,
            ExpModWindowSize::Four => 10 * (modulus.len() + 4) + 8,
        };
        let workspace_end_ptr = workspace.as_ptr().wrapping_add(workspace_len);
        let crypto_ram_end_ptr = crypto_ram.as_ptr_range().end;
        if workspace_end_ptr > crypto_ram_end_ptr {
            return Err(ExpModFailure::RunOutOfCryptoRam {
                workspace_end_ptr,
                crypto_ram_end_ptr,
            });
        }
        let mut pukcl_params = c_abi::PukclParams::default();
        unsafe {
            // Note: `exponent` outside of Crypto RAM is not supported
            pukcl_params.header.u2Option = PUKCL_EXPMOD_EXPINPUKCCRAM
                | window_size.get_windows_size_mask()
                | mode.get_mode_mask();
            let mut service_params = &mut pukcl_params.params.ExpMod;
            service_params.nu1XBase = output.pukcc_base();
            service_params.nu1ModBase = modulus_cr.pukcc_base();
            service_params.nu1CnsBase = cns_cr.pukcc_base();
            service_params.nu1PrecompBase = workspace.pukcc_base();
            service_params.pfu1ExpBase = exponent_cr.as_ptr() as _;
            service_params.u2ModLength = modulus.len() as _;
            service_params.u2ExpLength = (exponent.len() + padding_for_exponent) as _;
            service_params.u1Blinding = 0;
        }

        unsafe { c_abi::ExpMod::call(&mut pukcl_params) };
        match pukcl_params.header.u2Status.into() {
            PukclReturnCode::Ok => {}
            error_code => return Err(ExpModFailure::ServiceFailure(error_code)),
        }

        buffer
            .iter_mut()
            .zip(output.iter().rev())
            .for_each(|(target_iter, source_iter)| *target_iter = *source_iter);

        Ok(&buffer[..modulus.len()])
    }

    /// Service producing a reduction constant value
    fn zp_calculate_cns<'a>(
        &self,
        buffer: &'a mut [u8],
        modulus: &[u8],
    ) -> Result<&'a [u8], CalculateCnsFailure> {
        const PUKCL_REDMOD_SETUP: u16 = 0x0100;
        if modulus.len() % 4 != 0 {
            return Err(CalculateCnsFailure::WrongInputParameterAlignment {
                faulty_slice: "modulus",
            });
        }

        // Even though documentation says that CNS occupies len(modulus) + 12 of space,
        // it is only needed for computation, 7 MSB bytes are zeroes. This distinction
        // between lengths allows to skip these 7 MSB zero bytes.
        let cns_length = modulus.len() + 12;
        let actual_cns_length = modulus.len() + 5;

        if buffer.len() < actual_cns_length {
            return Err(CalculateCnsFailure::WrongInputParameterLength {
                faulty_slice: "buffer",
                actual_length: buffer.len(),
                expected_length: ExpectedLengthError::AtLeast(actual_cns_length),
            });
        }
        let (modulus_cr, cns_cr, workspace_r, workspace_x, mut __);
        let mut crypto_ram = unsafe { c_abi::CryptoRam::new() };
        copy_to_cryptoram! {
            crypto_ram,
            (modulus_cr, modulus.iter().cloned().rev()),
            (__, repeat(0).take(4)),
            (cns_cr, repeat(0).take(actual_cns_length)),
            (__, repeat(0).take(cns_length - actual_cns_length)),
             // GF(p) -> 64 bytes
            (workspace_r, repeat(0).take(64)),
            (workspace_x, 0..0)
        };
        let mut pukcl_params = c_abi::PukclParams::default();
        unsafe {
            // Flag that switches behaviour of `RedMod` service into CNS generator
            pukcl_params.header.u2Option = PUKCL_REDMOD_SETUP;
            let mut service_params = &mut pukcl_params.params.RedMod;
            service_params.nu1ModBase = modulus_cr.pukcc_base();
            service_params.nu1CnsBase = cns_cr.pukcc_base();
            service_params.u2ModLength = modulus.len() as _;
            service_params.nu1RBase = workspace_r.pukcc_base();
            service_params.nu1XBase = workspace_x.pukcc_base();
        }
        unsafe { c_abi::RedMod::call(&mut pukcl_params) };
        match pukcl_params.header.u2Status.into() {
            PukclReturnCode::Ok => {}
            error_code => return Err(CalculateCnsFailure::ServiceFailure(error_code)),
        }

        buffer
            .iter_mut()
            .zip(cns_cr.iter().rev())
            .for_each(|(target_iter, source_iter)| *target_iter = *source_iter);
        Ok(&buffer[..actual_cns_length])
    }
}

/// An error type representing failure modes a [`Pukcc::self_test`] service
#[derive(Debug)]
pub struct SelfTestFailure(c_abi::SelfTest);

/// An error type representing failure modes for a
/// [`Pukcc::zp_ecdsa_sign_with_entropy`] and
/// [`Pukcc::zp_ecdsa_sign_with_raw_k`] service
#[allow(missing_docs)]
#[derive(Debug)]
pub enum EcdsaSignFailure {
    WrongInputParameterLength {
        faulty_slice: &'static str,
        expected_length: usize,
        actual_length: usize,
    },
    InvalidCurve(curves::CurveVerificationFailure),
    BasePointZCoordinateIsNotZero,
    ServiceFailure(PukclReturnCode),
}

/// An error type representing failure modes for a
/// [`Pukcc::zp_ecdsa_verify_signature`] service
#[allow(missing_docs)]
#[derive(Debug)]
pub enum EcdsaSignatureVerificationFailure {
    WrongInputParameterLength {
        faulty_slice: &'static str,
        expected_length: usize,
        actual_length: usize,
    },
    InvalidCurve(curves::CurveVerificationFailure),
    ServiceFailure(PukclReturnCode),
}

/// An error type specifying an expected length of a slice in question
#[allow(missing_docs)]
#[derive(Debug)]
pub enum ExpectedLengthError {
    AtMost(usize),
    AtLeast(usize),
    Exactly(usize),
}

/// An enum describing available modes of operation of
/// `Pukcc::modular_exponentiation` algoritm
#[allow(missing_docs)]
#[derive(Debug)]
pub enum ExpModMode {
    Regular,
    Fast,
}

impl ExpModMode {
    /// Function mapping the enum variant with a low level mask value needed in
    /// [`c_abi::PukclHeader::u2Option`] for [`c_abi::ExpMod`] service
    pub fn get_mode_mask(&self) -> u2 {
        use ExpModMode::*;
        match self {
            Regular => 0x01,
            Fast => 0x04,
        }
    }
}

/// An enum describing allowed, predefined window sizes for a calculation
/// workspace in CryptoRAM for [`Pukcc::modular_exponentiation`] algorithm
#[allow(missing_docs)]
#[derive(Debug)]
pub enum ExpModWindowSize {
    /// 3 * (len(modulus) + 4) + 8 bytes allowed to be used as a workspace
    One,
    /// 4 * (len(modulus) + 4) + 8 bytes allowed to be used as a workspace
    Two,
    /// 6 * (len(modulus) + 4) + 8 bytes allowed to be used as a workspace
    Three,
    /// 10 * (len(modulus) + 4) + 8 bytes allowed to be used as a workspace
    Four,
}

impl ExpModWindowSize {
    /// Function mapping the enum variant with a low level mask value needed in
    /// [`c_abi::PukclHeader::u2Option`] for [`c_abi::ExpMod`] service
    pub fn get_windows_size_mask(&self) -> u2 {
        use ExpModWindowSize::*;
        match self {
            One => 0x00,
            Two => 0x08,
            Three => 0x10,
            Four => 0x18,
        }
    }
}

/// An error type representing failure modes for a
/// [`Pukcc::modular_exponentiation`] service
#[allow(missing_docs)]
#[derive(Debug)]
pub enum ExpModFailure {
    WrongInputParameterLength {
        faulty_slice: &'static str,
        expected_length: ExpectedLengthError,
        actual_length: usize,
    },
    /// Should be 4-aligned
    WrongInputParameterAlignment {
        faulty_slice: &'static str,
    },
    RunOutOfCryptoRam {
        workspace_end_ptr: *const u8,
        crypto_ram_end_ptr: *const u8,
    },
    CalculateCnsFailure(CalculateCnsFailure),
    ServiceFailure(PukclReturnCode),
}

/// An error type representing failure modes for a
/// `Pukcc::zp_calculate_cns` service
#[allow(missing_docs)]
#[derive(Debug)]
pub enum CalculateCnsFailure {
    WrongInputParameterLength {
        faulty_slice: &'static str,
        expected_length: ExpectedLengthError,
        actual_length: usize,
    },
    /// Should be 4-aligned
    WrongInputParameterAlignment {
        faulty_slice: &'static str,
    },
    ServiceFailure(PukclReturnCode),
}

impl From<CalculateCnsFailure> for ExpModFailure {
    fn from(f: CalculateCnsFailure) -> Self {
        ExpModFailure::CalculateCnsFailure(f)
    }
}

// PukclReturnCode <-> c_abi::PukclReturnCode
impl core::convert::From<c_abi::PukclReturnCode> for PukclReturnCode {
    fn from(v: c_abi::PukclReturnCode) -> Self {
        use PukclReturnCode::*;
        match v.0 {
            0x0000 => Ok,
            0xC001 => Severe(PukclReturnCodeSevere::ComputationNotStarted),
            0xC002 => Severe(PukclReturnCodeSevere::UnknownService),
            0xC003 => Severe(PukclReturnCodeSevere::UnexploitableOptions),
            0xC004 => Severe(PukclReturnCodeSevere::HardwareIssue),
            0xC005 => Severe(PukclReturnCodeSevere::WrongHardware),
            0xC006 => Severe(PukclReturnCodeSevere::LibraryMalformed),
            0xC007 => Severe(PukclReturnCodeSevere::Error),
            0xC008 => Severe(PukclReturnCodeSevere::UnknownSubservice),
            0xC010 => Severe(PukclReturnCodeSevere::OverlapNotAllowed),
            0xC011 => Severe(PukclReturnCodeSevere::ParamNotInPukccram),
            0xC012 => Severe(PukclReturnCodeSevere::ParamNotInRam),
            0xC013 => Severe(PukclReturnCodeSevere::ParamNotInCpuram),
            0xC014 => Severe(PukclReturnCodeSevere::ParamWrongLength),
            0xC015 => Severe(PukclReturnCodeSevere::ParamBadAlignment),
            0xC016 => Severe(PukclReturnCodeSevere::ParamXBiggerThanY),
            0xC017 => Severe(PukclReturnCodeSevere::ParamLengthTooSmall),
            0xC101 => Severe(PukclReturnCodeSevere::DivisionByZero),
            0xC102 => Severe(PukclReturnCodeSevere::MalformedModulus),
            0xC103 => Severe(PukclReturnCodeSevere::FaultDetected),
            0xC104 => Severe(PukclReturnCodeSevere::MalformedKey),
            0xC105 => Severe(PukclReturnCodeSevere::AprioriOk),
            0xC106 => Severe(PukclReturnCodeSevere::WrongService),
            0x8001 => Warning(PukclReturnCodeWarning::PointAtInfinity),
            0x8002 => Warning(PukclReturnCodeWarning::WrongSignature),
            0x8003 => Warning(PukclReturnCodeWarning::WrongSelectnumber),
            0x8004 => Warning(PukclReturnCodeWarning::PointIsNotOnCurve),
            0x4001 => Info(PukclReturnCodeInfo::NumberIsNotPrime),
            0x4002 => Info(PukclReturnCodeInfo::NumberIsPrime),
            code => Unknown { code },
        }
    }
}

impl core::convert::From<PukclReturnCode> for c_abi::PukclReturnCode {
    fn from(v: PukclReturnCode) -> Self {
        use PukclReturnCode::*;
        Self(match v {
            Ok => 0x0000,
            Severe(PukclReturnCodeSevere::ComputationNotStarted) => 0xC001,
            Severe(PukclReturnCodeSevere::UnknownService) => 0xC002,
            Severe(PukclReturnCodeSevere::UnexploitableOptions) => 0xC003,
            Severe(PukclReturnCodeSevere::HardwareIssue) => 0xC004,
            Severe(PukclReturnCodeSevere::WrongHardware) => 0xC005,
            Severe(PukclReturnCodeSevere::LibraryMalformed) => 0xC006,
            Severe(PukclReturnCodeSevere::Error) => 0xC007,
            Severe(PukclReturnCodeSevere::UnknownSubservice) => 0xC008,
            Severe(PukclReturnCodeSevere::OverlapNotAllowed) => 0xC010,
            Severe(PukclReturnCodeSevere::ParamNotInPukccram) => 0xC011,
            Severe(PukclReturnCodeSevere::ParamNotInRam) => 0xC012,
            Severe(PukclReturnCodeSevere::ParamNotInCpuram) => 0xC013,
            Severe(PukclReturnCodeSevere::ParamWrongLength) => 0xC014,
            Severe(PukclReturnCodeSevere::ParamBadAlignment) => 0xC015,
            Severe(PukclReturnCodeSevere::ParamXBiggerThanY) => 0xC016,
            Severe(PukclReturnCodeSevere::ParamLengthTooSmall) => 0xC017,
            Severe(PukclReturnCodeSevere::DivisionByZero) => 0xC101,
            Severe(PukclReturnCodeSevere::MalformedModulus) => 0xC102,
            Severe(PukclReturnCodeSevere::FaultDetected) => 0xC103,
            Severe(PukclReturnCodeSevere::MalformedKey) => 0xC104,
            Severe(PukclReturnCodeSevere::AprioriOk) => 0xC105,
            Severe(PukclReturnCodeSevere::WrongService) => 0xC106,
            Warning(PukclReturnCodeWarning::PointAtInfinity) => 0x8001,
            Warning(PukclReturnCodeWarning::WrongSignature) => 0x8002,
            Warning(PukclReturnCodeWarning::WrongSelectnumber) => 0x8003,
            Warning(PukclReturnCodeWarning::PointIsNotOnCurve) => 0x8004,
            Info(PukclReturnCodeInfo::NumberIsNotPrime) => 0x4001,
            Info(PukclReturnCodeInfo::NumberIsPrime) => 0x4002,
            Unknown { code } => code,
        })
    }
}

/// An enum type that is a human readable representation of a low-level
/// [`c_abi::PukclReturnCode`] type. Useful when used together with a [`Debug`]
/// traits and formatters.
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum PukclReturnCode {
    Ok,
    Info(PukclReturnCodeInfo),
    Warning(PukclReturnCodeWarning),
    Severe(PukclReturnCodeSevere),
    Unknown { code: u16 },
}

/// [`PukclReturnCode`] nested enum subtype
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum PukclReturnCodeInfo {
    NumberIsNotPrime,
    NumberIsPrime,
}

/// [`PukclReturnCode`] nested enum subtype
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum PukclReturnCodeWarning {
    PointAtInfinity,
    WrongSignature,
    WrongSelectnumber,
    PointIsNotOnCurve,
}

/// [`PukclReturnCode`] nested enum subtype
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug)]
pub enum PukclReturnCodeSevere {
    ComputationNotStarted,
    UnknownService,
    UnexploitableOptions,
    HardwareIssue,
    WrongHardware,
    LibraryMalformed,
    Error,
    UnknownSubservice,
    OverlapNotAllowed,
    ParamNotInPukccram,
    ParamNotInRam,
    ParamNotInCpuram,
    ParamWrongLength,
    ParamBadAlignment,
    ParamXBiggerThanY,
    ParamLengthTooSmall,
    DivisionByZero,
    MalformedModulus,
    FaultDetected,
    MalformedKey,
    AprioriOk,
    WrongService,
}

fn padding_for_len(len: usize) -> usize {
    const ALIGNMENT: usize = 4;
    if len % ALIGNMENT != 0 {
        ALIGNMENT - (len % ALIGNMENT)
    } else {
        0
    }
}