diff --git a/device-protocol b/device-protocol index 2ec999a9..71829739 160000 --- a/device-protocol +++ b/device-protocol @@ -1 +1 @@ -Subproject commit 2ec999a9b2e5174da5981e85f66845a97cdaa877 +Subproject commit 7182973919e88ac49cc219f30f17ec17488f9fde diff --git a/keepkeylib/clearsign_catalog.py b/keepkeylib/clearsign_catalog.py index f78a5b4a..5d283ec7 100644 --- a/keepkeylib/clearsign_catalog.py +++ b/keepkeylib/clearsign_catalog.py @@ -954,10 +954,14 @@ def _bytes_tail(b): [{'name': 'sender', 'format': ARG_FORMAT_ADDRESS, 'value': addr('0x9406Cc6185a346906296840746125a0E44976454')}, {'name': 'nonce', 'format': ARG_FORMAT_STRING, 'value': b'UserOperation nonce: 12'}, {'name': 'beneficiary', 'format': ARG_FORMAT_ADDRESS, 'value': addr('0x' + '43' * 20)}, - {'name': 'innerCall', 'format': ARG_FORMAT_STRING, 'value': b'decoded separately, not raw'}], + {'name': 'innerCall', 'format': ARG_FORMAT_STRING, 'value': b'empty (representative)'}], why='A bundler-submitted meta-tx: the EntryPoint singleton validates and executes a batch of ' - 'smart-account operations; the inner callData (what the smart account will actually do) ' - 'must be decoded and shown, never left as an opaque blob one layer inside another.', + 'smart-account operations. KNOWN GAP, disclosed: this representative UserOp carries an ' + 'EMPTY inner callData (the array-of-dynamic-tuples nesting is beyond the current static ' + 'ABI encoder), so this flow proves sender/nonce/beneficiary are decoded but does NOT ' + 'prove the inner callData — what the smart account will actually do — is decoded. A real ' + 'UserOp with non-empty callData would need it decoded and shown, never left as an opaque ' + 'blob one layer inside another; that inner-decode capability is future work.', source='https://etherscan.io/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032 (EntryPoint v0.7)', ), ) diff --git a/keepkeylib/client.py b/keepkeylib/client.py index 4fdb5449..3ae9a69f 100644 --- a/keepkeylib/client.py +++ b/keepkeylib/client.py @@ -711,16 +711,57 @@ def ethereum_send_tx_metadata(self, signed_payload, metadata_version, key_id): return self.call(msg) @expect(proto.Success) - def load_clearsign_signer(self, key_id, pubkey, alias): - """Load a runtime clearsign signer (compressed pubkey + alias) into a - key slot. Triggers a mandatory on-device confirmation; RAM-only, the - signer is gone on reboot. Metadata verified by a loaded signer shows - a warning screen naming the alias before every clearsign page.""" + def load_clearsign_signer(self, key_id, pubkey, alias, icon=None, + icon_width=None, icon_height=None, persist=None): + """Load a clearsign signer (compressed pubkey + alias) into a key slot. + Triggers a mandatory on-device confirmation. Metadata verified by a + loaded signer shows a warning screen naming the alias before every + clearsign page. + + icon (optional, <= 384 bytes) is an identity logo shown on the trust + screen. It is RUN-LENGTH ENCODED with byte-valued pixels, NOT a packed + bitmap: draw_bitmap_mono_rle() in keepkey-firmware lib/board/draw.c is + the decoder of record, and it is what every bundled image already uses. + + Grammar -- read n = int8(data[i++]): + n in [1, 127] RUN : one value byte follows; emit it n times. + n in [-127, -1] LITERAL : (-n) value bytes follow; emit each once. + n == 0 : invalid. + n == -128 (0x80) : INVALID -- the device's run counter is + int8_t and cannot represent 128. Split a + 128-byte literal into two packets. + The stream must decode EXACTLY: no run may straddle the end of the + image, exactly icon_width*icon_height pixels are emitted (row-major), + and the whole input must be consumed -- trailing packets are rejected. + The device validates this before showing or storing the icon. See + LoadClearsignSigner.icon in messages-ethereum.proto for the grammar and + a golden vector. + + icon_width and icon_height are required with icon. + icon_width : 1..40 -- the confirm screen's icon column + (LEFT_MARGIN_WITH_ICON). Text begins at x=40 and the + icon is drawn after it, so a wider icon would paint over + the alias, fingerprint and the "NOT verified by KeepKey" + warning. Capped, not clipped. + icon_height : 1..64 -- the icon column is 64px tall. + Omit all three for a text-only identity. + + persist=True also writes the identity to flash, so it survives reboot + and is reloaded automatically. The default is RAM-only (gone on + reboot).""" msg = eth_proto.LoadClearsignSigner( key_id=key_id, pubkey=pubkey, alias=alias, ) + if icon is not None: + msg.icon = icon + if icon_width is not None: + msg.icon_width = icon_width + if icon_height is not None: + msg.icon_height = icon_height + if persist is not None: + msg.persist = persist return self.call(msg) @session diff --git a/keepkeylib/hive.py b/keepkeylib/hive.py index 8222ba68..c8758b89 100644 --- a/keepkeylib/hive.py +++ b/keepkeylib/hive.py @@ -32,6 +32,24 @@ def sign_tx(client, address_n, chain_id, ref_block_num, ref_block_prefix, })) +def sign_message(client, address_n, message): + """Keychain signBuffer contract: sig over SHA256(raw message bytes) only — + no chain_id prepend, no message prefix.""" + if isinstance(message, str): + message = message.encode('utf-8') + return client.call(proto.HiveSignMessage(address_n=address_n, message=message)) + + +def sign_operations(client, address_n, serialized_tx, chain_id=None): + """Sign a host-serialized Graphene transaction (HiveSignOperations). + Firmware parses the bytes and clear-signs the phase-1 op table + (vote, comment, custom_json); digest = SHA256(chain_id || tx).""" + kwargs = dict(address_n=address_n, serialized_tx=serialized_tx) + if chain_id is not None: + kwargs['chain_id'] = chain_id + return client.call(proto.HiveSignOperations(**kwargs)) + + def sign_account_create(client, address_n, chain_id, ref_block_num, ref_block_prefix, expiration, creator, new_account_name, fee_amount=3000, owner_key='', active_key='', posting_key='', memo_key=''): diff --git a/keepkeylib/mapping.py b/keepkeylib/mapping.py index 954c0539..5b851dc0 100644 --- a/keepkeylib/mapping.py +++ b/keepkeylib/mapping.py @@ -114,6 +114,11 @@ def check_missing(): 1607: ('HiveSignedAccountCreate', hive_proto), 1608: ('HiveSignAccountUpdate', hive_proto), 1609: ('HiveSignedAccountUpdate', hive_proto), + # 1610-1613 reserved: NEAR + 1614: ('HiveSignMessage', hive_proto), + 1615: ('HiveSignedMessage', hive_proto), + 1616: ('HiveSignOperations', hive_proto), + 1617: ('HiveSignedOperations', hive_proto), } for wire_id, (msg_name, mod) in _hive_wire_ids.items(): msg_class = getattr(mod, msg_name, None) diff --git a/keepkeylib/messages_ethereum_pb2.py b/keepkeylib/messages_ethereum_pb2.py index a4f5efcd..a20d679a 100644 --- a/keepkeylib/messages_ethereum_pb2.py +++ b/keepkeylib/messages_ethereum_pb2.py @@ -20,7 +20,7 @@ name='messages-ethereum.proto', package='', syntax='proto2', - serialized_pb=_b('\n\x17messages-ethereum.proto\x1a\x0btypes.proto\"=\n\x12\x45thereumGetAddress\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x14\n\x0cshow_display\x18\x02 \x01(\x08\"7\n\x0f\x45thereumAddress\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x02(\x0c\x12\x13\n\x0b\x61\x64\x64ress_str\x18\x02 \x01(\t\"\x95\x03\n\x0e\x45thereumSignTx\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\r\n\x05nonce\x18\x02 \x01(\x0c\x12\x11\n\tgas_price\x18\x03 \x01(\x0c\x12\x11\n\tgas_limit\x18\x04 \x01(\x0c\x12\n\n\x02to\x18\x05 \x01(\x0c\x12\r\n\x05value\x18\x06 \x01(\x0c\x12\x1a\n\x12\x64\x61ta_initial_chunk\x18\x07 \x01(\x0c\x12\x13\n\x0b\x64\x61ta_length\x18\x08 \x01(\r\x12\x14\n\x0cto_address_n\x18\t \x03(\r\x12(\n\x0c\x61\x64\x64ress_type\x18\n \x01(\x0e\x32\x12.OutputAddressType\x12\x10\n\x08\x63hain_id\x18\x0c \x01(\r\x12\x17\n\x0fmax_fee_per_gas\x18\r \x01(\x0c\x12 \n\x18max_priority_fee_per_gas\x18\x0e \x01(\x0c\x12\x13\n\x0btoken_value\x18\x64 \x01(\x0c\x12\x10\n\x08token_to\x18\x65 \x01(\x0c\x12\x16\n\x0etoken_shortcut\x18\x66 \x01(\t\x12\x0f\n\x07tx_type\x18g \x01(\r\x12\x0c\n\x04type\x18h \x01(\rJ\x04\x08\x0b\x10\x0c\"\x8c\x01\n\x11\x45thereumTxRequest\x12\x13\n\x0b\x64\x61ta_length\x18\x01 \x01(\r\x12\x13\n\x0bsignature_v\x18\x02 \x01(\r\x12\x13\n\x0bsignature_r\x18\x03 \x01(\x0c\x12\x13\n\x0bsignature_s\x18\x04 \x01(\x0c\x12\x0c\n\x04hash\x18\x05 \x01(\x0c\x12\x15\n\rsignature_der\x18\x06 \x01(\x0c\"#\n\rEthereumTxAck\x12\x12\n\ndata_chunk\x18\x01 \x01(\x0c\"V\n\x12\x45thereumTxMetadata\x12\x16\n\x0esigned_payload\x18\x01 \x01(\x0c\x12\x18\n\x10metadata_version\x18\x02 \x01(\r\x12\x0e\n\x06key_id\x18\x03 \x01(\r\"F\n\x13\x45thereumMetadataAck\x12\x16\n\x0e\x63lassification\x18\x01 \x02(\r\x12\x17\n\x0f\x64isplay_summary\x18\x02 \x01(\t\"D\n\x13LoadClearsignSigner\x12\x0e\n\x06key_id\x18\x01 \x01(\r\x12\x0e\n\x06pubkey\x18\x02 \x01(\x0c\x12\r\n\x05\x61lias\x18\x03 \x01(\t\"9\n\x13\x45thereumSignMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07message\x18\x02 \x02(\x0c\"L\n\x15\x45thereumVerifyMessage\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\x0c\">\n\x18\x45thereumMessageSignature\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\"_\n\x15\x45thereumSignTypedHash\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x1d\n\x15\x64omain_separator_hash\x18\x02 \x02(\x0c\x12\x14\n\x0cmessage_hash\x18\x03 \x01(\x0c\"\x8b\x01\n\x1a\x45thereumTypedDataSignature\x12\x11\n\tsignature\x18\x01 \x02(\x0c\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x02(\t\x12\x1d\n\x15\x64omain_separator_hash\x18\x03 \x01(\x0c\x12\x14\n\x0chas_msg_hash\x18\x04 \x02(\x08\x12\x14\n\x0cmessage_hash\x18\x05 \x01(\x0c\"\x85\x01\n\x16\x45thereum712TypesValues\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x13\n\x0b\x65ip712types\x18\x02 \x02(\t\x12\x17\n\x0f\x65ip712primetype\x18\x03 \x02(\t\x12\x12\n\neip712data\x18\x04 \x02(\t\x12\x16\n\x0e\x65ip712typevals\x18\x05 \x02(\rB4\n\x1a\x63om.keepkey.deviceprotocolB\x16KeepKeyMessageEthereum') + serialized_pb=_b('\n\x17messages-ethereum.proto\x1a\x0btypes.proto\"=\n\x12\x45thereumGetAddress\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x14\n\x0cshow_display\x18\x02 \x01(\x08\"7\n\x0f\x45thereumAddress\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x02(\x0c\x12\x13\n\x0b\x61\x64\x64ress_str\x18\x02 \x01(\t\"\x95\x03\n\x0e\x45thereumSignTx\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\r\n\x05nonce\x18\x02 \x01(\x0c\x12\x11\n\tgas_price\x18\x03 \x01(\x0c\x12\x11\n\tgas_limit\x18\x04 \x01(\x0c\x12\n\n\x02to\x18\x05 \x01(\x0c\x12\r\n\x05value\x18\x06 \x01(\x0c\x12\x1a\n\x12\x64\x61ta_initial_chunk\x18\x07 \x01(\x0c\x12\x13\n\x0b\x64\x61ta_length\x18\x08 \x01(\r\x12\x14\n\x0cto_address_n\x18\t \x03(\r\x12(\n\x0c\x61\x64\x64ress_type\x18\n \x01(\x0e\x32\x12.OutputAddressType\x12\x10\n\x08\x63hain_id\x18\x0c \x01(\r\x12\x17\n\x0fmax_fee_per_gas\x18\r \x01(\x0c\x12 \n\x18max_priority_fee_per_gas\x18\x0e \x01(\x0c\x12\x13\n\x0btoken_value\x18\x64 \x01(\x0c\x12\x10\n\x08token_to\x18\x65 \x01(\x0c\x12\x16\n\x0etoken_shortcut\x18\x66 \x01(\t\x12\x0f\n\x07tx_type\x18g \x01(\r\x12\x0c\n\x04type\x18h \x01(\rJ\x04\x08\x0b\x10\x0c\"\x8c\x01\n\x11\x45thereumTxRequest\x12\x13\n\x0b\x64\x61ta_length\x18\x01 \x01(\r\x12\x13\n\x0bsignature_v\x18\x02 \x01(\r\x12\x13\n\x0bsignature_r\x18\x03 \x01(\x0c\x12\x13\n\x0bsignature_s\x18\x04 \x01(\x0c\x12\x0c\n\x04hash\x18\x05 \x01(\x0c\x12\x15\n\rsignature_der\x18\x06 \x01(\x0c\"#\n\rEthereumTxAck\x12\x12\n\ndata_chunk\x18\x01 \x01(\x0c\"V\n\x12\x45thereumTxMetadata\x12\x16\n\x0esigned_payload\x18\x01 \x01(\x0c\x12\x18\n\x10metadata_version\x18\x02 \x01(\r\x12\x0e\n\x06key_id\x18\x03 \x01(\r\"F\n\x13\x45thereumMetadataAck\x12\x16\n\x0e\x63lassification\x18\x01 \x02(\r\x12\x17\n\x0f\x64isplay_summary\x18\x02 \x01(\t\"\x8c\x01\n\x13LoadClearsignSigner\x12\x0e\n\x06key_id\x18\x01 \x01(\r\x12\x0e\n\x06pubkey\x18\x02 \x01(\x0c\x12\r\n\x05\x61lias\x18\x03 \x01(\t\x12\x0c\n\x04icon\x18\x04 \x01(\x0c\x12\x12\n\nicon_width\x18\x05 \x01(\r\x12\x13\n\x0bicon_height\x18\x06 \x01(\r\x12\x0f\n\x07persist\x18\x07 \x01(\x08\"9\n\x13\x45thereumSignMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07message\x18\x02 \x02(\x0c\"L\n\x15\x45thereumVerifyMessage\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\x0c\">\n\x18\x45thereumMessageSignature\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\"_\n\x15\x45thereumSignTypedHash\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x1d\n\x15\x64omain_separator_hash\x18\x02 \x02(\x0c\x12\x14\n\x0cmessage_hash\x18\x03 \x01(\x0c\"\x8b\x01\n\x1a\x45thereumTypedDataSignature\x12\x11\n\tsignature\x18\x01 \x02(\x0c\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x02(\t\x12\x1d\n\x15\x64omain_separator_hash\x18\x03 \x01(\x0c\x12\x14\n\x0chas_msg_hash\x18\x04 \x02(\x08\x12\x14\n\x0cmessage_hash\x18\x05 \x01(\x0c\"\x85\x01\n\x16\x45thereum712TypesValues\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x13\n\x0b\x65ip712types\x18\x02 \x02(\t\x12\x17\n\x0f\x65ip712primetype\x18\x03 \x02(\t\x12\x12\n\neip712data\x18\x04 \x02(\t\x12\x16\n\x0e\x65ip712typevals\x18\x05 \x02(\rB4\n\x1a\x63om.keepkey.deviceprotocolB\x16KeepKeyMessageEthereum') , dependencies=[types__pb2.DESCRIPTOR,]) @@ -461,6 +461,34 @@ message_type=None, enum_type=None, containing_type=None, is_extension=False, extension_scope=None, options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='icon', full_name='LoadClearsignSigner.icon', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='icon_width', full_name='LoadClearsignSigner.icon_width', index=4, + number=5, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='icon_height', full_name='LoadClearsignSigner.icon_height', index=5, + number=6, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='persist', full_name='LoadClearsignSigner.persist', index=6, + number=7, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), ], extensions=[ ], @@ -473,8 +501,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=908, - serialized_end=976, + serialized_start=909, + serialized_end=1049, ) @@ -511,8 +539,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=978, - serialized_end=1035, + serialized_start=1051, + serialized_end=1108, ) @@ -556,8 +584,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1037, - serialized_end=1113, + serialized_start=1110, + serialized_end=1186, ) @@ -594,8 +622,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1115, - serialized_end=1177, + serialized_start=1188, + serialized_end=1250, ) @@ -639,8 +667,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1179, - serialized_end=1274, + serialized_start=1252, + serialized_end=1347, ) @@ -698,8 +726,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1277, - serialized_end=1416, + serialized_start=1350, + serialized_end=1489, ) @@ -757,8 +785,8 @@ extension_ranges=[], oneofs=[ ], - serialized_start=1419, - serialized_end=1552, + serialized_start=1492, + serialized_end=1625, ) _ETHEREUMSIGNTX.fields_by_name['address_type'].enum_type = types__pb2._OUTPUTADDRESSTYPE diff --git a/keepkeylib/messages_hive_pb2.py b/keepkeylib/messages_hive_pb2.py index 1d12c922..c83b6460 100644 --- a/keepkeylib/messages_hive_pb2.py +++ b/keepkeylib/messages_hive_pb2.py @@ -19,7 +19,7 @@ name='messages-hive.proto', package='', syntax='proto2', - serialized_pb=_b('\n\x13messages-hive.proto\"I\n\x10HiveGetPublicKey\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x14\n\x0cshow_display\x18\x02 \x01(\x08\x12\x0c\n\x04role\x18\x03 \x01(\r\";\n\rHivePublicKey\x12\x12\n\npublic_key\x18\x01 \x01(\t\x12\x16\n\x0eraw_public_key\x18\x02 \x01(\x0c\"C\n\x11HiveGetPublicKeys\x12\x18\n\raccount_index\x18\x01 \x01(\r:\x01\x30\x12\x14\n\x0cshow_display\x18\x02 \x01(\x08\"^\n\x0eHivePublicKeys\x12\x11\n\towner_key\x18\x01 \x01(\t\x12\x12\n\nactive_key\x18\x02 \x01(\t\x12\x10\n\x08memo_key\x18\x03 \x01(\t\x12\x13\n\x0bposting_key\x18\x04 \x01(\t\"\xd6\x01\n\nHiveSignTx\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x10\n\x08\x63hain_id\x18\x02 \x01(\x0c\x12\x15\n\rref_block_num\x18\x03 \x01(\r\x12\x18\n\x10ref_block_prefix\x18\x04 \x01(\r\x12\x12\n\nexpiration\x18\x05 \x01(\r\x12\x0c\n\x04\x66rom\x18\x06 \x01(\t\x12\n\n\x02to\x18\x07 \x01(\t\x12\x0e\n\x06\x61mount\x18\x08 \x01(\x04\x12\x10\n\x08\x64\x65\x63imals\x18\t \x01(\r\x12\x14\n\x0c\x61sset_symbol\x18\n \x01(\t\x12\x0c\n\x04memo\x18\x0b \x01(\t\"8\n\x0cHiveSignedTx\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x15\n\rserialized_tx\x18\x02 \x01(\x0c\"\x8e\x02\n\x15HiveSignAccountCreate\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x10\n\x08\x63hain_id\x18\x02 \x01(\x0c\x12\x15\n\rref_block_num\x18\x03 \x01(\r\x12\x18\n\x10ref_block_prefix\x18\x04 \x01(\r\x12\x12\n\nexpiration\x18\x05 \x01(\r\x12\x0f\n\x07\x63reator\x18\x06 \x01(\t\x12\x18\n\x10new_account_name\x18\x07 \x01(\t\x12\x11\n\towner_key\x18\x08 \x01(\t\x12\x12\n\nactive_key\x18\t \x01(\t\x12\x13\n\x0bposting_key\x18\n \x01(\t\x12\x10\n\x08memo_key\x18\x0b \x01(\t\x12\x12\n\nfee_amount\x18\x0c \x01(\x04\"C\n\x17HiveSignedAccountCreate\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x15\n\rserialized_tx\x18\x02 \x01(\x0c\"\xf0\x01\n\x15HiveSignAccountUpdate\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x10\n\x08\x63hain_id\x18\x02 \x01(\x0c\x12\x15\n\rref_block_num\x18\x03 \x01(\r\x12\x18\n\x10ref_block_prefix\x18\x04 \x01(\r\x12\x12\n\nexpiration\x18\x05 \x01(\r\x12\x0f\n\x07\x61\x63\x63ount\x18\x06 \x01(\t\x12\x15\n\rnew_owner_key\x18\x07 \x01(\t\x12\x16\n\x0enew_active_key\x18\x08 \x01(\t\x12\x17\n\x0fnew_posting_key\x18\t \x01(\t\x12\x14\n\x0cnew_memo_key\x18\n \x01(\t\"C\n\x17HiveSignedAccountUpdate\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x15\n\rserialized_tx\x18\x02 \x01(\x0c\x42\x39\n#com.shapeshift.keepkey.lib.protobufB\x12KeepKeyMessageHive') + serialized_pb=_b('\n\x13messages-hive.proto\"I\n\x10HiveGetPublicKey\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x14\n\x0cshow_display\x18\x02 \x01(\x08\x12\x0c\n\x04role\x18\x03 \x01(\r\";\n\rHivePublicKey\x12\x12\n\npublic_key\x18\x01 \x01(\t\x12\x16\n\x0eraw_public_key\x18\x02 \x01(\x0c\"C\n\x11HiveGetPublicKeys\x12\x18\n\raccount_index\x18\x01 \x01(\r:\x01\x30\x12\x14\n\x0cshow_display\x18\x02 \x01(\x08\"^\n\x0eHivePublicKeys\x12\x11\n\towner_key\x18\x01 \x01(\t\x12\x12\n\nactive_key\x18\x02 \x01(\t\x12\x10\n\x08memo_key\x18\x03 \x01(\t\x12\x13\n\x0bposting_key\x18\x04 \x01(\t\"\xd6\x01\n\nHiveSignTx\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x10\n\x08\x63hain_id\x18\x02 \x01(\x0c\x12\x15\n\rref_block_num\x18\x03 \x01(\r\x12\x18\n\x10ref_block_prefix\x18\x04 \x01(\r\x12\x12\n\nexpiration\x18\x05 \x01(\r\x12\x0c\n\x04\x66rom\x18\x06 \x01(\t\x12\n\n\x02to\x18\x07 \x01(\t\x12\x0e\n\x06\x61mount\x18\x08 \x01(\x04\x12\x10\n\x08\x64\x65\x63imals\x18\t \x01(\r\x12\x14\n\x0c\x61sset_symbol\x18\n \x01(\t\x12\x0c\n\x04memo\x18\x0b \x01(\t\"8\n\x0cHiveSignedTx\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x15\n\rserialized_tx\x18\x02 \x01(\x0c\"\x8e\x02\n\x15HiveSignAccountCreate\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x10\n\x08\x63hain_id\x18\x02 \x01(\x0c\x12\x15\n\rref_block_num\x18\x03 \x01(\r\x12\x18\n\x10ref_block_prefix\x18\x04 \x01(\r\x12\x12\n\nexpiration\x18\x05 \x01(\r\x12\x0f\n\x07\x63reator\x18\x06 \x01(\t\x12\x18\n\x10new_account_name\x18\x07 \x01(\t\x12\x11\n\towner_key\x18\x08 \x01(\t\x12\x12\n\nactive_key\x18\t \x01(\t\x12\x13\n\x0bposting_key\x18\n \x01(\t\x12\x10\n\x08memo_key\x18\x0b \x01(\t\x12\x12\n\nfee_amount\x18\x0c \x01(\x04\"C\n\x17HiveSignedAccountCreate\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x15\n\rserialized_tx\x18\x02 \x01(\x0c\"\xf0\x01\n\x15HiveSignAccountUpdate\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x10\n\x08\x63hain_id\x18\x02 \x01(\x0c\x12\x15\n\rref_block_num\x18\x03 \x01(\r\x12\x18\n\x10ref_block_prefix\x18\x04 \x01(\r\x12\x12\n\nexpiration\x18\x05 \x01(\r\x12\x0f\n\x07\x61\x63\x63ount\x18\x06 \x01(\t\x12\x15\n\rnew_owner_key\x18\x07 \x01(\t\x12\x16\n\x0enew_active_key\x18\x08 \x01(\t\x12\x17\n\x0fnew_posting_key\x18\t \x01(\t\x12\x14\n\x0cnew_memo_key\x18\n \x01(\t\"C\n\x17HiveSignedAccountUpdate\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x15\n\rserialized_tx\x18\x02 \x01(\x0c\"5\n\x0fHiveSignMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07message\x18\x02 \x01(\x0c\":\n\x11HiveSignedMessage\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x12\x12\n\npublic_key\x18\x02 \x01(\x0c\"P\n\x12HiveSignOperations\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x10\n\x08\x63hain_id\x18\x02 \x01(\x0c\x12\x15\n\rserialized_tx\x18\x03 \x01(\x0c\")\n\x14HiveSignedOperations\x12\x11\n\tsignature\x18\x01 \x01(\x0c\x42\x39\n#com.shapeshift.keepkey.lib.protobufB\x12KeepKeyMessageHive') ) @@ -614,6 +614,158 @@ serialized_end=1251, ) + +_HIVESIGNMESSAGE = _descriptor.Descriptor( + name='HiveSignMessage', + full_name='HiveSignMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='address_n', full_name='HiveSignMessage.address_n', index=0, + number=1, type=13, cpp_type=3, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='message', full_name='HiveSignMessage.message', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1253, + serialized_end=1306, +) + + +_HIVESIGNEDMESSAGE = _descriptor.Descriptor( + name='HiveSignedMessage', + full_name='HiveSignedMessage', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='signature', full_name='HiveSignedMessage.signature', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='public_key', full_name='HiveSignedMessage.public_key', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1308, + serialized_end=1366, +) + + +_HIVESIGNOPERATIONS = _descriptor.Descriptor( + name='HiveSignOperations', + full_name='HiveSignOperations', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='address_n', full_name='HiveSignOperations.address_n', index=0, + number=1, type=13, cpp_type=3, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='chain_id', full_name='HiveSignOperations.chain_id', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='serialized_tx', full_name='HiveSignOperations.serialized_tx', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1368, + serialized_end=1448, +) + + +_HIVESIGNEDOPERATIONS = _descriptor.Descriptor( + name='HiveSignedOperations', + full_name='HiveSignedOperations', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='signature', full_name='HiveSignedOperations.signature', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto2', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1450, + serialized_end=1491, +) + DESCRIPTOR.message_types_by_name['HiveGetPublicKey'] = _HIVEGETPUBLICKEY DESCRIPTOR.message_types_by_name['HivePublicKey'] = _HIVEPUBLICKEY DESCRIPTOR.message_types_by_name['HiveGetPublicKeys'] = _HIVEGETPUBLICKEYS @@ -624,6 +776,10 @@ DESCRIPTOR.message_types_by_name['HiveSignedAccountCreate'] = _HIVESIGNEDACCOUNTCREATE DESCRIPTOR.message_types_by_name['HiveSignAccountUpdate'] = _HIVESIGNACCOUNTUPDATE DESCRIPTOR.message_types_by_name['HiveSignedAccountUpdate'] = _HIVESIGNEDACCOUNTUPDATE +DESCRIPTOR.message_types_by_name['HiveSignMessage'] = _HIVESIGNMESSAGE +DESCRIPTOR.message_types_by_name['HiveSignedMessage'] = _HIVESIGNEDMESSAGE +DESCRIPTOR.message_types_by_name['HiveSignOperations'] = _HIVESIGNOPERATIONS +DESCRIPTOR.message_types_by_name['HiveSignedOperations'] = _HIVESIGNEDOPERATIONS _sym_db.RegisterFileDescriptor(DESCRIPTOR) HiveGetPublicKey = _reflection.GeneratedProtocolMessageType('HiveGetPublicKey', (_message.Message,), dict( @@ -696,6 +852,34 @@ )) _sym_db.RegisterMessage(HiveSignedAccountUpdate) +HiveSignMessage = _reflection.GeneratedProtocolMessageType('HiveSignMessage', (_message.Message,), dict( + DESCRIPTOR = _HIVESIGNMESSAGE, + __module__ = 'messages_hive_pb2' + # @@protoc_insertion_point(class_scope:HiveSignMessage) + )) +_sym_db.RegisterMessage(HiveSignMessage) + +HiveSignedMessage = _reflection.GeneratedProtocolMessageType('HiveSignedMessage', (_message.Message,), dict( + DESCRIPTOR = _HIVESIGNEDMESSAGE, + __module__ = 'messages_hive_pb2' + # @@protoc_insertion_point(class_scope:HiveSignedMessage) + )) +_sym_db.RegisterMessage(HiveSignedMessage) + +HiveSignOperations = _reflection.GeneratedProtocolMessageType('HiveSignOperations', (_message.Message,), dict( + DESCRIPTOR = _HIVESIGNOPERATIONS, + __module__ = 'messages_hive_pb2' + # @@protoc_insertion_point(class_scope:HiveSignOperations) + )) +_sym_db.RegisterMessage(HiveSignOperations) + +HiveSignedOperations = _reflection.GeneratedProtocolMessageType('HiveSignedOperations', (_message.Message,), dict( + DESCRIPTOR = _HIVESIGNEDOPERATIONS, + __module__ = 'messages_hive_pb2' + # @@protoc_insertion_point(class_scope:HiveSignedOperations) + )) +_sym_db.RegisterMessage(HiveSignedOperations) + DESCRIPTOR.has_options = True DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('\n#com.shapeshift.keepkey.lib.protobufB\022KeepKeyMessageHive')) diff --git a/keepkeylib/messages_pb2.py b/keepkeylib/messages_pb2.py index a6989aab..d7b8712a 100644 --- a/keepkeylib/messages_pb2.py +++ b/keepkeylib/messages_pb2.py @@ -21,7 +21,7 @@ name='messages.proto', package='', syntax='proto2', - serialized_pb=_b('\n\x0emessages.proto\x1a\x0btypes.proto\"\x0c\n\nInitialize\"\r\n\x0bGetFeatures\"\xaa\x04\n\x08\x46\x65\x61tures\x12\x0e\n\x06vendor\x18\x01 \x01(\t\x12\x15\n\rmajor_version\x18\x02 \x01(\r\x12\x15\n\rminor_version\x18\x03 \x01(\r\x12\x15\n\rpatch_version\x18\x04 \x01(\r\x12\x17\n\x0f\x62ootloader_mode\x18\x05 \x01(\x08\x12\x11\n\tdevice_id\x18\x06 \x01(\t\x12\x16\n\x0epin_protection\x18\x07 \x01(\x08\x12\x1d\n\x15passphrase_protection\x18\x08 \x01(\x08\x12\x10\n\x08language\x18\t \x01(\t\x12\r\n\x05label\x18\n \x01(\t\x12\x18\n\x05\x63oins\x18\x0b \x03(\x0b\x32\t.CoinType\x12\x13\n\x0binitialized\x18\x0c \x01(\x08\x12\x10\n\x08revision\x18\r \x01(\x0c\x12\x17\n\x0f\x62ootloader_hash\x18\x0e \x01(\x0c\x12\x10\n\x08imported\x18\x0f \x01(\x08\x12\x12\n\npin_cached\x18\x10 \x01(\x08\x12\x19\n\x11passphrase_cached\x18\x11 \x01(\x08\x12\x1d\n\x08policies\x18\x12 \x03(\x0b\x32\x0b.PolicyType\x12\r\n\x05model\x18\x15 \x01(\t\x12\x18\n\x10\x66irmware_variant\x18\x16 \x01(\t\x12\x15\n\rfirmware_hash\x18\x17 \x01(\x0c\x12\x11\n\tno_backup\x18\x18 \x01(\x08\x12\x1c\n\x14wipe_code_protection\x18\x19 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x1a \x01(\r\"*\n\x0cGetCoinTable\x12\r\n\x05start\x18\x01 \x01(\r\x12\x0b\n\x03\x65nd\x18\x02 \x01(\r\"L\n\tCoinTable\x12\x18\n\x05table\x18\x01 \x03(\x0b\x32\t.CoinType\x12\x11\n\tnum_coins\x18\x02 \x01(\r\x12\x12\n\nchunk_size\x18\x03 \x01(\r\"\x0e\n\x0c\x43learSession\"y\n\rApplySettings\x12\x10\n\x08language\x18\x01 \x01(\t\x12\r\n\x05label\x18\x02 \x01(\t\x12\x16\n\x0euse_passphrase\x18\x03 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x04 \x01(\r\x12\x13\n\x0bu2f_counter\x18\x05 \x01(\r\"\x1b\n\tChangePin\x12\x0e\n\x06remove\x18\x01 \x01(\x08\"\x87\x01\n\x04Ping\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x19\n\x11\x62utton_protection\x18\x02 \x01(\x08\x12\x16\n\x0epin_protection\x18\x03 \x01(\x08\x12\x1d\n\x15passphrase_protection\x18\x04 \x01(\x08\x12\x1c\n\x14wipe_code_protection\x18\x05 \x01(\x08\"\x1a\n\x07Success\x12\x0f\n\x07message\x18\x01 \x01(\t\"6\n\x07\x46\x61ilure\x12\x1a\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x0c.FailureType\x12\x0f\n\x07message\x18\x02 \x01(\t\"?\n\rButtonRequest\x12 \n\x04\x63ode\x18\x01 \x01(\x0e\x32\x12.ButtonRequestType\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\"\x0b\n\tButtonAck\"7\n\x10PinMatrixRequest\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.PinMatrixRequestType\"\x1b\n\x0cPinMatrixAck\x12\x0b\n\x03pin\x18\x01 \x02(\t\"\x08\n\x06\x43\x61ncel\"\x13\n\x11PassphraseRequest\"#\n\rPassphraseAck\x12\x12\n\npassphrase\x18\x01 \x02(\t\"\x1a\n\nGetEntropy\x12\x0c\n\x04size\x18\x01 \x02(\r\"\x1a\n\x07\x45ntropy\x12\x0f\n\x07\x65ntropy\x18\x01 \x02(\x0c\"\xa2\x01\n\x0cGetPublicKey\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x18\n\x10\x65\x63\x64sa_curve_name\x18\x02 \x01(\t\x12\x14\n\x0cshow_display\x18\x03 \x01(\x08\x12\x1a\n\tcoin_name\x18\x04 \x01(\t:\x07\x42itcoin\x12\x33\n\x0bscript_type\x18\x05 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"4\n\tPublicKey\x12\x19\n\x04node\x18\x01 \x02(\x0b\x32\x0b.HDNodeType\x12\x0c\n\x04xpub\x18\x02 \x01(\t\"\xb3\x01\n\nGetAddress\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x1a\n\tcoin_name\x18\x02 \x01(\t:\x07\x42itcoin\x12\x14\n\x0cshow_display\x18\x03 \x01(\x08\x12+\n\x08multisig\x18\x04 \x01(\x0b\x32\x19.MultisigRedeemScriptType\x12\x33\n\x0bscript_type\x18\x05 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"\x1a\n\x07\x41\x64\x64ress\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x02(\t\"\x0c\n\nWipeDevice\"\xbb\x01\n\nLoadDevice\x12\x10\n\x08mnemonic\x18\x01 \x01(\t\x12\x19\n\x04node\x18\x02 \x01(\x0b\x32\x0b.HDNodeType\x12\x0b\n\x03pin\x18\x03 \x01(\t\x12\x1d\n\x15passphrase_protection\x18\x04 \x01(\x08\x12\x19\n\x08language\x18\x05 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x06 \x01(\t\x12\x15\n\rskip_checksum\x18\x07 \x01(\x08\x12\x13\n\x0bu2f_counter\x18\x08 \x01(\r\"\xe1\x01\n\x0bResetDevice\x12\x16\n\x0e\x64isplay_random\x18\x01 \x01(\x08\x12\x15\n\x08strength\x18\x02 \x01(\r:\x03\x32\x35\x36\x12\x1d\n\x15passphrase_protection\x18\x03 \x01(\x08\x12\x16\n\x0epin_protection\x18\x04 \x01(\x08\x12\x19\n\x08language\x18\x05 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x06 \x01(\t\x12\x11\n\tno_backup\x18\x07 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x08 \x01(\r\x12\x13\n\x0bu2f_counter\x18\t \x01(\r\"\x10\n\x0e\x45ntropyRequest\"\x1d\n\nEntropyAck\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\xff\x01\n\x0eRecoveryDevice\x12\x12\n\nword_count\x18\x01 \x01(\r\x12\x1d\n\x15passphrase_protection\x18\x02 \x01(\x08\x12\x16\n\x0epin_protection\x18\x03 \x01(\x08\x12\x19\n\x08language\x18\x04 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x05 \x01(\t\x12\x18\n\x10\x65nforce_wordlist\x18\x06 \x01(\x08\x12\x1c\n\x14use_character_cipher\x18\x07 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x08 \x01(\r\x12\x13\n\x0bu2f_counter\x18\t \x01(\r\x12\x0f\n\x07\x64ry_run\x18\n \x01(\x08\"\r\n\x0bWordRequest\"\x17\n\x07WordAck\x12\x0c\n\x04word\x18\x01 \x02(\t\";\n\x10\x43haracterRequest\x12\x10\n\x08word_pos\x18\x01 \x02(\r\x12\x15\n\rcharacter_pos\x18\x02 \x02(\r\"?\n\x0c\x43haracterAck\x12\x11\n\tcharacter\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65lete\x18\x02 \x01(\x08\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\"\x82\x01\n\x0bSignMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07message\x18\x02 \x02(\x0c\x12\x1a\n\tcoin_name\x18\x03 \x01(\t:\x07\x42itcoin\x12\x33\n\x0bscript_type\x18\x04 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"`\n\rVerifyMessage\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\x0c\x12\x1a\n\tcoin_name\x18\x04 \x01(\t:\x07\x42itcoin\"6\n\x10MessageSignature\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\x0c\"v\n\x0e\x45ncryptMessage\x12\x0e\n\x06pubkey\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x02 \x01(\x0c\x12\x14\n\x0c\x64isplay_only\x18\x03 \x01(\x08\x12\x11\n\taddress_n\x18\x04 \x03(\r\x12\x1a\n\tcoin_name\x18\x05 \x01(\t:\x07\x42itcoin\"@\n\x10\x45ncryptedMessage\x12\r\n\x05nonce\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x02 \x01(\x0c\x12\x0c\n\x04hmac\x18\x03 \x01(\x0c\"Q\n\x0e\x44\x65\x63ryptMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\r\n\x05nonce\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\x0c\x12\x0c\n\x04hmac\x18\x04 \x01(\x0c\"4\n\x10\x44\x65\x63ryptedMessage\x12\x0f\n\x07message\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\t\"\x8c\x01\n\x0e\x43ipherKeyValue\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\x0c\x12\x0f\n\x07\x65ncrypt\x18\x04 \x01(\x08\x12\x16\n\x0e\x61sk_on_encrypt\x18\x05 \x01(\x08\x12\x16\n\x0e\x61sk_on_decrypt\x18\x06 \x01(\x08\x12\n\n\x02iv\x18\x07 \x01(\x0c\"!\n\x10\x43ipheredKeyValue\x12\r\n\x05value\x18\x01 \x01(\x0c\"5\n\x10GetBip85Mnemonic\x12\x12\n\nword_count\x18\x01 \x02(\r\x12\r\n\x05index\x18\x02 \x02(\r\"!\n\rBip85Mnemonic\x12\x10\n\x08mnemonic\x18\x01 \x02(\t\"\xce\x01\n\x06SignTx\x12\x15\n\routputs_count\x18\x01 \x02(\r\x12\x14\n\x0cinputs_count\x18\x02 \x02(\r\x12\x1a\n\tcoin_name\x18\x03 \x01(\t:\x07\x42itcoin\x12\x12\n\x07version\x18\x04 \x01(\r:\x01\x31\x12\x14\n\tlock_time\x18\x05 \x01(\r:\x01\x30\x12\x0e\n\x06\x65xpiry\x18\x06 \x01(\r\x12\x14\n\x0coverwintered\x18\x07 \x01(\x08\x12\x18\n\x10version_group_id\x18\x08 \x01(\r\x12\x11\n\tbranch_id\x18\n \x01(\r\"\x85\x01\n\tTxRequest\x12\"\n\x0crequest_type\x18\x01 \x01(\x0e\x32\x0c.RequestType\x12&\n\x07\x64\x65tails\x18\x02 \x01(\x0b\x32\x15.TxRequestDetailsType\x12,\n\nserialized\x18\x03 \x01(\x0b\x32\x18.TxRequestSerializedType\"%\n\x05TxAck\x12\x1c\n\x02tx\x18\x01 \x01(\x0b\x32\x10.TransactionType\"+\n\x08RawTxAck\x12\x1f\n\x02tx\x18\x01 \x01(\x0b\x32\x13.RawTransactionType\"}\n\x0cSignIdentity\x12\x1f\n\x08identity\x18\x01 \x01(\x0b\x32\r.IdentityType\x12\x18\n\x10\x63hallenge_hidden\x18\x02 \x01(\x0c\x12\x18\n\x10\x63hallenge_visual\x18\x03 \x01(\t\x12\x18\n\x10\x65\x63\x64sa_curve_name\x18\x04 \x01(\t\"H\n\x0eSignedIdentity\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x12\n\npublic_key\x18\x02 \x01(\x0c\x12\x11\n\tsignature\x18\x03 \x01(\x0c\",\n\rApplyPolicies\x12\x1b\n\x06policy\x18\x01 \x03(\x0b\x32\x0b.PolicyType\"?\n\tFlashHash\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0e\n\x06length\x18\x02 \x01(\r\x12\x11\n\tchallenge\x18\x03 \x01(\x0c\":\n\nFlashWrite\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\r\n\x05\x65rase\x18\x03 \x01(\x08\"!\n\x11\x46lashHashResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"5\n\x12\x44\x65\x62ugLinkFlashDump\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0e\n\x06length\x18\x02 \x01(\r\"*\n\x1a\x44\x65\x62ugLinkFlashDumpResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x0b\n\tSoftReset\"\x0f\n\rFirmwareErase\"7\n\x0e\x46irmwareUpload\x12\x14\n\x0cpayload_hash\x18\x01 \x02(\x0c\x12\x0f\n\x07payload\x18\x02 \x02(\x0c\"#\n\x11\x44\x65\x62ugLinkDecision\x12\x0e\n\x06yes_no\x18\x01 \x02(\x08\"\x13\n\x11\x44\x65\x62ugLinkGetState\"\xd7\x02\n\x0e\x44\x65\x62ugLinkState\x12\x0e\n\x06layout\x18\x01 \x01(\x0c\x12\x0b\n\x03pin\x18\x02 \x01(\t\x12\x0e\n\x06matrix\x18\x03 \x01(\t\x12\x10\n\x08mnemonic\x18\x04 \x01(\t\x12\x19\n\x04node\x18\x05 \x01(\x0b\x32\x0b.HDNodeType\x12\x1d\n\x15passphrase_protection\x18\x06 \x01(\x08\x12\x12\n\nreset_word\x18\x07 \x01(\t\x12\x15\n\rreset_entropy\x18\x08 \x01(\x0c\x12\x1a\n\x12recovery_fake_word\x18\t \x01(\t\x12\x19\n\x11recovery_word_pos\x18\n \x01(\r\x12\x17\n\x0frecovery_cipher\x18\x0b \x01(\t\x12$\n\x1crecovery_auto_completed_word\x18\x0c \x01(\t\x12\x15\n\rfirmware_hash\x18\r \x01(\x0c\x12\x14\n\x0cstorage_hash\x18\x0e \x01(\x0c\"\x0f\n\rDebugLinkStop\";\n\x0c\x44\x65\x62ugLinkLog\x12\r\n\x05level\x18\x01 \x01(\r\x12\x0e\n\x06\x62ucket\x18\x02 \x01(\t\x12\x0c\n\x04text\x18\x03 \x01(\t\"\x15\n\x13\x44\x65\x62ugLinkFillConfig\" \n\x0e\x43hangeWipeCode\x12\x0e\n\x06remove\x18\x01 \x01(\x08*\xdd>\n\x0bMessageType\x12 \n\x16MessageType_Initialize\x10\x00\x1a\x04\x90\xb5\x18\x01\x12\x1a\n\x10MessageType_Ping\x10\x01\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Success\x10\x02\x1a\x04\x98\xb5\x18\x01\x12\x1d\n\x13MessageType_Failure\x10\x03\x1a\x04\x98\xb5\x18\x01\x12\x1f\n\x15MessageType_ChangePin\x10\x04\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_WipeDevice\x10\x05\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_FirmwareErase\x10\x06\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_FirmwareUpload\x10\x07\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_GetEntropy\x10\t\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Entropy\x10\n\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_GetPublicKey\x10\x0b\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_PublicKey\x10\x0c\x1a\x04\x98\xb5\x18\x01\x12 \n\x16MessageType_LoadDevice\x10\r\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_ResetDevice\x10\x0e\x1a\x04\x90\xb5\x18\x01\x12\x1c\n\x12MessageType_SignTx\x10\x0f\x1a\x04\x90\xb5\x18\x01\x12\x1e\n\x14MessageType_Features\x10\x11\x1a\x04\x98\xb5\x18\x01\x12&\n\x1cMessageType_PinMatrixRequest\x10\x12\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_PinMatrixAck\x10\x13\x1a\x04\x90\xb5\x18\x01\x12\x1c\n\x12MessageType_Cancel\x10\x14\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_TxRequest\x10\x15\x1a\x04\x98\xb5\x18\x01\x12\x1b\n\x11MessageType_TxAck\x10\x16\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_CipherKeyValue\x10\x17\x1a\x04\x90\xb5\x18\x01\x12\"\n\x18MessageType_ClearSession\x10\x18\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ApplySettings\x10\x19\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ButtonRequest\x10\x1a\x1a\x04\x98\xb5\x18\x01\x12\x1f\n\x15MessageType_ButtonAck\x10\x1b\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_GetAddress\x10\x1d\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Address\x10\x1e\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_EntropyRequest\x10#\x1a\x04\x98\xb5\x18\x01\x12 \n\x16MessageType_EntropyAck\x10$\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_SignMessage\x10&\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_VerifyMessage\x10\'\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_MessageSignature\x10(\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1dMessageType_PassphraseRequest\x10)\x1a\x04\x98\xb5\x18\x01\x12#\n\x19MessageType_PassphraseAck\x10*\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_RecoveryDevice\x10-\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_WordRequest\x10.\x1a\x04\x98\xb5\x18\x01\x12\x1d\n\x13MessageType_WordAck\x10/\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_CipheredKeyValue\x10\x30\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_EncryptMessage\x10\x31\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_EncryptedMessage\x10\x32\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_DecryptMessage\x10\x33\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_DecryptedMessage\x10\x34\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_SignIdentity\x10\x35\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_SignedIdentity\x10\x36\x1a\x04\x98\xb5\x18\x01\x12!\n\x17MessageType_GetFeatures\x10\x37\x1a\x04\x90\xb5\x18\x01\x12(\n\x1eMessageType_EthereumGetAddress\x10\x38\x1a\x04\x90\xb5\x18\x01\x12%\n\x1bMessageType_EthereumAddress\x10\x39\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_EthereumSignTx\x10:\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1dMessageType_EthereumTxRequest\x10;\x1a\x04\x98\xb5\x18\x01\x12#\n\x19MessageType_EthereumTxAck\x10<\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_CharacterRequest\x10P\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_CharacterAck\x10Q\x1a\x04\x90\xb5\x18\x01\x12\x1e\n\x14MessageType_RawTxAck\x10R\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ApplyPolicies\x10S\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_FlashHash\x10T\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_FlashWrite\x10U\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1dMessageType_FlashHashResponse\x10V\x1a\x04\x98\xb5\x18\x01\x12(\n\x1eMessageType_DebugLinkFlashDump\x10W\x1a\x04\xa0\xb5\x18\x01\x12\x30\n&MessageType_DebugLinkFlashDumpResponse\x10X\x1a\x04\xa8\xb5\x18\x01\x12\x1f\n\x15MessageType_SoftReset\x10Y\x1a\x04\xa0\xb5\x18\x01\x12\'\n\x1dMessageType_DebugLinkDecision\x10\x64\x1a\x04\xa0\xb5\x18\x01\x12\'\n\x1dMessageType_DebugLinkGetState\x10\x65\x1a\x04\xa0\xb5\x18\x01\x12$\n\x1aMessageType_DebugLinkState\x10\x66\x1a\x04\xa8\xb5\x18\x01\x12#\n\x19MessageType_DebugLinkStop\x10g\x1a\x04\xa0\xb5\x18\x01\x12\"\n\x18MessageType_DebugLinkLog\x10h\x1a\x04\xa8\xb5\x18\x01\x12)\n\x1fMessageType_DebugLinkFillConfig\x10i\x1a\x04\xa8\xb5\x18\x01\x12\"\n\x18MessageType_GetCoinTable\x10j\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_CoinTable\x10k\x1a\x04\x98\xb5\x18\x01\x12)\n\x1fMessageType_EthereumSignMessage\x10l\x1a\x04\x90\xb5\x18\x01\x12+\n!MessageType_EthereumVerifyMessage\x10m\x1a\x04\x90\xb5\x18\x01\x12.\n$MessageType_EthereumMessageSignature\x10n\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_ChangeWipeCode\x10o\x1a\x04\x90\xb5\x18\x01\x12+\n!MessageType_EthereumSignTypedHash\x10p\x1a\x04\x90\xb5\x18\x01\x12\x30\n&MessageType_EthereumTypedDataSignature\x10q\x1a\x04\x98\xb5\x18\x01\x12,\n\"MessageType_Ethereum712TypesValues\x10r\x1a\x04\x90\xb5\x18\x01\x12(\n\x1eMessageType_EthereumTxMetadata\x10s\x1a\x04\x90\xb5\x18\x01\x12)\n\x1fMessageType_EthereumMetadataAck\x10t\x1a\x04\x98\xb5\x18\x01\x12)\n\x1fMessageType_LoadClearsignSigner\x10u\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_GetBip85Mnemonic\x10x\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_Bip85Mnemonic\x10y\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_RippleGetAddress\x10\x90\x03\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_RippleAddress\x10\x91\x03\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_RippleSignTx\x10\x92\x03\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_RippleSignedTx\x10\x93\x03\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_ThorchainGetAddress\x10\xf4\x03\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_ThorchainAddress\x10\xf5\x03\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_ThorchainSignTx\x10\xf6\x03\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_ThorchainMsgRequest\x10\xf7\x03\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_ThorchainMsgAck\x10\xf8\x03\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_ThorchainSignedTx\x10\xf9\x03\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_EosGetPublicKey\x10\xd8\x04\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_EosPublicKey\x10\xd9\x04\x1a\x04\x98\xb5\x18\x01\x12 \n\x15MessageType_EosSignTx\x10\xda\x04\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_EosTxActionRequest\x10\xdb\x04\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_EosTxActionAck\x10\xdc\x04\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_EosSignedTx\x10\xdd\x04\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_NanoGetAddress\x10\xbc\x05\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_NanoAddress\x10\xbd\x05\x1a\x04\x98\xb5\x18\x01\x12!\n\x16MessageType_NanoSignTx\x10\xbe\x05\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_NanoSignedTx\x10\xbf\x05\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_SolanaGetAddress\x10\xee\x05\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_SolanaAddress\x10\xef\x05\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_SolanaSignTx\x10\xf0\x05\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_SolanaSignedTx\x10\xf1\x05\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_SolanaSignMessage\x10\xf2\x05\x1a\x04\x90\xb5\x18\x01\x12-\n\"MessageType_SolanaMessageSignature\x10\xf3\x05\x1a\x04\x98\xb5\x18\x01\x12\x30\n%MessageType_SolanaSignOffchainMessage\x10\xf4\x05\x1a\x04\x90\xb5\x18\x01\x12\x35\n*MessageType_SolanaOffchainMessageSignature\x10\xf5\x05\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_BinanceGetAddress\x10\xa0\x06\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_BinanceAddress\x10\xa1\x06\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_BinanceGetPublicKey\x10\xa2\x06\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_BinancePublicKey\x10\xa3\x06\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_BinanceSignTx\x10\xa4\x06\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_BinanceTxRequest\x10\xa5\x06\x1a\x04\x98\xb5\x18\x01\x12)\n\x1eMessageType_BinanceTransferMsg\x10\xa6\x06\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_BinanceOrderMsg\x10\xa7\x06\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_BinanceCancelMsg\x10\xa8\x06\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_BinanceSignedTx\x10\xa9\x06\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_CosmosGetAddress\x10\x84\x07\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_CosmosAddress\x10\x85\x07\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_CosmosSignTx\x10\x86\x07\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_CosmosMsgRequest\x10\x87\x07\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_CosmosMsgAck\x10\x88\x07\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_CosmosSignedTx\x10\x89\x07\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_CosmosMsgDelegate\x10\x8a\x07\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_CosmosMsgUndelegate\x10\x8b\x07\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_CosmosMsgRedelegate\x10\x8c\x07\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_CosmosMsgRewards\x10\x8d\x07\x1a\x04\x98\xb5\x18\x01\x12+\n MessageType_CosmosMsgIBCTransfer\x10\x8e\x07\x1a\x04\x98\xb5\x18\x01\x12+\n MessageType_TendermintGetAddress\x10\xe8\x07\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_TendermintAddress\x10\xe9\x07\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_TendermintSignTx\x10\xea\x07\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_TendermintMsgRequest\x10\xeb\x07\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_TendermintMsgAck\x10\xec\x07\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_TendermintMsgSend\x10\xed\x07\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_TendermintSignedTx\x10\xee\x07\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_TendermintMsgDelegate\x10\xef\x07\x1a\x04\x98\xb5\x18\x01\x12.\n#MessageType_TendermintMsgUndelegate\x10\xf0\x07\x1a\x04\x98\xb5\x18\x01\x12.\n#MessageType_TendermintMsgRedelegate\x10\xf1\x07\x1a\x04\x98\xb5\x18\x01\x12+\n MessageType_TendermintMsgRewards\x10\xf2\x07\x1a\x04\x98\xb5\x18\x01\x12/\n$MessageType_TendermintMsgIBCTransfer\x10\xf3\x07\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_OsmosisGetAddress\x10\xcc\x08\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_OsmosisAddress\x10\xcd\x08\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_OsmosisSignTx\x10\xce\x08\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_OsmosisMsgRequest\x10\xcf\x08\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_OsmosisMsgAck\x10\xd0\x08\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_OsmosisMsgSend\x10\xd1\x08\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_OsmosisMsgDelegate\x10\xd2\x08\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_OsmosisMsgUndelegate\x10\xd3\x08\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_OsmosisMsgRedelegate\x10\xd4\x08\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_OsmosisMsgRewards\x10\xd5\x08\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_OsmosisMsgLPAdd\x10\xd6\x08\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_OsmosisMsgLPRemove\x10\xd7\x08\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_OsmosisMsgLPStake\x10\xd8\x08\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_OsmosisMsgLPUnstake\x10\xd9\x08\x1a\x04\x90\xb5\x18\x01\x12,\n!MessageType_OsmosisMsgIBCTransfer\x10\xda\x08\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_OsmosisMsgSwap\x10\xdb\x08\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_OsmosisSignedTx\x10\xdc\x08\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_MayachainGetAddress\x10\xb0\t\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_MayachainAddress\x10\xb1\t\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_MayachainSignTx\x10\xb2\t\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_MayachainMsgRequest\x10\xb3\t\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_MayachainMsgAck\x10\xb4\t\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_MayachainSignedTx\x10\xb5\t\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_ZcashSignPCZT\x10\x94\n\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_ZcashPCZTAction\x10\x95\n\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_ZcashPCZTActionAck\x10\x96\n\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_ZcashSignedPCZT\x10\x97\n\x1a\x04\x98\xb5\x18\x01\x12)\n\x1eMessageType_ZcashGetOrchardFVK\x10\x98\n\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_ZcashOrchardFVK\x10\x99\n\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_ZcashTransparentInput\x10\x9a\n\x1a\x04\x90\xb5\x18\x01\x12-\n\"MessageType_ZcashTransparentSigned\x10\x9b\n\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_ZcashDisplayAddress\x10\x9c\n\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_ZcashAddress\x10\x9d\n\x1a\x04\x98\xb5\x18\x01\x12-\n\"MessageType_ZcashTransparentOutput\x10\x9e\n\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_ZcashTransparentAck\x10\x9f\n\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_TronGetAddress\x10\xf8\n\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_TronAddress\x10\xf9\n\x1a\x04\x98\xb5\x18\x01\x12!\n\x16MessageType_TronSignTx\x10\xfa\n\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_TronSignedTx\x10\xfb\n\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_TronSignMessage\x10\xfc\n\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_TronMessageSignature\x10\xfd\n\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_TronVerifyMessage\x10\xfe\n\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_TronSignTypedHash\x10\xff\n\x1a\x04\x90\xb5\x18\x01\x12-\n\"MessageType_TronTypedDataSignature\x10\x80\x0b\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_TonGetAddress\x10\xdc\x0b\x1a\x04\x90\xb5\x18\x01\x12!\n\x16MessageType_TonAddress\x10\xdd\x0b\x1a\x04\x98\xb5\x18\x01\x12 \n\x15MessageType_TonSignTx\x10\xde\x0b\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_TonSignedTx\x10\xdf\x0b\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_TonSignMessage\x10\xe0\x0b\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_TonMessageSignature\x10\xe1\x0b\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_HiveGetPublicKey\x10\xc0\x0c\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_HivePublicKey\x10\xc1\x0c\x1a\x04\x98\xb5\x18\x01\x12!\n\x16MessageType_HiveSignTx\x10\xc2\x0c\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_HiveSignedTx\x10\xc3\x0c\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_HiveGetPublicKeys\x10\xc4\x0c\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_HivePublicKeys\x10\xc5\x0c\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_HiveSignAccountCreate\x10\xc6\x0c\x1a\x04\x90\xb5\x18\x01\x12.\n#MessageType_HiveSignedAccountCreate\x10\xc7\x0c\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_HiveSignAccountUpdate\x10\xc8\x0c\x1a\x04\x90\xb5\x18\x01\x12.\n#MessageType_HiveSignedAccountUpdate\x10\xc9\x0c\x1a\x04\x98\xb5\x18\x01\x42,\n\x1a\x63om.keepkey.deviceprotocolB\x0eKeepKeyMessage') + serialized_pb=_b('\n\x0emessages.proto\x1a\x0btypes.proto\"\x0c\n\nInitialize\"\r\n\x0bGetFeatures\"\xaa\x04\n\x08\x46\x65\x61tures\x12\x0e\n\x06vendor\x18\x01 \x01(\t\x12\x15\n\rmajor_version\x18\x02 \x01(\r\x12\x15\n\rminor_version\x18\x03 \x01(\r\x12\x15\n\rpatch_version\x18\x04 \x01(\r\x12\x17\n\x0f\x62ootloader_mode\x18\x05 \x01(\x08\x12\x11\n\tdevice_id\x18\x06 \x01(\t\x12\x16\n\x0epin_protection\x18\x07 \x01(\x08\x12\x1d\n\x15passphrase_protection\x18\x08 \x01(\x08\x12\x10\n\x08language\x18\t \x01(\t\x12\r\n\x05label\x18\n \x01(\t\x12\x18\n\x05\x63oins\x18\x0b \x03(\x0b\x32\t.CoinType\x12\x13\n\x0binitialized\x18\x0c \x01(\x08\x12\x10\n\x08revision\x18\r \x01(\x0c\x12\x17\n\x0f\x62ootloader_hash\x18\x0e \x01(\x0c\x12\x10\n\x08imported\x18\x0f \x01(\x08\x12\x12\n\npin_cached\x18\x10 \x01(\x08\x12\x19\n\x11passphrase_cached\x18\x11 \x01(\x08\x12\x1d\n\x08policies\x18\x12 \x03(\x0b\x32\x0b.PolicyType\x12\r\n\x05model\x18\x15 \x01(\t\x12\x18\n\x10\x66irmware_variant\x18\x16 \x01(\t\x12\x15\n\rfirmware_hash\x18\x17 \x01(\x0c\x12\x11\n\tno_backup\x18\x18 \x01(\x08\x12\x1c\n\x14wipe_code_protection\x18\x19 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x1a \x01(\r\"*\n\x0cGetCoinTable\x12\r\n\x05start\x18\x01 \x01(\r\x12\x0b\n\x03\x65nd\x18\x02 \x01(\r\"L\n\tCoinTable\x12\x18\n\x05table\x18\x01 \x03(\x0b\x32\t.CoinType\x12\x11\n\tnum_coins\x18\x02 \x01(\r\x12\x12\n\nchunk_size\x18\x03 \x01(\r\"\x0e\n\x0c\x43learSession\"y\n\rApplySettings\x12\x10\n\x08language\x18\x01 \x01(\t\x12\r\n\x05label\x18\x02 \x01(\t\x12\x16\n\x0euse_passphrase\x18\x03 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x04 \x01(\r\x12\x13\n\x0bu2f_counter\x18\x05 \x01(\r\"\x1b\n\tChangePin\x12\x0e\n\x06remove\x18\x01 \x01(\x08\"\x87\x01\n\x04Ping\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x19\n\x11\x62utton_protection\x18\x02 \x01(\x08\x12\x16\n\x0epin_protection\x18\x03 \x01(\x08\x12\x1d\n\x15passphrase_protection\x18\x04 \x01(\x08\x12\x1c\n\x14wipe_code_protection\x18\x05 \x01(\x08\"\x1a\n\x07Success\x12\x0f\n\x07message\x18\x01 \x01(\t\"6\n\x07\x46\x61ilure\x12\x1a\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x0c.FailureType\x12\x0f\n\x07message\x18\x02 \x01(\t\"?\n\rButtonRequest\x12 \n\x04\x63ode\x18\x01 \x01(\x0e\x32\x12.ButtonRequestType\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\t\"\x0b\n\tButtonAck\"7\n\x10PinMatrixRequest\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.PinMatrixRequestType\"\x1b\n\x0cPinMatrixAck\x12\x0b\n\x03pin\x18\x01 \x02(\t\"\x08\n\x06\x43\x61ncel\"\x13\n\x11PassphraseRequest\"#\n\rPassphraseAck\x12\x12\n\npassphrase\x18\x01 \x02(\t\"\x1a\n\nGetEntropy\x12\x0c\n\x04size\x18\x01 \x02(\r\"\x1a\n\x07\x45ntropy\x12\x0f\n\x07\x65ntropy\x18\x01 \x02(\x0c\"\xa2\x01\n\x0cGetPublicKey\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x18\n\x10\x65\x63\x64sa_curve_name\x18\x02 \x01(\t\x12\x14\n\x0cshow_display\x18\x03 \x01(\x08\x12\x1a\n\tcoin_name\x18\x04 \x01(\t:\x07\x42itcoin\x12\x33\n\x0bscript_type\x18\x05 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"4\n\tPublicKey\x12\x19\n\x04node\x18\x01 \x02(\x0b\x32\x0b.HDNodeType\x12\x0c\n\x04xpub\x18\x02 \x01(\t\"\xb3\x01\n\nGetAddress\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x1a\n\tcoin_name\x18\x02 \x01(\t:\x07\x42itcoin\x12\x14\n\x0cshow_display\x18\x03 \x01(\x08\x12+\n\x08multisig\x18\x04 \x01(\x0b\x32\x19.MultisigRedeemScriptType\x12\x33\n\x0bscript_type\x18\x05 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"\x1a\n\x07\x41\x64\x64ress\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x02(\t\"\x0c\n\nWipeDevice\"\xbb\x01\n\nLoadDevice\x12\x10\n\x08mnemonic\x18\x01 \x01(\t\x12\x19\n\x04node\x18\x02 \x01(\x0b\x32\x0b.HDNodeType\x12\x0b\n\x03pin\x18\x03 \x01(\t\x12\x1d\n\x15passphrase_protection\x18\x04 \x01(\x08\x12\x19\n\x08language\x18\x05 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x06 \x01(\t\x12\x15\n\rskip_checksum\x18\x07 \x01(\x08\x12\x13\n\x0bu2f_counter\x18\x08 \x01(\r\"\xe1\x01\n\x0bResetDevice\x12\x16\n\x0e\x64isplay_random\x18\x01 \x01(\x08\x12\x15\n\x08strength\x18\x02 \x01(\r:\x03\x32\x35\x36\x12\x1d\n\x15passphrase_protection\x18\x03 \x01(\x08\x12\x16\n\x0epin_protection\x18\x04 \x01(\x08\x12\x19\n\x08language\x18\x05 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x06 \x01(\t\x12\x11\n\tno_backup\x18\x07 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x08 \x01(\r\x12\x13\n\x0bu2f_counter\x18\t \x01(\r\"\x10\n\x0e\x45ntropyRequest\"\x1d\n\nEntropyAck\x12\x0f\n\x07\x65ntropy\x18\x01 \x01(\x0c\"\xff\x01\n\x0eRecoveryDevice\x12\x12\n\nword_count\x18\x01 \x01(\r\x12\x1d\n\x15passphrase_protection\x18\x02 \x01(\x08\x12\x16\n\x0epin_protection\x18\x03 \x01(\x08\x12\x19\n\x08language\x18\x04 \x01(\t:\x07\x65nglish\x12\r\n\x05label\x18\x05 \x01(\t\x12\x18\n\x10\x65nforce_wordlist\x18\x06 \x01(\x08\x12\x1c\n\x14use_character_cipher\x18\x07 \x01(\x08\x12\x1a\n\x12\x61uto_lock_delay_ms\x18\x08 \x01(\r\x12\x13\n\x0bu2f_counter\x18\t \x01(\r\x12\x0f\n\x07\x64ry_run\x18\n \x01(\x08\"\r\n\x0bWordRequest\"\x17\n\x07WordAck\x12\x0c\n\x04word\x18\x01 \x02(\t\";\n\x10\x43haracterRequest\x12\x10\n\x08word_pos\x18\x01 \x02(\r\x12\x15\n\rcharacter_pos\x18\x02 \x02(\r\"?\n\x0c\x43haracterAck\x12\x11\n\tcharacter\x18\x01 \x01(\t\x12\x0e\n\x06\x64\x65lete\x18\x02 \x01(\x08\x12\x0c\n\x04\x64one\x18\x03 \x01(\x08\"\x82\x01\n\x0bSignMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0f\n\x07message\x18\x02 \x02(\x0c\x12\x1a\n\tcoin_name\x18\x03 \x01(\t:\x07\x42itcoin\x12\x33\n\x0bscript_type\x18\x04 \x01(\x0e\x32\x10.InputScriptType:\x0cSPENDADDRESS\"`\n\rVerifyMessage\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\x0c\x12\x1a\n\tcoin_name\x18\x04 \x01(\t:\x07\x42itcoin\"6\n\x10MessageSignature\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x11\n\tsignature\x18\x02 \x01(\x0c\"v\n\x0e\x45ncryptMessage\x12\x0e\n\x06pubkey\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x02 \x01(\x0c\x12\x14\n\x0c\x64isplay_only\x18\x03 \x01(\x08\x12\x11\n\taddress_n\x18\x04 \x03(\r\x12\x1a\n\tcoin_name\x18\x05 \x01(\t:\x07\x42itcoin\"@\n\x10\x45ncryptedMessage\x12\r\n\x05nonce\x18\x01 \x01(\x0c\x12\x0f\n\x07message\x18\x02 \x01(\x0c\x12\x0c\n\x04hmac\x18\x03 \x01(\x0c\"Q\n\x0e\x44\x65\x63ryptMessage\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\r\n\x05nonce\x18\x02 \x01(\x0c\x12\x0f\n\x07message\x18\x03 \x01(\x0c\x12\x0c\n\x04hmac\x18\x04 \x01(\x0c\"4\n\x10\x44\x65\x63ryptedMessage\x12\x0f\n\x07message\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61\x64\x64ress\x18\x02 \x01(\t\"\x8c\x01\n\x0e\x43ipherKeyValue\x12\x11\n\taddress_n\x18\x01 \x03(\r\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\x0c\x12\x0f\n\x07\x65ncrypt\x18\x04 \x01(\x08\x12\x16\n\x0e\x61sk_on_encrypt\x18\x05 \x01(\x08\x12\x16\n\x0e\x61sk_on_decrypt\x18\x06 \x01(\x08\x12\n\n\x02iv\x18\x07 \x01(\x0c\"!\n\x10\x43ipheredKeyValue\x12\r\n\x05value\x18\x01 \x01(\x0c\"5\n\x10GetBip85Mnemonic\x12\x12\n\nword_count\x18\x01 \x02(\r\x12\r\n\x05index\x18\x02 \x02(\r\"!\n\rBip85Mnemonic\x12\x10\n\x08mnemonic\x18\x01 \x02(\t\"\xce\x01\n\x06SignTx\x12\x15\n\routputs_count\x18\x01 \x02(\r\x12\x14\n\x0cinputs_count\x18\x02 \x02(\r\x12\x1a\n\tcoin_name\x18\x03 \x01(\t:\x07\x42itcoin\x12\x12\n\x07version\x18\x04 \x01(\r:\x01\x31\x12\x14\n\tlock_time\x18\x05 \x01(\r:\x01\x30\x12\x0e\n\x06\x65xpiry\x18\x06 \x01(\r\x12\x14\n\x0coverwintered\x18\x07 \x01(\x08\x12\x18\n\x10version_group_id\x18\x08 \x01(\r\x12\x11\n\tbranch_id\x18\n \x01(\r\"\x85\x01\n\tTxRequest\x12\"\n\x0crequest_type\x18\x01 \x01(\x0e\x32\x0c.RequestType\x12&\n\x07\x64\x65tails\x18\x02 \x01(\x0b\x32\x15.TxRequestDetailsType\x12,\n\nserialized\x18\x03 \x01(\x0b\x32\x18.TxRequestSerializedType\"%\n\x05TxAck\x12\x1c\n\x02tx\x18\x01 \x01(\x0b\x32\x10.TransactionType\"+\n\x08RawTxAck\x12\x1f\n\x02tx\x18\x01 \x01(\x0b\x32\x13.RawTransactionType\"}\n\x0cSignIdentity\x12\x1f\n\x08identity\x18\x01 \x01(\x0b\x32\r.IdentityType\x12\x18\n\x10\x63hallenge_hidden\x18\x02 \x01(\x0c\x12\x18\n\x10\x63hallenge_visual\x18\x03 \x01(\t\x12\x18\n\x10\x65\x63\x64sa_curve_name\x18\x04 \x01(\t\"H\n\x0eSignedIdentity\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\t\x12\x12\n\npublic_key\x18\x02 \x01(\x0c\x12\x11\n\tsignature\x18\x03 \x01(\x0c\",\n\rApplyPolicies\x12\x1b\n\x06policy\x18\x01 \x03(\x0b\x32\x0b.PolicyType\"?\n\tFlashHash\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0e\n\x06length\x18\x02 \x01(\r\x12\x11\n\tchallenge\x18\x03 \x01(\x0c\":\n\nFlashWrite\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\r\n\x05\x65rase\x18\x03 \x01(\x08\"!\n\x11\x46lashHashResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"5\n\x12\x44\x65\x62ugLinkFlashDump\x12\x0f\n\x07\x61\x64\x64ress\x18\x01 \x01(\r\x12\x0e\n\x06length\x18\x02 \x01(\r\"*\n\x1a\x44\x65\x62ugLinkFlashDumpResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x0b\n\tSoftReset\"\x0f\n\rFirmwareErase\"7\n\x0e\x46irmwareUpload\x12\x14\n\x0cpayload_hash\x18\x01 \x02(\x0c\x12\x0f\n\x07payload\x18\x02 \x02(\x0c\"#\n\x11\x44\x65\x62ugLinkDecision\x12\x0e\n\x06yes_no\x18\x01 \x02(\x08\"\x13\n\x11\x44\x65\x62ugLinkGetState\"\xd7\x02\n\x0e\x44\x65\x62ugLinkState\x12\x0e\n\x06layout\x18\x01 \x01(\x0c\x12\x0b\n\x03pin\x18\x02 \x01(\t\x12\x0e\n\x06matrix\x18\x03 \x01(\t\x12\x10\n\x08mnemonic\x18\x04 \x01(\t\x12\x19\n\x04node\x18\x05 \x01(\x0b\x32\x0b.HDNodeType\x12\x1d\n\x15passphrase_protection\x18\x06 \x01(\x08\x12\x12\n\nreset_word\x18\x07 \x01(\t\x12\x15\n\rreset_entropy\x18\x08 \x01(\x0c\x12\x1a\n\x12recovery_fake_word\x18\t \x01(\t\x12\x19\n\x11recovery_word_pos\x18\n \x01(\r\x12\x17\n\x0frecovery_cipher\x18\x0b \x01(\t\x12$\n\x1crecovery_auto_completed_word\x18\x0c \x01(\t\x12\x15\n\rfirmware_hash\x18\r \x01(\x0c\x12\x14\n\x0cstorage_hash\x18\x0e \x01(\x0c\"\x0f\n\rDebugLinkStop\";\n\x0c\x44\x65\x62ugLinkLog\x12\r\n\x05level\x18\x01 \x01(\r\x12\x0e\n\x06\x62ucket\x18\x02 \x01(\t\x12\x0c\n\x04text\x18\x03 \x01(\t\"\x15\n\x13\x44\x65\x62ugLinkFillConfig\" \n\x0e\x43hangeWipeCode\x12\x0e\n\x06remove\x18\x01 \x01(\x08*\x87@\n\x0bMessageType\x12 \n\x16MessageType_Initialize\x10\x00\x1a\x04\x90\xb5\x18\x01\x12\x1a\n\x10MessageType_Ping\x10\x01\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Success\x10\x02\x1a\x04\x98\xb5\x18\x01\x12\x1d\n\x13MessageType_Failure\x10\x03\x1a\x04\x98\xb5\x18\x01\x12\x1f\n\x15MessageType_ChangePin\x10\x04\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_WipeDevice\x10\x05\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_FirmwareErase\x10\x06\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_FirmwareUpload\x10\x07\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_GetEntropy\x10\t\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Entropy\x10\n\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_GetPublicKey\x10\x0b\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_PublicKey\x10\x0c\x1a\x04\x98\xb5\x18\x01\x12 \n\x16MessageType_LoadDevice\x10\r\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_ResetDevice\x10\x0e\x1a\x04\x90\xb5\x18\x01\x12\x1c\n\x12MessageType_SignTx\x10\x0f\x1a\x04\x90\xb5\x18\x01\x12\x1e\n\x14MessageType_Features\x10\x11\x1a\x04\x98\xb5\x18\x01\x12&\n\x1cMessageType_PinMatrixRequest\x10\x12\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_PinMatrixAck\x10\x13\x1a\x04\x90\xb5\x18\x01\x12\x1c\n\x12MessageType_Cancel\x10\x14\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_TxRequest\x10\x15\x1a\x04\x98\xb5\x18\x01\x12\x1b\n\x11MessageType_TxAck\x10\x16\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_CipherKeyValue\x10\x17\x1a\x04\x90\xb5\x18\x01\x12\"\n\x18MessageType_ClearSession\x10\x18\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ApplySettings\x10\x19\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ButtonRequest\x10\x1a\x1a\x04\x98\xb5\x18\x01\x12\x1f\n\x15MessageType_ButtonAck\x10\x1b\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_GetAddress\x10\x1d\x1a\x04\x90\xb5\x18\x01\x12\x1d\n\x13MessageType_Address\x10\x1e\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_EntropyRequest\x10#\x1a\x04\x98\xb5\x18\x01\x12 \n\x16MessageType_EntropyAck\x10$\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_SignMessage\x10&\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_VerifyMessage\x10\'\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_MessageSignature\x10(\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1dMessageType_PassphraseRequest\x10)\x1a\x04\x98\xb5\x18\x01\x12#\n\x19MessageType_PassphraseAck\x10*\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_RecoveryDevice\x10-\x1a\x04\x90\xb5\x18\x01\x12!\n\x17MessageType_WordRequest\x10.\x1a\x04\x98\xb5\x18\x01\x12\x1d\n\x13MessageType_WordAck\x10/\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_CipheredKeyValue\x10\x30\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_EncryptMessage\x10\x31\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_EncryptedMessage\x10\x32\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_DecryptMessage\x10\x33\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_DecryptedMessage\x10\x34\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_SignIdentity\x10\x35\x1a\x04\x90\xb5\x18\x01\x12$\n\x1aMessageType_SignedIdentity\x10\x36\x1a\x04\x98\xb5\x18\x01\x12!\n\x17MessageType_GetFeatures\x10\x37\x1a\x04\x90\xb5\x18\x01\x12(\n\x1eMessageType_EthereumGetAddress\x10\x38\x1a\x04\x90\xb5\x18\x01\x12%\n\x1bMessageType_EthereumAddress\x10\x39\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_EthereumSignTx\x10:\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1dMessageType_EthereumTxRequest\x10;\x1a\x04\x98\xb5\x18\x01\x12#\n\x19MessageType_EthereumTxAck\x10<\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_CharacterRequest\x10P\x1a\x04\x98\xb5\x18\x01\x12\"\n\x18MessageType_CharacterAck\x10Q\x1a\x04\x90\xb5\x18\x01\x12\x1e\n\x14MessageType_RawTxAck\x10R\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_ApplyPolicies\x10S\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_FlashHash\x10T\x1a\x04\x90\xb5\x18\x01\x12 \n\x16MessageType_FlashWrite\x10U\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1dMessageType_FlashHashResponse\x10V\x1a\x04\x98\xb5\x18\x01\x12(\n\x1eMessageType_DebugLinkFlashDump\x10W\x1a\x04\xa0\xb5\x18\x01\x12\x30\n&MessageType_DebugLinkFlashDumpResponse\x10X\x1a\x04\xa8\xb5\x18\x01\x12\x1f\n\x15MessageType_SoftReset\x10Y\x1a\x04\xa0\xb5\x18\x01\x12\'\n\x1dMessageType_DebugLinkDecision\x10\x64\x1a\x04\xa0\xb5\x18\x01\x12\'\n\x1dMessageType_DebugLinkGetState\x10\x65\x1a\x04\xa0\xb5\x18\x01\x12$\n\x1aMessageType_DebugLinkState\x10\x66\x1a\x04\xa8\xb5\x18\x01\x12#\n\x19MessageType_DebugLinkStop\x10g\x1a\x04\xa0\xb5\x18\x01\x12\"\n\x18MessageType_DebugLinkLog\x10h\x1a\x04\xa8\xb5\x18\x01\x12)\n\x1fMessageType_DebugLinkFillConfig\x10i\x1a\x04\xa8\xb5\x18\x01\x12\"\n\x18MessageType_GetCoinTable\x10j\x1a\x04\x90\xb5\x18\x01\x12\x1f\n\x15MessageType_CoinTable\x10k\x1a\x04\x98\xb5\x18\x01\x12)\n\x1fMessageType_EthereumSignMessage\x10l\x1a\x04\x90\xb5\x18\x01\x12+\n!MessageType_EthereumVerifyMessage\x10m\x1a\x04\x90\xb5\x18\x01\x12.\n$MessageType_EthereumMessageSignature\x10n\x1a\x04\x98\xb5\x18\x01\x12$\n\x1aMessageType_ChangeWipeCode\x10o\x1a\x04\x90\xb5\x18\x01\x12+\n!MessageType_EthereumSignTypedHash\x10p\x1a\x04\x90\xb5\x18\x01\x12\x30\n&MessageType_EthereumTypedDataSignature\x10q\x1a\x04\x98\xb5\x18\x01\x12,\n\"MessageType_Ethereum712TypesValues\x10r\x1a\x04\x90\xb5\x18\x01\x12(\n\x1eMessageType_EthereumTxMetadata\x10s\x1a\x04\x90\xb5\x18\x01\x12)\n\x1fMessageType_EthereumMetadataAck\x10t\x1a\x04\x98\xb5\x18\x01\x12)\n\x1fMessageType_LoadClearsignSigner\x10u\x1a\x04\x90\xb5\x18\x01\x12&\n\x1cMessageType_GetBip85Mnemonic\x10x\x1a\x04\x90\xb5\x18\x01\x12#\n\x19MessageType_Bip85Mnemonic\x10y\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_RippleGetAddress\x10\x90\x03\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_RippleAddress\x10\x91\x03\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_RippleSignTx\x10\x92\x03\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_RippleSignedTx\x10\x93\x03\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_ThorchainGetAddress\x10\xf4\x03\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_ThorchainAddress\x10\xf5\x03\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_ThorchainSignTx\x10\xf6\x03\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_ThorchainMsgRequest\x10\xf7\x03\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_ThorchainMsgAck\x10\xf8\x03\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_ThorchainSignedTx\x10\xf9\x03\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_EosGetPublicKey\x10\xd8\x04\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_EosPublicKey\x10\xd9\x04\x1a\x04\x98\xb5\x18\x01\x12 \n\x15MessageType_EosSignTx\x10\xda\x04\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_EosTxActionRequest\x10\xdb\x04\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_EosTxActionAck\x10\xdc\x04\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_EosSignedTx\x10\xdd\x04\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_NanoGetAddress\x10\xbc\x05\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_NanoAddress\x10\xbd\x05\x1a\x04\x98\xb5\x18\x01\x12!\n\x16MessageType_NanoSignTx\x10\xbe\x05\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_NanoSignedTx\x10\xbf\x05\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_SolanaGetAddress\x10\xee\x05\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_SolanaAddress\x10\xef\x05\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_SolanaSignTx\x10\xf0\x05\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_SolanaSignedTx\x10\xf1\x05\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_SolanaSignMessage\x10\xf2\x05\x1a\x04\x90\xb5\x18\x01\x12-\n\"MessageType_SolanaMessageSignature\x10\xf3\x05\x1a\x04\x98\xb5\x18\x01\x12\x30\n%MessageType_SolanaSignOffchainMessage\x10\xf4\x05\x1a\x04\x90\xb5\x18\x01\x12\x35\n*MessageType_SolanaOffchainMessageSignature\x10\xf5\x05\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_BinanceGetAddress\x10\xa0\x06\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_BinanceAddress\x10\xa1\x06\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_BinanceGetPublicKey\x10\xa2\x06\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_BinancePublicKey\x10\xa3\x06\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_BinanceSignTx\x10\xa4\x06\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_BinanceTxRequest\x10\xa5\x06\x1a\x04\x98\xb5\x18\x01\x12)\n\x1eMessageType_BinanceTransferMsg\x10\xa6\x06\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_BinanceOrderMsg\x10\xa7\x06\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_BinanceCancelMsg\x10\xa8\x06\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_BinanceSignedTx\x10\xa9\x06\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_CosmosGetAddress\x10\x84\x07\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_CosmosAddress\x10\x85\x07\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_CosmosSignTx\x10\x86\x07\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_CosmosMsgRequest\x10\x87\x07\x1a\x04\x98\xb5\x18\x01\x12#\n\x18MessageType_CosmosMsgAck\x10\x88\x07\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_CosmosSignedTx\x10\x89\x07\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_CosmosMsgDelegate\x10\x8a\x07\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_CosmosMsgUndelegate\x10\x8b\x07\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_CosmosMsgRedelegate\x10\x8c\x07\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_CosmosMsgRewards\x10\x8d\x07\x1a\x04\x98\xb5\x18\x01\x12+\n MessageType_CosmosMsgIBCTransfer\x10\x8e\x07\x1a\x04\x98\xb5\x18\x01\x12+\n MessageType_TendermintGetAddress\x10\xe8\x07\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_TendermintAddress\x10\xe9\x07\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_TendermintSignTx\x10\xea\x07\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_TendermintMsgRequest\x10\xeb\x07\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_TendermintMsgAck\x10\xec\x07\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_TendermintMsgSend\x10\xed\x07\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_TendermintSignedTx\x10\xee\x07\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_TendermintMsgDelegate\x10\xef\x07\x1a\x04\x98\xb5\x18\x01\x12.\n#MessageType_TendermintMsgUndelegate\x10\xf0\x07\x1a\x04\x98\xb5\x18\x01\x12.\n#MessageType_TendermintMsgRedelegate\x10\xf1\x07\x1a\x04\x98\xb5\x18\x01\x12+\n MessageType_TendermintMsgRewards\x10\xf2\x07\x1a\x04\x98\xb5\x18\x01\x12/\n$MessageType_TendermintMsgIBCTransfer\x10\xf3\x07\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_OsmosisGetAddress\x10\xcc\x08\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_OsmosisAddress\x10\xcd\x08\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_OsmosisSignTx\x10\xce\x08\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_OsmosisMsgRequest\x10\xcf\x08\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_OsmosisMsgAck\x10\xd0\x08\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_OsmosisMsgSend\x10\xd1\x08\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_OsmosisMsgDelegate\x10\xd2\x08\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_OsmosisMsgUndelegate\x10\xd3\x08\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_OsmosisMsgRedelegate\x10\xd4\x08\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_OsmosisMsgRewards\x10\xd5\x08\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_OsmosisMsgLPAdd\x10\xd6\x08\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_OsmosisMsgLPRemove\x10\xd7\x08\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_OsmosisMsgLPStake\x10\xd8\x08\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_OsmosisMsgLPUnstake\x10\xd9\x08\x1a\x04\x90\xb5\x18\x01\x12,\n!MessageType_OsmosisMsgIBCTransfer\x10\xda\x08\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_OsmosisMsgSwap\x10\xdb\x08\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_OsmosisSignedTx\x10\xdc\x08\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_MayachainGetAddress\x10\xb0\t\x1a\x04\x90\xb5\x18\x01\x12\'\n\x1cMessageType_MayachainAddress\x10\xb1\t\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_MayachainSignTx\x10\xb2\t\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_MayachainMsgRequest\x10\xb3\t\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_MayachainMsgAck\x10\xb4\t\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_MayachainSignedTx\x10\xb5\t\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_ZcashSignPCZT\x10\x94\n\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_ZcashPCZTAction\x10\x95\n\x1a\x04\x90\xb5\x18\x01\x12)\n\x1eMessageType_ZcashPCZTActionAck\x10\x96\n\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_ZcashSignedPCZT\x10\x97\n\x1a\x04\x98\xb5\x18\x01\x12)\n\x1eMessageType_ZcashGetOrchardFVK\x10\x98\n\x1a\x04\x90\xb5\x18\x01\x12&\n\x1bMessageType_ZcashOrchardFVK\x10\x99\n\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_ZcashTransparentInput\x10\x9a\n\x1a\x04\x90\xb5\x18\x01\x12-\n\"MessageType_ZcashTransparentSigned\x10\x9b\n\x1a\x04\x98\xb5\x18\x01\x12*\n\x1fMessageType_ZcashDisplayAddress\x10\x9c\n\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_ZcashAddress\x10\x9d\n\x1a\x04\x98\xb5\x18\x01\x12-\n\"MessageType_ZcashTransparentOutput\x10\x9e\n\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_ZcashTransparentAck\x10\x9f\n\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_TronGetAddress\x10\xf8\n\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_TronAddress\x10\xf9\n\x1a\x04\x98\xb5\x18\x01\x12!\n\x16MessageType_TronSignTx\x10\xfa\n\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_TronSignedTx\x10\xfb\n\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_TronSignMessage\x10\xfc\n\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_TronMessageSignature\x10\xfd\n\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_TronVerifyMessage\x10\xfe\n\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_TronSignTypedHash\x10\xff\n\x1a\x04\x90\xb5\x18\x01\x12-\n\"MessageType_TronTypedDataSignature\x10\x80\x0b\x1a\x04\x98\xb5\x18\x01\x12$\n\x19MessageType_TonGetAddress\x10\xdc\x0b\x1a\x04\x90\xb5\x18\x01\x12!\n\x16MessageType_TonAddress\x10\xdd\x0b\x1a\x04\x98\xb5\x18\x01\x12 \n\x15MessageType_TonSignTx\x10\xde\x0b\x1a\x04\x90\xb5\x18\x01\x12\"\n\x17MessageType_TonSignedTx\x10\xdf\x0b\x1a\x04\x98\xb5\x18\x01\x12%\n\x1aMessageType_TonSignMessage\x10\xe0\x0b\x1a\x04\x90\xb5\x18\x01\x12*\n\x1fMessageType_TonMessageSignature\x10\xe1\x0b\x1a\x04\x98\xb5\x18\x01\x12\'\n\x1cMessageType_HiveGetPublicKey\x10\xc0\x0c\x1a\x04\x90\xb5\x18\x01\x12$\n\x19MessageType_HivePublicKey\x10\xc1\x0c\x1a\x04\x98\xb5\x18\x01\x12!\n\x16MessageType_HiveSignTx\x10\xc2\x0c\x1a\x04\x90\xb5\x18\x01\x12#\n\x18MessageType_HiveSignedTx\x10\xc3\x0c\x1a\x04\x98\xb5\x18\x01\x12(\n\x1dMessageType_HiveGetPublicKeys\x10\xc4\x0c\x1a\x04\x90\xb5\x18\x01\x12%\n\x1aMessageType_HivePublicKeys\x10\xc5\x0c\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_HiveSignAccountCreate\x10\xc6\x0c\x1a\x04\x90\xb5\x18\x01\x12.\n#MessageType_HiveSignedAccountCreate\x10\xc7\x0c\x1a\x04\x98\xb5\x18\x01\x12,\n!MessageType_HiveSignAccountUpdate\x10\xc8\x0c\x1a\x04\x90\xb5\x18\x01\x12.\n#MessageType_HiveSignedAccountUpdate\x10\xc9\x0c\x1a\x04\x98\xb5\x18\x01\x12&\n\x1bMessageType_HiveSignMessage\x10\xce\x0c\x1a\x04\x90\xb5\x18\x01\x12(\n\x1dMessageType_HiveSignedMessage\x10\xcf\x0c\x1a\x04\x98\xb5\x18\x01\x12)\n\x1eMessageType_HiveSignOperations\x10\xd0\x0c\x1a\x04\x90\xb5\x18\x01\x12+\n MessageType_HiveSignedOperations\x10\xd1\x0c\x1a\x04\x98\xb5\x18\x01\x42,\n\x1a\x63om.keepkey.deviceprotocolB\x0eKeepKeyMessage') , dependencies=[types__pb2.DESCRIPTOR,]) @@ -839,11 +839,27 @@ name='MessageType_HiveSignedAccountUpdate', index=201, number=1609, options=_descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\230\265\030\001')), type=None), + _descriptor.EnumValueDescriptor( + name='MessageType_HiveSignMessage', index=202, number=1614, + options=_descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\220\265\030\001')), + type=None), + _descriptor.EnumValueDescriptor( + name='MessageType_HiveSignedMessage', index=203, number=1615, + options=_descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\230\265\030\001')), + type=None), + _descriptor.EnumValueDescriptor( + name='MessageType_HiveSignOperations', index=204, number=1616, + options=_descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\220\265\030\001')), + type=None), + _descriptor.EnumValueDescriptor( + name='MessageType_HiveSignedOperations', index=205, number=1617, + options=_descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\230\265\030\001')), + type=None), ], containing_type=None, options=None, serialized_start=5191, - serialized_end=13220, + serialized_end=13390, ) _sym_db.RegisterEnumDescriptor(_MESSAGETYPE) @@ -1050,6 +1066,10 @@ MessageType_HiveSignedAccountCreate = 1607 MessageType_HiveSignAccountUpdate = 1608 MessageType_HiveSignedAccountUpdate = 1609 +MessageType_HiveSignMessage = 1614 +MessageType_HiveSignedMessage = 1615 +MessageType_HiveSignOperations = 1616 +MessageType_HiveSignedOperations = 1617 @@ -4921,4 +4941,12 @@ _MESSAGETYPE.values_by_name["MessageType_HiveSignAccountUpdate"]._options = _descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\220\265\030\001')) _MESSAGETYPE.values_by_name["MessageType_HiveSignedAccountUpdate"].has_options = True _MESSAGETYPE.values_by_name["MessageType_HiveSignedAccountUpdate"]._options = _descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\230\265\030\001')) +_MESSAGETYPE.values_by_name["MessageType_HiveSignMessage"].has_options = True +_MESSAGETYPE.values_by_name["MessageType_HiveSignMessage"]._options = _descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\220\265\030\001')) +_MESSAGETYPE.values_by_name["MessageType_HiveSignedMessage"].has_options = True +_MESSAGETYPE.values_by_name["MessageType_HiveSignedMessage"]._options = _descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\230\265\030\001')) +_MESSAGETYPE.values_by_name["MessageType_HiveSignOperations"].has_options = True +_MESSAGETYPE.values_by_name["MessageType_HiveSignOperations"]._options = _descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\220\265\030\001')) +_MESSAGETYPE.values_by_name["MessageType_HiveSignedOperations"].has_options = True +_MESSAGETYPE.values_by_name["MessageType_HiveSignedOperations"]._options = _descriptor._ParseOptions(descriptor_pb2.EnumValueOptions(), _b('\230\265\030\001')) # @@protoc_insertion_point(module_scope) diff --git a/keepkeylib/signed_metadata.py b/keepkeylib/signed_metadata.py index 902f31c3..acad0960 100644 --- a/keepkeylib/signed_metadata.py +++ b/keepkeylib/signed_metadata.py @@ -246,6 +246,111 @@ def serialize_metadata( return bytes(buf) +# ── v2: static schema (no tx_hash, no values; device decodes calldata) ── +# +# METADATA_VERSION_SCHEMA blobs attest only HOW to decode a curated +# (chainId, contract, selector): the method label and, per argument, a name + +# display format (+ static decimals/symbol for token amounts). They carry NO +# tx_hash and NO argument values — the device decodes the values from the exact +# calldata it is about to sign. Signed once, OFFLINE; no per-tx signer. +# +# Firmware format (parse_v2_args in lib/firmware/signed_metadata.c): +# version(1)=0x02 + chain_id(4 BE) + contract(20) + selector(4) + +# method_len(2 BE) + method + num_args(1) + +# [per arg: name_len(1) + name + display_format(1) + +# (if TOKEN_AMOUNT: decimals(1) + symbol_len(1) + symbol)] + +# classification(1) + timestamp(4 BE) + key_id(1) + signature(64) + recovery(1) +# +# Supported display formats (fixed single ABI word at offset 4 + 32*i): +# ARG_FORMAT_ADDRESS, ARG_FORMAT_AMOUNT, ARG_FORMAT_TOKEN_AMOUNT. +METADATA_VERSION_SCHEMA = 2 + + +def serialize_schema_metadata( + chain_id: int, + contract_address: bytes, + selector: bytes, + method_name: str, + args: list, + classification: int = CLASSIFICATION_VERIFIED, + timestamp: int = None, + key_id: int = 3, +) -> bytes: + """Serialize a v2 (static schema) metadata payload (unsigned). + + Args mirror serialize_metadata(), minus tx_hash. Each entry of `args` is a + dict: {name, format, [decimals, symbol]} — NO 'value' (the device decodes it + from the calldata). `decimals`/`symbol` are required for TOKEN_AMOUNT and + ignored otherwise. Call sign_metadata() on the result. + """ + if timestamp is None: + timestamp = int(time.time()) + + assert len(contract_address) == 20 + assert len(selector) == 4 + assert len(method_name.encode('utf-8')) <= 64 + assert len(args) <= 8 + + buf = bytearray() + buf.append(METADATA_VERSION_SCHEMA) + buf.extend(struct.pack('>I', chain_id)) + buf.extend(contract_address) + buf.extend(selector) + + name_bytes = method_name.encode('utf-8') + buf.extend(struct.pack('>H', len(name_bytes))) + buf.extend(name_bytes) + + buf.append(len(args)) + for arg in args: + arg_name = arg['name'].encode('utf-8') + assert len(arg_name) <= 32 + buf.append(len(arg_name)) + buf.extend(arg_name) + + fmt = arg['format'] + assert fmt in (ARG_FORMAT_ADDRESS, ARG_FORMAT_AMOUNT, + ARG_FORMAT_TOKEN_AMOUNT), \ + 'v2 supports only fixed-word ADDRESS/AMOUNT/TOKEN_AMOUNT' + buf.append(fmt) + if fmt == ARG_FORMAT_TOKEN_AMOUNT: + sym = arg['symbol'].encode('ascii') + assert 0 < len(sym) <= 10 and sym.isalnum() + assert 0 <= arg['decimals'] <= 36 + buf.append(arg['decimals']) + buf.append(len(sym)) + buf.extend(sym) + + buf.append(classification) + buf.extend(struct.pack('>I', timestamp)) + buf.append(key_id) + + return bytes(buf) + + +def schema_calldata(selector: bytes, args: list) -> bytes: + """ABI-encode the calldata a v2 schema decodes: selector + one 32-byte head + word per arg. ADDRESS -> left-zero-padded 20-byte address; AMOUNT / + TOKEN_AMOUNT -> big-endian uint256. Used to build a tx whose calldata the + device will decode against a serialize_schema_metadata() blob. + + Each arg dict needs 'format' plus a concrete value: 'address' (20 bytes) for + ADDRESS, or 'amount' (int) for AMOUNT/TOKEN_AMOUNT. + """ + data = bytearray(selector) + for arg in args: + fmt = arg['format'] + if fmt == ARG_FORMAT_ADDRESS: + addr = arg['address'] + assert len(addr) == 20 + data.extend(b'\x00' * 12 + addr) + elif fmt in (ARG_FORMAT_AMOUNT, ARG_FORMAT_TOKEN_AMOUNT): + data.extend(int(arg['amount']).to_bytes(32, 'big')) + else: + raise AssertionError('unsupported v2 arg format %r' % fmt) + return bytes(data) + + def sign_metadata(payload: bytes, private_key: bytes = None) -> bytes: """Sign the canonical binary payload and return the complete signed blob. diff --git a/scripts/generate-test-report.py b/scripts/generate-test-report.py index e7fa6fdd..6e795db2 100644 --- a/scripts/generate-test-report.py +++ b/scripts/generate-test-report.py @@ -87,7 +87,7 @@ def add_page(self, lines, w=612, h=792): y, sz, txt = item[0], item[1], item[2] style = item[3] if len(item) > 3 else False color = item[4] if len(item) > 4 else None - txt = txt.replace('\\','\\\\').replace('(','\\(').replace(')','\\)') + txt = _ascii(txt).replace('\\','\\\\').replace('(','\\(').replace(')','\\)') if color: ops.append(f'{color[0]} {color[1]} {color[2]} rg') if style == 'ding': @@ -153,6 +153,20 @@ def write(self, path): CHECK = '\x34' CROSS = '\x38' +# Map non-Latin-1 Unicode punctuation to ASCII so it survives the PDF content +# stream (encoded latin-1); em-dashes etc. were rendering as '?'. +_ASCII_MAP = { + '—': '-', '–': '-', '→': '->', '←': '<-', + '’': "'", '‘': "'", '“': '"', '”': '"', + '…': '...', '•': '*', '₿': 'BTC', '≤': '<=', + '≥': '>=', '±': '+/-', +} +def _ascii(s): + for k, v in _ASCII_MAP.items(): + if k in s: + s = s.replace(k, v) + return s + class PB: def __init__(self, pdf): self.pdf = pdf; self.lines = []; self.y = 755 @@ -189,10 +203,18 @@ def finish(self): self._flush() def _lookup(results, mod, meth): - """Look up test result by module::method (precise), then bare method (fallback).""" - return results.get(f'{mod}::{meth}') or results.get(meth) or '' - -def ver_t(s): return tuple(int(x) for x in s.replace('v','').split('.')[:3]) + """Look up a test result by module::method. Every SECTIONS module is a + test_msg_* module, so parse_junit always emits a 'mod::meth' key -- there is + no bare-method fallback (it let a cross-module method-name collision render a + never-run test as PASS, defeating the --validate-junit release gate).""" + return results.get(f'{mod}::{meth}', '') + +def ver_t(s): + # Defensive: tolerate pre-release tags (7.15.0-rc3), 'v' prefixes and short + # versions ('7.15' -> (7,15,0)) so report/filter/validate never crash. + s = str(s).split('-')[0].replace('v', '') + parts = (s.split('.') + ['0', '0', '0'])[:3] + return tuple(int(''.join(ch for ch in p if ch.isdigit()) or '0') for p in parts) def ver_ge(a, b): return ver_t(a) >= ver_t(b) def _w(text, n=95): words, lines, cur = text.split(), [], '' @@ -202,24 +224,6 @@ def _w(text, n=95): if cur: lines.append(cur) return lines -def _is_setup_frame(path): - """Check if a screenshot is a setUp noise frame (IMPORT RECOVERY, WIPE, or blank/logo).""" - try: - pixels, w, h = _read_png_pixels(path) - # Count non-zero pixels -- blank/logo frames have very few or very specific patterns - lit = sum(1 for b in pixels if b > 128) - total = w * h - # Very blank (< 5% lit) = idle/logo screen - if lit < total * 0.05: - return True - # Check for "IMPORT RECOVERY" text by looking at pixel density in top-left region - # setUp always shows this screen -- it's ~20% lit with specific pattern - # Real test screens vary widely, so we check the raw bytes for known patterns - # Simple heuristic: if first 2 btn frames match, skip them (setUp wipe + load) - return False - except: - return False - def _frame_lit_ratio(path): """Fraction of lit pixels in an OLED PNG, or None if unreadable.""" try: @@ -247,19 +251,27 @@ def _pick_best_frame(test_dir, btn_files): if not btn_files: return None scored = [] + readable = [] for f in btn_files: r = _frame_lit_ratio(os.path.join(test_dir, f)) if r is None: continue + readable.append(f) # Blank/near-blank (idle, lock) or near-full (logo/inverted) = noise. if r < 0.02 or r > 0.55: continue scored.append((r, f)) - if not scored: - return None - # Most content-rich meaningful frame. - scored.sort() - return os.path.join(test_dir, scored[-1][1]) + if scored: + # Most content-rich meaningful frame. + scored.sort() + return os.path.join(test_dir, scored[-1][1]) + # Nothing landed in the meaningful density band, but we DID capture a + # readable frame (e.g. a dense QR / address screen brighter than the band, + # or a single-frame test). Show it rather than a bogus "OLED needed" + # placeholder when a real screenshot exists. + if readable: + return os.path.join(test_dir, readable[-1]) + return None def detect_fw(): try: @@ -409,13 +421,27 @@ def _arg_shown(a): '- Input: single capacitive button (confirm/reject)', '- USB: micro-B, HID + WebUSB transports, HID fallback', '- Storage: BIP-39 seed encrypted in isolated flash region', - '- Curves: secp256k1, ed25519, NIST P-256, Pallas (Zcash)', + '- Curves: secp256k1, ed25519, NIST P-256 (Pallas/Zcash only on KK_ZCASH_PRIVACY=ON builds)', '', 'SECURITY MODEL:', '- All private key operations happen on-device, keys never leave', '- Every transaction output displayed on OLED for user verification', '- PIN grid randomized on each prompt (position-based, not digit-based)', '- BIP-39 passphrase creates hidden wallets (plausible deniability)', + '', + 'FIRMWARE VARIANTS (7.15, PR #282):', + '- Full multi-chain (default): all coin families; firmware_variant = model name', + '- Bitcoin-only (KK_BITCOIN_ONLY): only Bitcoin + Testnet; all altcoin and', + ' shielded-Zcash handlers stripped; firmware_variant = KeepKeyBTC (EmulatorBTC', + ' on the emulator). Clients gate multi-chain-only tests on this string.', + '- Zcash shielded (KK_ZCASH_PRIVACY): adds the Orchard/Pallas engine; default OFF', + ' pending external audit. Mutually exclusive with KK_BITCOIN_ONLY.', + '', + 'SEED LOCK (7.15, PR #282):', + '- A seed created under bitcoin-only firmware is stamped in a reserved storage-', + ' version band. Multi-chain firmware refuses to load it and requires an explicit', + ' wipe (wipe-to-exit); the seed is never exposed to stripped-out code. Old', + ' multi-chain firmware treats the band as unknown and resets.', ], []), ('C', 'Core - Device Lifecycle', '7.0.0', @@ -696,17 +722,17 @@ def _arg_shown(a): 'Sign Dash transaction', 'Dash special transaction types (InstantSend-compatible).', []), ('B27', 'test_msg_signtx_grs', 'test_one_one_fee', 'Sign Groestlcoin tx', 'GRS uses Groestl hash instead of SHA-256d for tx hashing.', []), - ('B28', 'test_msg_signtx_zcash', 'test_transparent_one_one', - 'Sign Zcash transparent tx', - 'Zcash transparent transactions use Overwinter/Sapling serialization format with ' - 'version group IDs and expiry height.', - ['Zcash tx confirm']), + # Zcash transparent signing moved to its own section Y (Zcash Transparent). ]), ('E', 'Ethereum', '7.0.0', 'Ethereum covers native ETH transfers, ERC-20 tokens, EIP-1559 gas, personal message signing ' - '(EIP-191), and contract interactions. The device displays checksummed addresses (EIP-55), ' - 'values in ETH with 18-decimal precision, and gas parameters.', + '(EIP-191), and contract interactions. The device displays checksummed addresses (EIP-55) and ' + 'gas parameters. Amount UNIT rule: values below 1 gwei (1e9 wei) show as raw "Wei" (there is ' + 'no smaller human unit to scale to); values at or above 1 gwei show 18-decimal-scaled ETH (or ' + 'the chain-native ticker on other EVM chains). Some tests below use small conformance-vector ' + 'amounts (e.g. 10 wei) for deterministic-signature pinning — their OLED frames legitimately ' + 'show raw "Wei", not a display bug.', [ 'ETH TRANSFER: Show "Send X ETH to 0x..." -> show gas -> confirm -> sign with secp256k1', 'ERC-20: Decode transfer(to,amount) from contract data -> show token name + amount', @@ -769,6 +795,48 @@ def _arg_shown(a): '0x swap ETH to ERC-20', 'DEX aggregator swap via 0x protocol.', []), ('E15', 'test_msg_ethereum_cfunc', 'test_sign_execTx', 'Contract function call', 'Generic contract call signing.', []), + ('E16', 'test_sign_typed_data', 'test_ethereum_sign_typed_data_hash', + 'EIP-712 typed-data hash signing (legacy, no on-device display)', + 'KNOWN GAP, disclosed rather than hidden: EIP-712 (the standard behind wallet permits, ' + 'OpenSea listings, and DAO votes — a daily-driver format) is only supported at the ' + 'domain-separator-hash + message-hash level. The device signs two host-computed 32-byte ' + 'hashes; it does NOT parse or display the typed-data domain or message fields, so this ' + 'path shows the user no readable WHO/WHAT — it is effectively a blind hash-sign, not a ' + 'clear-sign. Full structured EIP-712 display is a firmware feature, not yet built.', + []), + ('E17', 'test_msg_ethereum_erc20_uniswap_liquidity', 'test_sign_uni_approve_liquidity_ETH', + 'Uniswap V2 add-liquidity approve (pending)', + 'PENDING, disclosed: known emulator limitation — an approve to an unknown (non-registry) ' + 'token contract cannot complete against the kkemu emulator (matches the sibling ' + 'add/remove-liquidity skips below); the device-firmware path is not in question, only ' + 'CI emulator coverage. Real-device testing is unaffected.', + []), + ('E18', 'test_msg_ethereum_erc20_uniswap_liquidity', 'test_sign_uni_add_liquidity_ETH', + 'Uniswap V2 add liquidity ETH+token (pending)', + 'PENDING, disclosed: same emulator limitation as E17 — a daily-driver LP-deposit flow ' + 'with no PDF proof on this build; tracked for real-device verification.', + []), + ('E19', 'test_msg_ethereum_erc20_uniswap_liquidity', 'test_sign_uni_remove_liquidity_ETH', + 'Uniswap V2 remove liquidity ETH+token (pending)', + 'PENDING, disclosed: same emulator limitation as E17.', + []), + ('E20', 'test_msg_ethereum_thorchain_deposit', 'test_deposit_legacy_selector', + 'THORChain router deposit() (legacy selector)', + 'Cross-chain swap via the THORChain router contract — a daily-driver EVM<->THORChain ' + 'swap path, natively decoded (asset/amount/memo) without clear-sign metadata.', + []), + ('E21', 'test_msg_ethereum_thorchain_deposit', 'test_deposit_with_expiry_selector', + 'THORChain router depositWithExpiry()', + 'Newer router selector variant with an expiry field; same native decode path.', + []), + ('E22', 'test_msg_ethereum_thorchain_deposit', + 'test_deposit_with_expiry_non_thor_address_blind_sign_blocked', + 'THORChain router call to a non-pinned address is blind-sign gated', + 'WHY it can be trusted: the router CONTRACT ADDRESS is pinned; a call shaped like a ' + 'THORChain deposit but sent to an unpinned address is refused native decoding and falls ' + 'through to the ordinary blind-sign gate instead of being silently native-decoded — the ' + 'fix for the router-spoofing / blind-sign-bypass class of attack.', + ['Blind sign disabled (Blocked)']), ]), ('R', 'Ripple (XRP)', '7.0.0', @@ -898,7 +966,7 @@ def _arg_shown(a): 'cause fund loss or invalid transactions on the block-lattice.', [])]), - # ===== 7.15.1 NEW FEATURES ===== + # ===== 7.15.0 NEW FEATURES ===== ('V', 'EVM Clear-Signing', '7.15.0', 'The purpose of clear-signing: instead of blind-signing an opaque hash, the device screen ' 'answers WHO / WHAT / WHY before the user approves. WHO = the validated contract address ' @@ -941,6 +1009,24 @@ def _arg_shown(a): 'Signature verification math', 'Unit test for the metadata blob signature algorithm.', []), ('V7', 'test_msg_ethereum_clear_signing', 'test_tampered_blob_fails_verification', 'Tampered blob fails', 'Any byte change in the blob invalidates the signature.', []), + ('V7a', 'test_msg_ethereum_clear_signing', 'test_empty_payload_returns_malformed', + 'Empty metadata payload rejected', 'A zero-length blob classifies MALFORMED, never VERIFIED.', []), + ('V7b', 'test_msg_ethereum_clear_signing', 'test_truncated_payload_returns_malformed', + 'Truncated metadata payload rejected', + 'A blob cut short of the minimum structural size classifies MALFORMED.', []), + ('V7c', 'test_msg_ethereum_clear_signing', 'test_extra_trailing_bytes_returns_malformed', + 'Trailing garbage bytes rejected', + 'A blob with extra bytes appended past its declared structure classifies MALFORMED — ' + 'the parser cannot be tricked by appended data.', []), + ('V7d', 'test_msg_ethereum_clear_signing', 'test_wrong_version_returns_malformed', + 'Unknown version byte rejected', 'A blob with a version byte the firmware does not ' + 'recognize classifies MALFORMED rather than being guessed-parsed.', []), + ('V7e', 'test_msg_ethereum_clear_signing', 'test_zero_signature_returns_malformed', + 'All-zero signature rejected', 'A blob with a zeroed signature field classifies ' + 'MALFORMED — an attacker cannot skip signing by leaving the field blank.', []), + ('V7f', 'test_msg_ethereum_clear_signing', 'test_empty_key_slot_returns_malformed', + 'Metadata against an empty key slot rejected', + 'A blob referencing a signer slot with no key loaded classifies MALFORMED.', []), ('V8', 'test_msg_ethereum_signtx', 'test_ethereum_blind_sign_allowed', 'Blind sign permitted (AdvancedMode ON)', 'Contract data with AdvancedMode enabled. Device allows signing. ' @@ -992,6 +1078,37 @@ def _arg_shown(a): 'Empty, oversized, control-char and format-specifier aliases are rejected — the alias ' 'is rendered on the warning screen, so it cannot carry a display-spoofing payload.', []), + + # ── ethereum signing-path guards (the blind-sign policy negative + # half + the EIP-1559 type/fee/chain_id regression suite) ── + ('VG1', 'test_msg_ethereum_signtx', 'test_ethereum_blind_sign_blocked', + 'Blind sign refused (AdvancedMode OFF)', + 'Unknown contract data with AdvancedMode disabled is hard-rejected before any confirm ' + 'screen — the negative half of the V8 policy pair.', + ['Blind signing disabled (Failure)']), + ('VG2', 'test_msg_ethereum_signing_guards', 'test_eip1559_requires_chain_id', + 'EIP-1559 requires chain_id', + 'A type-2 tx with no chain_id would hash a garbage pre-image and recover the wrong ' + 'signer; the device rejects it outright instead of signing an unbroadcastable tx.', + []), + ('VG3', 'test_msg_ethereum_signing_guards', 'test_eip1559_no_priority_fee_signs', + 'EIP-1559 zero priority fee signs correctly', + 'Regression test for the non-canonical-RLP wrong-signer bug: a type-2 tx with zero/' + 'absent priority fee must still hash and sign to the correct device address.', + []), + ('VG4', 'test_msg_ethereum_signing_guards', 'test_type2_without_max_fee_rejected', + 'Type-2 tx without max_fee_per_gas rejected', '', []), + ('VG5', 'test_msg_ethereum_signing_guards', 'test_legacy_with_max_fee_rejected', + 'Legacy tx with max_fee_per_gas rejected', + 'Mixing legacy gas_price semantics with EIP-1559 fee fields is refused rather than ' + 'silently mis-hashed.', + []), + ('VG6', 'test_msg_ethereum_signing_guards', + 'test_contract_handler_streamed_calldata_signs_full_data', + 'Streamed calldata signs the full payload', + 'A contract-clear-sign handler must not confirm only the first chunk while signing ' + 'unshown streamed bytes after it.', + []), ] + _V_CATALOG_TESTS + [ ('V%d' % (17 + len(_V_CATALOG_TESTS)), 'test_msg_ethereum_clear_signing', 'test_clearsign_batch_all_payloads', @@ -1006,6 +1123,66 @@ def _arg_shown(a): 'device accepts them; deviate by one byte and it refuses.' % ( len(CLEARSIGN_FLOWS) if CLEARSIGN_FLOWS else 0), []), + + # ── v2 static schema (no online signer) ────────────────────── + # v2 attests only the decode SCHEMA (no tx_hash, no arg values); the + # DEVICE decodes the argument values from the calldata it signs. This + # removes the per-tx online signer: the catalog is signed once, offline. + # Offline format tests run every cycle; the on-device decode test is + # gated to the release that ships v2 (METADATA_VERSION_SCHEMA). + ('VS1', 'test_msg_ethereum_clear_signing', 'test_layout_has_no_tx_hash', + 'v2 schema blob carries no tx_hash / no values', + 'The v2 (static schema) blob attests only how to decode a curated ' + '(chainId, contract, selector): method + per-arg name/format (+ static ' + 'decimals/symbol). It has NO committed tx_hash and NO argument values — ' + 'so it can be signed ONCE, offline, and served from a CDN with no hot ' + 'key. The device decodes the values itself from the calldata it signs.', + []), + ('VS2', 'test_msg_ethereum_clear_signing', + 'test_token_arg_carries_static_decimals_symbol_not_value', + 'v2 token arg = static decimals/symbol, value decoded on-device', + 'A TOKEN_AMOUNT arg encodes the token\'s static decimals + symbol (a ' + 'property of the contract), but NOT the amount — the amount is decoded ' + 'from the calldata word on-device, then rendered "1.5 USDC".', + []), + ('VS3', 'test_msg_ethereum_clear_signing', 'test_frozen_body_snapshot', + 'v2 wire format frozen vs firmware parser', + 'The canonical v2 body\'s length + sha256 are frozen, so the ' + 'serializer can never drift from firmware\'s parse_v2_args() undetected ' + '— the same byte-parity discipline the v1 reference vectors use.', + []), + ('VS4', 'test_msg_ethereum_clear_signing', 'test_rejects_dynamic_format', + 'v2 scope: fixed-word types only', + 'v2 decodes fixed single ABI words (ADDRESS / AMOUNT / TOKEN_AMOUNT) — ' + 'approve/transfer/transferFrom and fixed-arg calls. Dynamic types ' + '(string/bytes/arrays) are rejected by the serializer and fall to the ' + 'blind-sign path on-device; a bounded dynamic decoder is future work.', + []), + ('VS5', 'test_msg_ethereum_clear_signing', + 'test_v2_transfer_decodes_signs_and_recovers', + 'v2 on-device: decode from calldata, sign, recover', + 'END-TO-END with AdvancedMode OFF: a v2 transfer() schema blob + a real ' + 'transfer(to, amount) tx. The device decodes to/amount from the calldata ' + 'and clear-signs; the signature recovers to this device\'s signer over ' + 'the tx digest — so the who/what/why shown was bound to the exact tx, ' + 'with no tx_hash. The offline format tests above pin the wire format ' + 'the device decodes.', + ['Clearsign warning', 'v2 decoded transfer to/amount', 'Sign transaction']), + ('VS6', 'test_msg_ethereum_clear_signing', + 'test_v2_calldata_length_mismatch_falls_back_to_blind_sign_gate', + 'v2 decode-mismatch falls back to blind-sign (fail-closed)', + 'THE headline v2 security property: schema says 2 words, calldata carries 3. ' + 'decode_v2_args\' structural completeness check fails, so the device does NOT ' + 'clear-sign a decode that would not match what it is about to sign — it falls ' + 'through to the ordinary blind-sign gate, and AdvancedMode OFF hard-rejects it.', + ['Blind signing disabled (Failure)']), + ('VS7', 'test_msg_ethereum_clear_signing', + 'test_v2_unsupported_arg_format_returns_malformed', + 'v2 unsupported arg format rejected at blob load', + 'A hand-crafted v2 blob using an unsupported dynamic format (STRING) — the kind the ' + 'Python serializer itself refuses to build — is independently rejected by the ' + 'device\'s own parser as MALFORMED, before any calldata is even considered.', + []), ]), ('G', 'Hive', '7.15.0', @@ -1099,7 +1276,7 @@ def _arg_shown(a): ('T', 'TRON', '7.14.0', 'NEW: TRON with secp256k1 signing, base58 addresses. Blind-sign via raw_data. ' - 'Structured reconstruct-then-sign and TRC-20 clear-signing deferred to 7.15+.', + 'Structured reconstruct-then-sign and TRC-20 clear-signing deferred to a future release.', [ 'ADDRESS: m/44\'/195\'/0\'/0/0 -> full 34-char base58 TRON address', 'BLIND-SIGN: Raw protobuf data -> hash + sign', @@ -1122,7 +1299,7 @@ def _arg_shown(a): ('N', 'TON', '7.14.0', 'NEW: TON v4r2 wallet contracts. Ed25519 signing with structured field display. ' 'Blind-sign for raw transactions. Memo/comment support. ' - 'Full clear-sign with cell tree reconstruction deferred to 7.15+.', + 'Full clear-sign with cell tree reconstruction deferred to a future release.', [ 'ADDRESS: m/44\'/607\'/0\' -> full 48-char base64url TON address', 'STRUCTURED: Amount + address + memo shown as display context -> sign', @@ -1147,14 +1324,48 @@ def _arg_shown(a): 'Missing fields rejected', 'Incomplete data refused.', []), ]), - ('Z', 'Zcash Orchard', '7.14.0', - 'NEW: Shielded transactions via PCZT streaming. Orchard hides sender, recipient, and amount ' - 'using ZK proofs. Raw seed access (ZIP-32 Orchard derivation uses BIP-39 seed + Pallas curve). ' - 'Full Viewing Key (FVK) export for watch-only wallets, unified-address display with an ' - 'on-device seed-fingerprint attestation (ZIP-32 §6.1). NOTE: pure shielded Orchard action ' - 'signing (Z5-Z7) is deferred past 7.15 — legacy sighash needs header/orchard digests not yet ' - 'in firmware; those tests skip with that reason and do not block release. Transparent->Orchard ' - 'shielding, FVK export, address display and fingerprint binding are all live.', + ('Y', 'Zcash Transparent', '7.0.0', + 'Transparent t-address Zcash (send/receive) over the generic Bitcoin UTXO signing path with ' + 'Overwinter/Sapling-v4 branch handling. This is the Zcash functionality that ships ENABLED on ' + 'the default 7.15.0 build -- t1.../t3... addresses sign like Bitcoin (SECP256K1) with a ' + 'FeeOverThreshold guard. No shielded/Orchard engine is involved; contrast with section Z ' + '(shielded), which is withheld behind KK_ZCASH_PRIVACY.', + [ + 'INPUT: TxInputType over the Zcash coin (t-address, SECP256K1)', + 'METADATA: version_group_id + branch_id for the target upgrade', + 'CONFIRM: amount + destination on the OLED, then sign each input', + 'FEE GUARD: an implausibly high fee triggers a confirmation prompt', + ], + [ + ('Y1', 'test_msg_signtx_zcash', 'test_transparent_one_one', + 'Transparent 1-in 1-out', + 'Sign a standard transparent Zcash spend; the device shows the amount and destination ' + 't-address before producing a signature over the overwinter sighash.', + ['Zcash send confirm']), + ('Y2', 'test_msg_signtx_zcash', 'test_transparent_one_one_fee_too_high', + 'High-fee guard', + 'An implausibly high fee triggers the FeeOverThreshold confirmation before signing.', + []), + ('Y3', 'test_msg_signtx_zcash', 'test_shieldedIn_one_one_fee_1', + 'Transparent spend (fee scenario 1)', + 'Despite the legacy method name, this signs a transparent input/output over the same ' + 'overwinter path (no Orchard).', + []), + ('Y4', 'test_msg_signtx_zcash', 'test_shieldedIn_one_one_fee_2', + 'Transparent spend (fee scenario 2)', + 'Second transparent fee scenario over the overwinter path.', + []), + ]), + + ('Z', 'Zcash Shielded (Orchard)', '7.14.0', + 'Shielded Orchard (PCZT streaming, Full Viewing Key export, unified-address display with an ' + 'on-device ZIP-32 Sec 6.1 seed-fingerprint attestation) is WITHHELD on the default 7.15.0 build. ' + 'It is compile-gated behind the KK_ZCASH_PRIVACY build flag, which is DEFAULT-OFF pending an ' + 'external audit of the Orchard/Pallas engine. On this build the firmware does not register the ' + 'Zcash* shielded messages, so every test in this section SKIPS BY DESIGN (the requires_message ' + 'probe returns Failure_UnexpectedMessage) -- this is a deliberate policy hold, NOT missing or ' + 'broken support. To exercise these, build the KK_ZCASH_PRIVACY=ON variant. Transparent t-address ' + 'Zcash IS live and shipping -- see section Y (Zcash Transparent).', [ 'FVK: Derive ak, nk, rivk components via ZIP-32 Orchard path', 'ADDRESS: Device derives its own unified address + shows it; optional seed-fingerprint pin', @@ -1261,36 +1472,54 @@ def render(output_path, fw_version, results, screenshot_dir=None): active = [(l,t,mf,bg,fl,tests) for l,t,mf,bg,fl,tests in SECTIONS if ver_ge(fw_version, mf)] # Separate specs section (no tests) from test sections specs = [s for s in active if not s[5]] - # Sections with results first, pending sections at bottom. - # Within each group: existing chains first (proven), then new features. - has_results = [s for s in active if s[5] and any(_lookup(results, t[1], t[2]) for t in s[5])] - no_results = [s for s in active if s[5] and not any(_lookup(results, t[1], t[2]) for t in s[5])] - test_sections = has_results + no_results + + # Classify each section by its strongest per-test outcome so the report + # distinguishes "ran and passed/failed" from "skipped by design (build-flag + # or policy gated, e.g. KK_ZCASH_PRIVACY-off shielded Zcash)" from "no result + # at all". A design-skip is NOT missing firmware support. + def _section_state(s): + st = [_lookup(results, t[1], t[2]) for t in s[5]] + if any(x in ('pass', 'fail', 'error') for x in st): + return 'tested' + if any(x == 'skip' for x in st): + return 'withheld' # only skips -> intentionally gated on this build + return 'pending' # nothing ran -> feature not present + tested = [s for s in active if s[5] and _section_state(s) == 'tested'] + withheld = [s for s in active if s[5] and _section_state(s) == 'withheld'] + pending = [s for s in active if s[5] and _section_state(s) == 'pending'] + test_sections = tested + withheld + pending total = sum(len(s[5]) for s in test_sections) - passed = sum(1 for s in test_sections for t in s[5] if _lookup(results, t[1], t[2]) == 'pass') - failed = sum(1 for s in test_sections for t in s[5] if _lookup(results, t[1], t[2]) in ('fail','error')) - skipped = total - passed - failed + passed = sum(1 for s in test_sections for t in s[5] if _lookup(results, t[1], t[2]) == 'pass') + failed = sum(1 for s in test_sections for t in s[5] if _lookup(results, t[1], t[2]) in ('fail','error')) + skipped = sum(1 for s in test_sections for t in s[5] if _lookup(results, t[1], t[2]) == 'skip') + missing = total - passed - failed - skipped # Title pb.text(20, 'KeepKey Firmware Test Report', bold=True) pb.gap(2) - if passed == total and total > 0: - pb.text(11, f'Firmware {fw_version} | {ts} | ALL {total} TESTS PASSED', bold=True, color=GREEN) - elif failed > 0: + if failed > 0: pb.text(11, f'Firmware {fw_version} | {ts} | {failed} FAILED of {total} tests', bold=True, color=RED) + elif missing == 0 and total > 0: + # Everything that exists ran green; remaining are deliberate design-skips. + extra = f', {skipped} skipped (withheld)' if skipped else '' + pb.text(11, f'Firmware {fw_version} | {ts} | {passed}/{total} PASSED{extra}', bold=True, color=GREEN) else: - pb.text(10, f'Firmware {fw_version} | {ts} | {total} tests: {passed} passed, {skipped} pending') + parts = [f'{passed} passed'] + if skipped: parts.append(f'{skipped} skipped') + if missing: parts.append(f'{missing} pending') + pb.text(10, f'Firmware {fw_version} | {ts} | {total} tests: {", ".join(parts)}') pb.gap(6) pb.text(12, 'Sections', bold=True) - _shown_tested = _shown_pending = False + _hdr_withheld = _hdr_pending = False for letter, title, mf, _, _, tests in test_sections: - has_any = any(_lookup(results, t[1], t[2]) for t in tests) + state = _section_state((letter, title, mf, None, None, tests)) is_new = ver_t(mf) > (7, 10, 0) - if has_any and not _shown_tested: - _shown_tested = True - elif not has_any and not _shown_pending: - pb.text(9, f' --- Pending (no firmware support yet) ---', bold=True, color=GRAY) - _shown_pending = True + if state == 'withheld' and not _hdr_withheld: + pb.text(9, ' --- Withheld on this build (build-flag gated; skipped by design) ---', bold=True, color=GRAY) + _hdr_withheld = True + elif state == 'pending' and not _hdr_pending: + pb.text(9, ' --- Pending (no firmware support yet) ---', bold=True, color=GRAY) + _hdr_pending = True tag = ' [NEW]' if is_new else '' p = sum(1 for t in tests if _lookup(results, t[1], t[2]) == 'pass') if p == len(tests) and len(tests) > 0: @@ -1401,7 +1630,8 @@ def render(output_path, fw_version, results, screenshot_dir=None): pb.finish() pdf.write(output_path) - print(f'{output_path}: fw={fw_version}, {len(active)} sections, {total} tests ({passed} passed, {failed} failed, {skipped} pending)') + print(f'{output_path}: fw={fw_version}, {len(active)} sections, {total} tests ' + f'({passed} passed, {failed} failed, {skipped} skipped, {missing} pending)') def screenshot_filter(fw_version): """Return pytest -k expression for tests with non-empty screenshot expectations. diff --git a/tests/config.py b/tests/config.py index cca59765..8de09c0e 100644 --- a/tests/config.py +++ b/tests/config.py @@ -44,7 +44,12 @@ (_explicit_transport, sorted(_KNOWN_TRANSPORTS)) ) -if _explicit_transport == "dylib": +if os.getenv("KK_FORCE_UDP") == "1": + # Local-only escape hatch: skip HID/WebUSB autodetect so tests hit the + # UDP emulator even with a real KeepKey plugged in. NOT for CI. + hid_devices = [] + webusb_devices = [] +elif _explicit_transport == "dylib": # Skip HID/WebUSB autodetect — dylib is opt-in by env var. Without # this skip, a connected real KeepKey would win over the explicit # request and the dylib regression suite would route to hardware. diff --git a/tests/test_msg_cosmos_signtx.py b/tests/test_msg_cosmos_signtx.py index 5ca12076..703ed36f 100644 --- a/tests/test_msg_cosmos_signtx.py +++ b/tests/test_msg_cosmos_signtx.py @@ -61,10 +61,10 @@ def test_cosmos_sign_tx_memo(self): "cosmos18vhdczjut44gpsy804crfhnd5nq003nz0nf20v", 8675309 )], - memo="Epstein didn't kill himself.", + memo="test memo", sequence=3 ) - self.assertEqual(hexlify(signature.signature), "9f2434543bc4afd2fc7bb43db05facdd6d529aa7c467ef0d41e1c2954f68db9942b8eb431cf27b52d1b3d914bbde076960179b7f426bd1a182448bb9c245009c") + self.assertEqual(hexlify(signature.signature), "db0e8039f2cd0b7d06527074a7e9079b5cd3d973f3090e04a685cfef0f145a9262dd828faa421027e583dd58fa5c6942c1f7c82fd53e54fb668fe0ebe5f83a12") self.assertEqual(hexlify(signature.public_key), "03bee3af30e53a73f38abc5a2fcdac426d7b04eb72a8ebd3b01992e2d206e24ad8") diff --git a/tests/test_msg_ethereum_clear_signing.py b/tests/test_msg_ethereum_clear_signing.py index c91c0dd8..e6ae216f 100644 --- a/tests/test_msg_ethereum_clear_signing.py +++ b/tests/test_msg_ethereum_clear_signing.py @@ -27,6 +27,8 @@ import hashlib import struct +from keepkeylib import messages_ethereum_pb2 as messages_eth + try: import common except ImportError: @@ -36,6 +38,8 @@ from keepkeylib.signed_metadata import ( serialize_metadata, + serialize_schema_metadata, + schema_calldata, sign_metadata, build_test_metadata, token_amount_value, @@ -45,6 +49,7 @@ ARG_FORMAT_BYTES, ARG_FORMAT_STRING, ARG_FORMAT_TOKEN_AMOUNT, + METADATA_VERSION_SCHEMA, CLASSIFICATION_VERIFIED, CLASSIFICATION_OPAQUE, CLASSIFICATION_MALFORMED, @@ -640,7 +645,7 @@ def test_keccak256_known_vectors(self): 'permit2-permit-transfer-from': ('c0fde596537a6bf1e53b98d3746638b4249a7a90d8196fe4a9f40f711729ec84', 276), 'across-spokepool-depositv3': ('ab185113f0b47ef5f6e1fab6a6839df8b71bf8d48796afee64a61ba8b336ac01', 311), 'safe-exectransaction': ('00a523f8e02d196db7213813edfbeee2a707679b026c6c6b6f8af88d35bf4889', 274), - 'erc4337-entrypoint-v0.7-handleops': ('0b44fc0f98727877a1d6bd1346300d9fe4b537e48d901002ea241d01b79c52cd', 281), + 'erc4337-entrypoint-v0.7-handleops': ('218c253b00780eeeb4f47b343feba7fafe2ecf3441f32afbd13e555cd56db6d2', 276), 'eip7702-setcode-authorization': ('0518442c7172b8c57fcbd09ded11b54e1d20076c4b5e79a7490c4ae9c2096a18', 299), } @@ -696,6 +701,121 @@ def test_catalog_uses_only_hexfree_formats(self): (flow['key'], arg['name'])) +# ═══════════════════════════════════════════════════════════════════════ +# v2 static-schema blobs (offline) — no device required +# +# v2 attests only the decode SCHEMA (no tx_hash, no arg values); the device +# decodes the argument values from the calldata it signs. These offline tests +# pin the wire format serialize_schema_metadata() emits so it can never drift +# from firmware's parse_v2_args() / decode_v2_args() undetected. +# ═══════════════════════════════════════════════════════════════════════ + +# transfer(to, amount) on USDC — the canonical v2 fixture. amount is a token +# amount (6 decimals, "USDC"); the value is NOT in the blob, it is decoded from +# the calldata word by the device. +USDC_ADDRESS = bytes.fromhex('a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') +ERC20_TRANSFER_SELECTOR = bytes.fromhex('a9059cbb') +V2_SCHEMA_ARGS = [ + {'name': 'to', 'format': ARG_FORMAT_ADDRESS}, + {'name': 'amount', 'format': ARG_FORMAT_TOKEN_AMOUNT, + 'decimals': 6, 'symbol': 'USDC'}, +] + + +def _v2_transfer_blob(): + body = serialize_schema_metadata( + chain_id=1, contract_address=USDC_ADDRESS, + selector=ERC20_TRANSFER_SELECTOR, method_name='transfer', + args=V2_SCHEMA_ARGS, timestamp=0, key_id=TEST_KEY_ID) + return body, sign_metadata(body) + + +class TestClearSignV2SchemaOffline(unittest.TestCase): + """Offline byte-format tests for the v2 static-schema serializer.""" + + def test_version_byte_is_schema(self): + body, _ = _v2_transfer_blob() + self.assertEqual(body[0], METADATA_VERSION_SCHEMA) + + def test_layout_has_no_tx_hash(self): + """v2 body = version(1)+chain(4)+contract(20)+selector(4)+method... — + the selector sits at offset 25, immediately after the contract, with NO + 32-byte tx_hash in between (that is the whole point of v2).""" + body, _ = _v2_transfer_blob() + self.assertEqual(body[1:5], b'\x00\x00\x00\x01') # chain_id + self.assertEqual(body[5:25], USDC_ADDRESS) # contract + self.assertEqual(body[25:29], ERC20_TRANSFER_SELECTOR) # selector @25 + # method_len(2) + 'transfer'(8) then num_args + self.assertEqual(body[29:31], b'\x00\x08') + self.assertEqual(body[31:39], b'transfer') + self.assertEqual(body[39], len(V2_SCHEMA_ARGS)) + + def test_token_arg_carries_static_decimals_symbol_not_value(self): + """The token arg encodes name + format + decimals + symbol, and NO + value — decimals/symbol are static (a property of the contract), the + amount is decoded on-device from the calldata.""" + body, _ = _v2_transfer_blob() + # after num_args @39: arg0 'to' = len(1)+'to'(2)+format(1) = 4 bytes + p = 40 + self.assertEqual(body[p], 2) # name_len 'to' + self.assertEqual(body[p + 1:p + 3], b'to') + self.assertEqual(body[p + 3], ARG_FORMAT_ADDRESS) + p += 4 + # arg1 'amount' = len(1)+'amount'(6)+format(1)+decimals(1)+symlen(1)+'USDC'(4) + self.assertEqual(body[p], 6) + self.assertEqual(body[p + 1:p + 7], b'amount') + self.assertEqual(body[p + 7], ARG_FORMAT_TOKEN_AMOUNT) + self.assertEqual(body[p + 8], 6) # decimals + self.assertEqual(body[p + 9], 4) # symbol_len + self.assertEqual(body[p + 10:p + 14], b'USDC') + + def test_signed_blob_is_body_plus_65(self): + body, blob = _v2_transfer_blob() + self.assertEqual(len(blob), len(body) + 65) + + def test_frozen_body_snapshot(self): + """Freeze the canonical v2 UNSIGNED body's length + sha256. The body is + key-independent (no signature) and deterministic (timestamp=0), so this + is a pure wire-format drift gate: it trips iff serialize_schema_metadata() + changes the bytes, which must stay in lockstep with firmware's + parse_v2_args(). (The signature is exercised separately.)""" + body, _ = _v2_transfer_blob() + got = (len(body), hashlib.sha256(body).hexdigest()) + self.assertEqual(got, V2_BODY_SNAPSHOT, + 'v2 body drift: only update V2_BODY_SNAPSHOT if the wire ' + 'format intentionally changed (and firmware too)') + + def test_calldata_matches_schema_shape(self): + """schema_calldata() builds selector + one 32-byte word per arg, so the + device decodes exactly num_args words (the structural binding).""" + cd = schema_calldata(ERC20_TRANSFER_SELECTOR, [ + {'format': ARG_FORMAT_ADDRESS, 'address': VITALIK}, + {'format': ARG_FORMAT_TOKEN_AMOUNT, 'amount': 1500000}, + ]) + self.assertEqual(len(cd), 4 + 32 * 2) + self.assertEqual(cd[:4], ERC20_TRANSFER_SELECTOR) + self.assertEqual(cd[4:16], b'\x00' * 12) # address left-padding + self.assertEqual(cd[16:36], VITALIK) + self.assertEqual(int.from_bytes(cd[36:68], 'big'), 1500000) + + def test_rejects_dynamic_format(self): + """v2 only encodes fixed single-word types; STRING/BYTES are rejected by + the serializer (they have no fixed on-chain word).""" + with self.assertRaises(AssertionError): + serialize_schema_metadata( + chain_id=1, contract_address=USDC_ADDRESS, + selector=ERC20_TRANSFER_SELECTOR, method_name='x', + args=[{'name': 'label', 'format': ARG_FORMAT_STRING}]) + + +# Frozen len + sha256 of the canonical v2 UNSIGNED transfer body (timestamp=0, +# key-independent). Regenerate ONLY on an intentional wire-format change: +# python3 -c "from tests.test_msg_ethereum_clear_signing import _v2_transfer_blob; \ +# import hashlib; b,_=_v2_transfer_blob(); print(len(b), hashlib.sha256(b).hexdigest())" +V2_BODY_SNAPSHOT = ( + 64, '01a24001460f8a69684f3d2a10f75b14e7449d8912a3833f7f8758e8fccadc05') + + # ═══════════════════════════════════════════════════════════════════════ # Device tests — require KeepKey connected with test firmware # ═══════════════════════════════════════════════════════════════════════ @@ -1102,6 +1222,121 @@ def test_load_signer_key_id_out_of_range_rejected(self): alias=CI_SIGNER_ALIAS) +class TestClearSignV2Device(common.KeepKeyTest): + """Device integration for v2 (static schema) blobs. + + A v2 blob attests only the decode schema; the device decodes the argument + values from the calldata it signs. This exercises the full round-trip: load + signer -> send v2 metadata -> sign a matching transfer() tx -> the signature + recovers to this device's signer over the tx digest (so the who/what/why + shown was bound to the exact tx, with no committed tx_hash). + + v2 (METADATA_VERSION_SCHEMA) lands in the in-progress 7.15.0 line, so this + runs against the develop firmware alongside the v1 clear-sign device tests. + """ + + V2_FIRMWARE = "7.15.0" + + def setUp(self): + super().setUp() + self.requires_firmware(self.V2_FIRMWARE) + self.requires_message("EthereumTxMetadata") + self.requires_message("LoadClearsignSigner") + self.setup_mnemonic_nopin_nopassphrase() + self.client.load_clearsign_signer( + key_id=TEST_KEY_ID, pubkey=test_signer_compressed_pubkey(), + alias=CI_SIGNER_ALIAS) + self._drop_setup_screenshots() + + def test_v2_transfer_decodes_signs_and_recovers(self): + self.client.apply_policy("AdvancedMode", 0) + self._drop_setup_screenshots() + n = parse_path(DEVICE_PATH) + chain_id, nonce, gas_price, gas_limit, value = 1, 3, 20000000000, 250000, 0 + # transfer(to=VITALIK, amount=1.5 USDC) — the device decodes both from + # the calldata using the v2 schema (address word + token-amount word). + args = [ + {'format': ARG_FORMAT_ADDRESS, 'address': VITALIK}, + {'format': ARG_FORMAT_TOKEN_AMOUNT, 'amount': 1500000}, + ] + data = schema_calldata(ERC20_TRANSFER_SELECTOR, args) + _, blob = _v2_transfer_blob() + + resp = self.client.ethereum_send_tx_metadata( + signed_payload=blob, metadata_version=1, key_id=TEST_KEY_ID) + self.assertEqual(resp.classification, CLASSIFICATION_VERIFIED) + + sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( + n=n, nonce=nonce, gas_price=gas_price, gas_limit=gas_limit, + to=USDC_ADDRESS, value=value, data=data, chain_id=chain_id) + self.assertIsNotNone(sig_r) + self.assertIsNotNone(sig_s) + tx_hash = eth_sighash_legacy(nonce, gas_price, gas_limit, USDC_ADDRESS, + value, data, chain_id) + signer = recover_eth_signer(sig_r, sig_s, sig_v, tx_hash, chain_id) + self.assertEqual(signer, self.client.ethereum_get_address(n)) + + def test_v2_calldata_length_mismatch_falls_back_to_blind_sign_gate(self): + """The headline v2 security property: a blob's schema says 2 words, + but the calldata actually being signed carries 3. decode_v2_args' + structural completeness check (total calldata bytes must equal + exactly 4 + 32*num_args) fails, matches_tx returns false, and the tx + falls through to the ordinary blind-sign path — with AdvancedMode + OFF that is a hard reject, never a clear-signed-but-wrong display.""" + self.client.apply_policy("AdvancedMode", 0) + self._drop_setup_screenshots() + n = parse_path(DEVICE_PATH) + chain_id, nonce, gas_price, gas_limit, value = 1, 3, 20000000000, 250000, 0 + args = [ + {'format': ARG_FORMAT_ADDRESS, 'address': VITALIK}, + {'format': ARG_FORMAT_TOKEN_AMOUNT, 'amount': 1500000}, + ] + # calldata carries one EXTRA 32-byte word beyond the 2-arg schema. + data = schema_calldata(ERC20_TRANSFER_SELECTOR, args) + (b'\x00' * 32) + _, blob = _v2_transfer_blob() + + resp = self.client.ethereum_send_tx_metadata( + signed_payload=blob, metadata_version=1, key_id=TEST_KEY_ID) + self.assertEqual(resp.classification, CLASSIFICATION_VERIFIED) + + with self.assertRaises(CallException) as ctx: + self.client.ethereum_sign_tx( + n=n, nonce=nonce, gas_price=gas_price, gas_limit=gas_limit, + to=USDC_ADDRESS, value=value, data=data, chain_id=chain_id) + self.assertIn("Blind signing disabled", str(ctx.exception)) + + def test_v2_unsupported_arg_format_returns_malformed(self): + """v2 supports only fixed single-word ADDRESS/AMOUNT/TOKEN_AMOUNT arg + formats (decode_v2_args has no dynamic-type support, by design). The + Python serializer refuses to BUILD a STRING-format v2 blob (see the + offline test_rejects_dynamic_format), but a malicious or buggy host + could still hand-craft the raw bytes — the device's own parser must + independently reject an unsupported v2 arg format as MALFORMED at + blob-load time, before any calldata is even seen.""" + self._drop_setup_screenshots() + body = bytearray() + body.append(METADATA_VERSION_SCHEMA) + body.extend((1).to_bytes(4, 'big')) # chain_id + body.extend(USDC_ADDRESS) + body.extend(ERC20_TRANSFER_SELECTOR) + name = b'transfer' + body.extend(len(name).to_bytes(2, 'big')) + body.extend(name) + body.append(1) # num_args + arg_name = b'label' + body.append(len(arg_name)) + body.extend(arg_name) + body.append(ARG_FORMAT_STRING) # unsupported in v2 + body.append(CLASSIFICATION_VERIFIED) + body.extend((0).to_bytes(4, 'big')) # timestamp + body.append(TEST_KEY_ID) + blob = sign_metadata(bytes(body)) + + resp = self.client.ethereum_send_tx_metadata( + signed_payload=blob, metadata_version=1, key_id=TEST_KEY_ID) + self.assertEqual(resp.classification, CLASSIFICATION_MALFORMED) + + # ═══════════════════════════════════════════════════════════════════════ # Dynamically generate one full-confirm device test per CLEARSIGN_FLOWS # entry (mirrors keepkey-sdk tests/evm-clearsign): every real-world flow a @@ -1147,12 +1382,13 @@ def print_clearsign_flows(): for flow in CLEARSIGN_FLOWS: print() print('[%s] %s' % (flow['key'], flow['method'])) - print(' shows : %s' % flow['shows']) + shows = ', '.join('%s=%r' % (a['name'], a.get('value')) for a in flow['args']) + print(' shows : %s' % shows) print(' to : 0x%s' % flow['to'].hex()) print(' value : %d' % flow['value']) print(' calldata : 0x%s' % flow['data'].hex()) print(' tx_hash : 0x%s' % flow_tx_hash(flow).hex()) - print(' blob : %s' % flow_blob(flow, timestamp=REFERENCE_TIMESTAMP).hex()) + print(' blob : %s' % flow_blob(flow, TEST_KEY_ID, timestamp=REFERENCE_TIMESTAMP).hex()) def print_test_vectors(): @@ -1195,6 +1431,165 @@ def print_test_vectors(): print('\n' + '═' * 72) + +def _decode_icon_rle(data, width, height): + """Reference decoder for LoadClearsignSigner.icon, traced from the decoder + of record: keepkey-firmware lib/board/draw.c draw_bitmap_mono_rle(). + + The icon is NOT a packed 1bpp bitmap (a packed 64x64 needs 512 bytes and the + wire cap is 384). It is run-length encoded with byte-valued pixels: + n = int8(data[i++]); n in [1,127] -> RUN: emit the next value byte n times + n in [-127,-1] -> LITERAL: emit the next (-n) bytes once each + n == 0 -> invalid + n == -128 (0x80)-> invalid: firmware's counter is int8_t + and cannot represent 128, so the packet + is undecodable (it previously asserted / + ran with a negative counter under NDEBUG) + Pixels fill row-major until exactly width*height are emitted. + """ + seq = nonseq = i = 0 + out = [] + for _ in range(height): + for _ in range(width): + if i >= len(data): + raise ValueError("overrun reading RLE count") + if seq == 0 and nonseq == 0: + n = data[i] + n = n - 256 if n > 127 else n + i += 1 + if n == 0: + raise ValueError("n == 0 is invalid") + if n == -128: + # Mirror firmware: -(-128) overflows int8_t. Accepting 128 + # here would mask a decoder incompatibility. + raise ValueError("n == -128 (0x80) is invalid: undecodable") + if n < 0: + nonseq, seq = -n, 0 + else: + seq = n + if i >= len(data): + raise ValueError("overrun reading RLE value") + out.append(data[i]) + if seq > 0: + seq -= 1 + if seq == 0: + i += 1 + else: + i += 1 + nonseq -= 1 + # Exactness, mirroring firmware's draw_bitmap_mono_rle_valid(): a run that + # straddles the end of the image, or packets trailing past the last pixel, + # are NOT well-formed. The drawing path fills the canvas and stops, so it + # cannot catch these -- the validator must. + if seq != 0 or nonseq != 0: + raise ValueError("run straddles the end of the image") + if i != len(data): + raise ValueError("trailing packets after the final pixel") + return out + + +class TestClearsignSignerIcon(unittest.TestCase): + """Offline coverage for the LoadClearsignSigner identity-icon wire contract. + + Regression guard for the review finding that the proto documented a packed + 1bpp row-major bitmap while firmware fed the bytes to an RLE decoder — a + client following the old doc rendered a garbled/absent logo on a TRUST screen. + """ + + ICON_MAX = 384 # METADATA_ICON_MAX / CLEARSIGN_ICON_MAX / proto max_size + MAX_WIDTH = 40 # LEFT_MARGIN_WITH_ICON -- the confirm screen's icon column + MAX_HEIGHT = 64 # the icon column's height + + def test_geometry_caps_are_asymmetric(self): + # width is capped at the 40px text column, NOT at the 64px height: text + # begins at x=40 and the icon is drawn after it, so a wider icon paints + # over the alias/fingerprint/"NOT verified by KeepKey" warning. + self.assertLess(self.MAX_WIDTH, self.MAX_HEIGHT) + + def test_rle_is_the_format_of_record_not_a_size_workaround(self): + # Deliberately NOT justified by "packed wouldn't fit": at the legal max + # geometry a packed 1bpp icon is 40*64/8 = 320 bytes and WOULD fit the + # 384-byte cap. RLE is the format because draw_bitmap_mono_rle() is the + # decoder of record (shared with every bundled image) -- so the encoder + # contract is RLE regardless of what packed would cost. + self.assertLessEqual((self.MAX_WIDTH * self.MAX_HEIGHT) // 8, + self.ICON_MAX) + + def test_golden_vector_matches_the_documented_decode(self): + # The golden vector published in messages-ethereum.proto. + self.assertEqual( + _decode_icon_rle(bytes([0x03, 0xFF, 0xFF, 0x00]), 2, 2), + [0xFF, 0xFF, 0xFF, 0x00], + ) + + def test_run_and_literal_packets(self): + self.assertEqual(_decode_icon_rle(bytes([0x04, 0xAB]), 4, 1), + [0xAB] * 4) # RUN + self.assertEqual(_decode_icon_rle(bytes([0xFD, 0x01, 0x02, 0x03]), 3, 1), + [0x01, 0x02, 0x03]) # LITERAL (-3) + + def test_literal_of_128_is_invalid(self): + # 0x80 => n = -128. Spec-valid under the original doc, but firmware's + # int8_t counter cannot represent 128: it asserted (debug) or decoded + # with a negative counter (NDEBUG). Both proto and firmware now reject. + with self.assertRaises(ValueError): + _decode_icon_rle(bytes([0x80]) + bytes([0xAA] * 128), 128, 1) + + def test_literal_of_127_is_the_valid_boundary(self): + data = bytes([0x81]) + bytes(range(127)) + self.assertEqual(_decode_icon_rle(data, 127, 1), list(range(127))) + + def test_run_of_127_is_the_valid_boundary(self): + self.assertEqual(_decode_icon_rle(bytes([0x7F, 0x5A]), 127, 1), + [0x5A] * 127) + + def test_zero_count_is_invalid(self): + with self.assertRaises(ValueError): + _decode_icon_rle(bytes([0x00, 0xFF]), 1, 1) + + def test_straddling_run_is_rejected(self): + # 05 FF for a 2x2: RUN of 5 into a 4-pixel image. The draw path would + # fill 4 and report success; the stream is not well-formed. + with self.assertRaises(ValueError): + _decode_icon_rle(bytes([0x05, 0xFF]), 2, 2) + + def test_trailing_packets_are_rejected(self): + # Exactly fills 2x2, then carries an unread packet. + with self.assertRaises(ValueError): + _decode_icon_rle(bytes([0x04, 0xFF, 0x01, 0xAA]), 2, 2) + + def test_truncated_stream_is_rejected(self): + with self.assertRaises(ValueError): + _decode_icon_rle(bytes([0x08, 0xFF]), 4, 4) # claims 8, only 2 bytes + + def test_message_exposes_icon_dimensions_and_persist(self): + # Regression guard: the generated bindings previously carried only + # key_id/pubkey/alias, so constructing with icon raised ValueError. + icon = bytes([0x03, 0xFF, 0xFF, 0x00]) + msg = messages_eth.LoadClearsignSigner( + key_id=3, pubkey=b'\x02' * 33, alias="Pioneer", + icon=icon, icon_width=2, icon_height=2, persist=True, + ) + parsed = messages_eth.LoadClearsignSigner() + parsed.ParseFromString(msg.SerializeToString()) + self.assertEqual(parsed.icon, icon) + self.assertEqual(parsed.icon_width, 2) + self.assertEqual(parsed.icon_height, 2) + self.assertTrue(parsed.persist) + self.assertEqual(_decode_icon_rle(parsed.icon, parsed.icon_width, + parsed.icon_height), + [0xFF, 0xFF, 0xFF, 0x00]) + + def test_text_only_identity_omits_icon_fields(self): + msg = messages_eth.LoadClearsignSigner( + key_id=3, pubkey=b'\x02' * 33, alias="Pioneer") + parsed = messages_eth.LoadClearsignSigner() + parsed.ParseFromString(msg.SerializeToString()) + self.assertFalse(parsed.HasField('icon')) + self.assertFalse(parsed.HasField('icon_width')) + self.assertFalse(parsed.HasField('icon_height')) + + if __name__ == '__main__': import sys if '--vectors' in sys.argv: diff --git a/tests/test_msg_ethereum_signing_guards.py b/tests/test_msg_ethereum_signing_guards.py index 11e14da8..83b9416c 100644 --- a/tests/test_msg_ethereum_signing_guards.py +++ b/tests/test_msg_ethereum_signing_guards.py @@ -27,7 +27,7 @@ def test_eip1559_requires_chain_id(self): """type=2 with no chain_id: Stage 1 counts chain_id as 1 byte but hash_rlp_number(0) hashes nothing -> over-declared list header -> wrong/garbage signer. The device must reject rather than sign it.""" - self.requires_firmware("7.15.1") + self.requires_firmware("7.15.0") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() self.client.apply_policy("AdvancedMode", 1) @@ -49,7 +49,7 @@ def test_eip1559_no_priority_fee_signs(self): absent it must encode as the empty integer (0x80). Stage 1 always counts it, so Stage 2 must always hash it -- the device must still produce a valid signature (not desync the list header).""" - self.requires_firmware("7.15.1") + self.requires_firmware("7.15.0") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( @@ -69,7 +69,7 @@ def test_type2_without_max_fee_rejected(self): """Typed prefix (0x02) is chosen from msg.type but the fee fields from has_max_fee_per_gas. A type=2 tx carrying only gas_price would sign a malformed (legacy-fee-in-1559-envelope) field list -> reject.""" - self.requires_firmware("7.15.1") + self.requires_firmware("7.15.0") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() self.client.apply_policy("AdvancedMode", 1) @@ -88,7 +88,7 @@ def test_type2_without_max_fee_rejected(self): def test_legacy_with_max_fee_rejected(self): """A legacy tx (type omitted) carrying max_fee_per_gas would hash two fee fields into a legacy structure -> reject the mismatch.""" - self.requires_firmware("7.15.1") + self.requires_firmware("7.15.0") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() self.client.apply_policy("AdvancedMode", 1) @@ -117,7 +117,7 @@ def test_contract_handler_streamed_calldata_signs_full_data(self): the screen-level assertion (no 'Sablier' clear-sign summary appears for streamed calldata) is verified on-device / on the emulator via DebugLink layout.""" - self.requires_firmware("7.15.1") + self.requires_firmware("7.15.0") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() self.client.apply_policy("AdvancedMode", 1) diff --git a/tests/test_msg_ethereum_signtx.py b/tests/test_msg_ethereum_signtx.py index 192f8fcf..501b36d8 100644 --- a/tests/test_msg_ethereum_signtx.py +++ b/tests/test_msg_ethereum_signtx.py @@ -100,7 +100,7 @@ def test_ethereum_blind_sign_blocked(self): OLED shows 'Blind signing disabled' then Failure. """ - self.requires_firmware("7.15.1") + self.requires_firmware("7.15.0") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() self.client.apply_policy("AdvancedMode", 0) @@ -124,7 +124,7 @@ def test_ethereum_blind_sign_allowed(self): OLED shows 'BLIND SIGNATURE' before signing. """ - self.requires_firmware("7.15.1") + self.requires_firmware("7.15.0") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() self.client.apply_policy("AdvancedMode", 1) diff --git a/tests/test_msg_ethereum_thorchain_deposit.py b/tests/test_msg_ethereum_thorchain_deposit.py index f6c3a5a9..177b01a5 100644 --- a/tests/test_msg_ethereum_thorchain_deposit.py +++ b/tests/test_msg_ethereum_thorchain_deposit.py @@ -27,7 +27,7 @@ def _build_deposit_calldata(memo): """Build deposit(address,address,uint256,string) calldata (legacy selector).""" selector = bytes.fromhex("1fece7b4") vault = bytes(12) + bytes.fromhex(THOR_ROUTER) - asset = bytes(12) + bytes.fromhex(ETH_NATIVE) + asset = bytes(32) # address(0): the only native-ETH form the routers accept amount = (500000000000000000).to_bytes(32, "big") # 0.5 ETH memo_offset = (4 * 32).to_bytes(32, "big") # offset = 128 memo_bytes = memo.encode("ascii") @@ -41,7 +41,7 @@ def _build_deposit_with_expiry_calldata(memo, expiry=9999999999): """Build depositWithExpiry(address,address,uint256,string,uint256) calldata.""" selector = bytes.fromhex("44bc937b") vault = bytes(12) + bytes.fromhex(THOR_ROUTER) - asset = bytes(12) + bytes.fromhex(ETH_NATIVE) + asset = bytes(32) # address(0): the only native-ETH form the routers accept amount = (500000000000000000).to_bytes(32, "big") # 0.5 ETH memo_offset = (5 * 32).to_bytes(32, "big") # offset = 160 (after expiry) expiry_b = expiry.to_bytes(32, "big") diff --git a/tests/test_msg_hive.py b/tests/test_msg_hive.py index 082fb418..488a5e6c 100644 --- a/tests/test_msg_hive.py +++ b/tests/test_msg_hive.py @@ -30,6 +30,7 @@ """ import hashlib +import struct import unittest import common @@ -46,9 +47,12 @@ # SLIP-0048 roles (hardened offsets within the role component). ROLE_OWNER, ROLE_ACTIVE, ROLE_MEMO, ROLE_POSTING = 0, 1, 3, 4 +HIVE_OP_VOTE = 0 +HIVE_OP_COMMENT = 1 HIVE_OP_TRANSFER = 2 HIVE_OP_ACCOUNT_CREATE = 9 HIVE_OP_ACCOUNT_UPDATE = 10 +HIVE_OP_CUSTOM_JSON = 18 def hive_path(role, account_index=0): @@ -75,6 +79,54 @@ def recover_compressed(serialized_tx, sig65): return candidates[recid].to_string("compressed") +# ── Independent Graphene serializer for HiveSignOperations tests ────────── +# dhive-equivalent byte building, written here so firmware parser bugs can't +# cancel out against firmware serializer bugs. + +def _varint(n): + out = b"" + while True: + b_ = n & 0x7F + n >>= 7 + if n: + out += bytes([b_ | 0x80]) + else: + return out + bytes([b_]) + + +def _string(s): + if isinstance(s, str): + s = s.encode("utf-8") + return _varint(len(s)) + s + + +def _ops_tx(op_blobs, ref_num=12345, ref_prefix=67890, expiration=1700000000, + ext=b"\x00", opcount=None): + """header + varint op count + ops + extensions (default: empty).""" + head = struct.pack("transaction signature oracle.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignMessage") + self.setup_mnemonic_nopin_nopassphrase() + + from keepkeylib.client import CallException + message = bytes(range(0, 48)) # non-printable bytes + with self.assertRaises(CallException) as ctx: + hive.sign_message(self.client, hive_path(ROLE_POSTING), message) + self.assertIn("printable", str(ctx.exception)) + + def test_hive_sign_message_long_printable_ok(self): + """Printable text over the 128-byte display budget still signs — it + routes through the hex-preview confirm (never silently truncated + text), and the signature covers every byte.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignMessage") + self.setup_mnemonic_nopin_nopassphrase() + + message = (b"benign preamble. " * 20)[:300] # printable, > 128 bytes + posting = hive.get_public_key(self.client, hive_path(ROLE_POSTING), show_display=False) + resp = hive.sign_message(self.client, hive_path(ROLE_POSTING), message) + self.assertEqual(self._recover_message_signer(message, resp.signature), + posting.raw_public_key) + + def test_hive_sign_message_max_length_ok(self): + """A message of exactly 1024 bytes (the proto cap) still signs.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignMessage") + self.setup_mnemonic_nopin_nopassphrase() + + message = b"x" * 1024 + posting = hive.get_public_key(self.client, hive_path(ROLE_POSTING), show_display=False) + resp = hive.sign_message(self.client, hive_path(ROLE_POSTING), message) + self.assertEqual(self._recover_message_signer(message, resp.signature), + posting.raw_public_key) + + def test_hive_sign_message_rejects_oversize(self): + """1025 bytes must fail (nanopb max_size cap — the proto and handler + agree on 1024).""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignMessage") + self.setup_mnemonic_nopin_nopassphrase() + from keepkeylib.client import CallException + with self.assertRaises(CallException): + hive.sign_message(self.client, hive_path(ROLE_POSTING), b"x" * 1025) + + def test_hive_sign_message_rejects_bad_paths(self): + """Foreign trees, wrong network index, and unassigned roles must all + be rejected — same fence as the transaction handlers.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignMessage") + self.setup_mnemonic_nopin_nopassphrase() + from keepkeylib.client import CallException + h = 0x80000000 + bad_paths = [ + parse_path("m/44'/0'/0'/0/0"), # BIP-44 BTC + [h + 48, h + 3054, h + ROLE_POSTING, h, h], # registry 3054', not 13' + hive_path(2), # unassigned role 2' + hive_path(ROLE_OWNER), # owner' not a Keychain signBuffer role + [h + 48, h + 13, h + ROLE_POSTING, h], # short path + ] + for path in bad_paths: + with self.assertRaises(CallException) as ctx: + hive.sign_message(self.client, path, b"login challenge") + self.assertIn("Invalid Hive SLIP-0048 path", str(ctx.exception)) + + # ── Operations signing (HiveSignOperations — parsed generic ops) ────── + # The test builds transactions byte-exactly with its OWN serializer + # (below, module level) — never firmware-emitted bytes — so a parser bug + # and a serializer bug cannot cancel out. + + def _recover_ops_signer(self, tx, sig65): + """digest = SHA256(chain_id || serialized_tx), same as transfers.""" + self.assertEqual(len(sig65), 65) + recid = sig65[0] - 31 + self.assertTrue(0 <= recid <= 3, "unexpected recovery header byte %d" % sig65[0]) + digest = hashlib.sha256(HIVE_CHAIN_ID + tx).digest() + candidates = VerifyingKey.from_public_key_recovery_with_digest( + sig65[1:], digest, SECP256k1, hashfunc=hashlib.sha256, + sigdecode=sigdecode_string + ) + return candidates[recid].to_string("compressed") + + def test_hive_sign_ops_vote(self): + """A vote tx signs with the posting key and recovers to it.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignOperations") + self.setup_mnemonic_nopin_nopassphrase() + + tx = _ops_tx([_op_vote("kkvoter", "someauthor", "cool-post-permlink", 10000)]) + posting = hive.get_public_key(self.client, hive_path(ROLE_POSTING), show_display=False) + resp = hive.sign_operations(self.client, hive_path(ROLE_POSTING), tx, chain_id=HIVE_CHAIN_ID) + self.assertEqual(self._recover_ops_signer(tx, resp.signature), posting.raw_public_key) + + def test_hive_sign_ops_downvote_and_default_chain_id(self): + """Negative weight (downvote) signs; omitted chain_id defaults to + mainnet in firmware — recovery against the mainnet id proves it.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignOperations") + self.setup_mnemonic_nopin_nopassphrase() + + tx = _ops_tx([_op_vote("kkvoter", "spammer", "bad-post", -10000)]) + posting = hive.get_public_key(self.client, hive_path(ROLE_POSTING), show_display=False) + resp = hive.sign_operations(self.client, hive_path(ROLE_POSTING), tx) # no chain_id + self.assertEqual(self._recover_ops_signer(tx, resp.signature), posting.raw_public_key) + + def test_hive_sign_ops_comment(self): + """A top-level post (empty parent_author) with a unicode body signs — + the body routes through the non-ASCII display fallback.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignOperations") + self.setup_mnemonic_nopin_nopassphrase() + + body = "skate clip of the day — hardflip".encode("utf-8") + tx = _ops_tx([_op_comment("", "hive-173115", "kkauthor", + "my-first-post", "My first post", body, "{}")]) + posting = hive.get_public_key(self.client, hive_path(ROLE_POSTING), show_display=False) + resp = hive.sign_operations(self.client, hive_path(ROLE_POSTING), tx, chain_id=HIVE_CHAIN_ID) + self.assertEqual(self._recover_ops_signer(tx, resp.signature), posting.raw_public_key) + + def test_hive_sign_ops_custom_json_posting(self): + """custom_json with posting auths (Hive Engine style) signs with the + posting key.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignOperations") + self.setup_mnemonic_nopin_nopassphrase() + + tx = _ops_tx([_op_custom_json([], ["kkplayer"], "ssc-mainnet-hive", + '{"contractName":"tokens","contractAction":"transfer"}')]) + posting = hive.get_public_key(self.client, hive_path(ROLE_POSTING), show_display=False) + resp = hive.sign_operations(self.client, hive_path(ROLE_POSTING), tx, chain_id=HIVE_CHAIN_ID) + self.assertEqual(self._recover_ops_signer(tx, resp.signature), posting.raw_public_key) + + def test_hive_sign_ops_custom_json_active(self): + """custom_json with required_auths (active tier) must sign with the + ACTIVE key — and does.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignOperations") + self.setup_mnemonic_nopin_nopassphrase() + + tx = _ops_tx([_op_custom_json(["kkadmin"], [], "witness-ops", '{"op":"x"}')]) + active = hive.get_public_key(self.client, hive_path(ROLE_ACTIVE), show_display=False) + resp = hive.sign_operations(self.client, hive_path(ROLE_ACTIVE), tx, chain_id=HIVE_CHAIN_ID) + self.assertEqual(self._recover_ops_signer(tx, resp.signature), active.raw_public_key) + + def _assert_ops_fails(self, fragment, tx, path=None): + from keepkeylib.client import CallException + with self.assertRaises(CallException) as ctx: + hive.sign_operations(self.client, path or hive_path(ROLE_POSTING), + tx, chain_id=HIVE_CHAIN_ID) + if fragment: + self.assertIn(fragment, str(ctx.exception)) + + def test_hive_sign_ops_rejects_excluded_and_unknown_ops(self): + """Op types 2/9/10 are PERMANENTLY excluded (dedicated messages keep + their stronger invariants); unknown types reject too. The parser + refuses on the op-type byte, so the bodies never matter.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignOperations") + self.setup_mnemonic_nopin_nopassphrase() + for op_type in (2, 9, 10): + self._assert_ops_fails("dedicated message", + _ops_tx([_varint(op_type)])) + self._assert_ops_fails("unsupported operation", + _ops_tx([_varint(3)])) # comment_options + + def test_hive_sign_ops_rejects_malformed_structure(self): + """Zero ops, >4 ops, nonzero extensions, trailing bytes, overlong + varint, out-of-range weight — each refused with a specific error.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignOperations") + self.setup_mnemonic_nopin_nopassphrase() + + vote = _op_vote("kkvoter", "author", "permlink", 100) + self._assert_ops_fails("op count", _ops_tx([], opcount=0)) + self._assert_ops_fails("op count", _ops_tx([vote] * 5)) + self._assert_ops_fails("extensions must be empty", + _ops_tx([vote], ext=b"\x01")) + self._assert_ops_fails("trailing bytes", _ops_tx([vote]) + b"\x00") + # op_count as an overlong 6-byte varint encoding of 1 + head = struct.pack(" 2048) + from keepkeylib.client import CallException + with self.assertRaises(CallException): + hive.sign_operations(self.client, hive_path(ROLE_POSTING), tx, + chain_id=HIVE_CHAIN_ID) + + def test_hive_sign_message_rejects_chain_id_prefix(self): + """A 'message' that begins with the mainnet chain id would hash to a + broadcastable TRANSACTION digest (tx digest = SHA256(chain_id || tx)). + The firmware must refuse the collision.""" + self.requires_firmware("7.15.0") + self.requires_message("HiveSignMessage") + self.setup_mnemonic_nopin_nopassphrase() + from keepkeylib.client import CallException + disguised_tx = HIVE_CHAIN_ID + b"\x39\x30" + b"\x00" * 40 + with self.assertRaises(CallException) as ctx: + hive.sign_message(self.client, hive_path(ROLE_ACTIVE), disguised_tx) + self.assertIn("chain ID", str(ctx.exception)) + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_msg_mayachain_signtx.py b/tests/test_msg_mayachain_signtx.py index 8a0bae22..7e3f6806 100644 --- a/tests/test_msg_mayachain_signtx.py +++ b/tests/test_msg_mayachain_signtx.py @@ -7,6 +7,7 @@ import keepkeylib.messages_pb2 as proto import keepkeylib.types_pb2 as proto_types from keepkeylib.tools import parse_path +from keepkeylib.signed_metadata import eth_sighash_legacy, keccak256 DEFAULT_BIP32_PATH = "m/44h/931h/0h/0/0" @@ -23,6 +24,26 @@ def make_send(from_address, to_address, amount): } } +def recover_eth_signer(sig_r, sig_s, sig_v, digest, chain_id): + """Recover the 20-byte Ethereum signer from a legacy (EIP-155) signature. + + Mirrors the helper proven in test_msg_ethereum_clear_signing.py. Verifying + recovery — rather than asserting r/s lengths — means a wrong digest, wrong + calldata or wrong key fails the test, and it stays correct across router + changes without re-freezing vectors. + """ + from ecdsa import VerifyingKey, SECP256k1, util + if chain_id: + rec = sig_v - (35 + 2 * chain_id) + else: + rec = sig_v - 27 + keys = VerifyingKey.from_public_key_recovery_with_digest( + sig_r + sig_s, digest, SECP256k1, hashfunc=None, + sigdecode=util.sigdecode_string, + ) + return keccak256(keys[rec].to_string())[-20:] + + class TestMsgMayaChainSignTx(common.KeepKeyTest): @unittest.skip("TODO: capture expected signatures from emulator") @@ -72,16 +93,10 @@ def test_sign_eth_btc_swap(self): self.requires_firmware("7.1.0") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[2147483692,2147483708,2147483648,0,0], - nonce=0x0, - gas_price=0x5FB9ACA00, - gas_limit=0x186A0, - value=0x00, - to=unhexlify('d89dce570de35a6f42d3bca7dba50a6d89bfc2a2'), # Maya router (firmware-pinned) - address_type=0, - chain_id=1, - data=unhexlify('1fece7b4' + + address_n = [2147483692,2147483708,2147483648,0,0] + nonce, gas_price, gas_limit, value = 0x0, 0x5FB9ACA00, 0x186A0, 0x00 + to = unhexlify('e3985e6b61b814f7cdb188766562ba71b446b46d') # Maya router v4 (firmware-pinned) + data = unhexlify('1fece7b4' + '000000000000000000000000345b297ec83add7ff74d2f7933651bffa037d956' + # asgard vault address '0000000000000000000000000000000000000000000000000000000000000000' + # asset ETH '000000000000000000000000000000000000000000000065945acd2b867ef000' + # amount @@ -90,13 +105,22 @@ def test_sign_eth_btc_swap(self): # SWAP:BTC.BTC:0x41e5560054824ea6b0732e656e3ad64e20e94e45:420 '535741503a4254432e4254433a30783431653535363030353438323465613662' + # mayachain transaction memo '30373332653635366533616436346532306539346534353a3432300000000000') - ) - # `to` updated to the firmware-pinned Maya router; exact r/s change - # with it, so assert structure here and regenerate exact vectors - # on-device. + sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( + n=address_n, nonce=nonce, gas_price=gas_price, gas_limit=gas_limit, + value=value, to=to, address_type=0, chain_id=1, data=data) + # Verify the signature is over the EXACT tx above and by THIS device's + # key, rather than merely checking r/s lengths (which a wrong digest, + # wrong calldata or wrong key would also pass). Recovery keeps the test + # correct across router changes without re-freezing r/s vectors. self.assertIn(sig_v, [37, 38]) # EIP-155 chain_id=1 self.assertEqual(len(sig_r), 32) self.assertEqual(len(sig_s), 32) + digest = eth_sighash_legacy(nonce, gas_price, gas_limit, to, value, + data, 1) + signer = recover_eth_signer(sig_r, sig_s, sig_v, digest, 1) + # ethereum_get_address returns the raw 20 bytes. NB: KeepKeyTest's + # assertEqual override takes no msg argument. + self.assertEqual(signer, self.client.ethereum_get_address(address_n)) def test_sign_btc_add_liquidity(self): @@ -122,16 +146,10 @@ def test_sign_eth_add_liquidity(self): self.requires_firmware("7.9.1") self.requires_fullFeature() self.setup_mnemonic_nopin_nopassphrase() - sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( - n=[2147483692,2147483708,2147483648,0,0], - nonce=0x0, - gas_price=0x5FB9ACA00, - gas_limit=0x186A0, - value=0x00, - to=unhexlify('d89dce570de35a6f42d3bca7dba50a6d89bfc2a2'), # Maya router (firmware-pinned) - address_type=0, - chain_id=1, - data=unhexlify('1fece7b4' + + address_n = [2147483692,2147483708,2147483648,0,0] + nonce, gas_price, gas_limit, value = 0x0, 0x5FB9ACA00, 0x186A0, 0x00 + to = unhexlify('e3985e6b61b814f7cdb188766562ba71b446b46d') # Maya router v4 (firmware-pinned) + data = unhexlify('1fece7b4' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + @@ -140,14 +158,22 @@ def test_sign_eth_add_liquidity(self): # ADD:ETH.ETH:0xc5b2608927ea95ed43f842f553e3a27b09c050e8:420 '4144443a4554482e4554483a3078633562323630383932376561393565643433' + '663834326635353365336132376230396330353065383a343230000000000000') - - ) - # `to` updated to the firmware-pinned Maya router; exact r/s change - # with it, so assert structure here and regenerate exact vectors - # on-device. + sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( + n=address_n, nonce=nonce, gas_price=gas_price, gas_limit=gas_limit, + value=value, to=to, address_type=0, chain_id=1, data=data) + # Verify the signature is over the EXACT tx above and by THIS device's + # key, rather than merely checking r/s lengths (which a wrong digest, + # wrong calldata or wrong key would also pass). Recovery keeps the test + # correct across router changes without re-freezing r/s vectors. self.assertIn(sig_v, [37, 38]) # EIP-155 chain_id=1 self.assertEqual(len(sig_r), 32) self.assertEqual(len(sig_s), 32) + digest = eth_sighash_legacy(nonce, gas_price, gas_limit, to, value, + data, 1) + signer = recover_eth_signer(sig_r, sig_s, sig_v, digest, 1) + # ethereum_get_address returns the raw 20 bytes. NB: KeepKeyTest's + # assertEqual override takes no msg argument. + self.assertEqual(signer, self.client.ethereum_get_address(address_n)) @unittest.skip("TODO: capture expected signatures from emulator") def test_mayachain_remove_liquidity(self): diff --git a/tests/test_msg_recoverydevice_cipher.py b/tests/test_msg_recoverydevice_cipher.py index b72279fd..1521393e 100644 --- a/tests/test_msg_recoverydevice_cipher.py +++ b/tests/test_msg_recoverydevice_cipher.py @@ -174,7 +174,7 @@ def test_invalid_bip39_word_rejected(self): BIP-39 wordlist must return Failure immediately. Requires firmware 7.15.1+ (per-word validation). """ - self.requires_firmware("7.15.1") + self.requires_firmware("7.15.0") ret = self.client.call_raw(proto.RecoveryDevice(word_count=12, passphrase_protection=False, pin_protection=False, diff --git a/tests/test_msg_solana_signtx.py b/tests/test_msg_solana_signtx.py index 2aa1a34c..53fc3dcc 100644 --- a/tests/test_msg_solana_signtx.py +++ b/tests/test_msg_solana_signtx.py @@ -263,28 +263,104 @@ def _build_tx(self, from_pubkey, accounts, program_id, instr_data, extra_account # ================================================================ def test_solana_sign_token_transfer(self): - """SPL Token transfer — OLED shows 'Send [amount] tokens to [address]'.""" + """Unchecked SPL Transfer has no signed mint (the token being moved is + not provable), so it now requires AdvancedMode (blind-sign); only the + TransferChecked variant clear-signs.""" self.requires_fullFeature() self.setup_mnemonic_allallall() + from keepkeylib.client import CallException from_pubkey = self._get_from_pubkey() to_account = b'\x33' * 32 # destination token account - owner = from_pubkey # token owner = signer # SPL Token Transfer instruction: opcode=3 (u8) + amount (LE u64) instr_data = bytes([3]) + struct.pack('