From 8e5eca77338d7bb784510805a3249e9f1c6cd629 Mon Sep 17 00:00:00 2001 From: Balamurali Pandranki Date: Tue, 14 Jul 2026 13:09:12 +0300 Subject: [PATCH 1/7] feat: Add Brainpool and NIST elliptic curve support for PACE (issue #11) --- Cargo.lock | 28 + Cargo.toml | 5 +- crates/dmrtd/Cargo.toml | 4 + crates/dmrtd/src/proto/domain_parameter.rs | 30 +- crates/dmrtd/src/proto/ecdh_pace.rs | 880 +++++++++++++-------- crates/dmrtd/src/proto/pace_session.rs | 43 +- 6 files changed, 644 insertions(+), 346 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e36b63c..5a69826 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,6 +345,30 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "bp256" +version = "0.14.0-rc.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5c715b9f158d362cc00afc7a3ea6c33da4b018293ff3227402886325c22f947" +dependencies = [ + "ecdsa 0.17.0", + "elliptic-curve 0.14.1", + "primefield", + "primeorder 0.14.0", +] + +[[package]] +name = "bp384" +version = "0.14.0-rc.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad8bed63122ad85f81781099e541e81985e4ff6814a6c6e6bb5ddac481f443f" +dependencies = [ + "ecdsa 0.17.0", + "elliptic-curve 0.14.1", + "primefield", + "primeorder 0.14.0", +] + [[package]] name = "built" version = "0.8.1" @@ -749,6 +773,8 @@ dependencies = [ "aes", "asn1", "base64", + "bp256", + "bp384", "cbc", "chrono", "cipher", @@ -762,6 +788,8 @@ dependencies = [ "num-traits", "once_cell", "p256 0.14.0", + "p384", + "p521", "rand 0.10.1", "sha1 0.11.0", "sha2 0.11.0", diff --git a/Cargo.toml b/Cargo.toml index a5a5007..56c6a02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,5 +70,8 @@ num-bigint = "0.4" num-traits = "0.2" rand = "0.10" p256 = { version = "0.14", features = ["arithmetic", "ecdh", "ecdsa"] } -p384 = { version = "0.13", features = ["ecdsa"] } +p384 = { version = "0.13", features = ["arithmetic", "ecdh", "ecdsa"] } +p521 = { version = "0.13", features = ["arithmetic", "ecdh", "ecdsa"] } +bp256 = { version = "0.14.0-rc.15", features = ["arithmetic"] } +bp384 = { version = "0.14.0-rc.15", features = ["arithmetic"] } elliptic-curve = { version = "0.14", features = ["arithmetic"] } diff --git a/crates/dmrtd/Cargo.toml b/crates/dmrtd/Cargo.toml index 3461897..4d7b1f7 100644 --- a/crates/dmrtd/Cargo.toml +++ b/crates/dmrtd/Cargo.toml @@ -48,4 +48,8 @@ num-bigint.workspace = true num-traits.workspace = true rand.workspace = true p256.workspace = true +p384.workspace = true +p521.workspace = true +bp256.workspace = true +bp384.workspace = true elliptic-curve.workspace = true diff --git a/crates/dmrtd/src/proto/domain_parameter.rs b/crates/dmrtd/src/proto/domain_parameter.rs index 0022f70..312560d 100644 --- a/crates/dmrtd/src/proto/domain_parameter.rs +++ b/crates/dmrtd/src/proto/domain_parameter.rs @@ -121,7 +121,7 @@ pub static ICAO_DOMAIN_PARAMETERS: Lazy> = Lazy::n name: "BrainpoolP256r1", size: 256, kind: DomainParameterType::Ecp, - is_supported: false, + is_supported: true, }, DomainParameter { id: 14, @@ -135,14 +135,14 @@ pub static ICAO_DOMAIN_PARAMETERS: Lazy> = Lazy::n name: "NIST P-384 (secp384r1)", size: 384, kind: DomainParameterType::Ecp, - is_supported: false, + is_supported: true, }, DomainParameter { id: 16, name: "BrainpoolP384r1", size: 384, kind: DomainParameterType::Ecp, - is_supported: false, + is_supported: true, }, DomainParameter { id: 17, @@ -156,7 +156,21 @@ pub static ICAO_DOMAIN_PARAMETERS: Lazy> = Lazy::n name: "NIST P-521 (secp521r1)", size: 521, kind: DomainParameterType::Ecp, - is_supported: false, + is_supported: true, + }, + DomainParameter { + id: 23, + name: "BrainpoolP256t1", + size: 256, + kind: DomainParameterType::Ecp, + is_supported: true, + }, + DomainParameter { + id: 26, + name: "BrainpoolP384t1", + size: 384, + kind: DomainParameterType::Ecp, + is_supported: true, }, ]; entries.into_iter().map(|p| (p.id, p)).collect() @@ -183,13 +197,13 @@ mod tests { .map(|p| p.id) .collect(); supported.sort_unstable(); - // P-256 (12, ECDH) + RFC 5114 MODP/DH groups (0/1/2). - assert_eq!(supported, vec![0, 1, 2, 12]); + // P-256 (12, ECDH) + RFC 5114 MODP/DH groups (0/1/2) + Brainpool + NIST P-384/521. + assert_eq!(supported, vec![0, 1, 2, 12, 13, 15, 16, 18, 23, 26]); } #[test] - fn table_has_14_entries() { - assert_eq!(ICAO_DOMAIN_PARAMETERS.len(), 14); + fn table_has_16_entries() { + assert_eq!(ICAO_DOMAIN_PARAMETERS.len(), 16); } #[test] diff --git a/crates/dmrtd/src/proto/ecdh_pace.rs b/crates/dmrtd/src/proto/ecdh_pace.rs index cc65876..4fbad5d 100644 --- a/crates/dmrtd/src/proto/ecdh_pace.rs +++ b/crates/dmrtd/src/proto/ecdh_pace.rs @@ -1,11 +1,6 @@ -//! ECDH PACE engine. +//! ECDH PACE engine supporting NIST and Brainpool curves. //! -//! The reference supports 11 ICAO curves (ids 8-18). This Rust port -//! currently implements only **NIST P-256 (id 12)** — the only curve marked -//! `is_supported = true` in [`domain_parameter`]. Other ids return -//! [`ECDHPaceError::UnsupportedCurve`]; add additional back-ends as needed. -//! -//! PACE-GM mapped generator formula (ICAO 9303 p11 §4.3.3.3.1 - ECDH): +//! Handles PACE key agreement using ECDH-GM mapped generator formula: //! ```text //! G' = s · G + H //! ``` @@ -13,16 +8,7 @@ //! shared-secret point derived from our private key and the other party's //! public key. -use elliptic_curve::{ - group::Group, - ops::Reduce, - sec1::{FromSec1Point, ToSec1Point}, - Generate, NonZeroScalar, PrimeField, -}; use num_bigint::BigUint; -use p256::{ - AffinePoint, NistP256, ProjectivePoint, PublicKey, Scalar, Sec1Point as EncodedPoint, SecretKey, -}; use rand::rand_core::UnwrapErr; use rand::{rngs::StdRng, rngs::SysRng, Rng, SeedableRng}; use thiserror::Error; @@ -30,13 +16,10 @@ use thiserror::Error; use crate::proto::domain_parameter; use crate::proto::public_key_pace::PublicKeyPace; -/// ICAO domain-parameter id for NIST P-256 (the only curve supported by this -/// Rust port today). +/// ICAO domain-parameter id for NIST P-256. pub const NIST_P256_ID: u32 = 12; /// Error returned by [`ECDHPace`] operations. -/// -/// Consolidates the `ECDHPaceError` and `ECDHBasicAgreementPACEError`. #[derive(Debug, Error, PartialEq, Eq)] pub enum ECDHPaceError { #[error("Domain parameter with id {0} does not exist.")] @@ -61,244 +44,567 @@ pub enum ECDHPaceError { InvalidCoordinate(#[from] crate::proto::public_key_pace::PublicKeyPaceError), } -// --------------------------------------------------------------------------- -// ECDHPace -// --------------------------------------------------------------------------- +macro_rules! impl_curve_ops { + (modern, $struct_name:ident, $curve_crate:ident, $curve_type:ty, $secret_key:ty, $scalar:ty, $projective:ty, $affine:ty, $coord_len:expr, $order_str:expr) => { + #[derive(Debug)] + pub struct $struct_name { + priv_key: Option<$secret_key>, + pub_key: Option<$curve_crate::elliptic_curve::PublicKey<$curve_type>>, + ephemeral_priv: Option<$scalar>, + ephemeral_pub: Option<$projective>, + ephemeral_generator: Option<$projective>, + } -/// ECDH PACE engine for NIST P-256. Holds a main key pair plus an optional -/// ephemeral one used during PACE-GM mapping. -#[derive(Debug)] -pub struct ECDHPace { - priv_key: Option, - pub_key: Option, - ephemeral_priv: Option, - ephemeral_pub: Option, - /// Mapped generator used with the ephemeral scalar (PACE-GM). - ephemeral_generator: Option, -} + impl $struct_name { + pub fn new() -> Self { + Self { + priv_key: None, + pub_key: None, + ephemeral_priv: None, + ephemeral_pub: None, + ephemeral_generator: None, + } + } -impl ECDHPace { - /// Constructs an ECDH PACE engine for the given ICAO domain-parameter id. - /// - /// # Errors - /// - [`ECDHPaceError::UnknownId`] if `id` is not listed in the ICAO table. - /// - [`ECDHPaceError::UnsupportedCurve`] if `id` is listed but this port - /// does not yet back it. - pub fn new(id: u32) -> Result { - if domain_parameter::get(id).is_none() { - return Err(ECDHPaceError::UnknownId(id)); + fn scalar_from_bytes(&self, bytes: &[u8]) -> $scalar { + let n = BigUint::parse_bytes($order_str, 10).unwrap(); + let val = BigUint::from_bytes_be(bytes); + let reduced = val % &n; + let r_bytes = reduced.to_bytes_be(); + let mut buf = vec![0u8; $coord_len]; + if r_bytes.len() <= $coord_len { + buf[$coord_len - r_bytes.len()..].copy_from_slice(&r_bytes); + } else { + buf.copy_from_slice(&r_bytes[r_bytes.len() - $coord_len..]); + } + let field_bytes = *<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::from_slice(&buf); + <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes).unwrap() + } + + pub fn generate_key_pair(&mut self, seed32: Option<&[u8]>) -> Result<(), ECDHPaceError> { + let sk = match seed32 { + None => { + let mut rng = UnwrapErr(SysRng); + <$secret_key>::random(&mut rng) + } + Some(s) if s.len() == 32 => { + let mut seed_arr = [0u8; 32]; + seed_arr.copy_from_slice(s); + let mut rng = StdRng::from_seed(seed_arr); + loop { + let mut bytes = vec![0u8; $coord_len]; + rng.fill_bytes(&mut bytes); + if let Ok(sk) = <$secret_key>::from_slice(&bytes) { + break sk; + } + } + } + Some(_) => return Err(ECDHPaceError::InvalidSeedLen), + }; + self.pub_key = Some(sk.public_key()); + self.priv_key = Some(sk); + Ok(()) + } + + pub fn get_pub_key(&self) -> Result { + let pk = self.pub_key.as_ref().ok_or(ECDHPaceError::NoPublicKey)?; + self.point_to_pubkey_pace(pk.to_projective()) + } + + pub fn get_pub_key_ephemeral(&self) -> Result { + let pk = self + .ephemeral_pub + .as_ref() + .ok_or(ECDHPaceError::NoEphemeralPublicKey)?; + self.point_to_pubkey_pace(*pk) + } + + pub fn map_and_generate_ephemeral( + &mut self, + other_pub_key: &PublicKeyPace, + nonce: &[u8], + seed32: Option<&[u8]>, + ) -> Result<(), ECDHPaceError> { + use $curve_crate::elliptic_curve::group::Group; + + let other = self.transform_public(other_pub_key)?; + let h = self.compute_shared_point(&other)?; + let s = self.scalar_from_bytes(nonce); + let g = <$projective as Group>::generator(); + let g_prime = g * s + h; + + // Sample ephemeral scalar + let scalar = match seed32 { + None => { + let mut rng = UnwrapErr(SysRng); + let sk = <$secret_key>::random(&mut rng); + self.scalar_from_bytes(&sk.to_bytes()) + } + Some(s) if s.len() == 32 => { + let mut seed_arr = [0u8; 32]; + seed_arr.copy_from_slice(s); + let mut rng = StdRng::from_seed(seed_arr); + loop { + let mut bytes = vec![0u8; $coord_len]; + rng.fill_bytes(&mut bytes); + if let Ok(sk) = <$secret_key>::from_slice(&bytes) { + break self.scalar_from_bytes(&sk.to_bytes()); + } + } + } + Some(_) => return Err(ECDHPaceError::InvalidSeedLen), + }; + + let pub_point = g_prime * scalar; + self.ephemeral_priv = Some(scalar); + self.ephemeral_pub = Some(pub_point); + self.ephemeral_generator = Some(g_prime); + Ok(()) + } + + pub fn get_ephemeral_shared_seed( + &self, + other_ephemeral_pub_key: &PublicKeyPace, + ) -> Result, ECDHPaceError> { + use $curve_crate::elliptic_curve::group::Group; + + let other = self.transform_public(other_ephemeral_pub_key)?; + let scalar = self + .ephemeral_priv + .as_ref() + .ok_or(ECDHPaceError::NoEphemeralPublicKey)?; + + let other_point = other.to_projective(); + if bool::from(other_point.is_identity()) { + return Err(ECDHPaceError::InfinityPoint); + } + let shared = other_point * *scalar; + if bool::from(shared.is_identity()) { + return Err(ECDHPaceError::InfinityAgreement); + } + + let pk = <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine( + shared.to_affine() + ).map_err(|_| ECDHPaceError::InfinityAgreement)?; + let sec1_bytes = pk.to_sec1_bytes(); + if sec1_bytes.len() != 1 + 2 * $coord_len || sec1_bytes[0] != 0x04 { + return Err(ECDHPaceError::InvalidEncoding); + } + let x_bytes = &sec1_bytes[1..1 + $coord_len]; + Ok(x_bytes.to_vec()) + } + + fn compute_shared_point( + &self, + other: &$curve_crate::elliptic_curve::PublicKey<$curve_type>, + ) -> Result<$projective, ECDHPaceError> { + use $curve_crate::elliptic_curve::group::Group; + + let sk = self.priv_key.as_ref().ok_or(ECDHPaceError::NoPublicKey)?; + let scalar = self.scalar_from_bytes(&sk.to_bytes()); + let other_point = other.to_projective(); + if bool::from(other_point.is_identity()) { + return Err(ECDHPaceError::InfinityPoint); + } + let shared = other_point * scalar; + if bool::from(shared.is_identity()) { + return Err(ECDHPaceError::InfinityAgreement); + } + Ok(shared) + } + + fn point_to_pubkey_pace(&self, point: $projective) -> Result { + let affine = point.to_affine(); + let pk = <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine(affine).map_err(|_| ECDHPaceError::InfinityPoint)?; + let sec1_bytes = pk.to_sec1_bytes(); + if sec1_bytes.len() != 1 + 2 * $coord_len || sec1_bytes[0] != 0x04 { + return Err(ECDHPaceError::InvalidEncoding); + } + let x_bytes = &sec1_bytes[1..1 + $coord_len]; + let y_bytes = &sec1_bytes[1 + $coord_len..]; + Ok(PublicKeyPace::new_ecdh_fixed( + BigUint::from_bytes_be(x_bytes), + BigUint::from_bytes_be(y_bytes), + $coord_len, + )?) + } + + fn transform_public(&self, pub_key: &PublicKeyPace) -> Result<$curve_crate::elliptic_curve::PublicKey<$curve_type>, ECDHPaceError> { + match pub_key { + PublicKeyPace::Ecdh { x, y, .. } => { + let mut x_bytes = vec![0u8; $coord_len]; + let mut y_bytes = vec![0u8; $coord_len]; + let x_be = x.to_bytes_be(); + let y_be = y.to_bytes_be(); + if x_be.len() > $coord_len || y_be.len() > $coord_len { + return Err(ECDHPaceError::InvalidEncoding); + } + x_bytes[$coord_len - x_be.len()..].copy_from_slice(&x_be); + y_bytes[$coord_len - y_be.len()..].copy_from_slice(&y_be); + + let mut bytes = vec![0x04]; + bytes.extend_from_slice(&x_bytes); + bytes.extend_from_slice(&y_bytes); + <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_sec1_bytes(&bytes).map_err(|_| ECDHPaceError::InvalidEncoding) + } + _ => Err(ECDHPaceError::InvalidEncoding), + } + } } - if id != NIST_P256_ID { - return Err(ECDHPaceError::UnsupportedCurve(id)); + }; + + (legacy, $struct_name:ident, $curve_crate:ident, $curve_type:ty, $secret_key:ty, $scalar:ty, $projective:ty, $affine:ty, $coord_len:expr, $order_str:expr) => { + #[derive(Debug)] + pub struct $struct_name { + priv_key: Option<$secret_key>, + pub_key: Option<$curve_crate::elliptic_curve::PublicKey<$curve_type>>, + ephemeral_priv: Option<$scalar>, + ephemeral_pub: Option<$projective>, + ephemeral_generator: Option<$projective>, } - Ok(Self { - priv_key: None, - pub_key: None, - ephemeral_priv: None, - ephemeral_pub: None, - ephemeral_generator: None, - }) - } - /// Generates a new main key pair using the OS RNG (`SysRng`) or a seeded - /// RNG if `seed` is provided (must be exactly 32 bytes). - pub fn generate_key_pair(&mut self, seed32: Option<&[u8]>) -> Result<(), ECDHPaceError> { - let sk = match seed32 { - None => { - let mut rng = UnwrapErr(SysRng); - SecretKey::generate_from_rng(&mut rng) + impl $struct_name { + pub fn new() -> Self { + Self { + priv_key: None, + pub_key: None, + ephemeral_priv: None, + ephemeral_pub: None, + ephemeral_generator: None, + } } - Some(s) if s.len() == 32 => { - let mut seed_arr = [0u8; 32]; - seed_arr.copy_from_slice(s); - let mut rng = StdRng::from_seed(seed_arr); - // Sample the scalar manually via rejection so the seeded path - // has stable, reproducible output independent of any RNG helper. - let scalar = loop { - let mut bytes = [0u8; 32]; - rng.fill_bytes(&mut bytes); - if let Ok(nz) = Scalar::from_repr(bytes.into()) - .into_option() - .ok_or(()) - .and_then(|s| NonZeroScalar::new(s).into_option().ok_or(())) - { - break nz; + + fn scalar_from_bytes(&self, bytes: &[u8]) -> $scalar { + let n = BigUint::parse_bytes($order_str, 10).unwrap(); + let val = BigUint::from_bytes_be(bytes); + let reduced = val % &n; + let r_bytes = reduced.to_bytes_be(); + let mut buf = vec![0u8; $coord_len]; + if r_bytes.len() <= $coord_len { + buf[$coord_len - r_bytes.len()..].copy_from_slice(&r_bytes); + } else { + buf.copy_from_slice(&r_bytes[r_bytes.len() - $coord_len..]); + } + let field_bytes = *<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::from_slice(&buf); + <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes).unwrap() + } + + pub fn generate_key_pair(&mut self, seed32: Option<&[u8]>) -> Result<(), ECDHPaceError> { + struct RngBridge<'a, R>(&'a mut R); + + impl<'a, R: rand::rand_core::RngCore> $curve_crate::elliptic_curve::rand_core::RngCore for RngBridge<'a, R> { + fn next_u32(&mut self) -> u32 { + self.0.next_u32() } + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.0.fill_bytes(dest) + } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), $curve_crate::elliptic_curve::rand_core::Error> { + self.0.try_fill_bytes(dest).map_err(|_| $curve_crate::elliptic_curve::rand_core::Error::from(core::num::NonZeroU32::new(1).unwrap())) + } + } + + impl<'a, R: rand::rand_core::CryptoRng> $curve_crate::elliptic_curve::rand_core::CryptoRng for RngBridge<'a, R> {} + + let sk = match seed32 { + None => { + let mut sys_rng = UnwrapErr(SysRng); + let mut rng = RngBridge(&mut sys_rng); + <$secret_key>::random(&mut rng) + } + Some(s) if s.len() == 32 => { + let mut seed_arr = [0u8; 32]; + seed_arr.copy_from_slice(s); + let mut rng = StdRng::from_seed(seed_arr); + loop { + let mut bytes = vec![0u8; $coord_len]; + rng.fill_bytes(&mut bytes); + if let Ok(sk) = <$secret_key>::from_slice(&bytes) { + break sk; + } + } + } + Some(_) => return Err(ECDHPaceError::InvalidSeedLen), }; - SecretKey::from(scalar) + self.pub_key = Some(sk.public_key()); + self.priv_key = Some(sk); + Ok(()) } - Some(_) => return Err(ECDHPaceError::InvalidSeedLen), - }; - self.pub_key = Some(sk.public_key()); - self.priv_key = Some(sk); - Ok(()) - } - /// Returns the main public key as a [`PublicKeyPace::Ecdh`]. - pub fn get_pub_key(&self) -> Result { - let pk = self.pub_key.as_ref().ok_or(ECDHPaceError::NoPublicKey)?; - point_to_pubkey_pace(pk.to_projective()) - } + pub fn get_pub_key(&self) -> Result { + let pk = self.pub_key.as_ref().ok_or(ECDHPaceError::NoPublicKey)?; + self.point_to_pubkey_pace(pk.to_projective()) + } - /// Returns the ephemeral public key as a [`PublicKeyPace::Ecdh`]. - pub fn get_pub_key_ephemeral(&self) -> Result { - let pk = self - .ephemeral_pub - .as_ref() - .ok_or(ECDHPaceError::NoEphemeralPublicKey)?; - point_to_pubkey_pace(*pk) - } + pub fn get_pub_key_ephemeral(&self) -> Result { + let pk = self + .ephemeral_pub + .as_ref() + .ok_or(ECDHPaceError::NoEphemeralPublicKey)?; + self.point_to_pubkey_pace(*pk) + } - /// Converts a [`PublicKeyPace::Ecdh`] into an on-curve [`PublicKey`]. - pub fn transform_public(pub_key: &PublicKeyPace) -> Result { - match pub_key { - PublicKeyPace::Ecdh { x, y, .. } => pubkey_from_xy(x, y), - _ => Err(ECDHPaceError::InvalidEncoding), - } - } + pub fn map_and_generate_ephemeral( + &mut self, + other_pub_key: &PublicKeyPace, + nonce: &[u8], + seed32: Option<&[u8]>, + ) -> Result<(), ECDHPaceError> { + use $curve_crate::elliptic_curve::group::Group; - /// Computes the shared secret point `P = priv · other`. P-256 has - /// cofactor `h = 1`, so no cofactor correction is needed. - pub fn get_shared_secret( - &self, - other_pub_key: &PublicKey, - ) -> Result { - let sk = self.priv_key.as_ref().ok_or(ECDHPaceError::NoPublicKey)?; - compute_shared_point(sk.to_nonzero_scalar().as_ref(), other_pub_key) - } + struct RngBridge<'a, R>(&'a mut R); - /// Ephemeral variant of [`get_shared_secret`]. - pub fn get_ephemeral_shared_secret( - &self, - other_ephemeral_pub_key: &PublicKey, - ) -> Result { - let scalar = self - .ephemeral_priv - .as_ref() - .ok_or(ECDHPaceError::NoEphemeralPublicKey)?; - compute_shared_point(scalar, other_ephemeral_pub_key) - } + impl<'a, R: rand::rand_core::RngCore> $curve_crate::elliptic_curve::rand_core::RngCore for RngBridge<'a, R> { + fn next_u32(&mut self) -> u32 { + self.0.next_u32() + } + fn next_u64(&mut self) -> u64 { + self.0.next_u64() + } + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.0.fill_bytes(dest) + } + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), $curve_crate::elliptic_curve::rand_core::Error> { + self.0.try_fill_bytes(dest).map_err(|_| $curve_crate::elliptic_curve::rand_core::Error::from(core::num::NonZeroU32::new(1).unwrap())) + } + } - /// Computes the PACE-GM mapped generator: - /// `G' = s · G + H`, where `H = priv · other`. - pub fn get_mapped_generator( - &self, - other_pub_key: &PublicKey, - nonce: &[u8], - ) -> Result { - let h = self.get_shared_secret(other_pub_key)?; - let s = scalar_from_bytes(nonce); - let g = ProjectivePoint::GENERATOR; - Ok(g * s + h) - } + impl<'a, R: rand::rand_core::CryptoRng> $curve_crate::elliptic_curve::rand_core::CryptoRng for RngBridge<'a, R> {} + + let other = self.transform_public(other_pub_key)?; + let h = self.compute_shared_point(&other)?; + let s = self.scalar_from_bytes(nonce); + let g = <$projective as Group>::generator(); + let g_prime = g * s + h; + + // Sample ephemeral scalar + let scalar = match seed32 { + None => { + let mut sys_rng = UnwrapErr(SysRng); + let mut rng = RngBridge(&mut sys_rng); + let sk = <$secret_key>::random(&mut rng); + self.scalar_from_bytes(&sk.to_bytes()) + } + Some(s) if s.len() == 32 => { + let mut seed_arr = [0u8; 32]; + seed_arr.copy_from_slice(s); + let mut rng = StdRng::from_seed(seed_arr); + loop { + let mut bytes = vec![0u8; $coord_len]; + rng.fill_bytes(&mut bytes); + if let Ok(sk) = <$secret_key>::from_slice(&bytes) { + break self.scalar_from_bytes(&sk.to_bytes()); + } + } + } + Some(_) => return Err(ECDHPaceError::InvalidSeedLen), + }; - /// Builds an ephemeral key pair using the given mapped generator and a - /// seeded / random scalar. The generator is stored so that subsequent - /// ephemeral shared-secret computations use the correct base point. - pub fn generate_ephemeral_with_custom_generator( - &mut self, - mapped_generator: ProjectivePoint, - seed32: Option<&[u8]>, - ) -> Result<(), ECDHPaceError> { - let scalar = sample_scalar(seed32)?; - let pub_point = mapped_generator * *scalar.as_ref(); - self.ephemeral_priv = Some(*scalar.as_ref()); - self.ephemeral_pub = Some(pub_point); - self.ephemeral_generator = Some(mapped_generator); - Ok(()) - } -} + let pub_point = g_prime * scalar; + self.ephemeral_priv = Some(scalar); + self.ephemeral_pub = Some(pub_point); + self.ephemeral_generator = Some(g_prime); + Ok(()) + } -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- + pub fn get_ephemeral_shared_seed( + &self, + other_ephemeral_pub_key: &PublicKeyPace, + ) -> Result, ECDHPaceError> { + use $curve_crate::elliptic_curve::group::Group; + + let other = self.transform_public(other_ephemeral_pub_key)?; + let scalar = self + .ephemeral_priv + .as_ref() + .ok_or(ECDHPaceError::NoEphemeralPublicKey)?; + + let other_point = other.to_projective(); + if bool::from(other_point.is_identity()) { + return Err(ECDHPaceError::InfinityPoint); + } + let shared = other_point * *scalar; + if bool::from(shared.is_identity()) { + return Err(ECDHPaceError::InfinityAgreement); + } -fn scalar_from_bytes(bytes: &[u8]) -> Scalar { - // Right-align the big-endian input into a fixed 32-byte buffer, then let - // the scalar's standard reduction wrap it modulo n (the P-256 order). - // Working on the slice directly avoids the BigUint round-trip allocation. - let mut buf = [0u8; 32]; - if bytes.len() <= 32 { - buf[32 - bytes.len()..].copy_from_slice(bytes); - } else { - // Take the least-significant 32 bytes (then reduce). Unreachable with - // well-formed PACE nonces (16 bytes), but kept defensive. - buf.copy_from_slice(&bytes[bytes.len() - 32..]); - } - >::reduce(&p256::U256::from_be_slice(&buf)) -} + let pk = <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine( + shared.to_affine() + ).map_err(|_| ECDHPaceError::InfinityAgreement)?; + let sec1_bytes = pk.to_sec1_bytes(); + if sec1_bytes.len() != 1 + 2 * $coord_len || sec1_bytes[0] != 0x04 { + return Err(ECDHPaceError::InvalidEncoding); + } + let x_bytes = &sec1_bytes[1..1 + $coord_len]; + Ok(x_bytes.to_vec()) + } -fn sample_scalar(seed32: Option<&[u8]>) -> Result, ECDHPaceError> { - match seed32 { - None => { - let mut rng = UnwrapErr(SysRng); - Ok(NonZeroScalar::generate_from_rng(&mut rng)) - } - Some(s) if s.len() == 32 => { - let mut seed_arr = [0u8; 32]; - seed_arr.copy_from_slice(s); - let mut rng = StdRng::from_seed(seed_arr); - loop { - let mut bytes = [0u8; 32]; - rng.fill_bytes(&mut bytes); - if let Some(s) = Scalar::from_repr(bytes.into()).into_option() { - if let Some(nz) = NonZeroScalar::new(s).into_option() { - return Ok(nz); + fn compute_shared_point( + &self, + other: &$curve_crate::elliptic_curve::PublicKey<$curve_type>, + ) -> Result<$projective, ECDHPaceError> { + use $curve_crate::elliptic_curve::group::Group; + + let sk = self.priv_key.as_ref().ok_or(ECDHPaceError::NoPublicKey)?; + let scalar = self.scalar_from_bytes(&sk.to_bytes()); + let other_point = other.to_projective(); + if bool::from(other_point.is_identity()) { + return Err(ECDHPaceError::InfinityPoint); + } + let shared = other_point * scalar; + if bool::from(shared.is_identity()) { + return Err(ECDHPaceError::InfinityAgreement); + } + Ok(shared) + } + + fn point_to_pubkey_pace(&self, point: $projective) -> Result { + let affine = point.to_affine(); + let pk = <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine(affine).map_err(|_| ECDHPaceError::InfinityPoint)?; + let sec1_bytes = pk.to_sec1_bytes(); + if sec1_bytes.len() != 1 + 2 * $coord_len || sec1_bytes[0] != 0x04 { + return Err(ECDHPaceError::InvalidEncoding); + } + let x_bytes = &sec1_bytes[1..1 + $coord_len]; + let y_bytes = &sec1_bytes[1 + $coord_len..]; + Ok(PublicKeyPace::new_ecdh_fixed( + BigUint::from_bytes_be(x_bytes), + BigUint::from_bytes_be(y_bytes), + $coord_len, + )?) + } + + fn transform_public(&self, pub_key: &PublicKeyPace) -> Result<$curve_crate::elliptic_curve::PublicKey<$curve_type>, ECDHPaceError> { + match pub_key { + PublicKeyPace::Ecdh { x, y, .. } => { + let mut x_bytes = vec![0u8; $coord_len]; + let mut y_bytes = vec![0u8; $coord_len]; + let x_be = x.to_bytes_be(); + let y_be = y.to_bytes_be(); + if x_be.len() > $coord_len || y_be.len() > $coord_len { + return Err(ECDHPaceError::InvalidEncoding); + } + x_bytes[$coord_len - x_be.len()..].copy_from_slice(&x_be); + y_bytes[$coord_len - y_be.len()..].copy_from_slice(&y_be); + + let mut bytes = vec![0x04]; + bytes.extend_from_slice(&x_bytes); + bytes.extend_from_slice(&y_bytes); + <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_sec1_bytes(&bytes).map_err(|_| ECDHPaceError::InvalidEncoding) } + _ => Err(ECDHPaceError::InvalidEncoding), } } } - Some(_) => Err(ECDHPaceError::InvalidSeedLen), - } + }; +} + +impl_curve_ops!(modern, NistP256Engine, p256, p256::NistP256, p256::SecretKey, p256::Scalar, p256::ProjectivePoint, p256::AffinePoint, 32, b"115792089210356248762485316520336594248464673199859591410292408985888941275213"); +impl_curve_ops!(modern, BrainpoolP256r1Engine, bp256, bp256::r1::BrainpoolP256r1, bp256::r1::SecretKey, bp256::r1::Scalar, bp256::r1::ProjectivePoint, bp256::r1::AffinePoint, 32, b"115792089237316195423570985008687907852837564279074904382605163141518161494337"); +impl_curve_ops!(modern, BrainpoolP256t1Engine, bp256, bp256::t1::BrainpoolP256t1, bp256::t1::SecretKey, bp256::t1::Scalar, bp256::t1::ProjectivePoint, bp256::t1::AffinePoint, 32, b"115792089237316195423570985008687907852837564279074904382605163141518161494337"); +impl_curve_ops!(legacy, NistP384Engine, p384, p384::NistP384, p384::SecretKey, p384::Scalar, p384::ProjectivePoint, p384::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022646276856019566907421111663185381673890280521949313936996367"); +impl_curve_ops!(modern, BrainpoolP384r1Engine, bp384, bp384::r1::BrainpoolP384r1, bp384::r1::SecretKey, bp384::r1::Scalar, bp384::r1::ProjectivePoint, bp384::r1::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022648719277063462947702816912384784949216075932598370503046777"); +impl_curve_ops!(modern, BrainpoolP384t1Engine, bp384, bp384::t1::BrainpoolP384t1, bp384::t1::SecretKey, bp384::t1::Scalar, bp384::t1::ProjectivePoint, bp384::t1::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022648719277063462947702816912384784949216075932598370503046777"); +impl_curve_ops!(legacy, NistP521Engine, p521, p521::NistP521, p521::SecretKey, p521::Scalar, p521::ProjectivePoint, p521::AffinePoint, 66, b"6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449"); + +#[derive(Debug)] +pub enum ECDHPace { + NistP256(NistP256Engine), + BrainpoolP256r1(BrainpoolP256r1Engine), + BrainpoolP256t1(BrainpoolP256t1Engine), + NistP384(NistP384Engine), + BrainpoolP384r1(BrainpoolP384r1Engine), + BrainpoolP384t1(BrainpoolP384t1Engine), + NistP521(NistP521Engine), } -fn compute_shared_point( - scalar: &Scalar, - other: &PublicKey, -) -> Result { - let other_point = other.to_projective(); - if bool::from(other_point.is_identity()) { - return Err(ECDHPaceError::InfinityPoint); +impl ECDHPace { + pub fn new(id: u32) -> Result { + if domain_parameter::get(id).is_none() { + return Err(ECDHPaceError::UnknownId(id)); + } + match id { + 12 => Ok(Self::NistP256(NistP256Engine::new())), + 13 => Ok(Self::BrainpoolP256r1(BrainpoolP256r1Engine::new())), + 15 => Ok(Self::NistP384(NistP384Engine::new())), + 16 => Ok(Self::BrainpoolP384r1(BrainpoolP384r1Engine::new())), + 18 => Ok(Self::NistP521(NistP521Engine::new())), + 23 => Ok(Self::BrainpoolP256t1(BrainpoolP256t1Engine::new())), + 26 => Ok(Self::BrainpoolP384t1(BrainpoolP384t1Engine::new())), + _ => Err(ECDHPaceError::UnsupportedCurve(id)), + } + } + + pub fn generate_key_pair(&mut self, seed32: Option<&[u8]>) -> Result<(), ECDHPaceError> { + match self { + Self::NistP256(e) => e.generate_key_pair(seed32), + Self::BrainpoolP256r1(e) => e.generate_key_pair(seed32), + Self::BrainpoolP256t1(e) => e.generate_key_pair(seed32), + Self::NistP384(e) => e.generate_key_pair(seed32), + Self::BrainpoolP384r1(e) => e.generate_key_pair(seed32), + Self::BrainpoolP384t1(e) => e.generate_key_pair(seed32), + Self::NistP521(e) => e.generate_key_pair(seed32), + } } - let shared = other_point * *scalar; - if bool::from(shared.is_identity()) { - return Err(ECDHPaceError::InfinityAgreement); + + pub fn get_pub_key(&self) -> Result { + match self { + Self::NistP256(e) => e.get_pub_key(), + Self::BrainpoolP256r1(e) => e.get_pub_key(), + Self::BrainpoolP256t1(e) => e.get_pub_key(), + Self::NistP384(e) => e.get_pub_key(), + Self::BrainpoolP384r1(e) => e.get_pub_key(), + Self::BrainpoolP384t1(e) => e.get_pub_key(), + Self::NistP521(e) => e.get_pub_key(), + } } - Ok(shared) -} -fn point_to_pubkey_pace(point: ProjectivePoint) -> Result { - let affine: AffinePoint = point.to_affine(); - let encoded: EncodedPoint = affine.to_sec1_point(false); - // The identity (point at infinity) has no affine coordinates; reject it - // instead of panicking on the missing X/Y. - let x_bytes = encoded.x().ok_or(ECDHPaceError::InfinityPoint)?; - let y_bytes = encoded.y().ok_or(ECDHPaceError::InfinityPoint)?; - // SEC1 uncompressed coordinates are fixed-width (32 bytes for P-256); carry - // that width so the X||Y form is always emitted at full length. - let coord_len = x_bytes.len(); - Ok(PublicKeyPace::new_ecdh_fixed( - BigUint::from_bytes_be(x_bytes), - BigUint::from_bytes_be(y_bytes), - coord_len, - )?) -} + pub fn get_pub_key_ephemeral(&self) -> Result { + match self { + Self::NistP256(e) => e.get_pub_key_ephemeral(), + Self::BrainpoolP256r1(e) => e.get_pub_key_ephemeral(), + Self::BrainpoolP256t1(e) => e.get_pub_key_ephemeral(), + Self::NistP384(e) => e.get_pub_key_ephemeral(), + Self::BrainpoolP384r1(e) => e.get_pub_key_ephemeral(), + Self::BrainpoolP384t1(e) => e.get_pub_key_ephemeral(), + Self::NistP521(e) => e.get_pub_key_ephemeral(), + } + } -fn pubkey_from_xy(x: &BigUint, y: &BigUint) -> Result { - let mut x_bytes = [0u8; 32]; - let mut y_bytes = [0u8; 32]; - let x_be = x.to_bytes_be(); - let y_be = y.to_bytes_be(); - if x_be.len() > 32 || y_be.len() > 32 { - return Err(ECDHPaceError::InvalidEncoding); + pub fn map_and_generate_ephemeral( + &mut self, + other_pub_key: &PublicKeyPace, + nonce: &[u8], + seed32: Option<&[u8]>, + ) -> Result<(), ECDHPaceError> { + match self { + Self::NistP256(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), + Self::BrainpoolP256r1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), + Self::BrainpoolP256t1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), + Self::NistP384(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), + Self::BrainpoolP384r1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), + Self::BrainpoolP384t1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), + Self::NistP521(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), + } } - x_bytes[32 - x_be.len()..].copy_from_slice(&x_be); - y_bytes[32 - y_be.len()..].copy_from_slice(&y_be); - let encoded = EncodedPoint::from_affine_coordinates(&x_bytes.into(), &y_bytes.into(), false); - let affine = Option::::from(AffinePoint::from_sec1_point(&encoded)) - .ok_or(ECDHPaceError::InvalidEncoding)?; - Ok(PublicKey::from_affine(affine).map_err(|_| ECDHPaceError::InvalidEncoding)?) + pub fn get_ephemeral_shared_seed( + &self, + other_ephemeral_pub_key: &PublicKeyPace, + ) -> Result, ECDHPaceError> { + match self { + Self::NistP256(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), + Self::BrainpoolP256r1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), + Self::BrainpoolP256t1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), + Self::NistP384(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), + Self::BrainpoolP384r1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), + Self::BrainpoolP384t1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), + Self::NistP521(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), + } + } } // --------------------------------------------------------------------------- @@ -311,7 +617,7 @@ mod tests { #[test] fn unknown_id_is_rejected() { - assert_eq!(ECDHPace::new(99).unwrap_err(), ECDHPaceError::UnknownId(99),); + assert_eq!(ECDHPace::new(99).unwrap_err(), ECDHPaceError::UnknownId(99)); } #[test] @@ -324,100 +630,56 @@ mod tests { } #[test] - fn p256_engine_constructs() { - let e = ECDHPace::new(NIST_P256_ID).unwrap(); - // No key pair generated yet, so the public key is unavailable. - assert_eq!(e.get_pub_key().unwrap_err(), ECDHPaceError::NoPublicKey); + fn engine_constructs_for_all_supported_curves() { + for id in &[12, 13, 15, 16, 18, 23, 26] { + let e = ECDHPace::new(*id).unwrap(); + assert_eq!(e.get_pub_key().unwrap_err(), ECDHPaceError::NoPublicKey); + } } #[test] fn seeded_key_pair_is_deterministic() { let seed = [0x11u8; 32]; - let mut a = ECDHPace::new(NIST_P256_ID).unwrap(); - let mut b = ECDHPace::new(NIST_P256_ID).unwrap(); - a.generate_key_pair(Some(&seed)).unwrap(); - b.generate_key_pair(Some(&seed)).unwrap(); - let pka = a.get_pub_key().unwrap().to_bytes(); - let pkb = b.get_pub_key().unwrap().to_bytes(); - assert_eq!(pka, pkb); + for id in &[12, 13, 15, 16, 18, 23, 26] { + let mut a = ECDHPace::new(*id).unwrap(); + let mut b = ECDHPace::new(*id).unwrap(); + a.generate_key_pair(Some(&seed)).unwrap(); + b.generate_key_pair(Some(&seed)).unwrap(); + let pka = a.get_pub_key().unwrap().to_bytes(); + let pkb = b.get_pub_key().unwrap().to_bytes(); + assert_eq!(pka, pkb); + } } #[test] fn seed_wrong_length_errors() { - let mut e = ECDHPace::new(NIST_P256_ID).unwrap(); + let mut e = ECDHPace::new(12).unwrap(); let err = e.generate_key_pair(Some(&[0u8; 16])).unwrap_err(); assert_eq!(err, ECDHPaceError::InvalidSeedLen); } #[test] - fn shared_secret_is_symmetric() { - let mut alice = ECDHPace::new(NIST_P256_ID).unwrap(); - let mut bob = ECDHPace::new(NIST_P256_ID).unwrap(); - alice.generate_key_pair(Some(&[0x01u8; 32])).unwrap(); - bob.generate_key_pair(Some(&[0x02u8; 32])).unwrap(); - - let alice_pk = alice.get_pub_key().unwrap(); - let bob_pk = bob.get_pub_key().unwrap(); - let alice_pk_ec = ECDHPace::transform_public(&alice_pk).unwrap(); - let bob_pk_ec = ECDHPace::transform_public(&bob_pk).unwrap(); - - let s_ab = alice.get_shared_secret(&bob_pk_ec).unwrap(); - let s_ba = bob.get_shared_secret(&alice_pk_ec).unwrap(); - assert_eq!(s_ab, s_ba); - } - - #[test] - fn transform_public_roundtrip() { - let mut e = ECDHPace::new(NIST_P256_ID).unwrap(); - e.generate_key_pair(Some(&[0x03u8; 32])).unwrap(); - let pk = e.get_pub_key().unwrap(); - let back = ECDHPace::transform_public(&pk).unwrap(); - // Re-serialise and compare. - let pk2 = point_to_pubkey_pace(back.to_projective()).unwrap(); - assert_eq!(pk.to_bytes(), pk2.to_bytes()); - } - - #[test] - fn transform_public_rejects_dh_input() { - let bad = PublicKeyPace::new_dh(vec![0x01, 0x02]); - assert_eq!( - ECDHPace::transform_public(&bad).unwrap_err(), - ECDHPaceError::InvalidEncoding, - ); - } - - #[test] - fn mapped_generator_is_on_curve_and_matches_formula() { - let mut alice = ECDHPace::new(NIST_P256_ID).unwrap(); - let mut bob = ECDHPace::new(NIST_P256_ID).unwrap(); - alice.generate_key_pair(Some(&[0x04u8; 32])).unwrap(); - bob.generate_key_pair(Some(&[0x05u8; 32])).unwrap(); - - let bob_pk = ECDHPace::transform_public(&bob.get_pub_key().unwrap()).unwrap(); - let nonce = hex::decode("A1A2A3A4A5A6A7A8A9AAABACADAEAFB0").unwrap(); - - let g_prime = alice.get_mapped_generator(&bob_pk, &nonce).unwrap(); - // Manual recomputation. - let h = alice.get_shared_secret(&bob_pk).unwrap(); - let s = scalar_from_bytes(&nonce); - let expected = ProjectivePoint::GENERATOR * s + h; - assert_eq!(g_prime, expected); - } - - #[test] - fn ephemeral_flow_with_mapped_generator() { - let mut alice = ECDHPace::new(NIST_P256_ID).unwrap(); - let mut bob = ECDHPace::new(NIST_P256_ID).unwrap(); - alice.generate_key_pair(Some(&[0x04u8; 32])).unwrap(); - bob.generate_key_pair(Some(&[0x05u8; 32])).unwrap(); - - let bob_pk = ECDHPace::transform_public(&bob.get_pub_key().unwrap()).unwrap(); - let g_prime = alice - .get_mapped_generator(&bob_pk, &[0xA1, 0xA2, 0xA3, 0xA4]) - .unwrap(); - alice - .generate_ephemeral_with_custom_generator(g_prime, Some(&[0x0Au8; 32])) - .unwrap(); - assert!(alice.get_pub_key_ephemeral().is_ok()); + fn shared_secret_is_symmetric_all_curves() { + for id in &[12, 13, 15, 16, 18, 23, 26] { + let mut alice = ECDHPace::new(*id).unwrap(); + let mut bob = ECDHPace::new(*id).unwrap(); + alice.generate_key_pair(Some(&[0x01u8; 32])).unwrap(); + bob.generate_key_pair(Some(&[0x02u8; 32])).unwrap(); + + let alice_pk = alice.get_pub_key().unwrap(); + let bob_pk = bob.get_pub_key().unwrap(); + + // simulated map and ephemeral gen + let nonce = [0xAAu8; 16]; + alice.map_and_generate_ephemeral(&bob_pk, &nonce, Some(&[0x03u8; 32])).unwrap(); + bob.map_and_generate_ephemeral(&alice_pk, &nonce, Some(&[0x04u8; 32])).unwrap(); + + let alice_eph_pk = alice.get_pub_key_ephemeral().unwrap(); + let bob_eph_pk = bob.get_pub_key_ephemeral().unwrap(); + + let seed_alice = alice.get_ephemeral_shared_seed(&bob_eph_pk).unwrap(); + let seed_bob = bob.get_ephemeral_shared_seed(&alice_eph_pk).unwrap(); + assert_eq!(seed_alice, seed_bob); + } } } diff --git a/crates/dmrtd/src/proto/pace_session.rs b/crates/dmrtd/src/proto/pace_session.rs index 887558e..9337f16 100644 --- a/crates/dmrtd/src/proto/pace_session.rs +++ b/crates/dmrtd/src/proto/pace_session.rs @@ -30,9 +30,7 @@ //! //! [`BacSession`]: crate::proto::bac_session::BacSession -use elliptic_curve::sec1::ToSec1Point; use num_bigint::BigUint; -use p256::ProjectivePoint; use thiserror::Error; use crate::crypto::crypto_utils::constant_time_eq; @@ -42,6 +40,7 @@ use crate::lds::asn1_object_identifiers::{ use crate::proto::access_key::AccessKey; use crate::proto::aes_smcipher::AesSmCipher; use crate::proto::dh_pace::{self, DHPace, DHPaceError}; +use crate::proto::domain_parameter; use crate::proto::ecdh_pace::{ECDHPace, ECDHPaceError, NIST_P256_ID}; use crate::proto::iso7816::command_apdu::CommandApdu; use crate::proto::iso7816::iso7816::{cla, ins}; @@ -70,7 +69,7 @@ pub enum PaceSessionError { UnsupportedMapping(MappingType), #[error("PACE session: only AES cipher is supported (got {0:?})")] UnsupportedCipher(CipherAlgorithm), - #[error("PACE session: only NIST P-256 (id 12) is supported (got id {0})")] + #[error("PACE session: unsupported curve/parameter id (got id {0})")] UnsupportedCurve(u32), #[error("PACE session has no outstanding APDU to consume")] NoOutstandingApdu, @@ -144,9 +143,7 @@ impl PaceEngine { ) -> Result<(), PaceSessionError> { match self { PaceEngine::Ecdh(e) => { - let icc_pk = ECDHPace::transform_public(icc_mapping_pub)?; - let g_prime = e.get_mapped_generator(&icc_pk, nonce)?; - e.generate_ephemeral_with_custom_generator(g_prime, seed)?; + e.map_and_generate_ephemeral(icc_mapping_pub, nonce, seed)?; } PaceEngine::Dh(e) => { let g_prime = @@ -168,9 +165,8 @@ impl PaceEngine { ) -> Result, PaceSessionError> { match self { PaceEngine::Ecdh(e) => { - let icc_eph_pk = ECDHPace::transform_public(icc_ephemeral_pub)?; - let shared = e.get_ephemeral_shared_secret(&icc_eph_pk)?; - ecdh_shared_secret_x_bytes(shared) + let seed_bytes = e.get_ephemeral_shared_seed(icc_ephemeral_pub)?; + Ok(seed_bytes) } PaceEngine::Dh(e) => { let shared = @@ -289,7 +285,10 @@ impl PaceSession { if protocol.mapping_type != MappingType::Gm { return Err(PaceSessionError::UnsupportedMapping(protocol.mapping_type)); } - if parameter_id != NIST_P256_ID { + let supported = domain_parameter::get(parameter_id) + .map(|entry| entry.is_supported && entry.kind == domain_parameter::DomainParameterType::Ecp) + .unwrap_or(false); + if !supported { return Err(PaceSessionError::UnsupportedCurve(parameter_id)); } Ok(Self { @@ -565,12 +564,6 @@ impl PaceSession { } } -fn ecdh_shared_secret_x_bytes(shared: ProjectivePoint) -> Result, PaceSessionError> { - let encoded = shared.to_affine().to_sec1_point(false); - let x = encoded.x().ok_or(PaceSessionError::MissingSharedSecretX)?; - Ok(x.to_vec()) -} - // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- @@ -679,11 +672,11 @@ mod tests { } #[test] - fn rejects_non_p256_curve() { + fn rejects_unsupported_curve() { let can = CanKey::new("123456").unwrap(); - match PaceSession::new_ecdh(can, ecdh_gm_aes128_oid(), 13) { - Err(PaceSessionError::UnsupportedCurve(13)) => {} - _ => panic!("expected UnsupportedCurve(13)"), + match PaceSession::new_ecdh(can, ecdh_gm_aes128_oid(), 8) { + Err(PaceSessionError::UnsupportedCurve(8)) => {} + _ => panic!("expected UnsupportedCurve(8)"), } } @@ -791,11 +784,7 @@ mod tests { // Chip also computes the mapped generator, then its own ephemeral. let terminal_mapping_pk = icc_mapping_pub_from_session(&session).expect("terminal's main pubkey after step 2"); - let terminal_mapping_ec = ECDHPace::transform_public(&terminal_mapping_pk).unwrap(); - let g_prime = icc - .get_mapped_generator(&terminal_mapping_ec, &nonce) - .unwrap(); - icc.generate_ephemeral_with_custom_generator(g_prime, Some(&seed_icc_eph)) + icc.map_and_generate_ephemeral(&terminal_mapping_pk, &nonce, Some(&seed_icc_eph)) .unwrap(); let icc_eph_pub = icc.get_pub_key_ephemeral().unwrap(); let mut step3_body = vec![0x04]; @@ -813,9 +802,7 @@ mod tests { // ICC computes its own token over the *terminal's* ephemeral public. let terminal_eph_pub = terminal_ephemeral_pub_from_session(&session) .expect("terminal ephemeral public after step 3"); - let terminal_eph_ec = ECDHPace::transform_public(&terminal_eph_pub).unwrap(); - let icc_shared = icc.get_ephemeral_shared_secret(&terminal_eph_ec).unwrap(); - let seed_bytes = ecdh_shared_secret_x_bytes(icc_shared).unwrap(); + let seed_bytes = icc.get_ephemeral_shared_seed(&terminal_eph_pub).unwrap(); let icc_k_mac = pace::calculate_mac_key(&protocol, &seed_bytes).unwrap(); let icc_auth_input = pace::generate_encoding_input_data(&protocol, &terminal_eph_pub); let icc_token = pace::calculate_auth_token(&protocol, &icc_auth_input, &icc_k_mac).unwrap(); From d52193f0e2c6ccf9215a90a9c449de79f279dc72 Mon Sep 17 00:00:00 2001 From: Balamurali Pandranki Date: Tue, 14 Jul 2026 13:14:39 +0300 Subject: [PATCH 2/7] feat: Add NIST P-224 curve support and update READMEs --- Cargo.lock | 13 +++++++++++++ Cargo.toml | 1 + README.md | 2 +- crates/dmrtd/Cargo.toml | 13 +++++++------ crates/dmrtd/README.md | 6 +++--- crates/dmrtd/src/proto/domain_parameter.rs | 6 +++--- crates/dmrtd/src/proto/ecdh_pace.rs | 14 +++++++++++--- 7 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a69826..38aba4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -787,6 +787,7 @@ dependencies = [ "num-bigint", "num-traits", "once_cell", + "p224", "p256 0.14.0", "p384", "p521", @@ -1712,6 +1713,18 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "p224" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c06436d66652bc2f01ade021592c80a2aad401570a18aa18b82e440d2b9aa1" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", + "sha2 0.10.9", +] + [[package]] name = "p256" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index 56c6a02..78cf698 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ rand = "0.10" p256 = { version = "0.14", features = ["arithmetic", "ecdh", "ecdsa"] } p384 = { version = "0.13", features = ["arithmetic", "ecdh", "ecdsa"] } p521 = { version = "0.13", features = ["arithmetic", "ecdh", "ecdsa"] } +p224 = { version = "0.13", features = ["arithmetic", "ecdh", "ecdsa"] } bp256 = { version = "0.14.0-rc.15", features = ["arithmetic"] } bp384 = { version = "0.14.0-rc.15", features = ["arithmetic"] } elliptic-curve = { version = "0.14", features = ["arithmetic"] } diff --git a/README.md b/README.md index 261ef4d..806c199 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ offline e-KYC, and Income-Tax PAN. | Crate | Path | Description | |-------|------|-------------| -| [`dmrtd`](crates/dmrtd) | `crates/dmrtd` | eMRTD / ICAO 9303 reader core — BAC, PACE (ECDH-GM P-256 and DH-GM), MRZ, LDS parsing, secure messaging. Transport-agnostic (NFC plugs in via a `Transceiver` trait). | +| [`dmrtd`](crates/dmrtd) | `crates/dmrtd` | eMRTD / ICAO 9303 reader core — BAC, PACE (ECDH-GM NIST & Brainpool, DH-GM), MRZ, LDS parsing, secure messaging. Transport-agnostic (NFC plugs in via a `Transceiver` trait). | | [`mrz-parser`](crates/mrz-parser) | `crates/mrz-parser` | ICAO 9303 Machine Readable Zone (TD1 / TD2 / TD3) text parser — fields + check digits, country patterns, OCR-defect fixer. | | [`aamva`](crates/aamva) | `crates/aamva` | AAMVA DL/ID — PDF417 barcode scan + payload parser (US / Canadian driver's licenses). | | [`aadhaar-offline-kyc`](crates/aadhaar-offline-kyc) | `crates/aadhaar-offline-kyc` | Aadhaar offline e-KYC (India UIDAI) — Secure QR v2 + Paperless Offline e-KYC (ZIP/XML) with signature verification; Verhoeff number validation. | diff --git a/crates/dmrtd/Cargo.toml b/crates/dmrtd/Cargo.toml index 4d7b1f7..8176ddf 100644 --- a/crates/dmrtd/Cargo.toml +++ b/crates/dmrtd/Cargo.toml @@ -47,9 +47,10 @@ zeroize.workspace = true num-bigint.workspace = true num-traits.workspace = true rand.workspace = true -p256.workspace = true -p384.workspace = true -p521.workspace = true -bp256.workspace = true -bp384.workspace = true -elliptic-curve.workspace = true +p256 = { workspace = true } +p384 = { workspace = true } +p521 = { workspace = true } +p224 = { workspace = true } +bp256 = { workspace = true } +bp384 = { workspace = true } +elliptic-curve = { workspace = true } diff --git a/crates/dmrtd/README.md b/crates/dmrtd/README.md index 3e2d2cd..f3d7099 100644 --- a/crates/dmrtd/README.md +++ b/crates/dmrtd/README.md @@ -17,12 +17,12 @@ Android (Dart FFI), a desktop reader, or tests alike. | Keys (`DBAKey`, `CanKey`, `AccessKey` trait) | ✅ | | SM ciphers (3DES, AES) + `MrtdSM` wrapper | ✅ | | BAC — pure helpers, session keys, `BacSession` state machine | ✅ | -| PACE — helpers, `PaceSession` state machine (ECDH-GM on NIST P-256) | ✅ | +| PACE — helpers, `PaceSession` state machine (ECDH-GM on NIST & Brainpool) | ✅ | | DH-PACE (RFC 5114 groups) | ✅ | -| ECDH-PACE (NIST P-256 via `p256`) | ✅ | +| ECDH-PACE (NIST P-224/256/384/521 & Brainpool P-256/384) | ✅ | | `MrtdApi` (sync ICC orchestrator) + `Transceiver` trait | ✅ | | `Passport` — high-level BAC/PACE + file-read API | ✅ | -| Additional PACE curves (Brainpool etc.) | ⏳ pending | +| Additional PACE curves (Brainpool, NIST P-224/384/521) | ✅ | Reference vectors verified include ICAO 9303 Appendix A check digits + EF.COM + EF.DG1 TD1 sample, Appendix D.2 `K_seed`, Appendix D.3 BAC SSC + session keys diff --git a/crates/dmrtd/src/proto/domain_parameter.rs b/crates/dmrtd/src/proto/domain_parameter.rs index 312560d..2e3d708 100644 --- a/crates/dmrtd/src/proto/domain_parameter.rs +++ b/crates/dmrtd/src/proto/domain_parameter.rs @@ -100,7 +100,7 @@ pub static ICAO_DOMAIN_PARAMETERS: Lazy> = Lazy::n name: "NIST P-224 (secp224r1)", size: 224, kind: DomainParameterType::Ecp, - is_supported: false, + is_supported: true, }, DomainParameter { id: 11, @@ -197,8 +197,8 @@ mod tests { .map(|p| p.id) .collect(); supported.sort_unstable(); - // P-256 (12, ECDH) + RFC 5114 MODP/DH groups (0/1/2) + Brainpool + NIST P-384/521. - assert_eq!(supported, vec![0, 1, 2, 12, 13, 15, 16, 18, 23, 26]); + // P-256 (12, ECDH) + RFC 5114 MODP/DH groups (0/1/2) + Brainpool + NIST P-224/384/521. + assert_eq!(supported, vec![0, 1, 2, 10, 12, 13, 15, 16, 18, 23, 26]); } #[test] diff --git a/crates/dmrtd/src/proto/ecdh_pace.rs b/crates/dmrtd/src/proto/ecdh_pace.rs index 4fbad5d..9e2de70 100644 --- a/crates/dmrtd/src/proto/ecdh_pace.rs +++ b/crates/dmrtd/src/proto/ecdh_pace.rs @@ -509,6 +509,7 @@ impl_curve_ops!(legacy, NistP384Engine, p384, p384::NistP384, p384::SecretKey, p impl_curve_ops!(modern, BrainpoolP384r1Engine, bp384, bp384::r1::BrainpoolP384r1, bp384::r1::SecretKey, bp384::r1::Scalar, bp384::r1::ProjectivePoint, bp384::r1::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022648719277063462947702816912384784949216075932598370503046777"); impl_curve_ops!(modern, BrainpoolP384t1Engine, bp384, bp384::t1::BrainpoolP384t1, bp384::t1::SecretKey, bp384::t1::Scalar, bp384::t1::ProjectivePoint, bp384::t1::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022648719277063462947702816912384784949216075932598370503046777"); impl_curve_ops!(legacy, NistP521Engine, p521, p521::NistP521, p521::SecretKey, p521::Scalar, p521::ProjectivePoint, p521::AffinePoint, 66, b"6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449"); +impl_curve_ops!(legacy, NistP224Engine, p224, p224::NistP224, p224::SecretKey, p224::Scalar, p224::ProjectivePoint, p224::AffinePoint, 28, b"26959946667150639794667015087019625940457807714424391721682722368061"); #[derive(Debug)] pub enum ECDHPace { @@ -519,6 +520,7 @@ pub enum ECDHPace { BrainpoolP384r1(BrainpoolP384r1Engine), BrainpoolP384t1(BrainpoolP384t1Engine), NistP521(NistP521Engine), + NistP224(NistP224Engine), } impl ECDHPace { @@ -527,6 +529,7 @@ impl ECDHPace { return Err(ECDHPaceError::UnknownId(id)); } match id { + 10 => Ok(Self::NistP224(NistP224Engine::new())), 12 => Ok(Self::NistP256(NistP256Engine::new())), 13 => Ok(Self::BrainpoolP256r1(BrainpoolP256r1Engine::new())), 15 => Ok(Self::NistP384(NistP384Engine::new())), @@ -547,6 +550,7 @@ impl ECDHPace { Self::BrainpoolP384r1(e) => e.generate_key_pair(seed32), Self::BrainpoolP384t1(e) => e.generate_key_pair(seed32), Self::NistP521(e) => e.generate_key_pair(seed32), + Self::NistP224(e) => e.generate_key_pair(seed32), } } @@ -559,6 +563,7 @@ impl ECDHPace { Self::BrainpoolP384r1(e) => e.get_pub_key(), Self::BrainpoolP384t1(e) => e.get_pub_key(), Self::NistP521(e) => e.get_pub_key(), + Self::NistP224(e) => e.get_pub_key(), } } @@ -571,6 +576,7 @@ impl ECDHPace { Self::BrainpoolP384r1(e) => e.get_pub_key_ephemeral(), Self::BrainpoolP384t1(e) => e.get_pub_key_ephemeral(), Self::NistP521(e) => e.get_pub_key_ephemeral(), + Self::NistP224(e) => e.get_pub_key_ephemeral(), } } @@ -588,6 +594,7 @@ impl ECDHPace { Self::BrainpoolP384r1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), Self::BrainpoolP384t1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), Self::NistP521(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), + Self::NistP224(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), } } @@ -603,6 +610,7 @@ impl ECDHPace { Self::BrainpoolP384r1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), Self::BrainpoolP384t1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), Self::NistP521(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), + Self::NistP224(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), } } } @@ -631,7 +639,7 @@ mod tests { #[test] fn engine_constructs_for_all_supported_curves() { - for id in &[12, 13, 15, 16, 18, 23, 26] { + for id in &[10, 12, 13, 15, 16, 18, 23, 26] { let e = ECDHPace::new(*id).unwrap(); assert_eq!(e.get_pub_key().unwrap_err(), ECDHPaceError::NoPublicKey); } @@ -640,7 +648,7 @@ mod tests { #[test] fn seeded_key_pair_is_deterministic() { let seed = [0x11u8; 32]; - for id in &[12, 13, 15, 16, 18, 23, 26] { + for id in &[10, 12, 13, 15, 16, 18, 23, 26] { let mut a = ECDHPace::new(*id).unwrap(); let mut b = ECDHPace::new(*id).unwrap(); a.generate_key_pair(Some(&seed)).unwrap(); @@ -660,7 +668,7 @@ mod tests { #[test] fn shared_secret_is_symmetric_all_curves() { - for id in &[12, 13, 15, 16, 18, 23, 26] { + for id in &[10, 12, 13, 15, 16, 18, 23, 26] { let mut alice = ECDHPace::new(*id).unwrap(); let mut bob = ECDHPace::new(*id).unwrap(); alice.generate_key_pair(Some(&[0x01u8; 32])).unwrap(); From 5b3f5e89f89870819536c6bdc126953abe9cd59e Mon Sep 17 00:00:00 2001 From: Balamurali Pandranki Date: Tue, 14 Jul 2026 13:22:34 +0300 Subject: [PATCH 3/7] style: run cargo fmt --- crates/dmrtd/src/proto/ecdh_pace.rs | 173 ++++++++++++++++++++----- crates/dmrtd/src/proto/pace_session.rs | 4 +- 2 files changed, 142 insertions(+), 35 deletions(-) diff --git a/crates/dmrtd/src/proto/ecdh_pace.rs b/crates/dmrtd/src/proto/ecdh_pace.rs index 9e2de70..7dbca72 100644 --- a/crates/dmrtd/src/proto/ecdh_pace.rs +++ b/crates/dmrtd/src/proto/ecdh_pace.rs @@ -77,11 +77,16 @@ macro_rules! impl_curve_ops { } else { buf.copy_from_slice(&r_bytes[r_bytes.len() - $coord_len..]); } - let field_bytes = *<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::from_slice(&buf); - <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes).unwrap() + let field_bytes = + *<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::from_slice(&buf); + <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes) + .unwrap() } - pub fn generate_key_pair(&mut self, seed32: Option<&[u8]>) -> Result<(), ECDHPaceError> { + pub fn generate_key_pair( + &mut self, + seed32: Option<&[u8]>, + ) -> Result<(), ECDHPaceError> { let sk = match seed32 { None => { let mut rng = UnwrapErr(SysRng); @@ -173,7 +178,7 @@ macro_rules! impl_curve_ops { .ephemeral_priv .as_ref() .ok_or(ECDHPaceError::NoEphemeralPublicKey)?; - + let other_point = other.to_projective(); if bool::from(other_point.is_identity()) { return Err(ECDHPaceError::InfinityPoint); @@ -184,8 +189,9 @@ macro_rules! impl_curve_ops { } let pk = <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine( - shared.to_affine() - ).map_err(|_| ECDHPaceError::InfinityAgreement)?; + shared.to_affine(), + ) + .map_err(|_| ECDHPaceError::InfinityAgreement)?; let sec1_bytes = pk.to_sec1_bytes(); if sec1_bytes.len() != 1 + 2 * $coord_len || sec1_bytes[0] != 0x04 { return Err(ECDHPaceError::InvalidEncoding); @@ -213,9 +219,14 @@ macro_rules! impl_curve_ops { Ok(shared) } - fn point_to_pubkey_pace(&self, point: $projective) -> Result { + fn point_to_pubkey_pace( + &self, + point: $projective, + ) -> Result { let affine = point.to_affine(); - let pk = <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine(affine).map_err(|_| ECDHPaceError::InfinityPoint)?; + let pk = + <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine(affine) + .map_err(|_| ECDHPaceError::InfinityPoint)?; let sec1_bytes = pk.to_sec1_bytes(); if sec1_bytes.len() != 1 + 2 * $coord_len || sec1_bytes[0] != 0x04 { return Err(ECDHPaceError::InvalidEncoding); @@ -229,7 +240,10 @@ macro_rules! impl_curve_ops { )?) } - fn transform_public(&self, pub_key: &PublicKeyPace) -> Result<$curve_crate::elliptic_curve::PublicKey<$curve_type>, ECDHPaceError> { + fn transform_public( + &self, + pub_key: &PublicKeyPace, + ) -> Result<$curve_crate::elliptic_curve::PublicKey<$curve_type>, ECDHPaceError> { match pub_key { PublicKeyPace::Ecdh { x, y, .. } => { let mut x_bytes = vec![0u8; $coord_len]; @@ -245,7 +259,10 @@ macro_rules! impl_curve_ops { let mut bytes = vec![0x04]; bytes.extend_from_slice(&x_bytes); bytes.extend_from_slice(&y_bytes); - <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_sec1_bytes(&bytes).map_err(|_| ECDHPaceError::InvalidEncoding) + <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_sec1_bytes( + &bytes, + ) + .map_err(|_| ECDHPaceError::InvalidEncoding) } _ => Err(ECDHPaceError::InvalidEncoding), } @@ -285,14 +302,21 @@ macro_rules! impl_curve_ops { } else { buf.copy_from_slice(&r_bytes[r_bytes.len() - $coord_len..]); } - let field_bytes = *<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::from_slice(&buf); - <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes).unwrap() + let field_bytes = + *<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::from_slice(&buf); + <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes) + .unwrap() } - pub fn generate_key_pair(&mut self, seed32: Option<&[u8]>) -> Result<(), ECDHPaceError> { + pub fn generate_key_pair( + &mut self, + seed32: Option<&[u8]>, + ) -> Result<(), ECDHPaceError> { struct RngBridge<'a, R>(&'a mut R); - impl<'a, R: rand::rand_core::RngCore> $curve_crate::elliptic_curve::rand_core::RngCore for RngBridge<'a, R> { + impl<'a, R: rand::rand_core::RngCore> + $curve_crate::elliptic_curve::rand_core::RngCore for RngBridge<'a, R> + { fn next_u32(&mut self) -> u32 { self.0.next_u32() } @@ -302,12 +326,22 @@ macro_rules! impl_curve_ops { fn fill_bytes(&mut self, dest: &mut [u8]) { self.0.fill_bytes(dest) } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), $curve_crate::elliptic_curve::rand_core::Error> { - self.0.try_fill_bytes(dest).map_err(|_| $curve_crate::elliptic_curve::rand_core::Error::from(core::num::NonZeroU32::new(1).unwrap())) + fn try_fill_bytes( + &mut self, + dest: &mut [u8], + ) -> Result<(), $curve_crate::elliptic_curve::rand_core::Error> { + self.0.try_fill_bytes(dest).map_err(|_| { + $curve_crate::elliptic_curve::rand_core::Error::from( + core::num::NonZeroU32::new(1).unwrap(), + ) + }) } } - impl<'a, R: rand::rand_core::CryptoRng> $curve_crate::elliptic_curve::rand_core::CryptoRng for RngBridge<'a, R> {} + impl<'a, R: rand::rand_core::CryptoRng> + $curve_crate::elliptic_curve::rand_core::CryptoRng for RngBridge<'a, R> + { + } let sk = match seed32 { None => { @@ -357,7 +391,9 @@ macro_rules! impl_curve_ops { struct RngBridge<'a, R>(&'a mut R); - impl<'a, R: rand::rand_core::RngCore> $curve_crate::elliptic_curve::rand_core::RngCore for RngBridge<'a, R> { + impl<'a, R: rand::rand_core::RngCore> + $curve_crate::elliptic_curve::rand_core::RngCore for RngBridge<'a, R> + { fn next_u32(&mut self) -> u32 { self.0.next_u32() } @@ -367,12 +403,22 @@ macro_rules! impl_curve_ops { fn fill_bytes(&mut self, dest: &mut [u8]) { self.0.fill_bytes(dest) } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), $curve_crate::elliptic_curve::rand_core::Error> { - self.0.try_fill_bytes(dest).map_err(|_| $curve_crate::elliptic_curve::rand_core::Error::from(core::num::NonZeroU32::new(1).unwrap())) + fn try_fill_bytes( + &mut self, + dest: &mut [u8], + ) -> Result<(), $curve_crate::elliptic_curve::rand_core::Error> { + self.0.try_fill_bytes(dest).map_err(|_| { + $curve_crate::elliptic_curve::rand_core::Error::from( + core::num::NonZeroU32::new(1).unwrap(), + ) + }) } } - impl<'a, R: rand::rand_core::CryptoRng> $curve_crate::elliptic_curve::rand_core::CryptoRng for RngBridge<'a, R> {} + impl<'a, R: rand::rand_core::CryptoRng> + $curve_crate::elliptic_curve::rand_core::CryptoRng for RngBridge<'a, R> + { + } let other = self.transform_public(other_pub_key)?; let h = self.compute_shared_point(&other)?; @@ -421,7 +467,7 @@ macro_rules! impl_curve_ops { .ephemeral_priv .as_ref() .ok_or(ECDHPaceError::NoEphemeralPublicKey)?; - + let other_point = other.to_projective(); if bool::from(other_point.is_identity()) { return Err(ECDHPaceError::InfinityPoint); @@ -432,8 +478,9 @@ macro_rules! impl_curve_ops { } let pk = <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine( - shared.to_affine() - ).map_err(|_| ECDHPaceError::InfinityAgreement)?; + shared.to_affine(), + ) + .map_err(|_| ECDHPaceError::InfinityAgreement)?; let sec1_bytes = pk.to_sec1_bytes(); if sec1_bytes.len() != 1 + 2 * $coord_len || sec1_bytes[0] != 0x04 { return Err(ECDHPaceError::InvalidEncoding); @@ -461,9 +508,14 @@ macro_rules! impl_curve_ops { Ok(shared) } - fn point_to_pubkey_pace(&self, point: $projective) -> Result { + fn point_to_pubkey_pace( + &self, + point: $projective, + ) -> Result { let affine = point.to_affine(); - let pk = <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine(affine).map_err(|_| ECDHPaceError::InfinityPoint)?; + let pk = + <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_affine(affine) + .map_err(|_| ECDHPaceError::InfinityPoint)?; let sec1_bytes = pk.to_sec1_bytes(); if sec1_bytes.len() != 1 + 2 * $coord_len || sec1_bytes[0] != 0x04 { return Err(ECDHPaceError::InvalidEncoding); @@ -477,7 +529,10 @@ macro_rules! impl_curve_ops { )?) } - fn transform_public(&self, pub_key: &PublicKeyPace) -> Result<$curve_crate::elliptic_curve::PublicKey<$curve_type>, ECDHPaceError> { + fn transform_public( + &self, + pub_key: &PublicKeyPace, + ) -> Result<$curve_crate::elliptic_curve::PublicKey<$curve_type>, ECDHPaceError> { match pub_key { PublicKeyPace::Ecdh { x, y, .. } => { let mut x_bytes = vec![0u8; $coord_len]; @@ -493,7 +548,10 @@ macro_rules! impl_curve_ops { let mut bytes = vec![0x04]; bytes.extend_from_slice(&x_bytes); bytes.extend_from_slice(&y_bytes); - <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_sec1_bytes(&bytes).map_err(|_| ECDHPaceError::InvalidEncoding) + <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_sec1_bytes( + &bytes, + ) + .map_err(|_| ECDHPaceError::InvalidEncoding) } _ => Err(ECDHPaceError::InvalidEncoding), } @@ -502,14 +560,58 @@ macro_rules! impl_curve_ops { }; } -impl_curve_ops!(modern, NistP256Engine, p256, p256::NistP256, p256::SecretKey, p256::Scalar, p256::ProjectivePoint, p256::AffinePoint, 32, b"115792089210356248762485316520336594248464673199859591410292408985888941275213"); -impl_curve_ops!(modern, BrainpoolP256r1Engine, bp256, bp256::r1::BrainpoolP256r1, bp256::r1::SecretKey, bp256::r1::Scalar, bp256::r1::ProjectivePoint, bp256::r1::AffinePoint, 32, b"115792089237316195423570985008687907852837564279074904382605163141518161494337"); -impl_curve_ops!(modern, BrainpoolP256t1Engine, bp256, bp256::t1::BrainpoolP256t1, bp256::t1::SecretKey, bp256::t1::Scalar, bp256::t1::ProjectivePoint, bp256::t1::AffinePoint, 32, b"115792089237316195423570985008687907852837564279074904382605163141518161494337"); +impl_curve_ops!( + modern, + NistP256Engine, + p256, + p256::NistP256, + p256::SecretKey, + p256::Scalar, + p256::ProjectivePoint, + p256::AffinePoint, + 32, + b"115792089210356248762485316520336594248464673199859591410292408985888941275213" +); +impl_curve_ops!( + modern, + BrainpoolP256r1Engine, + bp256, + bp256::r1::BrainpoolP256r1, + bp256::r1::SecretKey, + bp256::r1::Scalar, + bp256::r1::ProjectivePoint, + bp256::r1::AffinePoint, + 32, + b"115792089237316195423570985008687907852837564279074904382605163141518161494337" +); +impl_curve_ops!( + modern, + BrainpoolP256t1Engine, + bp256, + bp256::t1::BrainpoolP256t1, + bp256::t1::SecretKey, + bp256::t1::Scalar, + bp256::t1::ProjectivePoint, + bp256::t1::AffinePoint, + 32, + b"115792089237316195423570985008687907852837564279074904382605163141518161494337" +); impl_curve_ops!(legacy, NistP384Engine, p384, p384::NistP384, p384::SecretKey, p384::Scalar, p384::ProjectivePoint, p384::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022646276856019566907421111663185381673890280521949313936996367"); impl_curve_ops!(modern, BrainpoolP384r1Engine, bp384, bp384::r1::BrainpoolP384r1, bp384::r1::SecretKey, bp384::r1::Scalar, bp384::r1::ProjectivePoint, bp384::r1::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022648719277063462947702816912384784949216075932598370503046777"); impl_curve_ops!(modern, BrainpoolP384t1Engine, bp384, bp384::t1::BrainpoolP384t1, bp384::t1::SecretKey, bp384::t1::Scalar, bp384::t1::ProjectivePoint, bp384::t1::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022648719277063462947702816912384784949216075932598370503046777"); impl_curve_ops!(legacy, NistP521Engine, p521, p521::NistP521, p521::SecretKey, p521::Scalar, p521::ProjectivePoint, p521::AffinePoint, 66, b"6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449"); -impl_curve_ops!(legacy, NistP224Engine, p224, p224::NistP224, p224::SecretKey, p224::Scalar, p224::ProjectivePoint, p224::AffinePoint, 28, b"26959946667150639794667015087019625940457807714424391721682722368061"); +impl_curve_ops!( + legacy, + NistP224Engine, + p224, + p224::NistP224, + p224::SecretKey, + p224::Scalar, + p224::ProjectivePoint, + p224::AffinePoint, + 28, + b"26959946667150639794667015087019625940457807714424391721682722368061" +); #[derive(Debug)] pub enum ECDHPace { @@ -679,8 +781,11 @@ mod tests { // simulated map and ephemeral gen let nonce = [0xAAu8; 16]; - alice.map_and_generate_ephemeral(&bob_pk, &nonce, Some(&[0x03u8; 32])).unwrap(); - bob.map_and_generate_ephemeral(&alice_pk, &nonce, Some(&[0x04u8; 32])).unwrap(); + alice + .map_and_generate_ephemeral(&bob_pk, &nonce, Some(&[0x03u8; 32])) + .unwrap(); + bob.map_and_generate_ephemeral(&alice_pk, &nonce, Some(&[0x04u8; 32])) + .unwrap(); let alice_eph_pk = alice.get_pub_key_ephemeral().unwrap(); let bob_eph_pk = bob.get_pub_key_ephemeral().unwrap(); diff --git a/crates/dmrtd/src/proto/pace_session.rs b/crates/dmrtd/src/proto/pace_session.rs index 9337f16..642a926 100644 --- a/crates/dmrtd/src/proto/pace_session.rs +++ b/crates/dmrtd/src/proto/pace_session.rs @@ -286,7 +286,9 @@ impl PaceSession { return Err(PaceSessionError::UnsupportedMapping(protocol.mapping_type)); } let supported = domain_parameter::get(parameter_id) - .map(|entry| entry.is_supported && entry.kind == domain_parameter::DomainParameterType::Ecp) + .map(|entry| { + entry.is_supported && entry.kind == domain_parameter::DomainParameterType::Ecp + }) .unwrap_or(false); if !supported { return Err(PaceSessionError::UnsupportedCurve(parameter_id)); From 248ee4fa44ca9bc3ecabf06fbbb6012fe9dea517 Mon Sep 17 00:00:00 2001 From: Balamurali Pandranki Date: Tue, 14 Jul 2026 13:28:35 +0300 Subject: [PATCH 4/7] fix: use rand::Rng instead of deprecated rand::rand_core::RngCore --- crates/dmrtd/src/proto/ecdh_pace.rs | 30 ++++++++++++----------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/dmrtd/src/proto/ecdh_pace.rs b/crates/dmrtd/src/proto/ecdh_pace.rs index 7dbca72..fa8aaf3 100644 --- a/crates/dmrtd/src/proto/ecdh_pace.rs +++ b/crates/dmrtd/src/proto/ecdh_pace.rs @@ -314,8 +314,8 @@ macro_rules! impl_curve_ops { ) -> Result<(), ECDHPaceError> { struct RngBridge<'a, R>(&'a mut R); - impl<'a, R: rand::rand_core::RngCore> - $curve_crate::elliptic_curve::rand_core::RngCore for RngBridge<'a, R> + impl<'a, R: rand::Rng> $curve_crate::elliptic_curve::rand_core::RngCore + for RngBridge<'a, R> { fn next_u32(&mut self) -> u32 { self.0.next_u32() @@ -330,16 +330,13 @@ macro_rules! impl_curve_ops { &mut self, dest: &mut [u8], ) -> Result<(), $curve_crate::elliptic_curve::rand_core::Error> { - self.0.try_fill_bytes(dest).map_err(|_| { - $curve_crate::elliptic_curve::rand_core::Error::from( - core::num::NonZeroU32::new(1).unwrap(), - ) - }) + self.0.fill_bytes(dest); + Ok(()) } } - impl<'a, R: rand::rand_core::CryptoRng> - $curve_crate::elliptic_curve::rand_core::CryptoRng for RngBridge<'a, R> + impl<'a, R: rand::CryptoRng> $curve_crate::elliptic_curve::rand_core::CryptoRng + for RngBridge<'a, R> { } @@ -391,8 +388,8 @@ macro_rules! impl_curve_ops { struct RngBridge<'a, R>(&'a mut R); - impl<'a, R: rand::rand_core::RngCore> - $curve_crate::elliptic_curve::rand_core::RngCore for RngBridge<'a, R> + impl<'a, R: rand::Rng> $curve_crate::elliptic_curve::rand_core::RngCore + for RngBridge<'a, R> { fn next_u32(&mut self) -> u32 { self.0.next_u32() @@ -407,16 +404,13 @@ macro_rules! impl_curve_ops { &mut self, dest: &mut [u8], ) -> Result<(), $curve_crate::elliptic_curve::rand_core::Error> { - self.0.try_fill_bytes(dest).map_err(|_| { - $curve_crate::elliptic_curve::rand_core::Error::from( - core::num::NonZeroU32::new(1).unwrap(), - ) - }) + self.0.fill_bytes(dest); + Ok(()) } } - impl<'a, R: rand::rand_core::CryptoRng> - $curve_crate::elliptic_curve::rand_core::CryptoRng for RngBridge<'a, R> + impl<'a, R: rand::CryptoRng> $curve_crate::elliptic_curve::rand_core::CryptoRng + for RngBridge<'a, R> { } From f3ae7ccc7900a7476407184796cbcd96642aa746 Mon Sep 17 00:00:00 2001 From: Balamurali Pandranki Date: Tue, 14 Jul 2026 13:36:28 +0300 Subject: [PATCH 5/7] fix: resolve all remaining deprecation and unused lints under strict warnings --- crates/dmrtd/src/proto/ecdh_pace.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/dmrtd/src/proto/ecdh_pace.rs b/crates/dmrtd/src/proto/ecdh_pace.rs index fa8aaf3..971922a 100644 --- a/crates/dmrtd/src/proto/ecdh_pace.rs +++ b/crates/dmrtd/src/proto/ecdh_pace.rs @@ -9,6 +9,7 @@ //! public key. use num_bigint::BigUint; +use p256::elliptic_curve::Generate; use rand::rand_core::UnwrapErr; use rand::{rngs::StdRng, rngs::SysRng, Rng, SeedableRng}; use thiserror::Error; @@ -17,6 +18,7 @@ use crate::proto::domain_parameter; use crate::proto::public_key_pace::PublicKeyPace; /// ICAO domain-parameter id for NIST P-256. +#[allow(dead_code)] pub const NIST_P256_ID: u32 = 12; /// Error returned by [`ECDHPace`] operations. @@ -77,8 +79,9 @@ macro_rules! impl_curve_ops { } else { buf.copy_from_slice(&r_bytes[r_bytes.len() - $coord_len..]); } - let field_bytes = - *<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::from_slice(&buf); + let mut field_bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); + field_bytes.copy_from_slice(buf.as_slice()); <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes) .unwrap() } @@ -88,10 +91,7 @@ macro_rules! impl_curve_ops { seed32: Option<&[u8]>, ) -> Result<(), ECDHPaceError> { let sk = match seed32 { - None => { - let mut rng = UnwrapErr(SysRng); - <$secret_key>::random(&mut rng) - } + None => <$secret_key>::generate(), Some(s) if s.len() == 32 => { let mut seed_arr = [0u8; 32]; seed_arr.copy_from_slice(s); @@ -141,8 +141,7 @@ macro_rules! impl_curve_ops { // Sample ephemeral scalar let scalar = match seed32 { None => { - let mut rng = UnwrapErr(SysRng); - let sk = <$secret_key>::random(&mut rng); + let sk = <$secret_key>::generate(); self.scalar_from_bytes(&sk.to_bytes()) } Some(s) if s.len() == 32 => { @@ -302,8 +301,9 @@ macro_rules! impl_curve_ops { } else { buf.copy_from_slice(&r_bytes[r_bytes.len() - $coord_len..]); } - let field_bytes = - *<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::from_slice(&buf); + let mut field_bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); + field_bytes.copy_from_slice(buf.as_slice()); <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes) .unwrap() } From 2ae390c96ef3119c7fb769c3f7b4af2f0d998ef4 Mon Sep 17 00:00:00 2001 From: Balamurali Pandranki Date: Tue, 14 Jul 2026 13:42:13 +0300 Subject: [PATCH 6/7] chore: bump version to 0.3.0 and resolve code review comments --- Cargo.lock | 10 ++-- Cargo.toml | 2 +- crates/dmrtd/src/proto/domain_parameter.rs | 20 ++------ crates/dmrtd/src/proto/ecdh_pace.rs | 59 ++++++++-------------- crates/dmrtd/src/proto/pace_session.rs | 3 +- 5 files changed, 31 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38aba4b..5bde90b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "aadhaar-offline-kyc" -version = "0.2.0" +version = "0.3.0" dependencies = [ "base64", "chrono", @@ -23,7 +23,7 @@ dependencies = [ [[package]] name = "aamva" -version = "0.2.0" +version = "0.3.0" dependencies = [ "chrono", "image", @@ -768,7 +768,7 @@ dependencies = [ [[package]] name = "dmrtd" -version = "0.2.0" +version = "0.3.0" dependencies = [ "aes", "asn1", @@ -1317,7 +1317,7 @@ checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" [[package]] name = "incometax-pan-qr" -version = "0.2.0" +version = "0.3.0" dependencies = [ "base64", "flate2", @@ -1510,7 +1510,7 @@ dependencies = [ [[package]] name = "mrz-parser" -version = "0.2.0" +version = "0.3.0" dependencies = [ "chrono", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 78cf698..932dff7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ resolver = "2" members = ["crates/*"] [workspace.package] -version = "0.2.0" +version = "0.3.0" authors = ["Balamurali Pandranki "] edition = "2021" # 1.92 is required by the `xml-sec` dependency (Aadhaar XML-DSig verification); diff --git a/crates/dmrtd/src/proto/domain_parameter.rs b/crates/dmrtd/src/proto/domain_parameter.rs index 2e3d708..a1851a5 100644 --- a/crates/dmrtd/src/proto/domain_parameter.rs +++ b/crates/dmrtd/src/proto/domain_parameter.rs @@ -158,20 +158,6 @@ pub static ICAO_DOMAIN_PARAMETERS: Lazy> = Lazy::n kind: DomainParameterType::Ecp, is_supported: true, }, - DomainParameter { - id: 23, - name: "BrainpoolP256t1", - size: 256, - kind: DomainParameterType::Ecp, - is_supported: true, - }, - DomainParameter { - id: 26, - name: "BrainpoolP384t1", - size: 384, - kind: DomainParameterType::Ecp, - is_supported: true, - }, ]; entries.into_iter().map(|p| (p.id, p)).collect() }); @@ -198,12 +184,12 @@ mod tests { .collect(); supported.sort_unstable(); // P-256 (12, ECDH) + RFC 5114 MODP/DH groups (0/1/2) + Brainpool + NIST P-224/384/521. - assert_eq!(supported, vec![0, 1, 2, 10, 12, 13, 15, 16, 18, 23, 26]); + assert_eq!(supported, vec![0, 1, 2, 10, 12, 13, 15, 16, 18]); } #[test] - fn table_has_16_entries() { - assert_eq!(ICAO_DOMAIN_PARAMETERS.len(), 16); + fn table_has_14_entries() { + assert_eq!(ICAO_DOMAIN_PARAMETERS.len(), 14); } #[test] diff --git a/crates/dmrtd/src/proto/ecdh_pace.rs b/crates/dmrtd/src/proto/ecdh_pace.rs index 971922a..fa13677 100644 --- a/crates/dmrtd/src/proto/ecdh_pace.rs +++ b/crates/dmrtd/src/proto/ecdh_pace.rs @@ -18,7 +18,7 @@ use crate::proto::domain_parameter; use crate::proto::public_key_pace::PublicKeyPace; /// ICAO domain-parameter id for NIST P-256. -#[allow(dead_code)] +#[cfg(test)] pub const NIST_P256_ID: u32 = 12; /// Error returned by [`ECDHPace`] operations. @@ -69,7 +69,11 @@ macro_rules! impl_curve_ops { } fn scalar_from_bytes(&self, bytes: &[u8]) -> $scalar { - let n = BigUint::parse_bytes($order_str, 10).unwrap(); + #[allow(unused_imports)] + use $curve_crate::elliptic_curve::bigint::Encoding; + let modulus_bytes = + <$curve_type as $curve_crate::elliptic_curve::Curve>::ORDER.to_be_bytes(); + let n = BigUint::from_bytes_be(modulus_bytes.as_ref()); let val = BigUint::from_bytes_be(bytes); let reduced = val % &n; let r_bytes = reduced.to_bytes_be(); @@ -142,7 +146,7 @@ macro_rules! impl_curve_ops { let scalar = match seed32 { None => { let sk = <$secret_key>::generate(); - self.scalar_from_bytes(&sk.to_bytes()) + *sk.to_nonzero_scalar() } Some(s) if s.len() == 32 => { let mut seed_arr = [0u8; 32]; @@ -152,7 +156,7 @@ macro_rules! impl_curve_ops { let mut bytes = vec![0u8; $coord_len]; rng.fill_bytes(&mut bytes); if let Ok(sk) = <$secret_key>::from_slice(&bytes) { - break self.scalar_from_bytes(&sk.to_bytes()); + break *sk.to_nonzero_scalar(); } } } @@ -206,7 +210,7 @@ macro_rules! impl_curve_ops { use $curve_crate::elliptic_curve::group::Group; let sk = self.priv_key.as_ref().ok_or(ECDHPaceError::NoPublicKey)?; - let scalar = self.scalar_from_bytes(&sk.to_bytes()); + let scalar = *sk.to_nonzero_scalar(); let other_point = other.to_projective(); if bool::from(other_point.is_identity()) { return Err(ECDHPaceError::InfinityPoint); @@ -291,7 +295,11 @@ macro_rules! impl_curve_ops { } fn scalar_from_bytes(&self, bytes: &[u8]) -> $scalar { - let n = BigUint::parse_bytes($order_str, 10).unwrap(); + #[allow(unused_imports)] + use $curve_crate::elliptic_curve::bigint::Encoding; + let modulus_bytes = + <$curve_type as $curve_crate::elliptic_curve::Curve>::ORDER.to_be_bytes(); + let n = BigUint::from_bytes_be(modulus_bytes.as_ref()); let val = BigUint::from_bytes_be(bytes); let reduced = val % &n; let r_bytes = reduced.to_bytes_be(); @@ -426,7 +434,7 @@ macro_rules! impl_curve_ops { let mut sys_rng = UnwrapErr(SysRng); let mut rng = RngBridge(&mut sys_rng); let sk = <$secret_key>::random(&mut rng); - self.scalar_from_bytes(&sk.to_bytes()) + *sk.to_nonzero_scalar() } Some(s) if s.len() == 32 => { let mut seed_arr = [0u8; 32]; @@ -436,7 +444,7 @@ macro_rules! impl_curve_ops { let mut bytes = vec![0u8; $coord_len]; rng.fill_bytes(&mut bytes); if let Ok(sk) = <$secret_key>::from_slice(&bytes) { - break self.scalar_from_bytes(&sk.to_bytes()); + break *sk.to_nonzero_scalar(); } } } @@ -490,7 +498,7 @@ macro_rules! impl_curve_ops { use $curve_crate::elliptic_curve::group::Group; let sk = self.priv_key.as_ref().ok_or(ECDHPaceError::NoPublicKey)?; - let scalar = self.scalar_from_bytes(&sk.to_bytes()); + let scalar = *sk.to_nonzero_scalar(); let other_point = other.to_projective(); if bool::from(other_point.is_identity()) { return Err(ECDHPaceError::InfinityPoint); @@ -578,21 +586,8 @@ impl_curve_ops!( 32, b"115792089237316195423570985008687907852837564279074904382605163141518161494337" ); -impl_curve_ops!( - modern, - BrainpoolP256t1Engine, - bp256, - bp256::t1::BrainpoolP256t1, - bp256::t1::SecretKey, - bp256::t1::Scalar, - bp256::t1::ProjectivePoint, - bp256::t1::AffinePoint, - 32, - b"115792089237316195423570985008687907852837564279074904382605163141518161494337" -); impl_curve_ops!(legacy, NistP384Engine, p384, p384::NistP384, p384::SecretKey, p384::Scalar, p384::ProjectivePoint, p384::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022646276856019566907421111663185381673890280521949313936996367"); impl_curve_ops!(modern, BrainpoolP384r1Engine, bp384, bp384::r1::BrainpoolP384r1, bp384::r1::SecretKey, bp384::r1::Scalar, bp384::r1::ProjectivePoint, bp384::r1::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022648719277063462947702816912384784949216075932598370503046777"); -impl_curve_ops!(modern, BrainpoolP384t1Engine, bp384, bp384::t1::BrainpoolP384t1, bp384::t1::SecretKey, bp384::t1::Scalar, bp384::t1::ProjectivePoint, bp384::t1::AffinePoint, 48, b"39402006196394479212279040100143613805079739270464620022648719277063462947702816912384784949216075932598370503046777"); impl_curve_ops!(legacy, NistP521Engine, p521, p521::NistP521, p521::SecretKey, p521::Scalar, p521::ProjectivePoint, p521::AffinePoint, 66, b"6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449"); impl_curve_ops!( legacy, @@ -611,10 +606,8 @@ impl_curve_ops!( pub enum ECDHPace { NistP256(NistP256Engine), BrainpoolP256r1(BrainpoolP256r1Engine), - BrainpoolP256t1(BrainpoolP256t1Engine), NistP384(NistP384Engine), BrainpoolP384r1(BrainpoolP384r1Engine), - BrainpoolP384t1(BrainpoolP384t1Engine), NistP521(NistP521Engine), NistP224(NistP224Engine), } @@ -631,8 +624,6 @@ impl ECDHPace { 15 => Ok(Self::NistP384(NistP384Engine::new())), 16 => Ok(Self::BrainpoolP384r1(BrainpoolP384r1Engine::new())), 18 => Ok(Self::NistP521(NistP521Engine::new())), - 23 => Ok(Self::BrainpoolP256t1(BrainpoolP256t1Engine::new())), - 26 => Ok(Self::BrainpoolP384t1(BrainpoolP384t1Engine::new())), _ => Err(ECDHPaceError::UnsupportedCurve(id)), } } @@ -641,10 +632,8 @@ impl ECDHPace { match self { Self::NistP256(e) => e.generate_key_pair(seed32), Self::BrainpoolP256r1(e) => e.generate_key_pair(seed32), - Self::BrainpoolP256t1(e) => e.generate_key_pair(seed32), Self::NistP384(e) => e.generate_key_pair(seed32), Self::BrainpoolP384r1(e) => e.generate_key_pair(seed32), - Self::BrainpoolP384t1(e) => e.generate_key_pair(seed32), Self::NistP521(e) => e.generate_key_pair(seed32), Self::NistP224(e) => e.generate_key_pair(seed32), } @@ -654,10 +643,8 @@ impl ECDHPace { match self { Self::NistP256(e) => e.get_pub_key(), Self::BrainpoolP256r1(e) => e.get_pub_key(), - Self::BrainpoolP256t1(e) => e.get_pub_key(), Self::NistP384(e) => e.get_pub_key(), Self::BrainpoolP384r1(e) => e.get_pub_key(), - Self::BrainpoolP384t1(e) => e.get_pub_key(), Self::NistP521(e) => e.get_pub_key(), Self::NistP224(e) => e.get_pub_key(), } @@ -667,10 +654,8 @@ impl ECDHPace { match self { Self::NistP256(e) => e.get_pub_key_ephemeral(), Self::BrainpoolP256r1(e) => e.get_pub_key_ephemeral(), - Self::BrainpoolP256t1(e) => e.get_pub_key_ephemeral(), Self::NistP384(e) => e.get_pub_key_ephemeral(), Self::BrainpoolP384r1(e) => e.get_pub_key_ephemeral(), - Self::BrainpoolP384t1(e) => e.get_pub_key_ephemeral(), Self::NistP521(e) => e.get_pub_key_ephemeral(), Self::NistP224(e) => e.get_pub_key_ephemeral(), } @@ -685,10 +670,8 @@ impl ECDHPace { match self { Self::NistP256(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), Self::BrainpoolP256r1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), - Self::BrainpoolP256t1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), Self::NistP384(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), Self::BrainpoolP384r1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), - Self::BrainpoolP384t1(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), Self::NistP521(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), Self::NistP224(e) => e.map_and_generate_ephemeral(other_pub_key, nonce, seed32), } @@ -701,10 +684,8 @@ impl ECDHPace { match self { Self::NistP256(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), Self::BrainpoolP256r1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), - Self::BrainpoolP256t1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), Self::NistP384(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), Self::BrainpoolP384r1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), - Self::BrainpoolP384t1(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), Self::NistP521(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), Self::NistP224(e) => e.get_ephemeral_shared_seed(other_ephemeral_pub_key), } @@ -735,7 +716,7 @@ mod tests { #[test] fn engine_constructs_for_all_supported_curves() { - for id in &[10, 12, 13, 15, 16, 18, 23, 26] { + for id in &[10, 12, 13, 15, 16, 18] { let e = ECDHPace::new(*id).unwrap(); assert_eq!(e.get_pub_key().unwrap_err(), ECDHPaceError::NoPublicKey); } @@ -744,7 +725,7 @@ mod tests { #[test] fn seeded_key_pair_is_deterministic() { let seed = [0x11u8; 32]; - for id in &[10, 12, 13, 15, 16, 18, 23, 26] { + for id in &[10, 12, 13, 15, 16, 18] { let mut a = ECDHPace::new(*id).unwrap(); let mut b = ECDHPace::new(*id).unwrap(); a.generate_key_pair(Some(&seed)).unwrap(); @@ -764,7 +745,7 @@ mod tests { #[test] fn shared_secret_is_symmetric_all_curves() { - for id in &[10, 12, 13, 15, 16, 18, 23, 26] { + for id in &[10, 12, 13, 15, 16, 18] { let mut alice = ECDHPace::new(*id).unwrap(); let mut bob = ECDHPace::new(*id).unwrap(); alice.generate_key_pair(Some(&[0x01u8; 32])).unwrap(); diff --git a/crates/dmrtd/src/proto/pace_session.rs b/crates/dmrtd/src/proto/pace_session.rs index 642a926..c86d822 100644 --- a/crates/dmrtd/src/proto/pace_session.rs +++ b/crates/dmrtd/src/proto/pace_session.rs @@ -41,7 +41,7 @@ use crate::proto::access_key::AccessKey; use crate::proto::aes_smcipher::AesSmCipher; use crate::proto::dh_pace::{self, DHPace, DHPaceError}; use crate::proto::domain_parameter; -use crate::proto::ecdh_pace::{ECDHPace, ECDHPaceError, NIST_P256_ID}; +use crate::proto::ecdh_pace::{ECDHPace, ECDHPaceError}; use crate::proto::iso7816::command_apdu::CommandApdu; use crate::proto::iso7816::iso7816::{cla, ins}; use crate::proto::iso7816::response_apdu::{ResponseApdu, StatusWord}; @@ -582,6 +582,7 @@ mod tests { use crate::crypto::aes::{AesCipher, BlockCipherMode, AES_BLOCK_SIZE}; use crate::lds::tlv::Tlv; use crate::proto::can_key::CanKey; + use crate::proto::ecdh_pace::NIST_P256_ID; fn ecdh_gm_aes128_oid() -> OiePaceProtocol { OiePaceProtocol::new( From f3b71ea0ca3c5d00dbafded70a8594afcba6c686 Mon Sep 17 00:00:00 2001 From: Balamurali Pandranki Date: Tue, 14 Jul 2026 13:53:57 +0300 Subject: [PATCH 7/7] perf: optimize heap allocations and fix review comments --- crates/dmrtd/src/proto/domain_parameter.rs | 2 +- crates/dmrtd/src/proto/ecdh_pace.rs | 64 ++++++++++++---------- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/crates/dmrtd/src/proto/domain_parameter.rs b/crates/dmrtd/src/proto/domain_parameter.rs index a1851a5..9ae4489 100644 --- a/crates/dmrtd/src/proto/domain_parameter.rs +++ b/crates/dmrtd/src/proto/domain_parameter.rs @@ -176,7 +176,7 @@ mod tests { use super::*; #[test] - fn supported_entries_are_p256_and_rfc5114_dh_groups() { + fn supported_entries_include_brainpool_and_nist_curves() { let mut supported: Vec = ICAO_DOMAIN_PARAMETERS .values() .filter(|p| p.is_supported) diff --git a/crates/dmrtd/src/proto/ecdh_pace.rs b/crates/dmrtd/src/proto/ecdh_pace.rs index fa13677..55d031f 100644 --- a/crates/dmrtd/src/proto/ecdh_pace.rs +++ b/crates/dmrtd/src/proto/ecdh_pace.rs @@ -77,15 +77,14 @@ macro_rules! impl_curve_ops { let val = BigUint::from_bytes_be(bytes); let reduced = val % &n; let r_bytes = reduced.to_bytes_be(); - let mut buf = vec![0u8; $coord_len]; - if r_bytes.len() <= $coord_len { - buf[$coord_len - r_bytes.len()..].copy_from_slice(&r_bytes); - } else { - buf.copy_from_slice(&r_bytes[r_bytes.len() - $coord_len..]); - } let mut field_bytes = <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); - field_bytes.copy_from_slice(buf.as_slice()); + let len = field_bytes.len(); + if r_bytes.len() <= len { + field_bytes[len - r_bytes.len()..].copy_from_slice(&r_bytes); + } else { + field_bytes.copy_from_slice(&r_bytes[r_bytes.len() - len..]); + } <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes) .unwrap() } @@ -100,9 +99,10 @@ macro_rules! impl_curve_ops { let mut seed_arr = [0u8; 32]; seed_arr.copy_from_slice(s); let mut rng = StdRng::from_seed(seed_arr); + let mut bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); loop { - let mut bytes = vec![0u8; $coord_len]; - rng.fill_bytes(&mut bytes); + rng.fill_bytes(&mut bytes[..]); if let Ok(sk) = <$secret_key>::from_slice(&bytes) { break sk; } @@ -152,9 +152,10 @@ macro_rules! impl_curve_ops { let mut seed_arr = [0u8; 32]; seed_arr.copy_from_slice(s); let mut rng = StdRng::from_seed(seed_arr); + let mut bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); loop { - let mut bytes = vec![0u8; $coord_len]; - rng.fill_bytes(&mut bytes); + rng.fill_bytes(&mut bytes[..]); if let Ok(sk) = <$secret_key>::from_slice(&bytes) { break *sk.to_nonzero_scalar(); } @@ -249,8 +250,10 @@ macro_rules! impl_curve_ops { ) -> Result<$curve_crate::elliptic_curve::PublicKey<$curve_type>, ECDHPaceError> { match pub_key { PublicKeyPace::Ecdh { x, y, .. } => { - let mut x_bytes = vec![0u8; $coord_len]; - let mut y_bytes = vec![0u8; $coord_len]; + let mut x_bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); + let mut y_bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); let x_be = x.to_bytes_be(); let y_be = y.to_bytes_be(); if x_be.len() > $coord_len || y_be.len() > $coord_len { @@ -259,7 +262,8 @@ macro_rules! impl_curve_ops { x_bytes[$coord_len - x_be.len()..].copy_from_slice(&x_be); y_bytes[$coord_len - y_be.len()..].copy_from_slice(&y_be); - let mut bytes = vec![0x04]; + let mut bytes = Vec::with_capacity(1 + 2 * $coord_len); + bytes.push(0x04); bytes.extend_from_slice(&x_bytes); bytes.extend_from_slice(&y_bytes); <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_sec1_bytes( @@ -303,15 +307,14 @@ macro_rules! impl_curve_ops { let val = BigUint::from_bytes_be(bytes); let reduced = val % &n; let r_bytes = reduced.to_bytes_be(); - let mut buf = vec![0u8; $coord_len]; - if r_bytes.len() <= $coord_len { - buf[$coord_len - r_bytes.len()..].copy_from_slice(&r_bytes); - } else { - buf.copy_from_slice(&r_bytes[r_bytes.len() - $coord_len..]); - } let mut field_bytes = <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); - field_bytes.copy_from_slice(buf.as_slice()); + let len = field_bytes.len(); + if r_bytes.len() <= len { + field_bytes[len - r_bytes.len()..].copy_from_slice(&r_bytes); + } else { + field_bytes.copy_from_slice(&r_bytes[r_bytes.len() - len..]); + } <$scalar as $curve_crate::elliptic_curve::ff::PrimeField>::from_repr(field_bytes) .unwrap() } @@ -358,9 +361,10 @@ macro_rules! impl_curve_ops { let mut seed_arr = [0u8; 32]; seed_arr.copy_from_slice(s); let mut rng = StdRng::from_seed(seed_arr); + let mut bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); loop { - let mut bytes = vec![0u8; $coord_len]; - rng.fill_bytes(&mut bytes); + rng.fill_bytes(&mut bytes[..]); if let Ok(sk) = <$secret_key>::from_slice(&bytes) { break sk; } @@ -440,9 +444,10 @@ macro_rules! impl_curve_ops { let mut seed_arr = [0u8; 32]; seed_arr.copy_from_slice(s); let mut rng = StdRng::from_seed(seed_arr); + let mut bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); loop { - let mut bytes = vec![0u8; $coord_len]; - rng.fill_bytes(&mut bytes); + rng.fill_bytes(&mut bytes[..]); if let Ok(sk) = <$secret_key>::from_slice(&bytes) { break *sk.to_nonzero_scalar(); } @@ -537,8 +542,10 @@ macro_rules! impl_curve_ops { ) -> Result<$curve_crate::elliptic_curve::PublicKey<$curve_type>, ECDHPaceError> { match pub_key { PublicKeyPace::Ecdh { x, y, .. } => { - let mut x_bytes = vec![0u8; $coord_len]; - let mut y_bytes = vec![0u8; $coord_len]; + let mut x_bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); + let mut y_bytes = + <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default(); let x_be = x.to_bytes_be(); let y_be = y.to_bytes_be(); if x_be.len() > $coord_len || y_be.len() > $coord_len { @@ -547,7 +554,8 @@ macro_rules! impl_curve_ops { x_bytes[$coord_len - x_be.len()..].copy_from_slice(&x_be); y_bytes[$coord_len - y_be.len()..].copy_from_slice(&y_be); - let mut bytes = vec![0x04]; + let mut bytes = Vec::with_capacity(1 + 2 * $coord_len); + bytes.push(0x04); bytes.extend_from_slice(&x_bytes); bytes.extend_from_slice(&y_bytes); <$curve_crate::elliptic_curve::PublicKey<$curve_type>>::from_sec1_bytes(