Skip to content

feat: Add Brainpool and NIST elliptic curve support for PACE (issue #11)#12

Merged
itsbalamurali merged 7 commits into
mainfrom
feat/expanded-pace-curves
Jul 14, 2026
Merged

feat: Add Brainpool and NIST elliptic curve support for PACE (issue #11)#12
itsbalamurali merged 7 commits into
mainfrom
feat/expanded-pace-curves

Conversation

@itsbalamurali

@itsbalamurali itsbalamurali commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

This PR implements issue #11, expanding PACE support beyond P-256 to include NIST and Brainpool elliptic curves. It adds support for the following curves in the dmrtd reader core library:

  • Brainpool Curves: brainpoolP256r1/t1, brainpoolP384r1/t1 (and registers brainpoolP320r1/t1, brainpoolP512r1/t1 as unsupported where pure-Rust implementations are unavailable)
  • NIST Curves: NIST P-384, NIST P-521

All unit tests, integration tests, and doc-tests pass successfully.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Adds full ECDH PACE support for NIST P-224/256/384/521 and Brainpool P-256/384 (r1), expanding beyond P-256 to meet issue #11. Also streamlines the ECDH API and improves performance in the PACE flow.

  • New Features

    • ECDH PACE now supports ids 10, 12, 13, 15, 16, 18 (NIST P-224/256/384/521; Brainpool P-256/384 r1). Domain parameters mark them supported; session validates ECP generically.
    • Simplified API: added map_and_generate_ephemeral(icc_pub, nonce, seed?) and get_ephemeral_shared_seed(icc_ephemeral_pub); removed older mapping/ephemeral methods. Dependencies: added bp256, bp384; enabled ECDH on p384; added p521, p224. Bumped workspace/crates to 0.3.0 and updated READMEs.
  • Refactors

    • Reworked ECDH engine into per-curve backends and optimized heap allocations and copies.
    • Replaced deprecated RNG trait usage with rand::Rng and resolved remaining strict-warning lints.

Written for commit f3b71ea. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

ECDH-PACE expands from P-256-only handling to NIST P-384/P-521 and Brainpool curves. Curve dependencies, domain-parameter support, macro-generated engines, enum dispatch, and PaceSession delegation are updated with multi-curve tests.

Changes

ECDH-PACE curve expansion

Layer / File(s) Summary
Curve dependencies and registry support
Cargo.toml, crates/dmrtd/Cargo.toml, crates/dmrtd/src/proto/domain_parameter.rs
Adds NIST and Brainpool curve dependencies and marks the corresponding domain parameters as supported.
Multi-curve ECDH-PACE engine
crates/dmrtd/src/proto/ecdh_pace.rs
Replaces the P-256-only struct with macro-generated curve engines and an ECDHPace enum supporting mapping, ephemeral generation, and shared-seed derivation.
PACE session delegation and validation
crates/dmrtd/src/proto/pace_session.rs
Uses registry-based curve validation and delegates ECDH mapping and shared-seed operations to ECDHPace; loopback tests use the new API.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PaceSession
  participant ECDHPace
  participant CurveEngine
  participant PublicKeyPace
  PaceSession->>ECDHPace: map_and_generate_ephemeral(...)
  ECDHPace->>CurveEngine: map public key and generate ephemeral key
  CurveEngine-->>PaceSession: ephemeral public key
  PaceSession->>ECDHPace: get_ephemeral_shared_seed(...)
  ECDHPace->>PublicKeyPace: decode peer ephemeral key
  CurveEngine-->>ECDHPace: derive shared seed
  ECDHPace-->>PaceSession: shared seed bytes
Loading

Possibly related issues

  • identity-crates issue 11 — Expands PACE support beyond P-256 with Brainpool and NIST P-384/P-521 curves.

Poem

I’m a bunny with curves in my hop,
P-384 and Brainpool now join the crop.
P-521 leaps high,
Seeds match by and by,
While PACE runs smooth at the mountaintop.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main change: adding Brainpool and NIST elliptic curve support for PACE.
Description check ✅ Passed The description is directly related to the PR and summarizes the expanded curve support and tests.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/expanded-pace-curves

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request expands the ECDH PACE engine to support multiple NIST and Brainpool curves (including NIST P-384, NIST P-521, Brainpool P256, and Brainpool P384) by refactoring the implementation into a macro-based architecture. Feedback focuses on optimizing performance within the generated macro code, specifically by avoiding redundant heap allocations (such as moving Vec allocations out of loops and using stack-allocated arrays for coordinate buffers) and caching the parsed curve order using OnceLock instead of parsing it on every scalar reduction.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +69 to +82
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()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Parsing the decimal string $order_str into a BigUint on every invocation of scalar_from_bytes is highly inefficient and causes unnecessary heap allocations. Since the curve order is constant, we can parse it once using std::sync::OnceLock. Additionally, allocating buf as a Vec on the heap is unnecessary since $coord_len is a constant expression; we can use a stack-allocated array instead.

Suggested change
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()
}
fn scalar_from_bytes(&self, bytes: &[u8]) -> $scalar {
static N: std::sync::OnceLock<BigUint> = std::sync::OnceLock::new();
let n = N.get_or_init(|| 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 = [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()
}

Comment on lines +277 to +290
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()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Parsing the decimal string $order_str into a BigUint on every invocation of scalar_from_bytes is highly inefficient and causes unnecessary heap allocations. Since the curve order is constant, we can parse it once using std::sync::OnceLock. Additionally, allocating buf as a Vec on the heap is unnecessary since $coord_len is a constant expression; we can use a stack-allocated array instead.

Suggested change
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()
}
fn scalar_from_bytes(&self, bytes: &[u8]) -> $scalar {
static N: std::sync::OnceLock<BigUint> = std::sync::OnceLock::new();
let n = N.get_or_init(|| 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 = [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()
}

Comment on lines +94 to +100
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Allocating bytes as a Vec inside the loop causes unnecessary heap allocations on every iteration. This is especially problematic for curves like P-521 where the loop can run multiple times on average. Moving the allocation outside the loop avoids this overhead.

Suggested change
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}
let mut bytes = vec![0u8; $coord_len];
loop {
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}

Comment on lines +147 to +153
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());
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Allocating bytes as a Vec inside the loop causes unnecessary heap allocations on every iteration. Moving the allocation outside the loop avoids this overhead.

Suggested change
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());
}
}
let mut bytes = vec![0u8; $coord_len];
loop {
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break self.scalar_from_bytes(&sk.to_bytes());
}
}

