assets: support tickers up to 24 characters
What changed, and why it matters
This commit updates the Blockstream Jade hardware wallet to support longer asset tickers (up to 24 characters) and adds validation to reject malformed tickers. It also fixes a potential display bug where a ticker longer than 8 characters could overflow a small on-screen buffer, which might have caused the device to show incorrect information during transaction confirmation.
Treat as a hardening/feature patch rather than an active vulnerability. Review whether any other UI paths or message fields still assume short tickers, and verify that the new validation rejects tickers that could be used for spoofing (e.g., lookalike characters, very long tickers). No urgent vendor disclosure is indicated from the commit alone.
Security signals we found
Buffer size increased from 8 to 25 bytes to match new ticker maximum
New input validation added for asset ticker length and character set
snprintf precision argument cast from size_t to int to avoid undefined behavior on some platforms
Comment indicates prior hard-coded assumption that registry tickers are max 5 characters
Evidence from the diff
The patch increases the maximum asset ticker length from 5/6 characters to 24, enlarges the ticker display buffers in sign_tx.c from 8 bytes to ASSET_TICKER_MAX_LEN+1 (25 bytes), and adds is_valid_asset_ticker() in assets.c to enforce length (3-24 or empty) and character set (alphanumeric plus ‘.’ and ‘-‘). It also casts ticker_len to int for snprintf precision fields. The change appears primarily defensive: without it, a ticker longer than 7 bytes could be truncated or, depending on snprintf behavior and buffer sizes, lead to a non-null-terminated string or UI misrepresentation.
Changed components
main/assets.cmain/assets.hmain/ui/sign_tx.cAsset ticker parsing and display pipelineElements/Liquid transaction signing UIInspect captured patch +36 / −5
### main/assets.c
@@ -1,4 +1,5 @@
#ifndef AMALGAMATED_BUILD
+#include <ctype.h>
#include <inttypes.h>
#include "assets.h"
@@ -19,6 +20,26 @@
#define ASSET_CONTRACT_BUFFER_LEN 768
+static bool is_valid_asset_ticker(const char* ticker, const size_t ticker_len)
+{
+ if (!ticker_len) {
+ return true; // An empty ticker is considered valid
+ }
+
+ if (!ticker || ticker_len < ASSET_TICKER_MIN_LEN || ticker_len > ASSET_TICKER_MAX_LEN) {
+ return false;
+ }
+
+ for (size_t i = 0; i < ticker_len; ++i) {
+ const uint8_t c = (uint8_t)ticker[i];
+ const bool valid = isalnum(c) || c == '.' || c == '-';
+ if (!valid) {
+ return false;
+ }
+ }
+ return true;
+}
+
// Compute the asset-id given the contract hash and the issuance prevout details
static void compute_asset_id(const uint8_t* contract_hash, const size_t contract_hash_len, const uint8_t* txhash,
const size_t txhash_len, const uint32_t index, uint8_t* assetid, const size_t assetid_len)
@@ -162,6 +183,12 @@ bool assets_get_allocate(const char* field, const CborValue* value, asset_info_t
asset->asset_id_len = asset_id_hex_len;
rpc_get_string_ptr("ticker", &contract, &asset->ticker, &asset->ticker_len);
+ if (!is_valid_asset_ticker(asset->ticker, asset->ticker_len)) {
+ JADE_LOGE("Invalid asset ticker (length %lu) for asset %.*s", (unsigned long)asset->ticker_len,
+ (int)asset->asset_id_len, asset->asset_id);
+ free(assets);
+ return false;
+ }
CborValue entity;
if (rpc_get_map("entity", &contract, &entity)) {
### main/assets.h
@@ -8,6 +8,10 @@
// Maximum allowed precision for supported assets
#define ASSET_PRECISION_MAX 8
+// Asset tickers are either empty or 3-24 ASCII characters
+#define ASSET_TICKER_MIN_LEN 3
+#define ASSET_TICKER_MAX_LEN 24
+
// NOTE: strings here may not be nul-terminated as may directly reference message fields
typedef struct _asset_info {
const char* asset_id;
### main/ui/sign_tx.c
@@ -135,7 +135,7 @@ static bool get_asset_display_info(const network_t network_id, const asset_info_
JADE_ASSERT(ret > 0 && ret < amount_len);
// Ticker
- ret = snprintf(ticker, ticker_len, "%.*s", asset_info.ticker_len, asset_info.ticker);
+ ret = snprintf(ticker, ticker_len, "%.*s", (int)asset_info.ticker_len, asset_info.ticker);
JADE_ASSERT(ret > 0 && ret < ticker_len);
} else {
JADE_LOGW("Asset data for asset-id: '%s' not found!", asset_id_hex);
@@ -541,7 +541,7 @@ bool show_elements_transaction_outputs_activity(const network_t network_id, cons
char issuer[128];
char asset_id_hex[2 * ASSET_TAG_LEN + 1];
char amount[32];
- char ticker[8]; // Registry tickers are max 5char ... but testnet policy asset ticker is 'L-TEST' ...
+ char ticker[ASSET_TICKER_MAX_LEN + 1];
const bool have_asset_info = get_asset_display_info(network_id, assets, num_assets, output_info[i].asset_id,
sizeof(output_info[i].asset_id), output_info[i].value, issuer, sizeof(issuer), asset_id_hex,
sizeof(asset_id_hex), amount, sizeof(amount), ticker, sizeof(ticker));
@@ -601,7 +601,7 @@ static bool show_elements_asset_summary_activity(const char* title, const char*
char issuer[128];
char asset_id_hex[2 * ASSET_TAG_LEN + 1];
char amount[32];
- char ticker[8]; // Registry tickers are max 5char ... but testnet policy asset ticker is 'L-TEST' ...
+ char ticker[ASSET_TICKER_MAX_LEN + 1];
const bool have_asset_info = get_asset_display_info(network_id, assets, num_assets, sums[i].asset_id,
sizeof(sums[i].asset_id), sums[i].value, issuer, sizeof(issuer), asset_id_hex, sizeof(asset_id_hex), amount,
sizeof(amount), ticker, sizeof(ticker));
@@ -782,8 +782,8 @@ bool show_elements_final_confirmation_activity(
JADE_ASSERT(asset_info.ticker_len);
// Ticker
- char ticker[8]; // Registry tickers are max 5char ... but testnet policy asset ticker is 'L-TEST' ...
- int ret = snprintf(ticker, sizeof(ticker), "%.*s", asset_info.ticker_len, asset_info.ticker);
+ char ticker[ASSET_TICKER_MAX_LEN + 1];
+ int ret = snprintf(ticker, sizeof(ticker), "%.*s", (int)asset_info.ticker_len, asset_info.ticker);
JADE_ASSERT(ret > 0 && ret < sizeof(ticker));
// Fee amount scaled and displayed at relevant precisionWhy this scored 50/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.