Skip to content

Draft: Restore XEdDSA material in keygen-excluded identity loads#10984

Draft
RCGV1 wants to merge 3 commits into
meshtastic:developfrom
RCGV1:codex/fix-ham-xeddsa-key-load
Draft

Draft: Restore XEdDSA material in keygen-excluded identity loads#10984
RCGV1 wants to merge 3 commits into
meshtastic:developfrom
RCGV1:codex/fix-ham-xeddsa-key-load

Conversation

@RCGV1

@RCGV1 RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member

Refs #10982

Related: #10966
Design: meshtastic/design#122

Summary

  • rebuild the in-memory XEdDSA private/public material when a valid persisted Curve25519 identity is installed through the keygen-excluded boot restore path;
  • use the same guarded deterministic conversion already used during key generation and public-key regeneration;
  • leave normal Curve25519 PKI behavior and XEdDSA-excluded targets unchanged;
  • add a regression that loads one persisted identity into two fresh crypto engines, compares the exact derived XEdDSA material, then signs with one instance and verifies with the other.

Normal keygen-capable builds already call regeneratePublicKey() before installing the private key. This draft is intentionally limited to deciding and covering behavior for MESHTASTIC_EXCLUDE_PKI_KEYGEN builds that restore an existing PKI identity.

Validation

  • native macOS crypto suite: 12/12 passed;
  • native macOS packet-signing suite: 29/29 passed;
  • T-Echo XEdDSA-enabled build: passed, 37.5% RAM, 92.6% flash, 46 KiB warm-region clearance;
  • Wio-E5 with MESHTASTIC_EXCLUDE_XEDDSA=1: passed, 40.7% RAM, 96.8% flash;
  • independent dedicated security review approved exact head 521c62015d39ca57b8180cf7e78e62ae48a2184b;
  • Trunk and git diff --check: clean.

The regression fails on the pre-fix baseline because a fresh engine has no reconstructed XEdDSA material after loading a persisted Curve private key, and passes with this change.

Draft / decision status

Keep this PR draft pending maintainer direction on whether XEdDSA signing is intended for the keygen-excluded restore path. No internal bench logs or captures are included.

🤝 Attestations

  • I have tested that my proposed changes behave as described within the validation limits above.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Heltec (Lora32) V3
    • LilyGo T-Deck
    • LilyGo T-Beam
    • RAK WisBlock 4631
    • Seeed Studio T-1000E tracker card
    • Other (build-only validation on T-Echo and Wio-E5)

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fc91b795-e5f2-4882-b5e6-63287faf2f67

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

XEdDSA key restoration

Layer / File(s) Summary
Derive XEdDSA material on key load
src/mesh/CryptoEngine.cpp
setDHPrivateKey now regenerates XEdDSA private and public keys from the loaded Curve25519 private key when XEdDSA support is enabled.
Validate persisted-key reload
test/test_crypto/test_main.cpp
Adds and registers a test that loads the same persisted key into two engines, compares derived material, and verifies cross-engine signing.แก

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The change rebuilds XEdDSA material on identity load, preserves excluded builds, and adds regression coverage as required by #10982.
Out of Scope Changes check ✅ Passed All changes are directly related to identity loading and regression coverage, with no unrelated edits evident.
Title check ✅ Passed The title matches the main change: restoring XEdDSA material when loading a persisted identity on keygen-excluded builds.
Description check ✅ Passed The description follows the template well, with summary, validation, draft status, and attestations filled in.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Flash this PR in the Web Flasher

firmware commit boards expires

Warning

This is an automated, unreviewed CI test build. Back up your device configuration
before flashing, and only flash devices you are able to recover.