Comment thread crates/dmrtd/src/proto/ecdh_pace.rs Outdated
Comment on lines +235 to +243
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Allocating x_bytes and y_bytes as heap-allocated Vecs is unnecessary since $coord_len is a constant expression. Using stack-allocated arrays avoids heap allocations and improves performance.

Suggested change
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 x_bytes = [0u8; $coord_len];
let mut y_bytes = [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);

Comment on lines +322 to +328
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Allocating bytes as a Vec inside the loop causes unnecessary heap allocations on every iteration. Moving the allocation outside the loop avoids this overhead.

Suggested change
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}
let mut bytes = vec![0u8; $coord_len];
loop {
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}

Comment on lines +395 to +401
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());
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Allocating bytes as a Vec inside the loop causes unnecessary heap allocations on every iteration. Moving the allocation outside the loop avoids this overhead.

Suggested change
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());
}
}
let mut bytes = vec![0u8; $coord_len];
loop {
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break self.scalar_from_bytes(&sk.to_bytes());
}
}

Comment thread crates/dmrtd/src/proto/ecdh_pace.rs Outdated
Comment on lines +483 to +491
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Allocating x_bytes and y_bytes as heap-allocated Vecs is unnecessary since $coord_len is a constant expression. Using stack-allocated arrays avoids heap allocations and improves performance.

Suggested change
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 x_bytes = [0u8; $coord_len];
let mut y_bytes = [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);

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/dmrtd/src/proto/ecdh_pace.rs (1)

11-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use rand::rand_core::Rng in RngBridge
Cargo.toml already pins rand = "0.10", so SysRng/UnwrapErr are fine. In crates/dmrtd/src/proto/ecdh_pace.rs:293-311, switch the RngBridge bound from rand::rand_core::RngCore to rand::rand_core::Rng to avoid the deprecation warning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/dmrtd/src/proto/ecdh_pace.rs` around lines 11 - 14, Update the
RngBridge generic bound around the implementation at lines 293–311 from
rand::rand_core::RngCore to rand::rand_core::Rng, preserving the existing bridge
behavior and imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/dmrtd/src/proto/ecdh_pace.rs`:
- Around line 47-254: Refactor the impl_curve_ops macro so the duplicated shared
methods—scalar_from_bytes, get_pub_key, get_pub_key_ephemeral,
compute_shared_point, point_to_pubkey_pace, and transform_public—are defined
once and reused by both modern and legacy arms, leaving only RNG-specific key
generation logic distinct. Hoist the legacy RngBridge implementation into one
private reusable helper and update both generate_key_pair and
map_and_generate_ephemeral to use it, eliminating the per-call-site and
per-curve duplicates.
- Around line 69-82: Update scalar handling in scalar_from_bytes,
compute_shared_point, and the ephemeral-scalar branches to avoid variable-time
BigUint reduction for secret scalars: use SecretKey::to_nonzero_scalar()
directly for private-key paths, while preserving the existing byte-reduction
path for public nonce inputs. Replace the hand-transcribed decimal curve-order
literals with the corresponding elliptic-curve Curve::ORDER constants.

---

Nitpick comments:
In `@crates/dmrtd/src/proto/ecdh_pace.rs`:
- Around line 11-14: Update the RngBridge generic bound around the
implementation at lines 293–311 from rand::rand_core::RngCore to
rand::rand_core::Rng, preserving the existing bridge behavior and imports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d7f1046b-2c1d-4d5a-ab6d-ec10d3fd69ca

📥 Commits

