fix: warn user before signing raw hashes in message signing (#846)
What changed, and why it matters
This commit adds a warning screen to Krux's message-signing feature when the user is about to sign a raw 32-byte hash or a 64-character hex string. Previously, the device would silently treat such inputs as already-hashed values and sign them directly. Because a Bitcoin transaction's signature hash (sighash) is also a 32-byte value, an attacker could trick a user into signing what looks like a harmless message but is actually a transaction hash, effectively authorizing a transaction. The fix asks the user to confirm before proceeding.
Users should upgrade to a release containing this commit. When signing messages, carefully review the new raw-hash warning and decline unless the source and content are fully trusted. Developers should consider whether raw-hash signing should be further restricted or require an explicit opt-in setting rather than a one-time prompt.
Security signals we found
Silent signing of raw 32-byte values allowed transaction sighash spoofing
Message signing path treated raw hashes identically to SHA256(message) digests
Fix explicitly references 'C3 of #843' as a security concern
CHANGELOG.md categorizes the change under 'Security Fixes'
No cryptographic validation added; only an interactive warning
Evidence from the diff
In src/krux/pages/home_pages/sign_message_ui.py, _compute_message_hash() now returns a tuple (hash, is_raw_hash). It flags is_raw_hash=True when the input is exactly 32 bytes or a 64-character hex string that decodes to 32 bytes. sign_standard_message() then displays a warning and requires explicit user confirmation before signing. The change is purely UI/UX hardening: it does not reject raw hashes, only warns. Tests were updated to account for the extra prompt.
Changed components
src/krux/pages/home_pages/sign_message_ui.pyMessage signing user flowTranslation/i18n files for warning stringInspect captured patch +46 / −7
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ff4765..856e413 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
### Security Fixes
- Reject PSBT inputs with non-standard sighash types before signing
+- Warn user before signing raw hashes in message signing
- Fix ZeroDivisionError in fee calculation for zero-value output PSBTs
- Validate multisig quorum: reject m=0 and m>n in key-value wallet files
diff --git a/i18n/translations/de-DE.json b/i18n/translations/de-DE.json
index 9efac85..f219461 100644
--- a/i18n/translations/de-DE.json
+++ b/i18n/translations/de-DE.json
@@ -276,6 +276,7 @@
"Signature:": "Signatur:",
"Signed Message": "Signierte Nachricht",
"Signed PSBT": "Signierte PSBT",
+ "Signing raw hash. Proceed only if you trust the source.": "Signieren von Roh-Hash. Fahren Sie nur fort, wenn Sie der Quelle vertrauen.",
"Signing…": "Unterzeichnung…",
"Single-sig": "Single-Sig",
"Size:": "Größe:",
diff --git a/i18n/translations/es-MX.json b/i18n/translations/es-MX.json
index ad181ab..2784413 100644
--- a/i18n/translations/es-MX.json
+++ b/i18n/translations/es-MX.json
@@ -276,6 +276,7 @@
"Signature:": "Firma:",
"Signed Message": "Mensaje Firmado",
"Signed PSBT": "PSBT Firmado",
+ "Signing raw hash. Proceed only if you trust the source.": "Firmar hash sin procesar. Proceda solo si confía en la fuente.",
"Signing…": "Firma…",
"Single-sig": "Single-sig",
"Size:": "Tamaño:",
diff --git a/i18n/translations/fr-FR.json b/i18n/translations/fr-FR.json
index e9be612..d761a90 100644
--- a/i18n/translations/fr-FR.json
+++ b/i18n/translations/fr-FR.json
@@ -276,6 +276,7 @@
"Signature:": "Signature :",
"Signed Message": "Message signé",
"Signed PSBT": "PSBT signé",
+ "Signing raw hash. Proceed only if you trust the source.": "Signature du hachage brut. Procédez uniquement si vous faites confiance à la source.",
"Signing…": "Signature…",
"Single-sig": "Clé unique",
"Size:": "Capacité :",
diff --git a/i18n/translations/ja-JP.json b/i18n/translations/ja-JP.json
index a2dfc29..facbd1e 100644
--- a/i18n/translations/ja-JP.json
+++ b/i18n/translations/ja-JP.json
@@ -276,6 +276,7 @@
"Signature:": "サイン:",
"Signed Message": "サイン付きメッセージ",
"Signed PSBT": "サインされたPSBT",
+ "Signing raw hash. Proceed only if you trust the source.": "生のハッシュに署名します.ソースを信頼している場合にのみ続行します.",
"Signing…": "署名…",
"Single-sig": "シングルサイン",
"Size:": "サイズ:",
diff --git a/i18n/translations/ko-KR.json b/i18n/translations/ko-KR.json
index 0124099..6b197e5 100644
--- a/i18n/translations/ko-KR.json
+++ b/i18n/translations/ko-KR.json
@@ -276,6 +276,7 @@
"Signature:": "서명:",
"Signed Message": "서명된 메시지",
"Signed PSBT": "서명된 PSBT",
+ "Signing raw hash. Proceed only if you trust the source.": "원시 해시에 서명합니다. 소스를 신뢰하는 경우에만 진행합니다.",
"Signing…": "서명…",
"Single-sig": "단일서명",
"Size:": "크기:",
diff --git a/i18n/translations/nl-NL.json b/i18n/translations/nl-NL.json
index 0595c04..7b9517b 100644
--- a/i18n/translations/nl-NL.json
+++ b/i18n/translations/nl-NL.json
@@ -276,6 +276,7 @@
"Signature:": "Handtekening:",
"Signed Message": "Bericht ondertekend",
"Signed PSBT": "PSBT ondertekend",
+ "Signing raw hash. Proceed only if you trust the source.": "RAW-hash ondertekenen. Ga alleen verder als je de bron vertrouwt.",
"Signing…": "Signing…",
"Single-sig": "Enkele sleutel",
"Size:": "Grootte:",
diff --git a/i18n/translations/pt-BR.json b/i18n/translations/pt-BR.json
index e506f1e..38c4f37 100644
--- a/i18n/translations/pt-BR.json
+++ b/i18n/translations/pt-BR.json
@@ -276,6 +276,7 @@
"Signature:": "Assinatura:",
"Signed Message": "Mensagem assinada",
"Signed PSBT": "PSBT assinada",
+ "Signing raw hash. Proceed only if you trust the source.": "Assinando hash bruto. Prossiga somente se você confiar na fonte.",
"Signing…": "Assinando…",
"Single-sig": "Single-sig",
"Size:": "Tamanho:",
diff --git a/i18n/translations/ru-RU.json b/i18n/translations/ru-RU.json
index 4b47d0d..cce4323 100644
--- a/i18n/translations/ru-RU.json
+++ b/i18n/translations/ru-RU.json
@@ -276,6 +276,7 @@
"Signature:": "Подпись:",
"Signed Message": "Подписанное Сообщение",
"Signed PSBT": "Подписанное PSBT",
+ "Signing raw hash. Proceed only if you trust the source.": "Подписание необработанного хэша. Продолжайте, только если вы доверяете источнику.",
"Signing…": "Подпись…",
"Single-sig": "Одна подпись",
"Size:": "Размер:",
diff --git a/i18n/translations/tr-TR.json b/i18n/translations/tr-TR.json
index 97747a7..c573f54 100644
--- a/i18n/translations/tr-TR.json
+++ b/i18n/translations/tr-TR.json
@@ -276,6 +276,7 @@
"Signature:": "İmza:",
"Signed Message": "İmzalı Mesaj",
"Signed PSBT": "İmzalı PSBT",
+ "Signing raw hash. Proceed only if you trust the source.": "Ham karma imzalanıyor. Yalnızca kaynağa güveniyorsanız devam edin.",
"Signing…": "İmzalama…",
"Single-sig": "Tek-imza",
"Size:": "Boyut:",
diff --git a/i18n/translations/vi-VN.json b/i18n/translations/vi-VN.json
index d901f57..9648090 100644
--- a/i18n/translations/vi-VN.json
+++ b/i18n/translations/vi-VN.json
@@ -276,6 +276,7 @@
"Signature:": "Chữ ký:",
"Signed Message": "Tin nhắn đã ký",
"Signed PSBT": "Đã ký PSBT",
+ "Signing raw hash. Proceed only if you trust the source.": "Ký mã băm thô. Chỉ tiến hành nếu bạn tin tưởng nguồn.",
"Signing…": "Biển báo…",
"Single-sig": "Khóa đơn",
"Size:": "Dung lượng:",
diff --git a/i18n/translations/zh-CN.json b/i18n/translations/zh-CN.json
index e6deb37..7cad610 100644
--- a/i18n/translations/zh-CN.json
+++ b/i18n/translations/zh-CN.json
@@ -276,6 +276,7 @@
"Signature:": "签名:",
"Signed Message": "签名消息",
"Signed PSBT": "已签名 PSBT",
+ "Signing raw hash. Proceed only if you trust the source.": "签名原始哈希.仅当您信任源时才继续.",
"Signing…": "签名…",
"Single-sig": "单签",
"Size:": "大小:",
diff --git a/src/krux/pages/home_pages/sign_message_ui.py b/src/krux/pages/home_pages/sign_message_ui.py
index 28e0374..63c47e5 100644
--- a/src/krux/pages/home_pages/sign_message_ui.py
+++ b/src/krux/pages/home_pages/sign_message_ui.py
@@ -234,10 +234,21 @@ class SignMessage(Utils):
def sign_standard_message(self, data):
"""Signs a standard message"""
- message_hash = self._compute_message_hash(data)
+ message_hash, is_raw_hash = self._compute_message_hash(data)
if message_hash is None:
return ""
+ if is_raw_hash:
+ self.ctx.display.clear()
+ self.ctx.display.draw_centered_text(
+ t("Warning:")
+ + "\n\n"
+ + t("Signing raw hash. Proceed only if you trust the source."),
+ highlight_prefix=":",
+ )
+ if not self.prompt(t("Proceed?"), BOTTOM_PROMPT_LINE):
+ return ""
+
self.ctx.display.clear()
self.ctx.display.draw_centered_text(
"SHA256:\n\n%s" % binascii.hexlify(message_hash).decode(),
@@ -251,15 +262,15 @@ class SignMessage(Utils):
return sig
def _compute_message_hash(self, data):
- """Computes the hash for the message"""
+ """Computes the hash for the message, returns (hash, is_raw_hash)"""
if len(data) == 32:
- return data
+ return data, True
if len(data) == 64:
try:
- return binascii.unhexlify(data)
+ return binascii.unhexlify(data), True
except:
pass
- return hashlib.sha256(data).digest()
+ return hashlib.sha256(data).digest(), False
def _export_signature(
self,
diff --git a/src/krux/translations/__init__.py b/src/krux/translations/__init__.py
index 82562ad..d8f2a71 100644
--- a/src/krux/translations/__init__.py
+++ b/src/krux/translations/__init__.py
@@ -310,6 +310,7 @@ ref_array = [
1512492264,
1988416729,
3672006076,
+ 2420425663,
1100365444,
2281377987,
2019512665,
diff --git a/src/krux/translations/de.py b/src/krux/translations/de.py
index 61c0455..af68bcf 100644
--- a/src/krux/translations/de.py
+++ b/src/krux/translations/de.py
@@ -298,6 +298,7 @@ translation_array = [
"Signatur:",
"Signierte Nachricht",
"Signierte PSBT",
+ "Signieren von Roh-Hash. Fahren Sie nur fort, wenn Sie der Quelle vertrauen.",
"Unterzeichnung…",
"Single-Sig",
"Größe:",
diff --git a/src/krux/translations/es.py b/src/krux/translations/es.py
index 865eb4a..6e49303 100644
--- a/src/krux/translations/es.py
+++ b/src/krux/translations/es.py
@@ -298,6 +298,7 @@ translation_array = [
"Firma:",
"Mensaje Firmado",
"PSBT Firmado",
+ "Firmar hash sin procesar. Proceda solo si confía en la fuente.",
"Firma…",
"Single-sig",
"Tamaño:",
diff --git a/src/krux/translations/fr.py b/src/krux/translations/fr.py
index c5f4b32..2a90570 100644
--- a/src/krux/translations/fr.py
+++ b/src/krux/translations/fr.py
@@ -298,6 +298,7 @@ translation_array = [
"Signature\u2009:",
"Message signé",
"PSBT signé",
+ "Signature du hachage brut. Procédez uniquement si vous faites confiance à la source.",
"Signature…",
"Clé unique",
"Capacité\u2009:",
diff --git a/src/krux/translations/ja.py b/src/krux/translations/ja.py
index c002e68..c033420 100644
--- a/src/krux/translations/ja.py
+++ b/src/krux/translations/ja.py
@@ -298,6 +298,7 @@ translation_array = [
"サイン:",
"サイン付きメッセージ",
"サインされたPSBT",
+ "生のハッシュに署名します.ソースを信頼している場合にのみ続行します.",
"署名…",
"シングルサイン",
"サイズ:",
diff --git a/src/krux/translations/ko.py b/src/krux/translations/ko.py
index 4bb8dae..d9556c9 100644
--- a/src/krux/translations/ko.py
+++ b/src/krux/translations/ko.py
@@ -298,6 +298,7 @@ translation_array = [
"서명:",
"서명된 메시지",
"서명된 PSBT",
+ "원시 해시에 서명합니다. 소스를 신뢰하는 경우에만 진행합니다.",
"서명…",
"단일서명",
"크기:",
diff --git a/src/krux/translations/nl.py b/src/krux/translations/nl.py
index 968f406..c3def0d 100644
--- a/src/krux/translations/nl.py
+++ b/src/krux/translations/nl.py
@@ -298,6 +298,7 @@ translation_array = [
"Handtekening:",
"Bericht ondertekend",
"PSBT ondertekend",
+ "RAW-hash ondertekenen. Ga alleen verder als je de bron vertrouwt.",
"Signing…",
"Enkele sleutel",
"Grootte:",
diff --git a/src/krux/translations/pt.py b/src/krux/translations/pt.py
index d26a531..c494f67 100644
--- a/src/krux/translations/pt.py
+++ b/src/krux/translations/pt.py
@@ -298,6 +298,7 @@ translation_array = [
"Assinatura:",
"Mensagem assinada",
"PSBT assinada",
+ "Assinando hash bruto. Prossiga somente se você confiar na fonte.",
"Assinando…",
"Single-sig",
"Tamanho:",
diff --git a/src/krux/translations/ru.py b/src/krux/translations/ru.py
index 1327eb7..418ea16 100644
--- a/src/krux/translations/ru.py
+++ b/src/krux/translations/ru.py
@@ -298,6 +298,7 @@ translation_array = [
"Подпись:",
"Подписанное Сообщение",
"Подписанное PSBT",
+ "Подписание необработанного хэша. Продолжайте, только если вы доверяете источнику.",
"Подпись…",
"Одна подпись",
"Размер:",
diff --git a/src/krux/translations/tr.py b/src/krux/translations/tr.py
index fd1cc29..f4fd01e 100644
--- a/src/krux/translations/tr.py
+++ b/src/krux/translations/tr.py
@@ -298,6 +298,7 @@ translation_array = [
"İmza:",
"İmzalı Mesaj",
"İmzalı PSBT",
+ "Ham karma imzalanıyor. Yalnızca kaynağa güveniyorsanız devam edin.",
"İmzalama…",
"Tek-imza",
"Boyut:",
diff --git a/src/krux/translations/vi.py b/src/krux/translations/vi.py
index 28e26bf..2da6152 100644
--- a/src/krux/translations/vi.py
+++ b/src/krux/translations/vi.py
@@ -298,6 +298,7 @@ translation_array = [
"Chữ ký:",
"Tin nhắn đã ký",
"Đã ký PSBT",
+ "Ký mã băm thô. Chỉ tiến hành nếu bạn tin tưởng nguồn.",
"Biển báo…",
"Khóa đơn",
"Dung lượng:",
diff --git a/src/krux/translations/zh.py b/src/krux/translations/zh.py
index 24f26a3..ae11284 100644
--- a/src/krux/translations/zh.py
+++ b/src/krux/translations/zh.py
@@ -298,6 +298,7 @@ translation_array = [
"签名:",
"签名消息",
"已签名 PSBT",
+ "签名原始哈希.仅当您信任源时才继续.",
"签名…",
"单签",
"大小:",
diff --git a/tests/pages/home_pages/test_sign_message_ui.py b/tests/pages/home_pages/test_sign_message_ui.py
index 46822e2..66af15b 100644
--- a/tests/pages/home_pages/test_sign_message_ui.py
+++ b/tests/pages/home_pages/test_sign_message_ui.py
@@ -21,6 +21,7 @@ def test_sign_message(mocker, m5stickv, tdata):
# 3 btn_seq
[
BUTTON_ENTER, # Load from camera
+ BUTTON_ENTER, # Raw hash warning - Proceed
BUTTON_ENTER, # Confirm to Sign SHA
BUTTON_ENTER, # Check signature
BUTTON_ENTER, # Sign to QR code
@@ -41,6 +42,7 @@ def test_sign_message(mocker, m5stickv, tdata):
None,
[
BUTTON_ENTER, # Load from camera
+ BUTTON_ENTER, # Raw hash warning - Proceed
BUTTON_ENTER, # Confirm to Sign SHA
BUTTON_ENTER, # Check signature
BUTTON_ENTER, # Sign to QR code
@@ -95,6 +97,7 @@ def test_sign_message(mocker, m5stickv, tdata):
MockPrinter(),
[
BUTTON_ENTER, # Load from camera
+ BUTTON_ENTER, # Raw hash warning - Proceed
BUTTON_ENTER, # Confirm to Sign SHA
BUTTON_ENTER, # Check signature
BUTTON_ENTER, # Sign to QR code
@@ -115,6 +118,7 @@ def test_sign_message(mocker, m5stickv, tdata):
MockPrinter(),
[
BUTTON_ENTER, # Load from camera
+ BUTTON_ENTER, # Raw hash warning - Proceed
BUTTON_ENTER, # Confirm to Sign SHA
BUTTON_ENTER, # Check signature
BUTTON_ENTER, # Sign to QR code
@@ -128,14 +132,14 @@ def test_sign_message(mocker, m5stickv, tdata):
"02707a62fdacc26ea9b63b1c197906f56ee0180d0bcf1966e1a2da34f5f3a09a9b",
None,
),
- # 6 Hex-encoded hash, Decline to sign
+ # 6 Hex-encoded hash, Decline at raw hash warning
(
"1af9487b14714080ce5556b4455fd06c4e0a5f719d8c0ea2b5a884e5ebfc6de7",
FORMAT_NONE,
None,
[
BUTTON_ENTER, # Load from camera
- BUTTON_PAGE, # Decline to sign
+ BUTTON_PAGE, # Decline raw hash warning
],
None,
None,
Why this scored 60/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.