Supported boards built by this PR (27)
Device Board Platform
Crowpanel Adv 3.5 TFT elecrow-adv-35-tft esp32-s3
Heltec HT62 heltec-ht62-esp32c3-sx1262 esp32-c3
Heltec Mesh Node 096 heltec-mesh-node-t096 nrf52840
Heltec Mesh Node T1 heltec-mesh-node-t1 nrf52840
Heltec Mesh Node T114 heltec-mesh-node-t114 nrf52840
Heltec V3 heltec-v3 esp32-s3
Heltec V4 heltec-v4 esp32-s3
Meshnology W10 meshnology_w10 esp32-s3
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
RAK WisMesh Tag rak_wismeshtag nrf52840
RAK WisBlock 11200 rak11200 esp32
RAK WisBlock 11310 rak11310 rp2040
RAK3312 rak3312 esp32-s3
RAK WisBlock 4631 rak4631 nrf52840
Seeed SenseCAP Mesh-Tracker-X1 seeed_mesh_tracker_X1 nrf52840
Seeed Wio Tracker L1 seeed_wio_tracker_L1 nrf52840
Seeed Xiao NRF52840 Kit seeed_xiao_nrf52840_kit nrf52840
Seeed Xiao ESP32-S3 seeed-xiao-s3 esp32-s3
Station G2 station-g2 esp32-s3
Station G3 station-g3 esp32-s3
LILYGO T-Deck t-deck-tft esp32-s3
LILYGO T-Echo t-echo nrf52840
LILYGO T-Echo Plus t-echo-plus nrf52840
LILYGO T-Impulse Plus t-impulse-plus nrf52840
LilyGo T3-C6 tlora-c6 esp32-c6
Seeed SenseCAP T1000-E tracker-t1000-e nrf52840

Build artifacts expire on 2026-08-11. Updated for f68b0b0.

@RCGV1

RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@RCGV1

RCGV1 commented Jul 10, 2026

Copy link
Copy Markdown
Member Author

