What changed, and why it matters
This commit fixes several bugs in the firmware of the Keystone 3 hardware wallet, mainly around how Arweave (AR) cryptocurrency keys and RSA prime numbers are handled. It replaces direct array indexing with safer lookups, adds checks for missing or corrupted data, stores a hash of sensitive RSA primes in a secure chip, and erases that data when an account is deleted. The changes reduce the chance that a malformed or out-of-range value could cause the device to read the wrong key, crash, or leave sensitive key material behind after deletion.
Treat this as a security-relevant hardening patch. Review whether the old direct-indexing paths were reachable from user-supplied chain values or persisted data, and confirm that the new SE hash page is correctly provisioned on existing devices (the VerifySeHash helper writes the expected hash if the page is empty, which could mask downgrade/rollback detection). Validate that all call sites of GetCurrentAccountPublicKey/GetCurrentAccountPath/GetXPubPath handle NULL returns safely.
Security signals we found
Out-of-bounds index fix: ChainType enum no longer used directly as array index for g_chainTable/g_accountPublicInfo
Sensitive-data cleanup: RSA prime flash region and SE hash are erased when an account is deleted
Tamper-response expansion: anti_tamper erase loop now covers the new RSA primes hash page
Integrity check addition: RSA primes must match both flash hash and secure-element hash
Input validation: Arweave setup check now validates hex string length instead of raw strlen
Memory/IO error handling: added null checks and error-code propagation in RSA flash read path
Evidence from the diff
The patch hardens AR/RSA handling: (1) account_public_info.c no longer uses raw ChainType as an index into g_chainTable or g_accountPublicInfo; it adds GetChainTableIndex() and returns NULL on mismatch, preventing out-of-bounds reads when a chain enum value is absent from the table. (2) rsa.c adds null/length/error checks after SRAM allocation, flash read, password retrieval, and AES decryption; it also verifies RSA primes against both the on-flash hash and a new secure-element (SE) hash before use, and writes that SE hash when primes are stored. (3) se_manager.c/h refactors hash read/write helpers and adds PAGE_WALLET1_RSA_PRIMES_HASH plus SetRsaPrimesHash/VerifyRsaPrimesHash. (4) anti_tamper.c now erases the new RSA primes hash page during tamper cleanup. (5) DeleteAccountPublicInfo() now erases the RSA flash region and clears the SE hash. (6) IsArweaveSetupComplete() uses a stricter hex-length validator. These are defensive fixes; the commit message gives no explicit security claim.
Changed components
src/crypto/account_public_info.csrc/crypto/account_public_info.hsrc/crypto/rsa.csrc/hardware_interface/anti_tamper.csrc/managers/se_manager.csrc/managers/se_manager.hsrc/ui/gui_chain/multi/web3/gui_ar.csrc/utils/user_utils.csrc/utils/user_utils.hInspect captured patch +173 / −81
diff --git a/src/crypto/account_public_info.c b/src/crypto/account_public_info.c
index 1a8553a..e2af8fe 100644
--- a/src/crypto/account_public_info.c
+++ b/src/crypto/account_public_info.c
@@ -58,7 +58,7 @@ static uint32_t GetTemplateWalletValue(const char* walletName, const char* key);
static void SetTemplateWalletValue(const char* walletName, const char* key, uint32_t value);
static void CleanupJson(cJSON* json);
static void FreePublicKeyRam(void);
-static bool IsHexString(const char *value);
+static int32_t GetChainTableIndex(ChainType chain);
static void PrintInfo(void);
static void SetIsTempAccount(bool isTemp);
@@ -559,12 +559,22 @@ static const ChainItem_t g_chainTable[] = {
#endif
};
+static int32_t GetChainTableIndex(ChainType chain)
+{
+ for (uint32_t i = 0; i < NUMBER_OF_ARRAYS(g_chainTable); i++) {
+ if (g_chainTable[i].chain == chain) {
+ return i;
+ }
+ }
+ return -1;
+}
+
#ifdef WEB3_VERSION
ChainType CheckSolPathSupport(char *path)
{
int startIndex = -1;
int endIndex = -1;
- for (int i = 0; i < XPUB_TYPE_NUM; i++) {
+ for (int i = 0; i < NUMBER_OF_ARRAYS(g_chainTable); i++) {
if (XPUB_TYPE_SOL_BIP44_0 == g_chainTable[i].chain) {
startIndex = i;
}
@@ -627,9 +637,12 @@ static SimpleResponse_c_char *ProcessKeyType(uint8_t *seed, int len, int cryptoK
}
}
-char *GetXPubPath(uint8_t index)
+char *GetXPubPath(uint8_t chain)
{
- ASSERT(index < XPUB_TYPE_NUM);
+ int32_t index = GetChainTableIndex(chain);
+ if (index < 0) {
+ return NULL;
+ }
return g_chainTable[index].path;
}
@@ -982,7 +995,7 @@ int32_t AccountPublicInfoSwitch(uint8_t accountIndex, const char *password, bool
#ifdef CYPHERPUNK_VERSION
if (!regeneratePubKey && IsZcashSupportedForCurrentMnemonic()) {
char *zcashEncrypted = GetCurrentAccountPublicKey(ZCASH_UFVK_ENCRYPTED_0);
- if (!IsHexString(zcashEncrypted)) {
+ if (!IsHexStringWithLen(zcashEncrypted, 0)) {
regeneratePubKey = true;
}
}
@@ -1004,26 +1017,6 @@ int32_t AccountPublicInfoSwitch(uint8_t accountIndex, const char *password, bool
return ret;
}
-static bool IsHexString(const char *value)
-{
- if (value == NULL) {
- return false;
- }
- size_t len = strnlen_s(value, PUB_KEY_MAX_LENGTH);
- if (len == 0 || (len % 2) != 0) {
- return false;
- }
- for (size_t i = 0; i < len; i++) {
- char c = value[i];
- if (!((c >= '0' && c <= '9') ||
- (c >= 'a' && c <= 'f') ||
- (c >= 'A' && c <= 'F'))) {
- return false;
- }
- }
- return true;
-}
-
static void SetIsTempAccount(bool isTemp)
{
g_isTempAccount = isTemp;
@@ -1176,24 +1169,37 @@ void DeleteAccountPublicInfo(uint8_t accountIndex)
for (eraseAddr = addr; eraseAddr < addr + SPI_FLASH_SIZE_USER1_MULTI_SIG_DATA; eraseAddr += GD25QXX_SECTOR_SIZE) {
Gd25FlashSectorErase(eraseAddr);
}
+#ifdef WEB3_VERSION
+ addr = SPI_FLASH_RSA_USER1_DATA + accountIndex * SPI_FLASH_ADDR_EACH_SIZE;
+ for (eraseAddr = addr; eraseAddr < addr + SPI_FLASH_RSA_SIZE_USER1_DATA; eraseAddr += GD25QXX_SECTOR_SIZE) {
+ Gd25FlashSectorErase(eraseAddr);
+ }
+ uint8_t rsaHash[32] = {0};
+ SetRsaPrimesHash(accountIndex, rsaHash);
+#endif
//remove current publickey info to avoid accident reading.
FreePublicKeyRam();
}
char *GetCurrentAccountPath(ChainType chain)
{
- return g_chainTable[chain].path;
+ int32_t index = GetChainTableIndex(chain);
+ if (index < 0) {
+ return NULL;
+ }
+ return g_chainTable[index].path;
}
char *GetCurrentAccountPublicKey(ChainType chain)
{
uint8_t accountIndex;
+ int32_t index = GetChainTableIndex(chain);
accountIndex = GetCurrentAccountIndex();
- if (accountIndex > 2) {
+ if (accountIndex > 2 || index < 0) {
return NULL;
}
- return g_accountPublicInfo[chain].value;
+ return g_accountPublicInfo[index].value;
}
/// @brief Get if the xPub already Exists.
@@ -1386,8 +1392,8 @@ static void FreePublicKeyRam(void)
static void PrintInfo(void)
{
char *value;
- for (uint32_t i = 0; i < XPUB_TYPE_NUM; i++) {
- value = GetCurrentAccountPublicKey(i);
+ for (uint32_t i = 0; i < NUMBER_OF_ARRAYS(g_chainTable); i++) {
+ value = GetCurrentAccountPublicKey(g_chainTable[i].chain);
if (value != NULL) {
printf("%s pub key=%s\r\n", g_chainTable[i].name, value);
}
diff --git a/src/crypto/account_public_info.h b/src/crypto/account_public_info.h
index 1cbc155..c61492f 100644
--- a/src/crypto/account_public_info.h
+++ b/src/crypto/account_public_info.h
@@ -280,7 +280,7 @@ void AccountPublicInfoTest(int argc, char *argv[]);
bool GetFirstReceive(const char* chainName);
void SetFirstReceive(const char* chainName, bool isFirst);
void AccountPublicHomeCoinGet(WalletState_t *walletList, uint8_t count);
-char *GetXPubPath(uint8_t index);
+char *GetXPubPath(uint8_t chain);
uint32_t GetAccountReceiveIndex(const char* chainName);
void SetAccountReceiveIndex(const char* chainName, uint32_t index);
uint32_t GetAccountReceivePath(const char* chainName);
diff --git a/src/crypto/rsa.c b/src/crypto/rsa.c
index 9a29c9e..6d9d69a 100644
--- a/src/crypto/rsa.c
+++ b/src/crypto/rsa.c
@@ -1,6 +1,8 @@
#ifdef WEB3_VERSION
#include "rsa.h"
#include "user_utils.h"
+#include "se_manager.h"
+#include "err_code.h"
static uint32_t GetRsaAddress();
static void RsaHashWithSalt(const uint8_t *data, uint8_t *hash);
@@ -41,6 +43,10 @@ static bool HasMatchingPrimesHash(Rsa_primes_t *primes, const uint8_t targethash
memcpy_s(bytes, SPI_FLASH_RSA_PRIME_SIZE, primes->p, SPI_FLASH_RSA_PRIME_SIZE);
memcpy_s(bytes + SPI_FLASH_RSA_PRIME_SIZE, SPI_FLASH_RSA_PRIME_SIZE, primes->q, SPI_FLASH_RSA_PRIME_SIZE);
uint8_t *sourceHash = SRAM_MALLOC(SPI_FLASH_RSA_HASH_SIZE);
+ if (sourceHash == NULL) {
+ memset_s(bytes, SPI_FLASH_RSA_ORIGIN_DATA_SIZE, 0, SPI_FLASH_RSA_ORIGIN_DATA_SIZE);
+ return false;
+ }
RsaHashWithSalt(bytes, sourceHash);
memset_s(bytes, SPI_FLASH_RSA_ORIGIN_DATA_SIZE, 0, SPI_FLASH_RSA_ORIGIN_DATA_SIZE);
bool ret = memcmp(sourceHash, targethash, SPI_FLASH_RSA_HASH_SIZE) == 0;
@@ -61,13 +67,22 @@ Rsa_primes_t *FlashReadRsaPrimes(void)
do {
primes = SRAM_MALLOC(sizeof(Rsa_primes_t));
+ if (primes == NULL) {
+ printf("Failed to alloc rsa primes\n");
+ break;
+ }
int readLen = Gd25FlashReadBuffer(GetRsaAddress(), fullData, sizeof(fullData));
#ifndef COMPILE_SIMULATOR
ASSERT(readLen == sizeof(fullData));
#endif
+ if (readLen != sizeof(fullData)) {
+ ret = ERR_GENERAL_FAIL;
+ break;
+ }
int len = (GetMnemonicType() == MNEMONIC_TYPE_BIP39) ? (int)sizeof(seed) : GetCurrentAccountEntropyLen();
if (SecretCacheGetPassword() == NULL) {
printf("password is empty\n");
+ ret = ERR_GENERAL_FAIL;
break;
}
ret = GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword());
@@ -75,13 +90,39 @@ Rsa_primes_t *FlashReadRsaPrimes(void)
memcpy_s(cryptData, sizeof(cryptData), fullData, sizeof(cryptData));
encData = aes256_decrypt_primes(seed, len, cryptData);
- CHECK_ERRCODE_BREAK("aes256_decrypt_primes", encData->error_code);
+ if (encData == NULL) {
+ printf("aes256_decrypt_primes response is null\n");
+ ret = ERR_GENERAL_FAIL;
+ break;
+ }
+ if (encData->error_code != SUCCESS_CODE) {
+ printf("aes256_decrypt_primes err,%d\n", encData->error_code);
+ ret = encData->error_code;
+ break;
+ }
+ if (encData->data == NULL) {
+ printf("aes256_decrypt_primes data is null\n");
+ ret = ERR_GENERAL_FAIL;
+ break;
+ }
memcpy_s(primes->p, SPI_FLASH_RSA_PRIME_SIZE, encData->data, SPI_FLASH_RSA_PRIME_SIZE);
memcpy_s(primes->q, SPI_FLASH_RSA_PRIME_SIZE, encData->data + SPI_FLASH_RSA_PRIME_SIZE, SPI_FLASH_RSA_PRIME_SIZE);
memcpy_s(hash, sizeof(hash), fullData + SPI_FLASH_RSA_DATA_SIZE, sizeof(hash));
- ASSERT(HasMatchingPrimesHash(primes, hash));
+ bool flashHashMatched = HasMatchingPrimesHash(primes, hash);
+ ASSERT(flashHashMatched);
+ if (!flashHashMatched) {
+ ret = ERR_GENERAL_FAIL;
+ break;
+ }
+ bool seHashMatched = VerifyRsaPrimesHash(GetCurrentAccountIndex(), hash);
+ ASSERT(seHashMatched);
+ if (!seHashMatched) {
+ ret = ERR_GENERAL_FAIL;
+ break;
+ }
+ ret = SUCCESS_CODE;
} while (0);
if (encData) {
@@ -145,6 +186,9 @@ int FlashWriteRsaPrimes(const uint8_t *data)
}
CLEAR_ARRAY(verifyBuf);
+ ret = SetRsaPrimesHash(GetCurrentAccountIndex(), hash);
+ CHECK_ERRCODE_BREAK("set rsa primes hash", ret);
+
ret = 0;
} while (0);
@@ -158,4 +202,4 @@ int FlashWriteRsaPrimes(const uint8_t *data)
CLEAR_ARRAY(seed);
return ret;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/hardware_interface/anti_tamper.c b/src/hardware_interface/anti_tamper.c
index 95243c2..e2ec1cb 100644
--- a/src/hardware_interface/anti_tamper.c
+++ b/src/hardware_interface/anti_tamper.c
@@ -16,6 +16,7 @@
#include "drv_sensor.h"
#include "drv_bpk.h"
#include "drv_otp.h"
+#include "se_manager.h"
#define TAMPER_MARK 0x5A
// #define TAMPER_OTP_FLAG
@@ -126,7 +127,7 @@ static void TamperEraseInfo(void)
Atecc608bEncryptWrite(15, 0, pageData);
DS28S60_Init();
CLEAR_ARRAY(pageData);
- for (uint32_t i = 0; i < 36; i++) {
+ for (uint32_t i = 0; i < PAGE_WALLET1_RSA_PRIMES_HASH + 3; i++) {
printf("erase index=%d\n", i);
DS28S60_HmacEncryptWrite(pageData, i);
}
diff --git a/src/managers/se_manager.c b/src/managers/se_manager.c
index b3e67f8..8d07be2 100644
--- a/src/managers/se_manager.c
+++ b/src/managers/se_manager.c
@@ -17,12 +17,15 @@
#endif
#define SHA256_COUNT 3
+#define SE_HASH_LEN 32
static int32_t SetNewKeyPieceToAtecc608b(uint8_t accountIndex, uint8_t *piece, const char *password);
static int32_t SetNewKeyPieceToDs28s60(uint8_t accountIndex, uint8_t *piece, const char *password);
static int32_t GetKeyPieceFromAtecc608b(uint8_t accountIndex, uint8_t *piece, const char *password);
static int32_t GetKeyPieceFromDs28s60(uint8_t accountIndex, uint8_t *piece, const char *password);
+static int32_t SetSeHash(uint8_t page, const uint8_t *info);
+static bool VerifySeHash(uint8_t page, uint8_t *info, bool writeExpectedIfEmpty);
static int32_t SetNewKeyPieceToAtecc608b(uint8_t accountIndex, uint8_t *piece, const char *password)
{
@@ -228,20 +231,44 @@ int32_t GetFpStateInfo(uint8_t *info)
return ret;
}
+static int32_t SetSeHash(uint8_t page, const uint8_t *info)
+{
+ uint8_t data[SE_HASH_LEN] = {0};
+ int32_t ret;
+
+ memcpy(data, info, SE_HASH_LEN);
+ ret = SE_HmacEncryptWrite(data, page);
+ CLEAR_ARRAY(data);
+ return ret;
+}
+
+static bool VerifySeHash(uint8_t page, uint8_t *info, bool writeExpectedIfEmpty)
+{
+ uint8_t data[SE_HASH_LEN] = {0};
+ int32_t ret;
+
+ ret = SE_HmacEncryptRead(data, page);
+ if (ret != SUCCESS_CODE) {
+ return false;
+ }
+ if (!memcmp(data, info, SE_HASH_LEN)) {
+ return true;
+ }
+ if (CheckAllFF(data, SE_HASH_LEN) || CheckAllZero(data, SE_HASH_LEN)) {
+ SetSeHash(page, writeExpectedIfEmpty ? info : data);
+ return true;
+ }
+ return false;
+}
+
/// @brief Set the wallet data hash.
/// @param[in] index
/// @param[in] info 32 byte info.
/// @return err code.
int32_t SetWalletDataHash(uint8_t index, uint8_t *info)
{
- uint8_t data[32] = {0};
- int32_t ret;
-
ASSERT(index <= 2);
-
- memcpy(data, info, 32);
- ret = SE_HmacEncryptWrite(data, PAGE_WALLET1_PUB_KEY_HASH + index);
- return ret;
+ return SetSeHash(PAGE_WALLET1_PUB_KEY_HASH + index, info);
}
/// @brief verify the wallet data hash.
@@ -250,54 +277,32 @@ int32_t SetWalletDataHash(uint8_t index, uint8_t *info)
/// @return result of verify.
bool VerifyWalletDataHash(uint8_t index, uint8_t *info)
{
- uint8_t data[32];
- int32_t ret;
+ ASSERT(index <= 2);
+ return VerifySeHash(PAGE_WALLET1_PUB_KEY_HASH + index, info, false);
+}
+int32_t SetRsaPrimesHash(uint8_t index, uint8_t *info)
+{
ASSERT(index <= 2);
+ return SetSeHash(PAGE_WALLET1_RSA_PRIMES_HASH + index, info);
+}
- ret = SE_HmacEncryptRead(data, PAGE_WALLET1_PUB_KEY_HASH + index);
- if (ret == SUCCESS_CODE && !memcmp(data, info, 32)) {
- return true;
- } else {
- if (CheckAllFF(data, 32) || CheckAllZero(data, 32)) {
- SetWalletDataHash(index, data);
- return true;
- } else {
- return false;
- }
- }
+bool VerifyRsaPrimesHash(uint8_t index, uint8_t *info)
+{
+ ASSERT(index <= 2);
+ return VerifySeHash(PAGE_WALLET1_RSA_PRIMES_HASH + index, info, true);
}
int32_t SetMultisigDataHash(uint8_t index, uint8_t *info)
{
- uint8_t data[32] = {0};
- int32_t ret;
-
ASSERT(index <= 2);
-
- memcpy(data, info, 32);
- ret = SE_HmacEncryptWrite(data, index * PAGE_NUM_PER_ACCOUNT + PAGE_INDEX_MULTISIG_CONFIG_HASH);
- return ret;
+ return SetSeHash(index * PAGE_NUM_PER_ACCOUNT + PAGE_INDEX_MULTISIG_CONFIG_HASH, info);
}
bool VerifyMultisigWalletDataHash(uint8_t index, uint8_t *info)
{
- uint8_t data[32];
- int32_t ret;
-
ASSERT(index <= 2);
-
- ret = SE_HmacEncryptRead(data, index * PAGE_NUM_PER_ACCOUNT + PAGE_INDEX_MULTISIG_CONFIG_HASH);
- if (ret == SUCCESS_CODE && !memcmp(data, info, 32)) {
- return true;
- } else {
- if (CheckAllFF(data, 32) || CheckAllZero(data, 32)) {
- SetMultisigDataHash(index, data);
- return true;
- } else {
- return false;
- }
- }
+ return VerifySeHash(index * PAGE_NUM_PER_ACCOUNT + PAGE_INDEX_MULTISIG_CONFIG_HASH, info, false);
}
/// @brief Get the fingerprint encrypted password which stored in SE.
diff --git a/src/managers/se_manager.h b/src/managers/se_manager.h
index 49ba8c6..60afdf4 100644
--- a/src/managers/se_manager.h
+++ b/src/managers/se_manager.h
@@ -28,6 +28,7 @@
#define PAGE_WALLET1_PUB_KEY_HASH 85
#define PAGE_WALLET2_PUB_KEY_HASH 86
#define PAGE_WALLET3_PUB_KEY_HASH 87
+#define PAGE_WALLET1_RSA_PRIMES_HASH 36
#define PAGE_PUBLIC_INFO 88
@@ -53,7 +54,9 @@ int32_t SignMessageWithDeviceKey(uint8_t *messageHash, uint8_t *signaure);
int32_t GetDevicePublicKey(uint8_t *pubkey);
int32_t SetWalletDataHash(uint8_t index, uint8_t *info);
bool VerifyWalletDataHash(uint8_t index, uint8_t *info);
+int32_t SetRsaPrimesHash(uint8_t index, uint8_t *info);
+bool VerifyRsaPrimesHash(uint8_t index, uint8_t *info);
int32_t SetMultisigDataHash(uint8_t index, uint8_t *info);
bool VerifyMultisigWalletDataHash(uint8_t index, uint8_t *info);
-#endif
\ No newline at end of file
+#endif
diff --git a/src/ui/gui_chain/multi/web3/gui_ar.c b/src/ui/gui_chain/multi/web3/gui_ar.c
index 146ac93..2470c7d 100644
--- a/src/ui/gui_chain/multi/web3/gui_ar.c
+++ b/src/ui/gui_chain/multi/web3/gui_ar.c
@@ -1,6 +1,7 @@
#include "gui_ar.h"
#include "gui_chain_components.h"
#include "rsa.h"
+#include "user_utils.h"
static bool g_isMulti = false;
static URParseResult *g_urResult = NULL;
@@ -9,6 +10,8 @@ static ArweaveRequestType g_requestType = ArweaveRequestTypeTransaction;
static void *g_parseResult = NULL;
static bool g_isAoTransfer = false;
+#define ARWEAVE_XPUB_HEX_LEN 1024
+
#define CHECK_FREE_PARSE_RESULT(result) \
if (result != NULL) \
{ \
@@ -85,7 +88,7 @@ static void TagsRender(cJSON *root, int size, lv_obj_t *parent)
bool IsArweaveSetupComplete(void)
{
char *xPub = GetCurrentAccountPublicKey(XPUB_TYPE_ARWEAVE);
- return xPub != NULL && strlen(xPub) == 1024;
+ return IsHexStringWithLen(xPub, ARWEAVE_XPUB_HEX_LEN);
}
PtrT_TransactionCheckResult GuiGetArCheckResult(void)
@@ -377,4 +380,4 @@ static void GuiArRenderDataItemDetail(lv_obj_t *parent, DisplayArweaveDataItem *
for (size_t i = 0; i < txData->tags->size; i++) {
lastView = CreateTransactionItemView(parent, txData->tags->data[i].name, txData->tags->data[i].value, lastView);
}
-}
\ No newline at end of file
+}
diff --git a/src/utils/user_utils.c b/src/utils/user_utils.c
index d49c410..7426fdb 100644
--- a/src/utils/user_utils.c
+++ b/src/utils/user_utils.c
@@ -3,6 +3,8 @@
#include "lvgl.h"
#include "user_memory.h"
+#define HEX_STRING_MAX_LENGTH 4096
+
uint32_t StrToHex(uint8_t *pbDest, const char *pbSrc)
{
char h1, h2;
@@ -78,6 +80,33 @@ bool CheckAllZero(const uint8_t *array, uint32_t len)
return true;
}
+bool IsHexStringWithLen(const char *value, size_t expectedLen)
+{
+ if (value == NULL) {
+ return false;
+ }
+ size_t maxLen = expectedLen == 0 ? HEX_STRING_MAX_LENGTH : expectedLen;
+ size_t len = strnlen_s(value, maxLen + 1);
+ if (len == 0 || (len % 2) != 0) {
+ return false;
+ }
+ if (len > maxLen) {
+ return false;
+ }
+ if (expectedLen != 0 && len != expectedLen) {
+ return false;
+ }
+ for (size_t i = 0; i < len; i++) {
+ char c = value[i];
+ if (!((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f') ||
+ (c >= 'A' && c <= 'F'))) {
+ return false;
+ }
+ }
+ return true;
+}
+
void RemoveFormatChar(char *str)
{
#ifndef COMPILE_SIMULATOR
@@ -292,4 +321,4 @@ void insert_16bit_value(uint8_t *frame, int offset, uint16_t value)
{
frame[offset] = (uint8_t)(value >> 8);
frame[offset + 1] = (uint8_t)(value & 0xFF);
-}
\ No newline at end of file
+}
diff --git a/src/utils/user_utils.h b/src/utils/user_utils.h
index 539329a..ceb1233 100644
--- a/src/utils/user_utils.h
+++ b/src/utils/user_utils.h
@@ -19,6 +19,7 @@ void ByteArrayToHexStr(uint8_t *array, uint32_t len, char *hex);
bool CheckEntropy(const uint8_t *array, uint32_t len);
bool CheckAllFF(const uint8_t *array, uint32_t len);
bool CheckAllZero(const uint8_t *array, uint32_t len);
+bool IsHexStringWithLen(const char *value, size_t expectedLen);
void RemoveFormatChar(char *str);
void ArrayRandom(char *words, char *out, int count);
int WordsListSlice(char *words, char wordsList[][10], uint8_t wordsCount);
Why this scored 57/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.