diff --git a/common/protob/messages-cardano.proto b/common/protob/messages-cardano.proto index f70aac826..4c604ba57 100644 --- a/common/protob/messages-cardano.proto +++ b/common/protob/messages-cardano.proto @@ -220,11 +220,27 @@ message CardanoSignTx { } } +/** + * Response: Serialised signed cardano transaction chunk + * @next CardanoSignedTxChunkAck + */ +message CardanoSignedTxChunk { + required bytes signed_tx_chunk = 1; // serialised, signed transaction chunk +} + +/** + * Request: Serialised signed cardano transaction chunk acknowledgement + * @next CardanoSignedTxChunk + * @next CardanoSignedTx + */ +message CardanoSignedTxChunkAck { +} + /** * Response: Serialised signed cardano transaction * @end */ message CardanoSignedTx { - required bytes tx_hash = 1; // hash of the transaction body - required bytes serialized_tx = 2; // serialized, signed transaction + required bytes tx_hash = 1; // hash of the transaction body + optional bytes serialized_tx = 2; // deprecated since transaction is sent in chunks now - kept for backwards compatibility } diff --git a/common/protob/messages.proto b/common/protob/messages.proto index b513628c5..c87f8a6b7 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -206,6 +206,8 @@ enum MessageType { MessageType_CardanoGetAddress = 307 [(wire_in) = true]; MessageType_CardanoAddress = 308 [(wire_out) = true]; MessageType_CardanoSignedTx = 310 [(wire_out) = true]; + MessageType_CardanoSignedTxChunk = 311 [(wire_out) = true]; + MessageType_CardanoSignedTxChunkAck = 312 [(wire_in) = true]; // Ripple MessageType_RippleGetAddress = 400 [(wire_in) = true]; diff --git a/common/tests/fixtures/cardano/sign_tx.chunked.json b/common/tests/fixtures/cardano/sign_tx.chunked.json new file mode 100644 index 000000000..227657a3c --- /dev/null +++ b/common/tests/fixtures/cardano/sign_tx.chunked.json @@ -0,0 +1,152 @@ +{ + "setup": { + "mnemonic": "all all all all all all all all all all all all", + "passphrase": "" + }, + "tests": [ + { + "description": "Large transaction to be sent in multiple chunks", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "metadata": "", + "input_flow": [["SWIPE", "YES"], ["SWIPE", "YES"]], + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/1", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/2", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/3", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/4", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/5", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/6", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/7", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/8", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/9", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/10", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/11", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/12", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/13", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/14", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/15", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/16", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/17", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/18", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/19", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + }, + { + "path": "m/1852'/1815'/0'/0/20", + "prev_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1z90z7zqwhya6mpk5q929ur897g3pp9kkgalpreny8y304r2dcrtx0sf3dluyu4erzr3xtmdnzvcyfzekkuteu2xagx0qeva0pr", + "amount": "7120787" + } + ] + }, + "result": { + "signed_tx_chunks": [ + "83a40095825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200", + "825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d5", + "93fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02000181825839115e2f", + "080eb93bad86d401545e0ce5f2221096d6477e11e6643922fa8d4dc0d667c1316ff84e572310e265edb31330448b36b7179e28dd419e1a006ca79302182a030aa10095825820068883fe4a4e30bd75bb034f1bc8317a5ee1df001a2e0e9c2e85c926fc9336d858405aa7e3aa7255946563f08efeed7eccd976ee1ddf3299e00e6080f4fb626bc6128ea73fc1bdb6f38d7cf704f2e6dcd1c37304be84526c1bd4416e1668627597038258200e86bde9a1630540366a17a6edae53ce5f3e0e2895d43ae6606204d2b485251b584039b93c1ed9a7fd0c662676f72ed2b826198a7d83bc81235f64c44a6cd8ac049c5dc287daed9a3a90210dc15283493739b8caae", + "b5f0260c3e40f38b05c77f540182582010ae189d1d30bef9214e006a0287fc5f2caf56576f04c9d3ef381f0f678561665840640e63049280c1cfa581eaed11f540ac6038a9a1aaa2348ebf7c12a0ecc6ff843b44d89419645c4a18e73e99b379456becebac39d6f5bafa0143367f746e890a82582023cf6064ec4c968e4428c016def464c053c2172c365eaf2c0deae9fc5dd0ba3d5840b6714125d8097420b14bf88519f94579f328ccb8a42bafef07440c7556448baa8b23b312d7702e73204fbd74fe6dcf8296a97af41670d84cc1efced76bca9d0d82582036a8ef21d5b98fdf23a27325cf643deaac35e912c835e35037f23d1061ae5b16584051218395", + "33df7920ce3f6a9c8cd999e25c2a2446842dd7bcc6d773572b43c53cdb52f17c9c0e8b7958ab43c43ad2506b1c6ee37282710984d8be705ec1fb2d0482582047ce718cbbd34f3bc56955c5f8042e0d0567b90a1d09e44df653905f7115c8ef58404ae2348c4c5dcbd60220408e3e6b7872cb3555e44d270391a6fe425f3107728b901fd2b94e823bb60c32fa5116351a998072b27f396f1c54b59615cee8b7560182582054b180a8930475a05ccb03601e67046fbd64cee911600f60ead0227291d2b29b5840b956abbffb8979d2488114093bc978abed0115691a0ac8601c2320d2f1a56bd6c4221107917daf36acbe395f1d784826a4375a57d6044664d364", + "77ee38437a0a8258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c158400835682f782fa88eb28378f2e757df887f467fbead67a9f6532c5f621b5004cd66ef3c841be4f1c9ad9bf2b8c1c60b65ef7cb6cbf05e07ca2ca72be73fc23f0082582064d248557635918ed0d48f86a85fa3e937e5e60b4522609e47baf2df3f5d08ce5840b162939928888bdfb029a55f63fb7bfcb7e5c0c9717f1aff9a3fe03783f0ab6aa1768e28154bfccc9e8870c9d6379816a4cdda68360015686a70f8dcb2e3be088258206883f530a417453da0a23db4f62038e82929537e76d298d212008e1481af5ae458408f4af77871fc4334dbdae7", + "c06683cb3eff1d5a59553003df901f03ba38030c6b05958aec9df6f8c40cac0242f083e8da94365cc4c901616e2a75773d7b2acd00825820717c70bfe8d702f0d152a1115ea51f6adcaa6daf97a8ae8da34fe2b6bcf2541658403ae173a3aeb799535e8ae1146b375effb3dfc7b21c3e9f470cd1399b185d413098df46fbcb42ed546fc7b15bb6566d57a19e34489793a58626d9f24d06ce4e08825820892ed16b87d6bf6f0e16fb653a0ecfca4f2603497e5447cddf68bad2ccc03a195840e66037ab7426fdf092c44a938a21a1e4da970c79b88b0c29a33d42460021b1df17d361754f45aab1e96459932151ae945a0e4c6f5c398103bc4ba1408216f60582", + "5820a9c289702b1c0f52ce8dce84d0b2757eb954ccbbf1fc76f17223459c542ca9d858403ced428cdb16c45a42296c41d1a5c44ef5b38a1636c821cbdee48ca847ad016631b272cf02fb408e0f49ba1747418e147c61c77c60cfd80f67b37110a0f8e709825820b3443a7e565cf2ed4621206dd3970f6cb5d07f9617eb1bf016cc319cc9a07086584062633826381581592aa51eacd16a6b10ac1494b7c25f61679abc542eb4983866c672bbce70071891177a27b786d386c06b75c2ffe3f8432be1ab799995025300825820bc60bd523cfa7efe19093a9cc81f6d0a52e5601c31218cea09e1e1bbddbc817b5840f8071ee2e1e199904a808534ef7c9304d5a2", + "b2be6676e7e4a4365e41ee87b398f395ac0b4f97e53b11f4224b490a6f46eda619e8419d5214f290304980fd4509825820bfd330ef41d6c87e878261036c3f4a841ba84860c15ea320d17f054a1404c9545840cd2d475e1beda466db62a250f92c605a52c7218ba105ab6525f8a600fbed956cb79b2458b7c44df2425f0a45f5c14d45eec9eb7612178033403b4a0a6a28ce07825820c84a4a07b502b91a889252529f3508785c9d869ccfb69400f1492abd7e3f50e35840061d83a3a12d6eaf652b3896d5c4ca49cb631e96c78a75a18e440d3241ba688295280609ecf89a0bb9ef23ec9ff9752a66b87bcc4792ddf77f69d56379c8ea0d825820d1a0466a8d", + "a2ed67161bfd60f0fd9728d66dce301bc130723d0d213b2e0a0f0458403d61f8e51ac3d2a9c9a82387fe51b90d88af7f2237855db84676df750c35d7b2c8b41ce101a01d6d5a78a4beba70144b8bb766653a5588601f619ea6fc0dbf07825820e15636c088c38e02381d78d6b5ff2eacc450c4a0848f4185473fe58e2c5c51a75840186dd0316861ddf153b62b392b47d69e7923279b27b776d509d5d1dbcb01575d4d6e479659861585f35bde0084d791a8a8169bf53987abad03983ce08fee0e01825820e246aa6392958f01fc8fafd5ac1cf5f28ef34af05820b49c98919753a76109c058401c059c88e542ebb45d7cab97b51407f13d910a2fa8d1d25fd5", + "e150fa5a5f35a9013e84cc6b730fbba7c229063a51c7682c869ba0b7de865b5c1708118200d901825820e90d7b0a6cf831b0042d37961dd528842860e77914e715bcece676c75353b8125840d2dda2d4ab5b8b660ab31a7002620246b40e58b0cfa8dea6ff6e406674b2c4070d3f207fc5719fb82449210586ea53bb84e150e1ae297772ad680b0cb20b5a08f6" + ], + "tx_hash": "b3b0701550ed6c6ed595c5d17d5cd06e829ae5440bdedf52e16f377406218026", + "serialized_tx": "83a40095825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e0200825820d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02000181825839115e2f080eb93bad86d401545e0ce5f2221096d6477e11e6643922fa8d4dc0d667c1316ff84e572310e265edb31330448b36b7179e28dd419e1a006ca79302182a030aa10095825820068883fe4a4e30bd75bb034f1bc8317a5ee1df001a2e0e9c2e85c926fc9336d858405aa7e3aa7255946563f08efeed7eccd976ee1ddf3299e00e6080f4fb626bc6128ea73fc1bdb6f38d7cf704f2e6dcd1c37304be84526c1bd4416e1668627597038258200e86bde9a1630540366a17a6edae53ce5f3e0e2895d43ae6606204d2b485251b584039b93c1ed9a7fd0c662676f72ed2b826198a7d83bc81235f64c44a6cd8ac049c5dc287daed9a3a90210dc15283493739b8caaeb5f0260c3e40f38b05c77f540182582010ae189d1d30bef9214e006a0287fc5f2caf56576f04c9d3ef381f0f678561665840640e63049280c1cfa581eaed11f540ac6038a9a1aaa2348ebf7c12a0ecc6ff843b44d89419645c4a18e73e99b379456becebac39d6f5bafa0143367f746e890a82582023cf6064ec4c968e4428c016def464c053c2172c365eaf2c0deae9fc5dd0ba3d5840b6714125d8097420b14bf88519f94579f328ccb8a42bafef07440c7556448baa8b23b312d7702e73204fbd74fe6dcf8296a97af41670d84cc1efced76bca9d0d82582036a8ef21d5b98fdf23a27325cf643deaac35e912c835e35037f23d1061ae5b1658405121839533df7920ce3f6a9c8cd999e25c2a2446842dd7bcc6d773572b43c53cdb52f17c9c0e8b7958ab43c43ad2506b1c6ee37282710984d8be705ec1fb2d0482582047ce718cbbd34f3bc56955c5f8042e0d0567b90a1d09e44df653905f7115c8ef58404ae2348c4c5dcbd60220408e3e6b7872cb3555e44d270391a6fe425f3107728b901fd2b94e823bb60c32fa5116351a998072b27f396f1c54b59615cee8b7560182582054b180a8930475a05ccb03601e67046fbd64cee911600f60ead0227291d2b29b5840b956abbffb8979d2488114093bc978abed0115691a0ac8601c2320d2f1a56bd6c4221107917daf36acbe395f1d784826a4375a57d6044664d36477ee38437a0a8258205d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c158400835682f782fa88eb28378f2e757df887f467fbead67a9f6532c5f621b5004cd66ef3c841be4f1c9ad9bf2b8c1c60b65ef7cb6cbf05e07ca2ca72be73fc23f0082582064d248557635918ed0d48f86a85fa3e937e5e60b4522609e47baf2df3f5d08ce5840b162939928888bdfb029a55f63fb7bfcb7e5c0c9717f1aff9a3fe03783f0ab6aa1768e28154bfccc9e8870c9d6379816a4cdda68360015686a70f8dcb2e3be088258206883f530a417453da0a23db4f62038e82929537e76d298d212008e1481af5ae458408f4af77871fc4334dbdae7c06683cb3eff1d5a59553003df901f03ba38030c6b05958aec9df6f8c40cac0242f083e8da94365cc4c901616e2a75773d7b2acd00825820717c70bfe8d702f0d152a1115ea51f6adcaa6daf97a8ae8da34fe2b6bcf2541658403ae173a3aeb799535e8ae1146b375effb3dfc7b21c3e9f470cd1399b185d413098df46fbcb42ed546fc7b15bb6566d57a19e34489793a58626d9f24d06ce4e08825820892ed16b87d6bf6f0e16fb653a0ecfca4f2603497e5447cddf68bad2ccc03a195840e66037ab7426fdf092c44a938a21a1e4da970c79b88b0c29a33d42460021b1df17d361754f45aab1e96459932151ae945a0e4c6f5c398103bc4ba1408216f605825820a9c289702b1c0f52ce8dce84d0b2757eb954ccbbf1fc76f17223459c542ca9d858403ced428cdb16c45a42296c41d1a5c44ef5b38a1636c821cbdee48ca847ad016631b272cf02fb408e0f49ba1747418e147c61c77c60cfd80f67b37110a0f8e709825820b3443a7e565cf2ed4621206dd3970f6cb5d07f9617eb1bf016cc319cc9a07086584062633826381581592aa51eacd16a6b10ac1494b7c25f61679abc542eb4983866c672bbce70071891177a27b786d386c06b75c2ffe3f8432be1ab799995025300825820bc60bd523cfa7efe19093a9cc81f6d0a52e5601c31218cea09e1e1bbddbc817b5840f8071ee2e1e199904a808534ef7c9304d5a2b2be6676e7e4a4365e41ee87b398f395ac0b4f97e53b11f4224b490a6f46eda619e8419d5214f290304980fd4509825820bfd330ef41d6c87e878261036c3f4a841ba84860c15ea320d17f054a1404c9545840cd2d475e1beda466db62a250f92c605a52c7218ba105ab6525f8a600fbed956cb79b2458b7c44df2425f0a45f5c14d45eec9eb7612178033403b4a0a6a28ce07825820c84a4a07b502b91a889252529f3508785c9d869ccfb69400f1492abd7e3f50e35840061d83a3a12d6eaf652b3896d5c4ca49cb631e96c78a75a18e440d3241ba688295280609ecf89a0bb9ef23ec9ff9752a66b87bcc4792ddf77f69d56379c8ea0d825820d1a0466a8da2ed67161bfd60f0fd9728d66dce301bc130723d0d213b2e0a0f0458403d61f8e51ac3d2a9c9a82387fe51b90d88af7f2237855db84676df750c35d7b2c8b41ce101a01d6d5a78a4beba70144b8bb766653a5588601f619ea6fc0dbf07825820e15636c088c38e02381d78d6b5ff2eacc450c4a0848f4185473fe58e2c5c51a75840186dd0316861ddf153b62b392b47d69e7923279b27b776d509d5d1dbcb01575d4d6e479659861585f35bde0084d791a8a8169bf53987abad03983ce08fee0e01825820e246aa6392958f01fc8fafd5ac1cf5f28ef34af05820b49c98919753a76109c058401c059c88e542ebb45d7cab97b51407f13d910a2fa8d1d25fd5e150fa5a5f35a9013e84cc6b730fbba7c229063a51c7682c869ba0b7de865b5c1708118200d901825820e90d7b0a6cf831b0042d37961dd528842860e77914e715bcece676c75353b8125840d2dda2d4ab5b8b660ab31a7002620246b40e58b0cfa8dea6ff6e406674b2c4070d3f207fc5719fb82449210586ea53bb84e150e1ae297772ad680b0cb20b5a08f6" + } + } + ] +} diff --git a/common/tests/fixtures/cardano/sign_tx.failed.json b/common/tests/fixtures/cardano/sign_tx.failed.json index 7bd8ac1c0..8f96fbd48 100644 --- a/common/tests/fixtures/cardano/sign_tx.failed.json +++ b/common/tests/fixtures/cardano/sign_tx.failed.json @@ -740,114 +740,6 @@ "error_message": "Invalid certificate path" } }, - { - "description": "Too many tokens in output", - "parameters": { - "protocol_magic": 764824073, - "network_id": 1, - "fee": 42, - "ttl": 10, - "certificates": [], - "withdrawals": [], - "metadata": "", - "inputs": [ - { - "path": "m/1852'/1815'/0'/0/0", - "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", - "prev_index": 0 - } - ], - "outputs": [ - { - "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", - "amount": "3003112", - "token_bundle": [ - { - "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", - "tokens": [ - { - "asset_name_bytes": "01aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "7878754" - }, - { - "asset_name_bytes": "02aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "1234" - }, - { - "asset_name_bytes": "03aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "1234" - }, - { - "asset_name_bytes": "04aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "1234" - }, - { - "asset_name_bytes": "05aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "1234" - }, - { - "asset_name_bytes": "06aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "1234" - }, - { - "asset_name_bytes": "07aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "1234" - }, - { - "asset_name_bytes": "08aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "1234" - }, - { - "asset_name_bytes": "09aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "1234" - } - ] - }, - { - "policy_id": "75a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", - "tokens": [ - { - "asset_name_bytes": "10aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "47" - }, - { - "asset_name_bytes": "11aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "47" - }, - { - "asset_name_bytes": "12aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "47" - }, - { - "asset_name_bytes": "13aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "47" - }, - { - "asset_name_bytes": "14aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "47" - }, - { - "asset_name_bytes": "15aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "47" - }, - { - "asset_name_bytes": "16aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "47" - }, - { - "asset_name_bytes": "17aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "amount": "47" - } - ] - } - ] - } - ] - }, - "result": { - "error_message": "Maximum tx output size" - } - }, { "description": "Repeated asset name in multiasset token group", "parameters": { diff --git a/core/src/apps/cardano/sign_tx.py b/core/src/apps/cardano/sign_tx.py index fb85fbcad..f5c948fb6 100644 --- a/core/src/apps/cardano/sign_tx.py +++ b/core/src/apps/cardano/sign_tx.py @@ -4,6 +4,8 @@ from trezor.crypto.curve import ed25519 from trezor.messages import CardanoAddressType, CardanoCertificateType from trezor.messages.CardanoAddressParametersType import CardanoAddressParametersType from trezor.messages.CardanoSignedTx import CardanoSignedTx +from trezor.messages.CardanoSignedTxChunk import CardanoSignedTxChunk +from trezor.messages.CardanoSignedTxChunkAck import CardanoSignedTxChunkAck from apps.common import cbor, safety_checks from apps.common.paths import validate_path @@ -78,13 +80,14 @@ if False: CborizedTokenBundle = Dict[bytes, Dict[bytes, int]] CborizedTxOutput = Tuple[bytes, Union[int, Tuple[int, CborizedTokenBundle]]] - + CborizedSignedTx = Tuple[Dict, Dict, Optional[cbor.Raw]] + TxHash = bytes METADATA_HASH_SIZE = 32 MINTING_POLICY_ID_LENGTH = 28 MAX_METADATA_LENGTH = 500 MAX_ASSET_NAME_LENGTH = 32 -MAX_TX_OUTPUT_SIZE = 512 +MAX_TX_CHUNK_SIZE = 256 @seed.with_keychain @@ -96,44 +99,50 @@ async def sign_tx( validate_network_info(msg.network_id, msg.protocol_magic) - if _has_stake_pool_registration(msg): - return await _sign_stake_pool_registration_tx(ctx, msg, keychain) - else: - return await _sign_standard_tx(ctx, msg, keychain) - - -async def _sign_standard_tx( - ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain -) -> CardanoSignedTx: try: - for i in msg.inputs: - await validate_path( - ctx, keychain, i.address_n, SCHEMA_ADDRESS.match(i.address_n) + if _has_stake_pool_registration(msg): + cborized_tx, tx_hash = await _sign_stake_pool_registration_tx( + ctx, msg, keychain ) + else: + cborized_tx, tx_hash = await _sign_ordinary_tx(ctx, msg, keychain) - _validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id) - _validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id) - _validate_withdrawals(msg.withdrawals) - _validate_metadata(msg.metadata) + signed_tx_chunks = cbor.encode_chunked(cborized_tx, MAX_TX_CHUNK_SIZE) - # display the transaction in UI - await _show_standard_tx(ctx, keychain, msg) + for signed_tx_chunk in signed_tx_chunks: + response = CardanoSignedTxChunk(signed_tx_chunk=signed_tx_chunk) + await ctx.call(response, CardanoSignedTxChunkAck) - # sign the transaction bundle and prepare the result - serialized_tx, tx_hash = _serialize_tx(keychain, msg) - tx = CardanoSignedTx(serialized_tx=serialized_tx, tx_hash=tx_hash) + return CardanoSignedTx(tx_hash=tx_hash, serialized_tx=None) except ValueError as e: if __debug__: log.exception(__name__, e) raise wire.ProcessError("Signing failed") - return tx + +async def _sign_ordinary_tx( + ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain +) -> Tuple[CborizedSignedTx, TxHash]: + for i in msg.inputs: + await validate_path( + ctx, keychain, i.address_n, SCHEMA_ADDRESS.match(i.address_n) + ) + + _validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id) + _validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id) + _validate_withdrawals(msg.withdrawals) + _validate_metadata(msg.metadata) + + # display the transaction in UI + await _show_standard_tx(ctx, keychain, msg) + + return _cborize_signed_tx(keychain, msg) async def _sign_stake_pool_registration_tx( ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain -) -> CardanoSignedTx: +) -> Tuple[CborizedSignedTx, TxHash]: """ We have a separate tx signing flow for stake pool registration because it's a transaction where the witnessable entries (i.e. inputs, withdrawals, etc.) @@ -146,26 +155,16 @@ async def _sign_stake_pool_registration_tx( except the stake pool certificate itself and we provide a witness only to the user's staking key in the list of pool owners. """ - try: - _validate_stake_pool_registration_tx_structure(msg) + _validate_stake_pool_registration_tx_structure(msg) - _ensure_no_signing_inputs(msg.inputs) - _validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id) - _validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id) - _validate_metadata(msg.metadata) + _ensure_no_signing_inputs(msg.inputs) + _validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id) + _validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id) + _validate_metadata(msg.metadata) - await _show_stake_pool_registration_tx(ctx, keychain, msg) + await _show_stake_pool_registration_tx(ctx, keychain, msg) - # sign the transaction bundle and prepare the result - serialized_tx, tx_hash = _serialize_tx(keychain, msg) - tx = CardanoSignedTx(serialized_tx=serialized_tx, tx_hash=tx_hash) - - except ValueError as e: - if __debug__: - log.exception(__name__, e) - raise wire.ProcessError("Signing failed") - - return tx + return _cborize_signed_tx(keychain, msg) def _has_stake_pool_registration(msg: CardanoSignTx) -> bool: @@ -221,7 +220,6 @@ def _validate_outputs( ) _validate_token_bundle(output.token_bundle) - _validate_max_tx_output_size(keychain, output, protocol_magic, network_id) if total_amount > LOVELACE_MAX_SUPPLY: raise wire.ProcessError("Total transaction amount is out of range!") @@ -255,29 +253,6 @@ def _validate_token_bundle(token_bundle: List[CardanoAssetGroupType]) -> None: seen_asset_name_bytes.add(asset_name_bytes) -def _validate_max_tx_output_size( - keychain: seed.Keychain, - output: CardanoTxOutputType, - protocol_magic: int, - network_id: int, -) -> None: - """ - This limitation is a mitigation measure to prevent sending - large (especially change) outputs containing many tokens that Trezor - would not be able to spend reliably given that - currently the full Cardano transaction is held in-memory. - Once Cardano-transaction signing is refactored to be streamed, this - limit can be lifted - """ - cborized_output = _cborize_output(keychain, output, protocol_magic, network_id) - serialized_output = cbor.encode(cborized_output) - - if len(serialized_output) > MAX_TX_OUTPUT_SIZE: - raise wire.ProcessError( - "Maximum tx output size (%s bytes) exceeded!" % MAX_TX_OUTPUT_SIZE - ) - - def _ensure_no_signing_inputs(inputs: List[CardanoTxInputType]) -> None: if any(i.address_n for i in inputs): raise INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS @@ -316,7 +291,9 @@ def _validate_metadata(metadata: Optional[bytes]) -> None: raise INVALID_METADATA -def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, bytes]: +def _cborize_signed_tx( + keychain: seed.Keychain, msg: CardanoSignTx +) -> Tuple[CborizedSignedTx, TxHash]: tx_body = _cborize_tx_body(keychain, msg) tx_hash = _hash_tx_body(tx_body) @@ -333,9 +310,7 @@ def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, b if msg.metadata: metadata = cbor.Raw(bytes(msg.metadata)) - serialized_tx = cbor.encode([tx_body, witnesses, metadata]) - - return serialized_tx, tx_hash + return (tx_body, witnesses, metadata), tx_hash def _cborize_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict: @@ -462,8 +437,13 @@ def _hash_metadata(metadata: bytes) -> bytes: def _hash_tx_body(tx_body: Dict) -> bytes: - tx_body_cbor = cbor.encode(tx_body) - return hashlib.blake2b(data=tx_body_cbor, outlen=32).digest() + tx_body_cbor_chunks = cbor.encode_streamed(tx_body) + + hashfn = hashlib.blake2b(outlen=32) + for chunk in tx_body_cbor_chunks: + hashfn.update(chunk) + + return hashfn.digest() def _cborize_witnesses( diff --git a/core/src/apps/common/cbor.py b/core/src/apps/common/cbor.py index 3db8579ea..d45a88832 100644 --- a/core/src/apps/common/cbor.py +++ b/core/src/apps/common/cbor.py @@ -7,10 +7,10 @@ from micropython import const from trezor import log, utils -from . import readers +from . import readers, writers if False: - from typing import Any, Iterable, List, Tuple, Union + from typing import Any, List, Tuple, Union, Iterator Value = Any CborSequence = Union[List[Value], Tuple[Value, ...]] @@ -55,7 +55,7 @@ def _header(typ: int, l: int) -> bytes: raise NotImplementedError("Length %d not suppported" % l) -def _cbor_encode(value: Value) -> Iterable[bytes]: +def _cbor_encode(value: Value) -> Iterator[bytes]: if isinstance(value, int): if value >= 0: yield _header(_CBOR_UNSIGNED_INT, value) @@ -230,6 +230,47 @@ def encode(value: Value) -> bytes: return b"".join(_cbor_encode(value)) +def encode_streamed(value: Value) -> Iterator[bytes]: + """ + Returns the encoded value as an iterable of the individual + CBOR "chunks", removing the need to reserve a continuous + chunk of memory for the full serialized representation of the value + """ + return _cbor_encode(value) + + +def encode_chunked(value: Value, max_chunk_size: int) -> Iterator[bytes]: + """ + Returns the encoded value as an iterable of chunks of a given size, + removing the need to reserve a continuous chunk of memory for the + full serialized representation of the value. + """ + if max_chunk_size <= 0: + raise ValueError + + chunks = encode_streamed(value) + + chunk_buffer = writers.empty_bytearray(max_chunk_size) + try: + current_chunk_view = utils.BufferReader(next(chunks)) + while True: + num_bytes_to_write = min( + current_chunk_view.remaining_count(), + max_chunk_size - len(chunk_buffer), + ) + chunk_buffer.extend(current_chunk_view.read(num_bytes_to_write)) + + if len(chunk_buffer) >= max_chunk_size: + yield chunk_buffer + chunk_buffer[:] = bytes() + + if current_chunk_view.remaining_count() == 0: + current_chunk_view = utils.BufferReader(next(chunks)) + except StopIteration: + if len(chunk_buffer) > 0: + yield chunk_buffer + + def decode(cbor: bytes) -> Value: r = utils.BufferReader(cbor) res = _cbor_decode(r) diff --git a/core/src/trezor/messages/CardanoSignedTx.py b/core/src/trezor/messages/CardanoSignedTx.py index 3306064ae..081b9b34f 100644 --- a/core/src/trezor/messages/CardanoSignedTx.py +++ b/core/src/trezor/messages/CardanoSignedTx.py @@ -17,7 +17,7 @@ class CardanoSignedTx(p.MessageType): self, *, tx_hash: bytes, - serialized_tx: bytes, + serialized_tx: bytes = None, ) -> None: self.tx_hash = tx_hash self.serialized_tx = serialized_tx @@ -26,5 +26,5 @@ class CardanoSignedTx(p.MessageType): def get_fields(cls) -> Dict: return { 1: ('tx_hash', p.BytesType, p.FLAG_REQUIRED), - 2: ('serialized_tx', p.BytesType, p.FLAG_REQUIRED), + 2: ('serialized_tx', p.BytesType, None), } diff --git a/core/src/trezor/messages/CardanoSignedTxChunk.py b/core/src/trezor/messages/CardanoSignedTxChunk.py new file mode 100644 index 000000000..99ce6e10b --- /dev/null +++ b/core/src/trezor/messages/CardanoSignedTxChunk.py @@ -0,0 +1,27 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List # noqa: F401 + from typing_extensions import Literal # noqa: F401 + except ImportError: + pass + + +class CardanoSignedTxChunk(p.MessageType): + MESSAGE_WIRE_TYPE = 311 + + def __init__( + self, + *, + signed_tx_chunk: bytes, + ) -> None: + self.signed_tx_chunk = signed_tx_chunk + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('signed_tx_chunk', p.BytesType, p.FLAG_REQUIRED), + } diff --git a/core/src/trezor/messages/CardanoSignedTxChunkAck.py b/core/src/trezor/messages/CardanoSignedTxChunkAck.py new file mode 100644 index 000000000..c07e37464 --- /dev/null +++ b/core/src/trezor/messages/CardanoSignedTxChunkAck.py @@ -0,0 +1,14 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List # noqa: F401 + from typing_extensions import Literal # noqa: F401 + except ImportError: + pass + + +class CardanoSignedTxChunkAck(p.MessageType): + MESSAGE_WIRE_TYPE = 312 diff --git a/core/src/trezor/messages/MessageType.py b/core/src/trezor/messages/MessageType.py index c88251150..cdfacef27 100644 --- a/core/src/trezor/messages/MessageType.py +++ b/core/src/trezor/messages/MessageType.py @@ -145,6 +145,8 @@ if not utils.BITCOIN_ONLY: CardanoGetAddress: Literal[307] = 307 CardanoAddress: Literal[308] = 308 CardanoSignedTx: Literal[310] = 310 + CardanoSignedTxChunk: Literal[311] = 311 + CardanoSignedTxChunkAck: Literal[312] = 312 RippleGetAddress: Literal[400] = 400 RippleAddress: Literal[401] = 401 RippleSignTx: Literal[402] = 402 diff --git a/core/tests/test_apps.common.cbor.py b/core/tests/test_apps.common.cbor.py index f41276473..e029cefcb 100644 --- a/core/tests/test_apps.common.cbor.py +++ b/core/tests/test_apps.common.cbor.py @@ -1,3 +1,5 @@ +import math + from common import * from apps.common.cbor import ( @@ -5,6 +7,8 @@ from apps.common.cbor import ( IndefiniteLengthArray, decode, encode, + encode_chunked, + encode_streamed, ) class TestCardanoCbor(unittest.TestCase): @@ -90,5 +94,53 @@ class TestCardanoCbor(unittest.TestCase): self.assertEqual(encode(value_tuple), encoded) self.assertEqual(decode(encoded), val) + def test_encode_streamed(self): + large_dict = {i: i for i in range(100)} + encoded = encode(large_dict) + + encoded_streamed = [ + bytes(item) for item in encode_streamed(large_dict) + ] + + self.assertEqual(b''.join(encoded_streamed), encoded) + + def test_encode_chunked(self): + large_dict = {i: i for i in range(100)} + encoded = encode(large_dict) + + encoded_len = len(encoded) + assert encoded_len == 354 + + arbitrary_encoded_len_factor = 59 + arbitrary_power_of_two = 64 + larger_than_encoded_len = encoded_len + 1 + + for max_chunk_size in [ + 1, + 10, + arbitrary_encoded_len_factor, + arbitrary_power_of_two, + encoded_len, + larger_than_encoded_len + ]: + encoded_chunks = [ + bytes(chunk) for chunk in encode_chunked(large_dict, max_chunk_size) + ] + + expected_number_of_chunks = math.ceil(len(encoded) / max_chunk_size) + self.assertEqual(len(encoded_chunks), expected_number_of_chunks) + + # all chunks except the last should be of chunk_size + for i in range(len(encoded_chunks) - 1): + self.assertEqual(len(encoded_chunks[i]), max_chunk_size) + + # last chunk should contain the remaining bytes or the whole chunk + remaining_bytes = len(encoded) % max_chunk_size + expected_last_chunk_size = remaining_bytes if remaining_bytes > 0 else max_chunk_size + self.assertEqual(len(encoded_chunks[-1]), expected_last_chunk_size) + + self.assertEqual(b''.join(encoded_chunks), encoded) + + if __name__ == '__main__': unittest.main() diff --git a/python/src/trezorlib/cardano.py b/python/src/trezorlib/cardano.py index ae10a2595..89e22a2af 100644 --- a/python/src/trezorlib/cardano.py +++ b/python/src/trezorlib/cardano.py @@ -17,7 +17,7 @@ from ipaddress import ip_address from typing import List, Optional -from . import messages, tools +from . import exceptions, messages, tools from .tools import expect PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42} @@ -370,4 +370,15 @@ def sign_tx( ) ) - return response + result = bytearray() + while isinstance(response, messages.CardanoSignedTxChunk): + result.extend(response.signed_tx_chunk) + response = client.call(messages.CardanoSignedTxChunkAck()) + + if not isinstance(response, messages.CardanoSignedTx): + raise exceptions.TrezorException("Unexpected response") + + if response.serialized_tx is not None: + result.extend(response.serialized_tx) + + return messages.CardanoSignedTx(tx_hash=response.tx_hash, serialized_tx=result) diff --git a/python/src/trezorlib/messages/CardanoSignedTx.py b/python/src/trezorlib/messages/CardanoSignedTx.py index 3f6d29060..dbb8e1be3 100644 --- a/python/src/trezorlib/messages/CardanoSignedTx.py +++ b/python/src/trezorlib/messages/CardanoSignedTx.py @@ -17,7 +17,7 @@ class CardanoSignedTx(p.MessageType): self, *, tx_hash: bytes, - serialized_tx: bytes, + serialized_tx: bytes = None, ) -> None: self.tx_hash = tx_hash self.serialized_tx = serialized_tx @@ -26,5 +26,5 @@ class CardanoSignedTx(p.MessageType): def get_fields(cls) -> Dict: return { 1: ('tx_hash', p.BytesType, p.FLAG_REQUIRED), - 2: ('serialized_tx', p.BytesType, p.FLAG_REQUIRED), + 2: ('serialized_tx', p.BytesType, None), } diff --git a/python/src/trezorlib/messages/CardanoSignedTxChunk.py b/python/src/trezorlib/messages/CardanoSignedTxChunk.py new file mode 100644 index 000000000..c2b683a58 --- /dev/null +++ b/python/src/trezorlib/messages/CardanoSignedTxChunk.py @@ -0,0 +1,27 @@ +# Automatically generated by pb2py +# fmt: off +from .. import protobuf as p + +if __debug__: + try: + from typing import Dict, List # noqa: F401 + from typing_extensions import Literal # noqa: F401 + except ImportError: + pass + + +class CardanoSignedTxChunk(p.MessageType): + MESSAGE_WIRE_TYPE = 311 + + def __init__( + self, + *, + signed_tx_chunk: bytes, + ) -> None: + self.signed_tx_chunk = signed_tx_chunk + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('signed_tx_chunk', p.BytesType, p.FLAG_REQUIRED), + } diff --git a/python/src/trezorlib/messages/CardanoSignedTxChunkAck.py b/python/src/trezorlib/messages/CardanoSignedTxChunkAck.py new file mode 100644 index 000000000..25d4e1224 --- /dev/null +++ b/python/src/trezorlib/messages/CardanoSignedTxChunkAck.py @@ -0,0 +1,14 @@ +# Automatically generated by pb2py +# fmt: off +from .. import protobuf as p + +if __debug__: + try: + from typing import Dict, List # noqa: F401 + from typing_extensions import Literal # noqa: F401 + except ImportError: + pass + + +class CardanoSignedTxChunkAck(p.MessageType): + MESSAGE_WIRE_TYPE = 312 diff --git a/python/src/trezorlib/messages/MessageType.py b/python/src/trezorlib/messages/MessageType.py index 6f7e20982..689ca500b 100644 --- a/python/src/trezorlib/messages/MessageType.py +++ b/python/src/trezorlib/messages/MessageType.py @@ -142,6 +142,8 @@ CardanoPublicKey: Literal[306] = 306 CardanoGetAddress: Literal[307] = 307 CardanoAddress: Literal[308] = 308 CardanoSignedTx: Literal[310] = 310 +CardanoSignedTxChunk: Literal[311] = 311 +CardanoSignedTxChunkAck: Literal[312] = 312 RippleGetAddress: Literal[400] = 400 RippleAddress: Literal[401] = 401 RippleSignTx: Literal[402] = 402 diff --git a/python/src/trezorlib/messages/__init__.py b/python/src/trezorlib/messages/__init__.py index e99f9d51d..5a0f143d4 100644 --- a/python/src/trezorlib/messages/__init__.py +++ b/python/src/trezorlib/messages/__init__.py @@ -35,6 +35,8 @@ from .CardanoPoolRelayParametersType import CardanoPoolRelayParametersType from .CardanoPublicKey import CardanoPublicKey from .CardanoSignTx import CardanoSignTx from .CardanoSignedTx import CardanoSignedTx +from .CardanoSignedTxChunk import CardanoSignedTxChunk +from .CardanoSignedTxChunkAck import CardanoSignedTxChunkAck from .CardanoTokenType import CardanoTokenType from .CardanoTxCertificateType import CardanoTxCertificateType from .CardanoTxInputType import CardanoTxInputType diff --git a/tests/device_tests/cardano/test_sign_tx.py b/tests/device_tests/cardano/test_sign_tx.py index 6d18b07b5..ce0060b6c 100644 --- a/tests/device_tests/cardano/test_sign_tx.py +++ b/tests/device_tests/cardano/test_sign_tx.py @@ -98,6 +98,49 @@ def test_cardano_sign_tx_failed(client, parameters, result): ) +@parametrize_using_common_fixtures("cardano/sign_tx.chunked.json") +def test_cardano_sign_tx_with_multiple_chunks(client, parameters, result): + inputs = [cardano.create_input(i) for i in parameters["inputs"]] + outputs = [cardano.create_output(o) for o in parameters["outputs"]] + certificates = [cardano.create_certificate(c) for c in parameters["certificates"]] + withdrawals = [cardano.create_withdrawal(w) for w in parameters["withdrawals"]] + + input_flow = parameters.get("input_flow", ()) + + expected_responses = [ + messages.PassphraseRequest(), + messages.ButtonRequest(), + messages.ButtonRequest(), + ] + expected_responses += [ + messages.CardanoSignedTxChunk(signed_tx_chunk=bytes.fromhex(signed_tx_chunk)) + for signed_tx_chunk in result["signed_tx_chunks"] + ] + expected_responses += [ + messages.CardanoSignedTx(tx_hash=bytes.fromhex(result["tx_hash"])) + ] + + with client: + client.set_input_flow(_to_device_actions(client, input_flow)) + client.set_expected_responses(expected_responses) + + response = cardano.sign_tx( + client=client, + inputs=inputs, + outputs=outputs, + fee=parameters["fee"], + ttl=parameters.get("ttl"), + validity_interval_start=parameters.get("validity_interval_start"), + certificates=certificates, + withdrawals=withdrawals, + metadata=bytes.fromhex(parameters["metadata"]), + protocol_magic=parameters["protocol_magic"], + network_id=parameters["network_id"], + ) + assert response.tx_hash.hex() == result["tx_hash"] + assert response.serialized_tx.hex() == result["serialized_tx"] + + def _to_device_actions(client, input_flow): if not input_flow: yield diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json index db9db7ec2..3c2799c60 100644 --- a/tests/ui_tests/fixtures.json +++ b/tests/ui_tests/fixtures.json @@ -62,11 +62,11 @@ "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[stake_deregistration_account_larger_than_100]": "ffae5d78193ba3e989f025b67c544ce77ac4fde9d547e4588a9fe6f90354a4c2", "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[testnet_protocol_magic_with_mainnet_network_id]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[testnet_transaction_with_mainnet_output]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", -"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[too_many_tokens_in_output]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[two_owners_with_path]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[unsupported_address_type]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_amount_is_too_large]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", "cardano-test_sign_tx.py::test_cardano_sign_tx_failed[withdrawal_has_non_staking_path]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0", +"cardano-test_sign_tx.py::test_cardano_sign_tx_with_multiple_chunks[large_transaction_to_be_-377cb9ff": "4a46dd8ac42295d853646a55ca1b85023b2235af6155be663b1de10a6c98def2", "test_autolock.py::test_apply_auto_lock_delay": "38c720e0d29b7487060f2a0f8d256a5e5b4f735511e049c43f6ea62e560603ae", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[0]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77", "test_autolock.py::test_apply_auto_lock_delay_out_of_range[1]": "6badfcdd682ecaf16311749ef7a6c07c6a4d0df402427c8dd5a48d476751ed77",