The label check requires an allowed repository label. This contributor account cannot apply labels; a maintainer will need to add the appropriate bug label.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/mesh/CryptoEngine.cpp`:
- Around line 289-295: Add an all-zero 32-byte private-key check in
CryptoEngine::setDHPrivateKey before calling XEdDSA::priv_curve_to_ed_keys; skip
XEdDSA key derivation for blank keys while preserving the existing key copy
behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f8a600f-c42d-4652-855e-a5a00f96fbc2

📥 Commits

Reviewing files that changed from the base of the PR and between 8abae90 and 521c620.

📒 Files selected for processing (2)
  • src/mesh/CryptoEngine.cpp
  • test/test_crypto/test_main.cpp

Comment thread src/mesh/CryptoEngine.cpp
Comment on lines 289 to 295
void CryptoEngine::setDHPrivateKey(uint8_t *_private_key)
{
memcpy(private_key, _private_key, 32);
#if !(MESHTASTIC_EXCLUDE_XEDDSA)
XEdDSA::priv_curve_to_ed_keys(private_key, xeddsa_private_key, xeddsa_public_key);
#endif
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all callers of setDHPrivateKey and check whether any can pass a blank/unvalidated key
rg -n -B5 -A2 '\bsetDHPrivateKey\s*\(' --type=cpp

# Inspect priv_curve_to_ed_keys implementation to confirm derivation behavior on zero input
ast-grep run --pattern 'void priv_curve_to_ed_keys($$$) { $$$ }' --lang cpp
rg -n 'priv_curve_to_ed_keys' -g '*.cpp' -g '*.h'

Repository: meshtastic/firmware

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map relevant symbols in CryptoEngine.cpp
ast-grep outline src/mesh/CryptoEngine.cpp --view expanded

# Find callers/usages of setDHPrivateKey and related key-setting flows
rg -n -C 3 '\bsetDHPrivateKey\s*\(' src
rg -n -C 3 '\bregeneratePublicKey\s*\(' src

# Find blank-key checks around XEdDSA material
rg -n -C 3 'memfll\s*\(\s*xeddsa_private_key|priv_curve_to_ed_keys|xeddsa_sign|memfll\s*\(\s*private_key' src/mesh/CryptoEngine.cpp src

Repository: meshtastic/firmware

Length of output: 25164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the implementation of the key-generation helpers and call paths
wc -l src/mesh/CryptoEngine.cpp
sed -n '1,220p' src/mesh/CryptoEngine.cpp
sed -n '220,360p' src/mesh/CryptoEngine.cpp
sed -n '360,520p' src/mesh/CryptoEngine.cpp

Repository: meshtastic/firmware

Length of output: 15250


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map CryptoEngine.cpp and inspect key-related paths
ast-grep outline src/mesh/CryptoEngine.cpp --view expanded
printf '\n--- callers ---\n'
rg -n -C 3 '\bsetDHPrivateKey\s*\(' src
printf '\n--- regeneratePublicKey ---\n'
rg -n -C 3 '\bregeneratePublicKey\s*\(' src/mesh/CryptoEngine.cpp
printf '\n--- xeddsa guard / derivation ---\n'
rg -n -C 2 'memfll\s*\(\s*xeddsa_private_key|priv_curve_to_ed_keys|xeddsa_sign|memfll\s*\(\s*private_key' src/mesh/CryptoEngine.cpp
printf '\n--- file slice ---\n'
wc -l src/mesh/CryptoEngine.cpp
sed -n '1,220p' src/mesh/CryptoEngine.cpp
sed -n '220,360p' src/mesh/CryptoEngine.cpp
sed -n '360,520p' src/mesh/CryptoEngine.cpp

Repository: meshtastic/firmware

Length of output: 20910


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the NodeDB boot/config flow around the setDHPrivateKey call sites
sed -n '3840,3945p' src/mesh/NodeDB.cpp
sed -n '460,510p' src/mesh/NodeDB.cpp

# Find places that populate or reset config.security.private_key
rg -n -C 3 'private_key\.size|private_key\.bytes|public_key\.size|public_key\.bytes|ensurePkiKeys|createNewIdentity' src/mesh/NodeDB.cpp src/mesh/CryptoEngine.cpp

Repository: meshtastic/firmware

Length of output: 25559


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for validation/sanitization of the stored security private key
rg -n -C 3 'memfll\s*\(\s*config\.security\.private_key|private_key\.size == 32|private_key\.size > 0|setDHPrivateKey\(' src/mesh/NodeDB.cpp src/mesh/CryptoEngine.cpp

# Inspect the exact context around the PKI-only branch that calls setDHPrivateKey directly
sed -n '480,500p' src/mesh/NodeDB.cpp

# Find all direct calls to setDHPrivateKey outside NodeDB::generateCryptoKeyPair
rg -n -C 2 '\bsetDHPrivateKey\s*\(' src | sed -n '1,120p'

Repository: meshtastic/firmware

Length of output: 7511


Guard XEdDSA derivation against all-zero private keys setDHPrivateKey() is still reached from the direct boot path in src/mesh/NodeDB.cpp for any persisted 32-byte key pair, so a stored all-zero private key would now populate xeddsa_private_key and let xeddsa_sign() start succeeding on an invalid identity. Add the blank-key check here.

🤖 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 `@src/mesh/CryptoEngine.cpp` around lines 289 - 295, Add an all-zero 32-byte
private-key check in CryptoEngine::setDHPrivateKey before calling
XEdDSA::priv_curve_to_ed_keys; skip XEdDSA key derivation for blank keys while
preserving the existing key copy behavior.

Source: Coding guidelines

@RCGV1 RCGV1 changed the title Rebuild XEdDSA material when loading an identity key Draft: Restore XEdDSA material in keygen-excluded identity loads Jul 10, 2026
@RCGV1

RCGV1 commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

Added a guard for blank persisted private keys so loading one clears XEdDSA signing material instead of retaining or deriving a signing identity. The native Linux simulator test now covers valid-key load followed by zero-key reload; test_crypto passes (13/13).

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