From 65af8eb2761f363078387bc75aed533355b4c7b9 Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:33:38 -0700 Subject: [PATCH 1/3] fix: initialize XEdDSA keys when loading identity --- src/mesh/CryptoEngine.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 73ccb21666b..7ca2ef5b2e7 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -289,6 +289,9 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_NodeInfoLite_ 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 } /** From 521c62015d39ca57b8180cf7e78e62ae48a2184b Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:00:00 -0700 Subject: [PATCH 2/3] test: cover persisted XEdDSA key loading --- test/test_crypto/test_main.cpp | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index 30ed9490dcd..f47ed6dbdfe 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -284,6 +284,29 @@ void test_XEdDSA_repeated_sign_is_randomized(void) TEST_ASSERT_TRUE(crypto->xeddsa_verify(pub, fromNode, packetId, portnum, message, sizeof(message), sig2)); } +void test_XEdDSA_persisted_private_key_reload(void) +{ + uint8_t curvePublic[32], persistedPrivate[32]; + uint8_t expectedEdPrivate[32], expectedEdPublic[32]; + crypto->generateKeyPair(curvePublic, persistedPrivate); + XEdDSA::priv_curve_to_ed_keys(persistedPrivate, expectedEdPrivate, expectedEdPublic); + + CryptoEngine firstLoad; + CryptoEngine secondLoad; + firstLoad.setDHPrivateKey(persistedPrivate); + secondLoad.setDHPrivateKey(persistedPrivate); + + TEST_ASSERT_EQUAL_MEMORY(expectedEdPrivate, firstLoad.xeddsa_private_key, sizeof(expectedEdPrivate)); + TEST_ASSERT_EQUAL_MEMORY(expectedEdPublic, firstLoad.xeddsa_public_key, sizeof(expectedEdPublic)); + TEST_ASSERT_EQUAL_MEMORY(firstLoad.xeddsa_private_key, secondLoad.xeddsa_private_key, sizeof(expectedEdPrivate)); + TEST_ASSERT_EQUAL_MEMORY(firstLoad.xeddsa_public_key, secondLoad.xeddsa_public_key, sizeof(expectedEdPublic)); + + const uint8_t payload[] = "persisted identity"; + uint8_t signature[XEDDSA_SIGNATURE_SIZE]; + TEST_ASSERT_TRUE(firstLoad.xeddsa_sign(0x12345678, 0xABCDEF01, 1, payload, sizeof(payload), signature)); + TEST_ASSERT_TRUE(secondLoad.xeddsa_verify(curvePublic, 0x12345678, 0xABCDEF01, 1, payload, sizeof(payload), signature)); +} + void test_AES_CTR(void) { uint8_t expected[32]; @@ -330,7 +353,8 @@ void setup() RUN_TEST(test_XEdDSA_curve_to_ed_cache); RUN_TEST(test_XEdDSA_max_payload); RUN_TEST(test_XEdDSA_repeated_sign_is_randomized); + RUN_TEST(test_XEdDSA_persisted_private_key_reload); exit(UNITY_END()); // stop unit testing } -void loop() {} \ No newline at end of file +void loop() {} From f68b0b04c9301c06fd44999b9d730a8d523e608f Mon Sep 17 00:00:00 2001 From: Benjamin Faershtein <119711889+RCGV1@users.noreply.github.com> Date: Sun, 12 Jul 2026 15:24:06 -0700 Subject: [PATCH 3/3] fix(crypto): reject blank persisted signing keys --- src/mesh/CryptoEngine.cpp | 5 +++++ test/test_crypto/test_main.cpp | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 7ca2ef5b2e7..a8bc7ff5bfa 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -290,6 +290,11 @@ void CryptoEngine::setDHPrivateKey(uint8_t *_private_key) { memcpy(private_key, _private_key, 32); #if !(MESHTASTIC_EXCLUDE_XEDDSA) + if (memfll(private_key, 0, sizeof(private_key))) { + memset(xeddsa_private_key, 0, sizeof(xeddsa_private_key)); + memset(xeddsa_public_key, 0, sizeof(xeddsa_public_key)); + return; + } XEdDSA::priv_curve_to_ed_keys(private_key, xeddsa_private_key, xeddsa_public_key); #endif } diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index f47ed6dbdfe..7023573633e 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -307,6 +307,22 @@ void test_XEdDSA_persisted_private_key_reload(void) TEST_ASSERT_TRUE(secondLoad.xeddsa_verify(curvePublic, 0x12345678, 0xABCDEF01, 1, payload, sizeof(payload), signature)); } +void test_XEdDSA_zero_private_key_reload_clears_signing_material(void) +{ + uint8_t curvePublic[32], privateKey[32], zeroKey[32] = {0}; + uint8_t signature[XEDDSA_SIGNATURE_SIZE]; + const uint8_t payload[] = "zero persisted identity"; + + crypto->generateKeyPair(curvePublic, privateKey); + crypto->setDHPrivateKey(privateKey); + TEST_ASSERT_TRUE(crypto->xeddsa_sign(0x1, 0x2, 1, payload, sizeof(payload), signature)); + + crypto->setDHPrivateKey(zeroKey); + TEST_ASSERT_EQUAL_MEMORY(zeroKey, crypto->xeddsa_private_key, sizeof(zeroKey)); + TEST_ASSERT_EQUAL_MEMORY(zeroKey, crypto->xeddsa_public_key, sizeof(zeroKey)); + TEST_ASSERT_FALSE(crypto->xeddsa_sign(0x1, 0x2, 1, payload, sizeof(payload), signature)); +} + void test_AES_CTR(void) { uint8_t expected[32]; @@ -354,6 +370,7 @@ void setup() RUN_TEST(test_XEdDSA_max_payload); RUN_TEST(test_XEdDSA_repeated_sign_is_randomized); RUN_TEST(test_XEdDSA_persisted_private_key_reload); + RUN_TEST(test_XEdDSA_zero_private_key_reload_clears_signing_material); exit(UNITY_END()); // stop unit testing }