Skip to content

Commit 0865cb3

Browse files
committed
feat(cardano): Message signing
1 parent 579cc0d commit 0865cb3

File tree

26 files changed

+2155
-241
lines changed

26 files changed

+2155
-241
lines changed

common/protob/messages-cardano.proto

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,48 @@ message CardanoTxBodyHash {
500500
*/
501501
message CardanoSignTxFinished {
502502
}
503+
504+
/**
505+
* Request: Ask device to sign a message containing arbitrary data
506+
* @start
507+
* @next CardanoMessageItemAck
508+
*/
509+
message CardanoSignMessageInit {
510+
optional uint32 protocol_magic = 1; // network's protocol magic
511+
optional uint32 network_id = 2; // network id - mainnet or testnet
512+
repeated uint32 signing_path = 3; // BIP-32-style path to derive the signing key from master node
513+
required uint32 payload_size = 4; // size of the payload to be signed
514+
required bool hash_payload = 5; // whether to hash the payload before signing
515+
required bool display_ascii = 6; // decode payload as ASCII
516+
optional CardanoAddressParametersType address_parameters = 7;
517+
required CardanoDerivationType derivation_type = 8;
518+
}
519+
520+
/**
521+
* @next CardanoMessagePayloadChunk
522+
* @next CardanoMessageItemHostAck
523+
*/
524+
message CardanoMessageItemAck {
525+
}
526+
527+
/**
528+
* @next CardanoMessageItemAck
529+
*/
530+
message CardanoMessagePayloadChunk {
531+
required bytes data = 1; // expected maximum chunk size is 1024 bytes
532+
}
533+
534+
/**
535+
* @next CardanoSignMessageFinished
536+
*/
537+
message CardanoMessageItemHostAck {
538+
}
539+
540+
/**
541+
* Response: Contains signature for message and address used in signed headers
542+
* @end
543+
*/
544+
message CardanoSignMessageFinished {
545+
required bytes signature = 1;
546+
required bytes address = 2;
547+
}

common/protob/messages.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ enum MessageType {
294294
MessageType_CardanoTxInlineDatumChunk = 335 [(wire_in) = true];
295295
MessageType_CardanoTxReferenceScriptChunk = 336 [(wire_in) = true];
296296
MessageType_CardanoTxReferenceInput = 337 [(wire_in) = true];
297+
MessageType_CardanoSignMessageInit = 338 [(wire_in) = true];
298+
MessageType_CardanoMessagePayloadChunk = 339 [(wire_in) = true];
299+
MessageType_CardanoMessageItemAck = 340 [(wire_out) = true];
300+
MessageType_CardanoMessageItemHostAck = 341 [(wire_in) = true];
301+
MessageType_CardanoSignMessageFinished = 342 [(wire_out) = true];
297302

298303
// Ripple
299304
MessageType_RippleGetAddress = 400 [(wire_in) = true];
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"setup": {
3+
"mnemonic": "all all all all all all all all all all all all",
4+
"passphrase": ""
5+
},
6+
"tests": [
7+
{
8+
"description": "Unhashed payload too long",
9+
"parameters": {
10+
"signing_path": "m/1852'/1815'/4'/0/0",
11+
"network_id": 1,
12+
"protocol_magic": 764824073,
13+
"payload": "566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765",
14+
"hash_payload": false,
15+
"display_ascii": false
16+
},
17+
"result": {
18+
"error_message": "Payload too long to sign without hashing"
19+
}
20+
},
21+
{
22+
"description": "Payload cannot be decoded to ASCII",
23+
"parameters": {
24+
"signing_path": "m/1852'/1815'/4'/0/0",
25+
"network_id": 1,
26+
"protocol_magic": 764824073,
27+
"payload": "ff",
28+
"hash_payload": false,
29+
"display_ascii": true
30+
},
31+
"result": {
32+
"error_message": "Payload cannot be decoded as ASCII or its decoding leads to a visually ambiguous string"
33+
}
34+
},
35+
{
36+
"description": "Payload is ambiguous when decoded as ASCII",
37+
"parameters": {
38+
"signing_path": "m/1852'/1815'/4'/0/0",
39+
"network_id": 1,
40+
"protocol_magic": 764824073,
41+
"payload": "20",
42+
"hash_payload": false,
43+
"display_ascii": true
44+
},
45+
"result": {
46+
"error_message": "Payload cannot be decoded as ASCII or its decoding leads to a visually ambiguous string"
47+
}
48+
}
49+
]
50+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
{
2+
"setup": {
3+
"mnemonic": "all all all all all all all all all all all all",
4+
"passphrase": ""
5+
},
6+
"tests": [
7+
{
8+
"description": "Sign short non-ASCII payload",
9+
"parameters": {
10+
"signing_path": "m/1852'/1815'/4'/0/0",
11+
"network_id": 1,
12+
"protocol_magic": 764824073,
13+
"payload": "ff00",
14+
"hash_payload": false,
15+
"display_ascii": false
16+
},
17+
"result": {
18+
"signature": "5ad6ba670e65353b2c1ad4053a1ed4a9348a73fe965ffa0afafa24bad06e3eb3e325d49029604c09bf665c3c43a750ec81a43b1f8b746b07e999b913b980d006",
19+
"address": "d9553a4de9c7ad8532abdb1d0a7f425b8007d25c9f1edcf0b5f5c3ba"
20+
}
21+
},
22+
{
23+
"description": "Sign short non-ASCII payload with address parameters",
24+
"parameters": {
25+
"signing_path": "m/1852'/1815'/4'/0/0",
26+
"network_id": 1,
27+
"protocol_magic": 764824073,
28+
"payload": "ff00",
29+
"hash_payload": false,
30+
"address_parameters": {
31+
"addressType": 0,
32+
"path": "m/1852'/1815'/0'/0/0",
33+
"stakingPath": "m/1852'/1815'/0'/2/0"
34+
},
35+
"display_ascii": false
36+
},
37+
"result": {
38+
"signature": "9efaff0b74c0beb2cadd727d8bafe13b31107235c5fc46c6c33e596e024d391c9fbe37072e43965add6ee0a4788562382031486b74fd59d636aa1ca3b1ddfe06",
39+
"address": "0180f9e2c88e6c817008f3a812ed889b4a4da8e0bd103f86e7335422aa122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277"
40+
}
41+
},
42+
{
43+
"description": "Sign short non-ASCII payload hash",
44+
"parameters": {
45+
"signing_path": "m/1852'/1815'/4'/0/0",
46+
"network_id": 1,
47+
"protocol_magic": 764824073,
48+
"payload": "ff00",
49+
"hash_payload": true,
50+
"display_ascii": false
51+
},
52+
"result": {
53+
"signature": "2c325e542fa78d76d916e50f50b85e770354a44e071f08fdb8ec5d0bcbf844cf70dcf5c87b7a51cd7f0269a59eec8d438c3c27eb42b971e7ccb7f864714c4b06",
54+
"address": "d9553a4de9c7ad8532abdb1d0a7f425b8007d25c9f1edcf0b5f5c3ba"
55+
}
56+
},
57+
{
58+
"description": "Sign short ASCII payload",
59+
"parameters": {
60+
"signing_path": "m/1852'/1815'/4'/0/0",
61+
"network_id": 1,
62+
"protocol_magic": 764824073,
63+
"payload": "54657374",
64+
"hash_payload": false,
65+
"display_ascii": true
66+
},
67+
"result": {
68+
"signature": "2201b8e7fa9ea919935e06ecc3e845433855066acaaf61cb8e624a2eb7139b73a9d126e7ee04548fff06ac933bd5419fc78c5aebee9b536cbee1481b52ec3e03",
69+
"address": "d9553a4de9c7ad8532abdb1d0a7f425b8007d25c9f1edcf0b5f5c3ba"
70+
}
71+
},
72+
{
73+
"description": "Sign short ASCII payload rendered as hex",
74+
"parameters": {
75+
"signing_path": "m/1852'/1815'/4'/0/0",
76+
"network_id": 1,
77+
"protocol_magic": 764824073,
78+
"payload": "54657374",
79+
"hash_payload": false,
80+
"display_ascii": false
81+
},
82+
"result": {
83+
"signature": "2201b8e7fa9ea919935e06ecc3e845433855066acaaf61cb8e624a2eb7139b73a9d126e7ee04548fff06ac933bd5419fc78c5aebee9b536cbee1481b52ec3e03",
84+
"address": "d9553a4de9c7ad8532abdb1d0a7f425b8007d25c9f1edcf0b5f5c3ba"
85+
}
86+
},
87+
{
88+
"description": "Sign empty payload",
89+
"parameters": {
90+
"signing_path": "m/1852'/1815'/4'/0/0",
91+
"network_id": 1,
92+
"protocol_magic": 764824073,
93+
"payload": "",
94+
"hash_payload": false,
95+
"display_ascii": false
96+
},
97+
"result": {
98+
"signature": "b09177a06cb2deba7ada89fec96fc4380e746f67c6b16a9ef9ae6b7cbbe941fdad8a8a573b809cd88db296b91b476c436033a29d86a63959e270047e47cd5d0d",
99+
"address": "d9553a4de9c7ad8532abdb1d0a7f425b8007d25c9f1edcf0b5f5c3ba"
100+
}
101+
},
102+
{
103+
"description": "Sign empty payload hash",
104+
"parameters": {
105+
"signing_path": "m/1852'/1815'/4'/0/0",
106+
"network_id": 1,
107+
"protocol_magic": 764824073,
108+
"payload": "",
109+
"hash_payload": true,
110+
"display_ascii": false
111+
},
112+
"result": {
113+
"signature": "a2503039a8ec620e05d9e4345339d61cd11480fbfcc75ea1a10789751a7c5f46ba06786eb1719da62db76c20313ad3445839b8117abac206cc4bd63ea623fc07",
114+
"address": "d9553a4de9c7ad8532abdb1d0a7f425b8007d25c9f1edcf0b5f5c3ba"
115+
}
116+
},
117+
{
118+
"description": "Sign long ASCII payload hash",
119+
"parameters": {
120+
"signing_path": "m/1852'/1815'/4'/0/0",
121+
"network_id": 1,
122+
"protocol_magic": 764824073,
123+
"payload": "566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765566572794c6f6e674d657373616765",
124+
"hash_payload": true,
125+
"display_ascii": true
126+
},
127+
"result": {
128+
"signature": "92331e75bb4c3208317ac422f2fc9d8b9b09d3f81cc487edaa7028d262553e5691532fb166a40e45eb2e4addd4280ff7e07bd4249e964d969e91555317b05f08",
129+
"address": "d9553a4de9c7ad8532abdb1d0a7f425b8007d25c9f1edcf0b5f5c3ba"
130+
}
131+
}
132+
]
133+
}

core/.changelog.d/noissue.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Cardano: Add support for signing arbitrary messages

core/src/all_modules.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,8 @@
459459
import apps.cardano.helpers.account_path_check
460460
apps.cardano.helpers.bech32
461461
import apps.cardano.helpers.bech32
462+
apps.cardano.helpers.chunks
463+
import apps.cardano.helpers.chunks
462464
apps.cardano.helpers.credential
463465
import apps.cardano.helpers.credential
464466
apps.cardano.helpers.hash_builder_collection
@@ -477,6 +479,8 @@
477479
import apps.cardano.native_script
478480
apps.cardano.seed
479481
import apps.cardano.seed
482+
apps.cardano.sign_message
483+
import apps.cardano.sign_message
480484
apps.cardano.sign_tx
481485
import apps.cardano.sign_tx
482486
apps.cardano.sign_tx.multisig_signer

core/src/apps/cardano/addresses.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646
CardanoAddressType.ENTERPRISE_SCRIPT,
4747
)
4848

49+
ADDRESS_TYPES_MESSAGE = (
50+
CardanoAddressType.BASE,
51+
CardanoAddressType.BASE_KEY_SCRIPT,
52+
CardanoAddressType.ENTERPRISE,
53+
CardanoAddressType.REWARD,
54+
)
55+
4956
ADDRESS_TYPES_PAYMENT = ADDRESS_TYPES_PAYMENT_KEY + ADDRESS_TYPES_PAYMENT_SCRIPT
5057

5158
_MIN_ADDRESS_BYTES_LENGTH = const(29)
@@ -242,6 +249,13 @@ def validate_cvote_payment_address_parameters(
242249
assert_params_cond(parameters.address_type in ADDRESS_TYPES_SHELLEY)
243250

244251

252+
def validate_message_address_parameters(
253+
parameters: messages.CardanoAddressParametersType,
254+
) -> None:
255+
validate_address_parameters(parameters)
256+
assert_params_cond(parameters.address_type in ADDRESS_TYPES_MESSAGE)
257+
258+
245259
def assert_cond(condition: bool) -> None:
246260
if not condition:
247261
raise ProcessError("Invalid address")
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from micropython import const
2+
from typing import TYPE_CHECKING
3+
4+
from trezor import messages, protobuf
5+
from trezor.wire import ProcessError
6+
from trezor.wire.context import call as ctx_call
7+
8+
if TYPE_CHECKING:
9+
from typing import Generic, TypeVar, Union
10+
11+
Chunk = Union[
12+
messages.CardanoTxInlineDatumChunk,
13+
messages.CardanoTxReferenceScriptChunk,
14+
messages.CardanoMessagePayloadChunk,
15+
]
16+
17+
C = TypeVar("C", bound=Chunk)
18+
else:
19+
# typechecker cheat
20+
Generic = (object,)
21+
C = Chunk = 0
22+
23+
MAX_CHUNK_SIZE = const(1024)
24+
25+
26+
def _get_chunks_count(size: int) -> int:
27+
"""Integer-only version of `ceil(size / MAX_CHUNK_SIZE)`."""
28+
assert size >= 0
29+
return 0 if size == 0 else (size - 1) // MAX_CHUNK_SIZE + 1
30+
31+
32+
def _validate_chunk(
33+
chunk: Chunk,
34+
chunk_index: int,
35+
total_size: int,
36+
) -> None:
37+
chunks_count = _get_chunks_count(total_size)
38+
assert chunk_index < chunks_count
39+
40+
if len(chunk.data) > MAX_CHUNK_SIZE:
41+
raise ProcessError("Invalid chunk: Too large")
42+
43+
is_last_chunk = chunk_index == chunks_count - 1
44+
45+
if not is_last_chunk and len(chunk.data) < MAX_CHUNK_SIZE:
46+
raise ProcessError("Invalid intermediate chunk: Too small")
47+
48+
if (
49+
is_last_chunk
50+
# check whether this chunk and preceding chunks add up to the supposed size
51+
and len(chunk.data) + MAX_CHUNK_SIZE * (chunks_count - 1) != total_size
52+
):
53+
raise ProcessError("Invalid last chunk: Size inconsistent with total bytes")
54+
55+
56+
class ChunkIterator(Generic[C]):
57+
def __init__(
58+
self,
59+
total_size: int,
60+
ack_msg: protobuf.MessageType,
61+
chunk_type: type[C],
62+
) -> None:
63+
self.ack_msg = ack_msg
64+
self.chunk_type = chunk_type
65+
self.chunk_index = 0
66+
self.chunks_count = _get_chunks_count(total_size)
67+
self.total_size = total_size
68+
69+
def __aiter__(self) -> "ChunkIterator":
70+
return self
71+
72+
async def __anext__(self) -> tuple[int, C]:
73+
if self.chunk_index >= self.chunks_count:
74+
raise StopAsyncIteration
75+
chunk: C = await ctx_call(self.ack_msg, self.chunk_type)
76+
_validate_chunk(chunk, chunk_index=self.chunk_index, total_size=self.total_size)
77+
result = (self.chunk_index, chunk)
78+
self.chunk_index += 1
79+
return result

0 commit comments

Comments
 (0)