Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions common/protob/messages-solana.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ option java_outer_classname = "TrezorMessageSolana";
/**
* Message version for Solana SignMessage
*/
enum SolanaMessageVersion {
enum SolanaOffChainMessageVersion {
MESSAGE_VERSION_0 = 0;
}

/**
* Message format for Solana SignMessage
*/
enum SolanaMessageFormat {
enum SolanaOffChainMessageFormat {
V0_RESTRICTED_ASCII = 0;
V0_LIMITED_UTF8 = 1;
}
Expand Down Expand Up @@ -57,24 +57,35 @@ message SolanaSignedTx {
}

/**
* Request: ask device to sign a message
* Request: ask device to sign a off-chain message
* @start
* @next SolanaSignedMessage
* @next SolanaMessageSignature
* @next Failure
*/
message SolanaSignMessage {
message SolanaSignOffChainMessage {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
required bytes message = 2; // the message to sign
optional SolanaMessageVersion message_version = 3[default = MESSAGE_VERSION_0];
optional SolanaMessageFormat message_format = 4[default = V0_RESTRICTED_ASCII];
optional SolanaOffChainMessageVersion message_version = 3[default = MESSAGE_VERSION_0];
optional SolanaOffChainMessageFormat message_format = 4[default = V0_RESTRICTED_ASCII];
optional bytes application_domain = 5; // application domain must be 32 bytes
}

/**
* Response: signature for message
* Request: ask device to sign arbitrary message except valid solana transaction
* @start
* @next SolanaMessageSignature
* @next Failure
*/
message SolanaSignUnsafeMessage {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
required bytes message = 2; // the message to sign
}

/**
* Response: signature for message signing
* @end
*/
message SolanaSignedMessage {
message SolanaMessageSignature {
required bytes signature = 1; // the signature of the message
required bytes public_key = 2; // the public key of the signer
optional bytes public_key = 2; // the public key of the signer
}
5 changes: 3 additions & 2 deletions common/protob/messages.proto
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,9 @@ enum MessageType {
MessageType_SolanaAddress = 10101 [(wire_out) = true];
MessageType_SolanaSignTx = 10102 [(wire_in) = true];
MessageType_SolanaSignedTx = 10103 [(wire_out) = true];
MessageType_SolanaSignMessage = 10104 [(wire_in) = true];
MessageType_SolanaSignedMessage = 10105 [(wire_out) = true];
MessageType_SolanaSignOffChainMessage = 10104 [(wire_in) = true];
MessageType_SolanaMessageSignature = 10105 [(wire_out) = true];
MessageType_SolanaSignUnsafeMessage = 10106 [(wire_in) = true];

// Cosmos
MessageType_CosmosGetAddress = 10800 [(wire_in) = true];
Expand Down
14 changes: 8 additions & 6 deletions core/src/all_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@
import trezor.enums.SafetyCheckLevel
trezor.enums.SdProtectOperationType
import trezor.enums.SdProtectOperationType
trezor.enums.SolanaMessageFormat
import trezor.enums.SolanaMessageFormat
trezor.enums.SolanaMessageVersion
import trezor.enums.SolanaMessageVersion
trezor.enums.SolanaOffChainMessageFormat
import trezor.enums.SolanaOffChainMessageFormat
trezor.enums.SolanaOffChainMessageVersion
import trezor.enums.SolanaOffChainMessageVersion
trezor.enums.TonWalletVersion
import trezor.enums.TonWalletVersion
trezor.enums.TonWorkChain
Expand Down Expand Up @@ -799,10 +799,12 @@
import apps.solana.message
apps.solana.publickey
import apps.solana.publickey
apps.solana.sign_message
import apps.solana.sign_message
apps.solana.sign_offchain_message
import apps.solana.sign_offchain_message
apps.solana.sign_tx
import apps.solana.sign_tx
apps.solana.sign_unsafe_message
import apps.solana.sign_unsafe_message
apps.solana.spl._layouts
import apps.solana.spl._layouts
apps.solana.spl._layouts.token_instructions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from trezor import wire
from trezor.crypto import base58
from trezor.crypto.curve import ed25519
from trezor.enums import SolanaMessageFormat, SolanaMessageVersion
from trezor.enums import SolanaOffChainMessageFormat, SolanaOffChainMessageVersion
from trezor.lvglui.scrs import lv
from trezor.messages import SolanaSignedMessage
from trezor.messages import SolanaMessageSignature
from trezor.ui.layouts.lvgl import confirm_sol_message
from trezor.utils import BufferWriter

Expand All @@ -17,17 +17,17 @@
from .publickey import PublicKey

if TYPE_CHECKING:
from trezor.messages import SolanaSignMessage
from trezor.messages import SolanaSignOffChainMessage
from apps.common.keychain import Keychain

# The signing domain
_SIGN_DOMAIN = b"\xffsolana offchain"
# The header version, currently only version 0 is introduced in proposal[https://github.com/solana-labs/solana/blob/master/docs/src/proposals/off-chain-message-signing.md]
_ALLOWED_HEADER_VERSIONS = (SolanaMessageVersion.MESSAGE_VERSION_0,)
_ALLOWED_HEADER_VERSIONS = (SolanaOffChainMessageVersion.MESSAGE_VERSION_0,)
# The allowed message formats
_ALLOWED_MESSAGE_FORMATS = (
SolanaMessageFormat.V0_RESTRICTED_ASCII,
SolanaMessageFormat.V0_LIMITED_UTF8,
SolanaOffChainMessageFormat.V0_RESTRICTED_ASCII,
SolanaOffChainMessageFormat.V0_LIMITED_UTF8,
)
# The number of signers
_SIGNER_COUNT = 1
Expand All @@ -50,9 +50,9 @@


@auto_keychain(__name__)
async def sign_message(
ctx: wire.Context, msg: SolanaSignMessage, keychain: Keychain
) -> SolanaSignedMessage:
async def sign_offchain_message(
ctx: wire.Context, msg: SolanaSignOffChainMessage, keychain: Keychain
) -> SolanaMessageSignature:
# sanitize message
sanitize_message(msg)
# path validation
Expand All @@ -75,18 +75,20 @@ async def sign_message(
# prepare the message
message_to_sign = prepare_message(msg, signer_pub_key_bytes)
signature = ed25519.sign(node.private_key(), message_to_sign)
return SolanaSignedMessage(signature=signature, public_key=signer_pub_key_bytes)
return SolanaMessageSignature(signature=signature, public_key=signer_pub_key_bytes)


def prepare_message(msg: SolanaSignMessage, signer_pub_key_bytes: bytes) -> bytes:
def prepare_message(
msg: SolanaSignOffChainMessage, signer_pub_key_bytes: bytes
) -> bytes:
"""Prepare the message to be signed."""

message = msg.message
message_length = len(message)
buffer = bytearray(
len(msg.message)
message_length
+ (_PREAMBLE_LENGTH if msg.application_domain else _PREAMBLE_LENGTH_LEDGER)
)
bw = BufferWriter(buffer)
message_length = len(msg.message)
writers.write_bytes_fixed(bw, _SIGN_DOMAIN, 16)
writers.write_uint8(bw, msg.message_version)
if msg.application_domain:
Expand All @@ -102,32 +104,33 @@ def prepare_message(msg: SolanaSignMessage, signer_pub_key_bytes: bytes) -> byte
return bytes(bw.buffer)


def sanitize_message(msg: SolanaSignMessage):
def sanitize_message(msg: SolanaSignOffChainMessage):
"""Sanitize the message."""
message = msg.message
if (
msg.application_domain
and len(msg.application_domain) != _APPLICATION_DOMAIN_LENGTH
):
raise wire.DataError(
f"Application domain must be 32 bytes, got {len(msg.application_domain)}"
)
if len(msg.message) > (
if len(message) > (
_MAX_MESSAGE_LENGTH if msg.application_domain else _MAX_MESSAGE_LENGTH_LEDGER
):
raise wire.DataError(
f"Message is too long, maximum length is {_MAX_MESSAGE_LENGTH} bytes, got {len(msg.message)}"
f"Message is too long, maximum length is {_MAX_MESSAGE_LENGTH} bytes, got {len(message)}"
)
if msg.message_version not in _ALLOWED_HEADER_VERSIONS:
raise wire.DataError(f"Message version must be 0, got {msg.message_version}")
if msg.message_format not in _ALLOWED_MESSAGE_FORMATS:
raise wire.DataError(f"Message format must be 0 or 1, got {msg.message_format}")
elif msg.message_format == SolanaMessageFormat.V0_RESTRICTED_ASCII:
if any(b < 0x20 or b > 0x7E for b in msg.message):
elif msg.message_format == SolanaOffChainMessageFormat.V0_RESTRICTED_ASCII:
if any(b < 0x20 or b > 0x7E for b in message):
raise wire.DataError(
"Message format 0 must contain only printable characters"
)
elif msg.message_format == SolanaMessageFormat.V0_LIMITED_UTF8:
elif msg.message_format == SolanaOffChainMessageFormat.V0_LIMITED_UTF8:
try:
msg.message.decode("utf-8")
message.decode("utf-8")
except UnicodeDecodeError:
raise wire.DataError("Message format 1 must be a valid UTF-8 string")
58 changes: 58 additions & 0 deletions core/src/apps/solana/sign_unsafe_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from typing import TYPE_CHECKING

from trezor import wire
from trezor.crypto.curve import ed25519
from trezor.lvglui.scrs import lv
from trezor.messages import SolanaMessageSignature
from trezor.ui.layouts.lvgl import confirm_sol_message

from apps.common import paths, seed
from apps.common.helpers import validate_message
from apps.common.keychain import auto_keychain
from apps.common.signverify import decode_message

from . import ICON, PRIMARY_COLOR
from .message import Message
from .publickey import PublicKey

if TYPE_CHECKING:
from trezor.messages import SolanaSignUnsafeMessage
from apps.common.keychain import Keychain


@auto_keychain(__name__)
async def sign_unsafe_message(
ctx: wire.Context, msg: SolanaSignUnsafeMessage, keychain: Keychain
) -> SolanaMessageSignature:
# sanitize message
message = msg.message
sanitize_message(message)

# path validation
await paths.validate_path(ctx, keychain, msg.address_n)

node = keychain.derive(msg.address_n)

signer_pub_key_bytes = seed.remove_ed25519_prefix(node.public_key())
ctx.primary_color, ctx.icon_path = lv.color_hex(PRIMARY_COLOR), ICON
decoded_message = decode_message(message)
address = str(PublicKey(signer_pub_key_bytes))

# display the decoded message to confirm
await confirm_sol_message(ctx, address, None, decoded_message, is_unsafe=True)

signature = ed25519.sign(node.private_key(), message)
return SolanaMessageSignature(signature=signature)


def sanitize_message(message: bytes) -> None:
validate_message(message)
try:
_ = Message.deserialize(message)
raise wire.DataError("Valid transaction message format is not allowed")
except BaseException as e:
if __debug__:
import sys

sys.print_exception(e) # type: ignore["print_exception" is not a known member of module]
return
6 changes: 4 additions & 2 deletions core/src/apps/workflow_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,10 @@ def find_message_handler_module(msg_type: int) -> str:
return "apps.solana.get_address"
if msg_type == MessageType.SolanaSignTx:
return "apps.solana.sign_tx"
if msg_type == MessageType.SolanaSignMessage:
return "apps.solana.sign_message"
if msg_type == MessageType.SolanaSignUnsafeMessage:
return "apps.solana.sign_unsafe_message"
if msg_type == MessageType.SolanaSignOffChainMessage:
return "apps.solana.sign_offchain_message"

# starcoin
if msg_type == MessageType.StarcoinGetAddress:
Expand Down
5 changes: 3 additions & 2 deletions core/src/trezor/enums/MessageType.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,9 @@
SolanaAddress = 10101
SolanaSignTx = 10102
SolanaSignedTx = 10103
SolanaSignMessage = 10104
SolanaSignedMessage = 10105
SolanaSignOffChainMessage = 10104
SolanaMessageSignature = 10105
SolanaSignUnsafeMessage = 10106
CosmosGetAddress = 10800
CosmosAddress = 10801
CosmosSignTx = 10802
Expand Down
9 changes: 5 additions & 4 deletions core/src/trezor/enums/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,9 @@ class MessageType(IntEnum):
SolanaAddress = 10101
SolanaSignTx = 10102
SolanaSignedTx = 10103
SolanaSignMessage = 10104
SolanaSignedMessage = 10105
SolanaSignOffChainMessage = 10104
SolanaMessageSignature = 10105
SolanaSignUnsafeMessage = 10106
CosmosGetAddress = 10800
CosmosAddress = 10801
CosmosSignTx = 10802
Expand Down Expand Up @@ -709,10 +710,10 @@ class NEMImportanceTransferMode(IntEnum):
ImportanceTransfer_Activate = 1
ImportanceTransfer_Deactivate = 2

class SolanaMessageVersion(IntEnum):
class SolanaOffChainMessageVersion(IntEnum):
MESSAGE_VERSION_0 = 0

class SolanaMessageFormat(IntEnum):
class SolanaOffChainMessageFormat(IntEnum):
V0_RESTRICTED_ASCII = 0
V0_LIMITED_UTF8 = 1

Expand Down
13 changes: 13 additions & 0 deletions core/src/trezor/lvglui/i18n/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1883,4 +1883,17 @@
POWER_ON_LOW_BATTERY_DESC = 860
# Auto Lock/Shutdown
ITEM__AUTO_LOCK_AND_SHUTDOWN = 861
# Solana Raw Signing
SECURITY__SOLANA_RAW_SIGNING_TITLE = 862
# Allows signing raw Solana messages without processing or validation. This ma
# y expose you to phishing, blind signing, and unauthorized approvals. Use wit
# h caution.
SECURITY__SOLANA_RAW_SIGNING_DESC = 863
# Enable Solana Raw Signing?
SECURITY__SOLANA_RAW_SIGNING_ENABLE_TITLE = 864
# This may expose you to phishing, blind signing, and unauthorized transaction
# s. Enable only if you fully understand the risks.
SECURITY__SOLANA_RAW_SIGNING_ENABLE_DESC = 865
# Risk of phishing & blind signing. Proceed only if you trust the source.
SECURITY__SOLANA_RAW_SIGNING_TX_WARNING = 866
# fmt: on
5 changes: 5 additions & 0 deletions core/src/trezor/lvglui/i18n/locales/de.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,5 +862,10 @@
"Schwache Batterie",
"Ausschalten",
"Automatische Sperre/Abschaltung",
"Solana Raw Signing",
"Ermöglicht das Signieren von rohen Solana-Nachrichten ohne Verarbeitung oder Validierung. Dies kann Sie Phishing, blindem Signieren und unbefugten Genehmigungen aussetzen. Verwenden Sie es mit Vorsicht.",
"Solana Raw Signing aktivieren?",
"Dies kann Sie Phishing, blindem Signieren und unautorisierten Transaktionen aussetzen. Aktivieren Sie dies nur, wenn Sie die Risiken vollständig verstehen.",
"Risiko von Phishing & blindem Signieren. Fahren Sie nur fort, wenn Sie der Quelle vertrauen.",
]
# fmt: on
5 changes: 5 additions & 0 deletions core/src/trezor/lvglui/i18n/locales/en.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,5 +862,10 @@
"Low Battery",
"Powering off",
"Auto Lock/Shutdown",
"Solana Raw Signing",
"Allows signing raw Solana messages without processing or validation. This may expose you to phishing, blind signing, and unauthorized approvals. Use with caution.",
"Enable Solana Raw Signing?",
"This may expose you to phishing, blind signing, and unauthorized transactions. Enable only if you fully understand the risks.",
"Risk of phishing & blind signing. Proceed only if you trust the source.",
]
# fmt: on
5 changes: 5 additions & 0 deletions core/src/trezor/lvglui/i18n/locales/es.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,5 +862,10 @@
"Batería baja",
"Apagando",
"Bloqueo/Apagado Automático",
"Firma en bruto de Solana",
"Permite firmar mensajes sin procesar de Solana sin procesamiento ni validación. Esto puede exponerte a phishing, firmas ciegas y aprobaciones no autorizadas. Úsalo con precaución.",
"¿Habilitar la firma sin procesar de Solana?",
"Esto puede exponerte a phishing, firmas ciegas y transacciones no autorizadas. Activa solo si comprendes completamente los riesgos.",
"Riesgo de phishing y firma a ciegas. Proceda solo si confía en la fuente.",
]
# fmt: on
5 changes: 5 additions & 0 deletions core/src/trezor/lvglui/i18n/locales/fr.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,5 +862,10 @@
"Batterie faible",
"Mise hors tension",
"Verrouillage/Arrêt automatique",
"Signature brute Solana",
"Permet de signer des messages Solana bruts sans traitement ni validation. Cela peut vous exposer à des tentatives de phishing, à des signatures aveugles et à des approbations non autorisées. Utilisez avec prudence.",
"Activer la signature brute Solana ?",
"Cela peut vous exposer à des tentatives de phishing, à des signatures à l'aveugle et à des transactions non autorisées. Activez uniquement si vous comprenez pleinement les risques.",
"Risque de phishing et de signature à l'aveugle. Ne continuez que si vous faites confiance à la source.",
]
# fmt: on
5 changes: 5 additions & 0 deletions core/src/trezor/lvglui/i18n/locales/it.py
Original file line number Diff line number Diff line change
Expand Up @@ -862,5 +862,10 @@
"Batteria scarica",
"Spegnimento",
"Blocco automatico/spegnimento",
"Firma Raw di Solana",
"Permette di firmare messaggi Solana grezzi senza elaborazione o validazione. Questo potrebbe esporvi a phishing, firme alla cieca e approvazioni non autorizzate. Usare con cautela.",
"Abilitare Solana Raw Signing?",
"Questo potrebbe esporvi a phishing, firme alla cieca e transazioni non autorizzate. Abilitate solo se comprendete pienamente i rischi.",
"Rischio di phishing e firma alla cieca. Procedi solo se ti fidi della fonte.",
]
# fmt: on
Loading
Loading