Reviewing files that changed from the base of the PR and between e4f8cc1 and 8e5eca7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • Cargo.toml
  • crates/dmrtd/Cargo.toml
  • crates/dmrtd/src/proto/domain_parameter.rs
  • crates/dmrtd/src/proto/ecdh_pace.rs
  • crates/dmrtd/src/proto/pace_session.rs

Comment thread crates/dmrtd/src/proto/ecdh_pace.rs
Comment thread crates/dmrtd/src/proto/ecdh_pace.rs

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crates/dmrtd/src/proto/domain_parameter.rs Outdated
Comment thread crates/dmrtd/src/proto/ecdh_pace.rs
@itsbalamurali

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the dmrtd crate to support additional PACE curves, including NIST P-224, P-384, P-521, and Brainpool P-256 and P-384, refactoring the ECDHPace engine to use a macro-based implementation for curve operations. The review feedback focuses on performance optimizations within these macro-generated operations, specifically recommending the use of stack-allocated FieldBytes and pre-allocated vectors to avoid unnecessary heap allocations during scalar reduction, key pair generation, and public key serialization.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/dmrtd/src/proto/ecdh_pace.rs Outdated
Comment on lines +80 to +88
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());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a temporary Vec on the heap (buf) during scalar reduction. We can write the bytes directly into field_bytes to improve performance and reduce memory overhead.

Suggested change
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 mut field_bytes =
<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default();
if r_bytes.len() <= field_bytes.len() {
field_bytes[field_bytes.len() - r_bytes.len()..].copy_from_slice(&r_bytes);
} else {
field_bytes.copy_from_slice(&r_bytes[r_bytes.len() - field_bytes.len()..]);
}

Comment on lines +103 to +109
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a new Vec on the heap (bytes) inside the loop. We can allocate FieldBytes on the stack once and reuse it across iterations.

Suggested change
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}
let mut bytes = <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default();
loop {
rng.fill_bytes(bytes.as_mut_slice());
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}

Comment on lines +155 to +161
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break *sk.to_nonzero_scalar();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a new Vec on the heap (bytes) inside the loop. We can allocate FieldBytes on the stack once and reuse it across iterations.

Suggested change
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break *sk.to_nonzero_scalar();
}
}
let mut bytes = <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default();
loop {
rng.fill_bytes(bytes.as_mut_slice());
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break *sk.to_nonzero_scalar();
}
}

Comment thread crates/dmrtd/src/proto/ecdh_pace.rs Outdated
Comment on lines +252 to +264
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid heap allocations for x_bytes and y_bytes by using stack-allocated FieldBytes. Additionally, pre-allocate bytes with capacity to prevent multiple reallocations during serialization.

                        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 {
                            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::with_capacity(1 + 2 * $coord_len);
                        bytes.push(0x04);
                        bytes.extend_from_slice(&x_bytes);
                        bytes.extend_from_slice(&y_bytes);

Comment thread crates/dmrtd/src/proto/ecdh_pace.rs Outdated
Comment on lines +306 to +314
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());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a temporary Vec on the heap (buf) during scalar reduction. We can write the bytes directly into field_bytes to improve performance and reduce memory overhead.

Suggested change
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 mut field_bytes =
<$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default();
if r_bytes.len() <= field_bytes.len() {
field_bytes[field_bytes.len() - r_bytes.len()..].copy_from_slice(&r_bytes);
} else {
field_bytes.copy_from_slice(&r_bytes[r_bytes.len() - field_bytes.len()..]);
}

Comment on lines +361 to +367
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a new Vec on the heap (bytes) inside the loop. We can allocate FieldBytes on the stack once and reuse it across iterations.

Suggested change
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}
let mut bytes = <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default();
loop {
rng.fill_bytes(bytes.as_mut_slice());
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break sk;
}
}

Comment on lines +443 to +449
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break *sk.to_nonzero_scalar();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid allocating a new Vec on the heap (bytes) inside the loop. We can allocate FieldBytes on the stack once and reuse it across iterations.

Suggested change
loop {
let mut bytes = vec![0u8; $coord_len];
rng.fill_bytes(&mut bytes);
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break *sk.to_nonzero_scalar();
}
}
let mut bytes = <$curve_crate::elliptic_curve::FieldBytes<$curve_type>>::default();
loop {
rng.fill_bytes(bytes.as_mut_slice());
if let Ok(sk) = <$secret_key>::from_slice(&bytes) {
break *sk.to_nonzero_scalar();
}
}

Comment thread crates/dmrtd/src/proto/ecdh_pace.rs Outdated
Comment on lines +540 to +552
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid heap allocations for x_bytes and y_bytes by using stack-allocated FieldBytes. Additionally, pre-allocate bytes with capacity to prevent multiple reallocations during serialization.

                        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 {
                            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::with_capacity(1 + 2 * $coord_len);
                        bytes.push(0x04);
                        bytes.extend_from_slice(&x_bytes);
                        bytes.extend_from_slice(&y_bytes);

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread crates/dmrtd/src/proto/domain_parameter.rs
@itsbalamurali
itsbalamurali merged commit 6164b93 into main Jul 14, 2026
4 checks passed
@itsbalamurali
itsbalamurali deleted the feat/expanded-pace-curves branch July 14, 2026 11:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant