From 1f33ab420da7c99ed71a95ab7c8fee9ec6731b7f Mon Sep 17 00:00:00 2001 From: gabrielkerekes Date: Fri, 23 Jul 2021 14:54:32 +0200 Subject: [PATCH] feat(cardano): update transaction signing for multisig --- common/protob/messages-cardano.proto | 22 +- common/protob/messages.proto | 3 +- .../fixtures/cardano/sign_tx.failed.json | 739 +++++++++++++++++- common/tests/fixtures/cardano/sign_tx.json | 205 ++++- .../cardano/sign_tx.multisig.failed.json | 319 ++++++++ .../fixtures/cardano/sign_tx.multisig.json | 443 +++++++++++ .../fixtures/cardano/sign_tx.slip39.json | 12 +- ...ign_tx_stake_pool_registration.failed.json | 433 +++++++++- .../sign_tx_stake_pool_registration.json | 24 +- core/src/apps/cardano/address.py | 18 + core/src/apps/cardano/certificates.py | 50 +- core/src/apps/cardano/helpers/__init__.py | 4 +- .../cardano/helpers/account_path_check.py | 6 +- core/src/apps/cardano/helpers/utils.py | 33 +- core/src/apps/cardano/layout.py | 147 ++-- core/src/apps/cardano/sign_tx.py | 241 ++++-- core/src/trezor/enums/CardanoTxSigningMode.py | 1 + core/src/trezor/enums/MessageType.py | 1 + core/src/trezor/enums/__init__.py | 2 + core/src/trezor/messages.py | 26 +- core/tests/test_apps.cardano.certificate.py | 357 +++++++++ python/src/trezorlib/cardano.py | 206 +++-- python/src/trezorlib/cli/cardano.py | 7 + python/src/trezorlib/messages.py | 32 +- tests/device_tests/cardano/test_sign_tx.py | 26 +- 25 files changed, 3113 insertions(+), 244 deletions(-) create mode 100644 common/tests/fixtures/cardano/sign_tx.multisig.failed.json create mode 100644 common/tests/fixtures/cardano/sign_tx.multisig.json create mode 100644 core/tests/test_apps.cardano.certificate.py diff --git a/common/protob/messages-cardano.proto b/common/protob/messages-cardano.proto index 654fc8a55..b50588505 100644 --- a/common/protob/messages-cardano.proto +++ b/common/protob/messages-cardano.proto @@ -61,6 +61,7 @@ enum CardanoTxAuxiliaryDataSupplementType { enum CardanoTxSigningMode { ORDINARY_TRANSACTION = 0; POOL_REGISTRATION_AS_OWNER = 1; + MULTISIG_TRANSACTION = 2; } enum CardanoTxWitnessType { @@ -193,6 +194,7 @@ message CardanoSignTxInit { required bool has_auxiliary_data = 10; optional uint64 validity_interval_start = 11; required uint32 witness_requests_count = 12; + required uint32 minting_asset_groups_count = 13; } /** @@ -230,7 +232,8 @@ message CardanoAssetGroup { */ message CardanoToken { required bytes asset_name_bytes = 1; // asset name as bytestring (may be either ascii string or hash) - required uint64 amount = 2; // asset amount + optional uint64 amount = 2; // asset amount + optional sint64 mint_amount = 3; // mint amount (can also be negative in which case the tokens are burnt) } /** @@ -288,9 +291,10 @@ message CardanoPoolParametersType { */ message CardanoTxCertificate { required CardanoCertificateType type = 1; // certificate type - repeated uint32 path = 2; // BIP-32 path to derive (staking) key + repeated uint32 path = 2; // stake credential key path optional bytes pool = 3; // pool hash optional CardanoPoolParametersType pool_parameters = 4; // used for stake pool registration certificate + optional bytes script_hash = 5; // stake credential script hash } /** @@ -298,8 +302,9 @@ message CardanoPoolParametersType { * @next CardanoTxItemAck */ message CardanoTxWithdrawal { - repeated uint32 path = 1; + repeated uint32 path = 1; // stake credential key path required uint64 amount = 2; + optional bytes script_hash = 3; // stake credential script hash } /** @@ -322,6 +327,14 @@ message CardanoTxAuxiliaryData { optional bytes hash = 2; } +/** + * Request: Transaction mint + * @next CardanoTxItemAck + */ +message CardanoTxMint { + required uint32 asset_groups_count = 1; +} + /** * Response: Acknowledgement of the last transaction item received * @next CardanoTxInput @@ -334,6 +347,7 @@ message CardanoTxAuxiliaryData { * @next CardanoTxWithdrawal * @next CardanoTxAuxiliaryData * @next CardanoTxWitnessRequest + * @next CardanoTxMint */ message CardanoTxItemAck { } @@ -442,7 +456,7 @@ message CardanoSignTx { message CardanoTokenType { required bytes asset_name_bytes = 1; // asset name as bytestring (may be either ascii string or hash) - required uint64 amount = 2; // asset amount + required uint64 amount = 2; // asset amount } /** diff --git a/common/protob/messages.proto b/common/protob/messages.proto index b61f2428b..4bfd80f04 100644 --- a/common/protob/messages.proto +++ b/common/protob/messages.proto @@ -264,7 +264,8 @@ enum MessageType { MessageType_CardanoPoolOwner = 328 [(wire_in) = true]; MessageType_CardanoPoolRelayParameters = 329 [(wire_in) = true]; MessageType_CardanoGetNativeScriptHash = 330 [(wire_in) = true]; - MessageType_CardanoNativeScriptHash = 331 [(wire_in) = true, (wire_out) = true]; + MessageType_CardanoNativeScriptHash = 331 [(wire_out) = true]; + MessageType_CardanoTxMint = 332 [(wire_in) = true]; // Ripple MessageType_RippleGetAddress = 400 [(wire_in) = true]; diff --git a/common/tests/fixtures/cardano/sign_tx.failed.json b/common/tests/fixtures/cardano/sign_tx.failed.json index 1980d0a62..4dde96f27 100644 --- a/common/tests/fixtures/cardano/sign_tx.failed.json +++ b/common/tests/fixtures/cardano/sign_tx.failed.json @@ -27,7 +27,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid address" @@ -56,7 +58,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid address" @@ -85,7 +89,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid address" @@ -114,7 +120,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid address" @@ -143,7 +151,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid address" @@ -172,7 +182,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Fee is out of range" @@ -206,7 +218,9 @@ "amount": "1000000" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Total transaction amount is out of range!" @@ -235,7 +249,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Output address network mismatch" @@ -264,7 +280,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Output address network mismatch" @@ -293,7 +311,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid address" @@ -322,7 +342,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid address" @@ -351,7 +373,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid network id/protocol magic combination!" @@ -380,7 +404,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid network id/protocol magic combination!" @@ -409,7 +435,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid address" @@ -441,7 +469,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid output" @@ -475,7 +505,118 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid certificate" + } + }, + { + "description": "Certificate has multisig path", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 0, + "path": "m/1854'/1815'/0'/0/0" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid certificate" + } + }, + { + "description": "Certificate has script hash", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 0, + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid certificate" + } + }, + { + "description": "Certificate has both path and script_hash", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 0, + "path": "m/1852'/1815'/0'/0/0", + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" @@ -510,7 +651,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" @@ -544,7 +687,81 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid withdrawal" + } + }, + { + "description": "Withdrawal has multisig path", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [ + { + "path": "m/1854'/1815'/0'/0/0", + "amount": "1000" + } + ], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid withdrawal" + } + }, + { + "description": "Withdrawal has script hash", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [ + { + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd", + "amount": "1000" + } + ], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid withdrawal" @@ -578,7 +795,46 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid withdrawal" + } + }, + { + "description": "Withdrawal contains both path and script_hash", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [ + { + "path": "m/1852'/1815'/0'/2/0", + "amount": "1000", + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid withdrawal" @@ -616,7 +872,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Duplicate withdrawals" @@ -647,7 +905,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid auxiliary data" @@ -686,7 +946,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid auxiliary data" @@ -727,7 +989,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid auxiliary data" @@ -762,7 +1026,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid change output path" @@ -797,12 +1063,82 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid change output staking path" } }, + { + "description": "Change output with script in payment part", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + }, + { + "addressType": 1, + "scriptPaymentHash": "0d5acbf6a1dfb0c8724e60df314987315ccbf78bb6c0f9b6f3d568fe", + "stakingPath": "m/1852'/1815'/0'/2/0", + "amount": "7120787" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid address parameters" + } + }, + { + "description": "Output with reward address", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "stake1uyfz49rtntfa9h0s98f6s28sg69weemgjhc4e8hm66d5yacalmqha", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid address" + } + }, { "description": "Stake deregistration account larger than 100", "parameters": { @@ -831,7 +1167,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate path" @@ -875,7 +1213,9 @@ ] } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid token bundle in output" @@ -924,12 +1264,323 @@ ] } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid token bundle in output" } }, + { + "description": "Additional witness requests in ORDINARY_TRANSACTION", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid witness request" + } + }, + { + "description": "1854 input path in ORDINARY_TRANSACTION", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1854'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2", + "amount": "3003112" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid witness request" + } + }, + { + "description": "1854 change output path in ORDINARY_TRANSACTION", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "addressType": 0, + "path": "m/1854'/1815'/0'/0/0", + "stakingPath": "m/1852'/1815'/0'/2/0", + "amount": "7120787" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid address parameters" + } + }, + { + "description": "Ordinary transaction with token minting with 1854 path", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "validity_interval_start": 47, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1234", + "token_bundle": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "100" + } + ] + }, + { + "policy_id": "a5a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "100" + } + ] + } + ] + } + ], + "mint": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "100" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-100" + } + ] + }, + { + "policy_id": "a5a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "100" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-100" + } + ] + } + ], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid witness request" + } + }, + { + "description": "Ordinary transaction without token minting but with a 1855 additional witness request", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "validity_interval_start": 47, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1234" + } + ], + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1855'/1815'/0'" + } + ] + }, + "result": { + "error_message": "Invalid witness request" + } + }, + { + "description": "Ordinary transaction with long token minting path", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "validity_interval_start": 47, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "2000000", + "token_bundle": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "7878754" + } + ] + }, + { + "policy_id": "96a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "amount": "1234" + } + ] + } + ] + } + ], + "mint": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-7878754" + } + ] + }, + { + "policy_id": "96a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-1234" + } + ] + } + ], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1855'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid witness request" + } + }, { "description": "Input and change output account mismatch", "parameters": { @@ -959,7 +1610,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid witness request" @@ -993,7 +1646,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid witness request" @@ -1027,7 +1682,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid witness request" @@ -1067,7 +1724,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" @@ -1107,7 +1766,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid withdrawal" @@ -1146,7 +1807,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid withdrawal" @@ -1179,7 +1842,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid witness request" @@ -1209,7 +1874,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid witness request" diff --git a/common/tests/fixtures/cardano/sign_tx.json b/common/tests/fixtures/cardano/sign_tx.json index e4f3e0ed6..e2a22f0fb 100644 --- a/common/tests/fixtures/cardano/sign_tx.json +++ b/common/tests/fixtures/cardano/sign_tx.json @@ -27,7 +27,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6", @@ -69,7 +71,9 @@ "amount": "1000000" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "81b14b7e62972127eb33c0b1198de6430540ad3a98eec621a3194f2baac43a43", @@ -112,7 +116,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "16fe72bb198be423677577e6326f1f648ec5fc11263b072006382d8125a6edda", @@ -164,7 +170,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "7e16a0b47bdfc37abf4ddd3143f7481af07ffe7abd68f752676f5b0b2890d05b", @@ -225,7 +233,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "5ddbb530b8a89e2b08fc91db03950c876c4a9c1c3fb6e628c4cab638b1c97648", @@ -269,7 +279,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "1fc82ce2420c173a0947eaf49af76fcd6f4e400e2bfb5fa152a482ea12dde24b", @@ -313,7 +325,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "abd1b24ac0638251398444ee136110f952738df32a512ce35894f8453d0e8edf", @@ -356,7 +370,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "d1610bb89bece22ed3158738bc1fbb31c6af0685053e2993361e3380f49afad9", @@ -401,7 +417,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "40535fa8f88515f1da008d3cdf544cf9dbf1675c3cb0adb13b74b9293f1b7096", @@ -443,7 +461,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "d3570557b197604109481a80aeb66cd2cfabc57f802ad593bacc12eb658e5d72", @@ -485,7 +505,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "1a3a295908afd8b2afc368071272d6964be6ee0af062bb765aea65ca454dc0c9", @@ -522,7 +544,9 @@ } ], "outputs": [], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "03535791d04fc1b4457fada025f1c1f7778b5c2d7fa580bbac8abd53b85d3255", @@ -569,7 +593,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "439764b5f7e08839881536a3191faeaf111e75d9f00f83b102c5c1c6fa9fcaf9", @@ -617,7 +643,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "3aca1784d151dc75bdbb80fae71bda3f4b26af3f5fd71bd5e9e9bbcdd2b64ad1", @@ -670,7 +698,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "22c67f12e6f6aa0f2f09fd27d472b19c7208ccd7c3af4b09604fd5d462c1de2b", @@ -719,7 +749,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "cc068a25994ef6a90cdab8adfbe302d6f742de9901ba2495dd64a09f2ef951f5", @@ -764,7 +796,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "1875f1d59a53f1cb4c43949867d72bcfd857fa3b64feb88f41b78ddaa1a21cbf", @@ -812,7 +846,9 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "839a587109358e0aa81b8fb3d5fa74665fac303425ec544a4db7f6ba4e882dff", @@ -863,7 +899,9 @@ "amount": "1000000" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "47cf79f20c6c62edb4162b3b232a57afc1bd0b57c7fd8389555276408a004776", @@ -918,7 +956,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "b7269ddc59e4094a6581c653e0d5dc1e553e3a5fb6ffae47d3d094dff1cfe87b", @@ -986,7 +1026,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "0b929def7bd9f44f5602f809bc0f9be30521f6b93d625525cf33b956993bfb22", @@ -1024,7 +1066,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "b621e22f7cb9aac1a70a3362fde88bdfd31fc100e20f3f3c24a7b853536b4f50", @@ -1071,7 +1115,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "00d393f7fc9a8c17b3efccb44dad9d7e15fdaf2d942a3a455b52b5be016066dd", @@ -1120,7 +1166,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "f4b7315ec080d05024d1f7bf6795dd234c6624970d8e272a245702de539feaa2", @@ -1164,7 +1212,9 @@ "amount": "7120787" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "cabc87a76ad8944e8a97a7cbf9c893a77ed7d1bd963c428c3786d663adb7f0dd", @@ -1299,7 +1349,9 @@ "nonce": 22634813 } }, - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "ee0dfef8b97857ebe7aa8935af50e9f8f608ff4054c0c034600750d722d90631", @@ -1335,6 +1387,107 @@ "catalyst_signature": "74f27d877bbb4a5fc4f7c56869905c11f70bad0af3de24b23afaa1d024e750930f434ecc4b73e5d1723c2cb8548e8bf6098ac876487b3a6ed0891cb76994d409" } } + }, + { + "description": "Ordinary transaction with token minting", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "validity_interval_start": 47, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": "m/1852'/1815'/0'/0/0", + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "2000000", + "token_bundle": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "7878754" + } + ] + }, + { + "policy_id": "96a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "amount": "1234" + } + ] + } + ] + } + ], + "mint": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-7878754" + } + ] + }, + { + "policy_id": "96a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-1234" + } + ] + } + ], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1855'/1815'/0'" + } + ] + }, + "result": { + "tx_hash": "042c1d3a6eab693d2ea6b186a88aed038159e7eb581da80464bca7339fb9afe0", + "witnesses": [ + { + "type": 1, + "pub_key": "5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1", + "signature": "ff10637250efa74970675169585720dd5b663c49ecf523ac6214e11a74858f80ec6ef4c86ea66666ec7102fe78c92bcc4e76d50a7bff1fd9660757e94863ba09", + "chain_code": null + }, + { + "type": 1, + "pub_key": "b75258e4f61eb7b313d8554c2fe10673cf214ca2d762bfd53ec3b7846e2ee872", + "signature": "d42665ef7855bfe6898b440476ec8967f8ce786a30865a27e0c091b912b8fd87cad2f7d2f1adeb0e2a7201f2ca020a41f48fb982cb3b7f278dab848192d42e0d", + "chain_code": null + } + ] + } } ] } diff --git a/common/tests/fixtures/cardano/sign_tx.multisig.failed.json b/common/tests/fixtures/cardano/sign_tx.multisig.failed.json new file mode 100644 index 000000000..da5454f00 --- /dev/null +++ b/common/tests/fixtures/cardano/sign_tx.multisig.failed.json @@ -0,0 +1,319 @@ +{ + "setup": { + "mnemonic": "all all all all all all all all all all all all", + "passphrase": "" + }, + "tests": [ + { + "description": "Multisig transaction with stake registration certificate containing a path", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 0, + "path": "m/1852'/1815'/0'/0/0" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid certificate" + } + }, + { + "description": "Multisig transaction with stake deregistration certificate containing a path", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 1, + "path": "m/1852'/1815'/0'/0/0" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + }, + { + "path": "m/1854'/1815'/2'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid certificate" + } + }, + { + "description": "Multisig transaction with stake delegation certificate containing a path", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 2, + "path": "m/1852'/1815'/0'/0/0", + "pool": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid certificate" + } + }, + { + "description": "Multisig transaction with 1852 multisig witness request", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1852'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid witness request" + } + }, + { + "description": "Multisig transaction with output containing address parameters", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "addressType": 0, + "path": "m/1852'/1815'/0'/0/0", + "stakingPath": "m/1852'/1815'/0'/2/0", + "amount": "7120787" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid output" + } + }, + { + "description": "Multisig transaction without minting but with a 1855 additional witness request", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1855'/1815'/0'" + } + ] + }, + "result": { + "error_message": "Invalid witness request" + } + }, + { + "description": "Multisig transaction with long token minting path", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "validity_interval_start": 47, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "2000000", + "token_bundle": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "7878754" + } + ] + }, + { + "policy_id": "96a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "amount": "1234" + } + ] + } + ] + } + ], + "mint": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-7878754" + } + ] + }, + { + "policy_id": "96a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-1234" + } + ] + } + ], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1855'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Invalid witness request" + } + } + ] +} diff --git a/common/tests/fixtures/cardano/sign_tx.multisig.json b/common/tests/fixtures/cardano/sign_tx.multisig.json new file mode 100644 index 000000000..e5bcbe30a --- /dev/null +++ b/common/tests/fixtures/cardano/sign_tx.multisig.json @@ -0,0 +1,443 @@ +{ + "setup": { + "mnemonic": "all all all all all all all all all all all all", + "passphrase": "" + }, + "tests": [ + { + "description": "Multisig transaction with token minting", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "validity_interval_start": 47, + "certificates": [], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "2000000", + "token_bundle": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "7878754" + } + ] + }, + { + "policy_id": "96a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "amount": "1234" + } + ] + } + ] + } + ], + "mint": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-7878754" + } + ] + }, + { + "policy_id": "96a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-1234" + } + ] + } + ], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + }, + { + "path": "m/1855'/1815'/0'" + } + ] + }, + "result": { + "tx_hash": "042c1d3a6eab693d2ea6b186a88aed038159e7eb581da80464bca7339fb9afe0", + "witnesses": [ + { + "type": 1, + "pub_key": "b10be5c0d11ad8292bbe69e220ca0cfbe154610b3041a8e72f9d515c226ab3b1", + "signature": "ef08436c998df4fd4aade2ce240d92d8851783b688a949c167aa070e885ffb592943767ddae0b826265a307405cf9865b6f66fbfa2e5a39797950104b7b13d0d", + "chain_code": null + }, + { + "type": 1, + "pub_key": "b75258e4f61eb7b313d8554c2fe10673cf214ca2d762bfd53ec3b7846e2ee872", + "signature": "d42665ef7855bfe6898b440476ec8967f8ce786a30865a27e0c091b912b8fd87cad2f7d2f1adeb0e2a7201f2ca020a41f48fb982cb3b7f278dab848192d42e0d", + "chain_code": null + } + ] + } + }, + { + "description": "Multisig transaction with stake registration certificate", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 0, + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + } + ] + }, + "result": { + "tx_hash": "ed9fc2755091fa72b58e9dd06db05cce87c0c6f3962f587d5fc348fe478f0752", + "witnesses": [ + { + "type": 1, + "pub_key": "b10be5c0d11ad8292bbe69e220ca0cfbe154610b3041a8e72f9d515c226ab3b1", + "signature": "dccfcce8a2a17673c0e465a60a334eabbe326127d3dd04b727702ea486ed7c231259353c0890cfcb8209169eda7a139aeec42c77ce87231b0b9c250efb64450e", + "chain_code": null + } + ] + } + }, + { + "description": "Multisig transaction with stake registration and stake delegation certificates", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 0, + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + }, + { + "type": 2, + "pool": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973", + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + }, + { + "path": "m/1854'/1815'/0'/2/0" + } + ] + }, + "result": { + "tx_hash": "26fb07b23368898665829283985ffe6c4cb2ec13758e83f467b78e5061f9619b", + "witnesses": [ + { + "type": 1, + "pub_key": "b10be5c0d11ad8292bbe69e220ca0cfbe154610b3041a8e72f9d515c226ab3b1", + "signature": "c3fc7aae0a78b3b888f68775da3b9ba1e5478f2003e8c1f0b558172acd23205f2652e7e021f5041a4a1a785fad4f711ca80a9b39afd2939644d4da47d86f7b05", + "chain_code": null + }, + { + "type": 1, + "pub_key": "f2ef4ecd21ad28a8d270ca7be7e96c87f60dc821e13c0d0c5870344e9693637c", + "signature": "982247b7a3a3625eaae74d4710f0d9a9b4bae6f0e201c31544f056ad3d7e5940e477cedf3f83fa0e37152e5f97585d910296e95395677dee047e204864187f09", + "chain_code": null + } + ] + } + }, + { + "description": "Multisig transaction with stake deregistration", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 1, + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + }, + { + "path": "m/1854'/1815'/0'/2/0" + } + ] + }, + "result": { + "tx_hash": "c4e70484c964eca910219047542632ac9a9ac81f11f5d5afd8bb1b0ef4366d69", + "witnesses": [ + { + "type": 1, + "pub_key": "b10be5c0d11ad8292bbe69e220ca0cfbe154610b3041a8e72f9d515c226ab3b1", + "signature": "059fa17fb8e8302083d110ec4587d6ce80b3bc15baa75e0a2d449df190ce462d0e6ebc67d96f74fa6ce0b149714d1ef24f40c24846fef9d58405c6e2287e540b", + "chain_code": null + }, + { + "type": 1, + "pub_key": "f2ef4ecd21ad28a8d270ca7be7e96c87f60dc821e13c0d0c5870344e9693637c", + "signature": "dc51848d3257f8f6783d6a53736ba638bc62c7098e5ec6d4d2b313520c78c689942f6e2542ba2b6b9749b7a57d4c8658c84fbc5b1e2847159eb0c256298bcd01", + "chain_code": null + } + ] + } + }, + { + "description": "Multisig transaction with stake deregistration and withdrawal", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 1, + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "withdrawals": [ + { + "amount": "1000", + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "auxiliary_data": null, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + }, + { + "path": "m/1854'/1815'/0'/2/0" + } + ] + }, + "result": { + "tx_hash": "e02d252c5cad2a4d8f163069cd7f0822c7876d16af9ad8ac2d461655812b2d1b", + "witnesses": [ + { + "type": 1, + "pub_key": "b10be5c0d11ad8292bbe69e220ca0cfbe154610b3041a8e72f9d515c226ab3b1", + "signature": "882994b27b1886a2f7ae3b42e08f3ce2c9c5b7d82e467135e0069f396a18f89696e882dbeadce0b3af8a10edbfb55057e6909e8232ac0107cc4fbf647493720b", + "chain_code": null + }, + { + "type": 1, + "pub_key": "f2ef4ecd21ad28a8d270ca7be7e96c87f60dc821e13c0d0c5870344e9693637c", + "signature": "cc119eb4e7f27d5c316a5d1301850a2f3e4d08c267d5422cae8e4f00178a55d053a2288ed0a55fc8ec05bd8c1cd5fee5a713da85d489a2a02ac273866e36ae06", + "chain_code": null + } + ] + } + }, + { + "description": "Multisig transaction with most elements filled and shared with Ledger", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "validity_interval_start": 47, + "certificates": [ + { + "type": 0, + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + }, + { + "type": 1, + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + }, + { + "type": 2, + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd", + "pool": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + } + ], + "withdrawals": [ + { + "amount": "1000", + "script_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + } + ], + "auxiliary_data": { + "hash": "58ec01578fcdfdc376f09631a7b2adc608eaf57e3720484c7ff37c13cff90fdf" + }, + "inputs": [ + { + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "2000000", + "token_bundle": [ + { + "policy_id": "0d63e8d2c5a00cbcffbdf9112487c443466e1ea7d8c834df5ac5c425", + "tokens": [ + { + "asset_name_bytes": "74657374436f696e", + "amount": "7878754" + } + ] + } + ] + } + ], + "mint": [ + { + "policy_id": "0d63e8d2c5a00cbcffbdf9112487c443466e1ea7d8c834df5ac5c425", + "tokens": [ + { + "asset_name_bytes": "74657374436f696e", + "mint_amount": "7878754" + }, + { + "asset_name_bytes": "75657374436f696e", + "mint_amount": "-7878754" + } + ] + } + ], + "signing_mode": "MULTISIG_TRANSACTION", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + }, + { + "path": "m/1854'/1815'/0'/2/0" + }, + { + "path": "m/1855'/1815'/0'" + } + ] + }, + "result": { + "tx_hash": "2be64c04ea3f5bac3c224ec47a4157ade91fc6ab4fd6b83ce3d57b2e9186720b", + "witnesses": [ + { + "type": 1, + "pub_key": "b10be5c0d11ad8292bbe69e220ca0cfbe154610b3041a8e72f9d515c226ab3b1", + "signature": "38a56a46b21caef91742ffafdec202ed96809c3070c9bfd51db5c750d77edbfb8514d9cd2255ab5a857dd8a63706ae0ca29e390fba6af7a906b186aed117b809", + "chain_code": null + }, + { + "type": 1, + "pub_key": "f2ef4ecd21ad28a8d270ca7be7e96c87f60dc821e13c0d0c5870344e9693637c", + "signature": "0c9071c421fe207ac1d9102643eac8ddf5ff29238782956b5706b9f1f084dfc5c087b4ceda6d079f8bb6438d3b556d3ac97565a87a8ec33f11856408b0480400", + "chain_code": null + }, + { + "type": 1, + "pub_key": "b75258e4f61eb7b313d8554c2fe10673cf214ca2d762bfd53ec3b7846e2ee872", + "signature": "85bf1bc71c04c72ae8184885b9d5eadd49b2c27bd332a42bc42c35b49429509350795bbdb716a95946b7c30cb62f20e1d39e4be3df5625a141f3e3c2e3526e02", + "chain_code": null + } + ] + } + } + ] +} diff --git a/common/tests/fixtures/cardano/sign_tx.slip39.json b/common/tests/fixtures/cardano/sign_tx.slip39.json index 2879e9069..fcf613f04 100644 --- a/common/tests/fixtures/cardano/sign_tx.slip39.json +++ b/common/tests/fixtures/cardano/sign_tx.slip39.json @@ -31,7 +31,9 @@ "amount": "3003112" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6", @@ -73,7 +75,9 @@ "amount": "1000000" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "4c43ce4c72f145b145ae7add414722735e250d048f61c4585a5becafcbffa6ae", @@ -115,7 +119,9 @@ "amount": "1000000" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "tx_hash": "93a2c3cfb67ef1e4bae167b0f443c3370664bdb9171bc9cd41bad98e5cc049b2", diff --git a/common/tests/fixtures/cardano/sign_tx_stake_pool_registration.failed.json b/common/tests/fixtures/cardano/sign_tx_stake_pool_registration.failed.json index 10529d005..679caae40 100644 --- a/common/tests/fixtures/cardano/sign_tx_stake_pool_registration.failed.json +++ b/common/tests/fixtures/cardano/sign_tx_stake_pool_registration.failed.json @@ -48,7 +48,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" @@ -101,7 +103,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" @@ -151,7 +155,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" @@ -201,7 +207,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" @@ -272,10 +280,12 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { - "error_message": "Stakepool registration transaction cannot contain other certificates nor withdrawals" + "error_message": "Stakepool registration transaction cannot contain other certificates, withdrawals or minting" } }, { @@ -305,7 +315,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" @@ -360,10 +372,12 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { - "error_message": "Stakepool registration transaction cannot contain other certificates nor withdrawals" + "error_message": "Stakepool registration transaction cannot contain other certificates, withdrawals or minting" } }, { @@ -410,7 +424,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "Stakepool registration transaction can only contain staking witnesses" @@ -459,7 +475,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "ProcessError: Invalid address" @@ -508,7 +526,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "ProcessError: Invalid address" @@ -591,7 +611,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid pool owner staking path" @@ -674,11 +696,394 @@ "amount": "1" } ], - "signing_mode": "ORDINARY_TRANSACTION" + "mint": [], + "signing_mode": "ORDINARY_TRANSACTION", + "additional_witness_requests": [] }, "result": { "error_message": "Invalid certificate" } + }, + { + "description": "Sample stake pool registration certificate with 1854 additional witness request", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 3, + "pool_parameters": { + "pool_id": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973", + "vrf_key_hash": "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640", + "pledge": 500000000, + "cost": 340000000, + "margin": { + "numerator": 1, + "denominator": 2 + }, + "reward_account": "stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + "owners": [ + { + "staking_key_path": "m/1852'/1815'/0'/2/0" + }, + { + "staking_key_hash": "3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711" + } + ], + "relays": [ + { + "type": 0, + "ipv4_address": "192.168.0.1", + "ipv6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "port": 1234 + }, + { + "type": 0, + "ipv6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "port": 1234 + }, + { + "type": 0, + "ipv4_address": "192.168.0.1", + "port": 1234 + }, + { + "type": 1, + "host_name": "www.test.test", + "port": 1234 + }, + { + "type": 2, + "host_name": "www.test2.test" + } + ], + "metadata": { + "url": "https://www.test.test", + "hash": "914c57c1f12bbf4a82b12d977d4f274674856a11ed4b9b95bd70f5d41c5064a6" + } + } + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": null, + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [ + { + "path": "m/1854'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Stakepool registration transaction can only contain staking witnesses" + } + }, + { + "description": "Sample stake pool registration certificate with 1855 additional witness request", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 3, + "pool_parameters": { + "pool_id": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973", + "vrf_key_hash": "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640", + "pledge": 500000000, + "cost": 340000000, + "margin": { + "numerator": 1, + "denominator": 2 + }, + "reward_account": "stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + "owners": [ + { + "staking_key_path": "m/1852'/1815'/0'/2/0" + }, + { + "staking_key_hash": "3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711" + } + ], + "relays": [ + { + "type": 0, + "ipv4_address": "192.168.0.1", + "ipv6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "port": 1234 + }, + { + "type": 0, + "ipv6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "port": 1234 + }, + { + "type": 0, + "ipv4_address": "192.168.0.1", + "port": 1234 + }, + { + "type": 1, + "host_name": "www.test.test", + "port": 1234 + }, + { + "type": 2, + "host_name": "www.test2.test" + } + ], + "metadata": { + "url": "https://www.test.test", + "hash": "914c57c1f12bbf4a82b12d977d4f274674856a11ed4b9b95bd70f5d41c5064a6" + } + } + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": null, + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [ + { + "path": "m/1855'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Stakepool registration transaction can only contain staking witnesses" + } + }, + { + "description": "Sample stake pool registration certificate with token minting", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 3, + "pool_parameters": { + "pool_id": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973", + "vrf_key_hash": "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640", + "pledge": 500000000, + "cost": 340000000, + "margin": { + "numerator": 1, + "denominator": 2 + }, + "reward_account": "stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + "owners": [ + { + "staking_key_path": "m/1852'/1815'/0'/2/0" + }, + { + "staking_key_hash": "3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711" + } + ], + "relays": [ + { + "type": 0, + "ipv4_address": "192.168.0.1", + "ipv6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "port": 1234 + }, + { + "type": 0, + "ipv6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "port": 1234 + }, + { + "type": 0, + "ipv4_address": "192.168.0.1", + "port": 1234 + }, + { + "type": 1, + "host_name": "www.test.test", + "port": 1234 + }, + { + "type": 2, + "host_name": "www.test2.test" + } + ], + "metadata": { + "url": "https://www.test.test", + "hash": "914c57c1f12bbf4a82b12d977d4f274674856a11ed4b9b95bd70f5d41c5064a6" + } + } + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": null, + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r", + "amount": "1" + } + ], + "mint": [ + { + "policy_id": "95a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "100" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-100" + } + ] + }, + { + "policy_id": "a5a292ffee938be03e9bae5657982a74e9014eb4960108c9e23a5b39", + "tokens": [ + { + "asset_name_bytes": "74652474436f696e", + "mint_amount": "100" + }, + { + "asset_name_bytes": "75652474436f696e", + "mint_amount": "-100" + } + ] + } + ], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [ + { + "path": "m/1855'/1815'/0'/0/0" + } + ] + }, + "result": { + "error_message": "Stakepool registration transaction cannot contain other certificates, withdrawals or minting" + } + }, + { + "description": "Sample stake pool registration certificate with change output", + "parameters": { + "protocol_magic": 764824073, + "network_id": 1, + "fee": 42, + "ttl": 10, + "certificates": [ + { + "type": 3, + "pool_parameters": { + "pool_id": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973", + "vrf_key_hash": "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640", + "pledge": 500000000, + "cost": 340000000, + "margin": { + "numerator": 1, + "denominator": 2 + }, + "reward_account": "stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + "owners": [ + { + "staking_key_path": "m/1852'/1815'/0'/2/0" + }, + { + "staking_key_hash": "3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711" + } + ], + "relays": [ + { + "type": 0, + "ipv4_address": "192.168.0.1", + "ipv6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "port": 1234 + }, + { + "type": 0, + "ipv6_address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "port": 1234 + }, + { + "type": 0, + "ipv4_address": "192.168.0.1", + "port": 1234 + }, + { + "type": 1, + "host_name": "www.test.test", + "port": 1234 + }, + { + "type": 2, + "host_name": "www.test2.test" + } + ], + "metadata": { + "url": "https://www.test.test", + "hash": "914c57c1f12bbf4a82b12d977d4f274674856a11ed4b9b95bd70f5d41c5064a6" + } + } + } + ], + "withdrawals": [], + "auxiliary_data": null, + "inputs": [ + { + "path": null, + "prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7", + "prev_index": 0 + } + ], + "outputs": [ + { + "addressType": 0, + "path": "m/1852'/1815'/0'/0/0", + "stakingPath": "m/1852'/1815'/0'/2/0", + "amount": "7120787" + } + ], + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] + }, + "result": { + "error_message": "Invalid output" + } } ] } diff --git a/common/tests/fixtures/cardano/sign_tx_stake_pool_registration.json b/common/tests/fixtures/cardano/sign_tx_stake_pool_registration.json index 085326b49..a324262e9 100644 --- a/common/tests/fixtures/cardano/sign_tx_stake_pool_registration.json +++ b/common/tests/fixtures/cardano/sign_tx_stake_pool_registration.json @@ -81,7 +81,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "tx_hash": "e3b9a5657bf62609465a930c8359d774c73944973cfc5a104a0f0ed1e1e8db21", @@ -172,7 +174,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "tx_hash": "c0d944db15446cf05e8db014685414c928d4d9a3e96aea229234be56eeae34c5", @@ -264,7 +268,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "tx_hash": "f3d62758ff2f520e7256e65be9d8165da60c7979a97202c19d625709412411fd", @@ -322,7 +328,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "tx_hash": "504f9214142996e0b7e315103b25d88a4afa3d01dd5be22376921b52b01483c3", @@ -380,7 +388,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "tx_hash": "12921b4f8e77f815e0c8ed97c541fbd5ba38a6d3323f4ff1af0cb934b8ac6b39", @@ -473,7 +483,9 @@ "amount": "1" } ], - "signing_mode": "POOL_REGISTRATION_AS_OWNER" + "mint": [], + "signing_mode": "POOL_REGISTRATION_AS_OWNER", + "additional_witness_requests": [] }, "result": { "tx_hash": "880fafab19a36407e9af300c2905e2f6bc8a8debd8b625005f56994d242ba460", diff --git a/core/src/apps/cardano/address.py b/core/src/apps/cardano/address.py index a4e2d8bcc..26d5d43a3 100644 --- a/core/src/apps/cardano/address.py +++ b/core/src/apps/cardano/address.py @@ -209,6 +209,24 @@ def _validate_script_hash(script_hash: bytes | None) -> None: raise INVALID_ADDRESS_PARAMETERS +def validate_output_address_parameters( + parameters: CardanoAddressParametersType, +) -> None: + validate_address_parameters(parameters) + + if parameters.address_type in ( + CardanoAddressType.BASE_SCRIPT_KEY, + CardanoAddressType.BASE_SCRIPT_SCRIPT, + CardanoAddressType.POINTER_SCRIPT, + CardanoAddressType.ENTERPRISE_SCRIPT, + CardanoAddressType.REWARD, + CardanoAddressType.REWARD_SCRIPT, + ): + # Change outputs with script payment part are forbidden. + # Reward addresses are forbidden as outputs in general, see also validate_output_address + raise INVALID_ADDRESS_PARAMETERS + + def _validate_address_and_get_type( address: str, protocol_magic: int, network_id: int ) -> int: diff --git a/core/src/apps/cardano/certificates.py b/core/src/apps/cardano/certificates.py index e2fe8b542..b4247cd69 100644 --- a/core/src/apps/cardano/certificates.py +++ b/core/src/apps/cardano/certificates.py @@ -13,8 +13,11 @@ from .address import ( ) from .helpers import ADDRESS_KEY_HASH_SIZE, INVALID_CERTIFICATE, LOVELACE_MAX_SUPPLY from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT +from .helpers.utils import validate_stake_credential if False: + from typing import Any + from trezor.messages import ( CardanoPoolMetadataType, CardanoPoolOwner, @@ -56,13 +59,16 @@ def validate_certificate( ): raise INVALID_CERTIFICATE + _validate_certificate_structure(certificate) + if certificate.type in ( CardanoCertificateType.STAKE_DELEGATION, CardanoCertificateType.STAKE_REGISTRATION, CardanoCertificateType.STAKE_DEREGISTRATION, ): - if not SCHEMA_STAKING_ANY_ACCOUNT.match(certificate.path): - raise INVALID_CERTIFICATE + validate_stake_credential( + certificate.path, certificate.script_hash, signing_mode, INVALID_CERTIFICATE + ) if certificate.type == CardanoCertificateType.STAKE_DELEGATION: if not certificate.pool or len(certificate.pool) != POOL_HASH_SIZE: @@ -78,6 +84,25 @@ def validate_certificate( account_path_checker.add_certificate(certificate) +def _validate_certificate_structure(certificate: CardanoTxCertificate) -> None: + path = certificate.path + script_hash = certificate.script_hash + pool = certificate.pool + pool_parameters = certificate.pool_parameters + + fields_to_be_empty: dict[CardanoCertificateType, tuple[Any, ...]] = { + CardanoCertificateType.STAKE_REGISTRATION: (pool, pool_parameters), + CardanoCertificateType.STAKE_DELEGATION: (pool_parameters,), + CardanoCertificateType.STAKE_DEREGISTRATION: (pool, pool_parameters), + CardanoCertificateType.STAKE_POOL_REGISTRATION: (path, script_hash, pool), + } + + if certificate.type not in fields_to_be_empty or any( + fields_to_be_empty[certificate.type] + ): + raise INVALID_CERTIFICATE + + def cborize_certificate( keychain: seed.Keychain, certificate: CardanoTxCertificate ) -> CborSequence: @@ -87,18 +112,35 @@ def cborize_certificate( ): return ( certificate.type, - (0, get_public_key_hash(keychain, certificate.path)), + cborize_certificate_stake_credential( + keychain, certificate.path, certificate.script_hash + ), ) elif certificate.type == CardanoCertificateType.STAKE_DELEGATION: return ( certificate.type, - (0, get_public_key_hash(keychain, certificate.path)), + cborize_certificate_stake_credential( + keychain, certificate.path, certificate.script_hash + ), certificate.pool, ) else: raise INVALID_CERTIFICATE +def cborize_certificate_stake_credential( + keychain: seed.Keychain, path: list[int], script_hash: bytes | None +) -> tuple[int, bytes]: + if path: + return 0, get_public_key_hash(keychain, path) + + if script_hash: + return 1, script_hash + + # should be unreachable unless there's a bug in validation + raise INVALID_CERTIFICATE + + def cborize_initial_pool_registration_certificate_fields( certificate: CardanoTxCertificate, ) -> CborSequence: diff --git a/core/src/apps/cardano/helpers/__init__.py b/core/src/apps/cardano/helpers/__init__.py index 8cb30147f..9b30e0c57 100644 --- a/core/src/apps/cardano/helpers/__init__.py +++ b/core/src/apps/cardano/helpers/__init__.py @@ -3,19 +3,21 @@ from trezor import wire INVALID_ADDRESS = wire.ProcessError("Invalid address") INVALID_ADDRESS_PARAMETERS = wire.ProcessError("Invalid address parameters") NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch") +INVALID_TX_SIGNING_REQUEST = wire.ProcessError("Invalid tx signing request") INVALID_OUTPUT = wire.ProcessError("Invalid output") INVALID_CERTIFICATE = wire.ProcessError("Invalid certificate") INVALID_WITHDRAWAL = wire.ProcessError("Invalid withdrawal") INVALID_TOKEN_BUNDLE_OUTPUT = wire.ProcessError("Invalid token bundle in output") INVALID_AUXILIARY_DATA = wire.ProcessError("Invalid auxiliary data") INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE = wire.ProcessError( - "Stakepool registration transaction cannot contain other certificates nor withdrawals" + "Stakepool registration transaction cannot contain other certificates, withdrawals or minting" ) INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES = wire.ProcessError( "Stakepool registration transaction can only contain staking witnesses" ) INVALID_WITNESS_REQUEST = wire.ProcessError("Invalid witness request") INVALID_NATIVE_SCRIPT = wire.ProcessError("Invalid native script") +INVALID_TOKEN_BUNDLE_MINT = wire.ProcessError("Invalid mint token bundle") LOVELACE_MAX_SUPPLY = 45_000_000_000 * 1_000_000 ADDRESS_KEY_HASH_SIZE = 28 diff --git a/core/src/apps/cardano/helpers/account_path_check.py b/core/src/apps/cardano/helpers/account_path_check.py index eb75e631b..d65bf3150 100644 --- a/core/src/apps/cardano/helpers/account_path_check.py +++ b/core/src/apps/cardano/helpers/account_path_check.py @@ -1,5 +1,5 @@ from ...common.paths import HARDENED -from ..seed import is_byron_path, is_shelley_path +from ..seed import is_byron_path, is_minting_path, is_multisig_path, is_shelley_path from . import ( INVALID_CERTIFICATE, INVALID_OUTPUT, @@ -33,6 +33,10 @@ class AccountPathChecker: self.account_path: object | list[int] = self.UNDEFINED def _add(self, path: list[int], error: wire.ProcessError) -> None: + # multi-sig and minting paths are always shown and thus don't need to be checked + if is_multisig_path(path) or is_minting_path(path): + return + account_path = to_account_path(path) if self.account_path is self.UNDEFINED: self.account_path = account_path diff --git a/core/src/apps/cardano/helpers/utils.py b/core/src/apps/cardano/helpers/utils.py index 1e38f2c06..30dec7616 100644 --- a/core/src/apps/cardano/helpers/utils.py +++ b/core/src/apps/cardano/helpers/utils.py @@ -1,11 +1,17 @@ from trezor.crypto import hashlib +from trezor.enums import CardanoTxSigningMode -from apps.cardano.helpers.paths import ACCOUNT_PATH_INDEX, unharden +from apps.cardano.helpers.paths import ( + ACCOUNT_PATH_INDEX, + SCHEMA_STAKING_ANY_ACCOUNT, + unharden, +) from apps.common.seed import remove_ed25519_prefix -from . import ADDRESS_KEY_HASH_SIZE, bech32 +from . import ADDRESS_KEY_HASH_SIZE, SCRIPT_HASH_SIZE, bech32 if False: + from trezor import wire from .. import seed @@ -79,3 +85,26 @@ def derive_public_key( node = keychain.derive(path) public_key = remove_ed25519_prefix(node.public_key()) return public_key if not extended else public_key + node.chain_code() + + +def validate_stake_credential( + path: list[int], + script_hash: bytes | None, + signing_mode: CardanoTxSigningMode, + error: wire.ProcessError, +) -> None: + if path and script_hash: + raise error + + if path: + if signing_mode != CardanoTxSigningMode.ORDINARY_TRANSACTION: + raise error + if not SCHEMA_STAKING_ANY_ACCOUNT.match(path): + raise error + elif script_hash: + if signing_mode != CardanoTxSigningMode.MULTISIG_TRANSACTION: + raise error + if len(script_hash) != SCRIPT_HASH_SIZE: + raise error + else: + raise error diff --git a/core/src/apps/cardano/layout.py b/core/src/apps/cardano/layout.py index f18465b97..bb092d529 100644 --- a/core/src/apps/cardano/layout.py +++ b/core/src/apps/cardano/layout.py @@ -5,6 +5,7 @@ from trezor.enums import ( CardanoCertificateType, CardanoNativeScriptHashDisplayFormat, CardanoNativeScriptType, + CardanoTxSigningMode, ) from trezor.messages import CardanoAddressParametersType from trezor.strings import format_amount @@ -14,6 +15,7 @@ from trezor.ui.layouts import ( confirm_output, confirm_path_warning, confirm_properties, + confirm_text, show_address, ) @@ -31,6 +33,7 @@ from .helpers.utils import ( format_stake_pool_id, to_account_path, ) +from .seed import is_minting_path, is_multisig_path if False: from trezor import wire @@ -184,6 +187,20 @@ async def show_script_hash( ) +async def show_transaction_signing_mode( + ctx: wire.Context, signing_mode: CardanoTxSigningMode +) -> None: + if signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION: + await confirm_metadata( + ctx, + "confirm_signing_mode", + title="Confirm transaction", + content="Confirming a multisig transaction.", + larger_vspace=True, + br_code=ButtonRequestType.Other, + ) + + async def confirm_sending( ctx: wire.Context, ada_amount: int, @@ -208,6 +225,8 @@ async def confirm_sending( async def confirm_sending_token( ctx: wire.Context, policy_id: bytes, token: CardanoToken ) -> None: + assert token.amount is not None # _validate_token + await confirm_properties( ctx, "confirm_token", @@ -315,6 +334,27 @@ async def show_warning_tx_output_contains_tokens(ctx: wire.Context) -> None: ) +async def confirm_witness_request( + ctx: wire.Context, + witness_path: list[int], +) -> None: + if is_multisig_path(witness_path): + path_title = "multi-sig path" + elif is_minting_path(witness_path): + path_title = "token minting path" + else: + path_title = "path" + + await confirm_text( + ctx, + "confirm_total", + title="Confirm transaction", + data=address_n_to_str(witness_path), + description="Sign transaction with %s:" % path_title, + br_code=ButtonRequestType.Other, + ) + + async def confirm_transaction( ctx: wire.Context, fee: int, @@ -354,13 +394,21 @@ async def confirm_certificate( # in this call assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION - props = [ + props: list[PropertyType] = [ ("Confirm:", CERTIFICATE_TYPE_NAMES[certificate.type]), - ( - "for account %s:" % format_account_number(certificate.path), - address_n_to_str(to_account_path(certificate.path)), - ), ] + + if certificate.path: + props.append( + ( + "for account %s:" % format_account_number(certificate.path), + address_n_to_str(to_account_path(certificate.path)), + ), + ) + else: + assert certificate.script_hash is not None # validate_certificate + props.append(("for script:", format_script_hash(certificate.script_hash))) + if certificate.type == CardanoCertificateType.STAKE_DELEGATION: assert certificate.pool is not None # validate_certificate props.append(("to pool:", format_stake_pool_id(certificate.pool))) @@ -504,18 +552,28 @@ async def confirm_stake_pool_registration_final( async def confirm_withdrawal( ctx: wire.Context, withdrawal: CardanoTxWithdrawal ) -> None: + props: list[PropertyType] = [ + ("Confirm withdrawal", None), + ] + + if withdrawal.path: + props.append( + ( + "for account %s:" % format_account_number(withdrawal.path), + address_n_to_str(to_account_path(withdrawal.path)), + ) + ) + else: + assert withdrawal.script_hash is not None # validate_withdrawal + props.append(("for script:", format_script_hash(withdrawal.script_hash))) + + props.append(("Amount:", format_coin_amount(withdrawal.amount))) + await confirm_properties( ctx, "confirm_withdrawal", title="Confirm transaction", - props=[ - ( - "Confirm withdrawal\nfor account %s:" - % format_account_number(withdrawal.path), - address_n_to_str(to_account_path(withdrawal.path)), - ), - ("Amount:", format_coin_amount(withdrawal.amount)), - ], + props=props, br_code=ButtonRequestType.Other, ) @@ -557,43 +615,40 @@ async def show_auxiliary_data_hash( ) -async def show_warning_address_foreign_staking_key( - ctx: wire.Context, - account_path: list[int], - staking_account_path: list[int], - staking_key_hash: bytes | None, -) -> None: - # TODO: confirm_properties not appropriate here - # instead, presumably, this should be a flow: - # 1. show_warning: Mismatch! continue? - # 2. confirm_blob(mismatched_value) - props: list[PropertyType] = [ - ( - "Stake rights associated with this address do not match your account %s:" - % format_account_number(account_path), - address_n_to_str(account_path), - ) - ] +async def show_warning_tx_contains_mint(ctx: wire.Context) -> None: + await confirm_metadata( + ctx, + "confirm_tokens", + title="Confirm transaction", + content="The transaction contains\nminting or burning of\ntokens.", + larger_vspace=True, + br_code=ButtonRequestType.Other, + ) - if staking_account_path: - props.append( - ( - "Stake account %s:" % format_account_number(staking_account_path), - address_n_to_str(staking_account_path), - ) - ) - else: - assert staking_key_hash is not None # _validate_base_address_staking_info - props.append(("Staking key:", staking_key_hash)) - props.append(("Continue?", None)) + +async def confirm_token_minting( + ctx: wire.Context, policy_id: bytes, token: CardanoToken +) -> None: + assert token.mint_amount is not None # _validate_token + is_minting = token.mint_amount >= 0 await confirm_properties( ctx, - "warning_foreign_stakingkey", - title="Warning", - props=props, - icon=ui.ICON_WRONG, - icon_color=ui.RED, + "confirm_mint", + title="Confirm transaction", + props=[ + ( + "Asset fingerprint:", + format_asset_fingerprint( + policy_id=policy_id, + asset_name_bytes=token.asset_name_bytes, + ), + ), + ( + "Amount %s:" % ("minted" if is_minting else "burned"), + format_amount(token.mint_amount, 0), + ), + ], br_code=ButtonRequestType.Other, ) diff --git a/core/src/apps/cardano/sign_tx.py b/core/src/apps/cardano/sign_tx.py index 3178b153c..b99cbffdd 100644 --- a/core/src/apps/cardano/sign_tx.py +++ b/core/src/apps/cardano/sign_tx.py @@ -23,6 +23,7 @@ from trezor.messages import ( CardanoTxHostAck, CardanoTxInput, CardanoTxItemAck, + CardanoTxMint, CardanoTxOutput, CardanoTxWithdrawal, CardanoTxWitnessRequest, @@ -36,8 +37,8 @@ from .address import ( derive_address_bytes, derive_human_readable_address, get_address_bytes_unsafe, - validate_address_parameters, validate_output_address, + validate_output_address_parameters, ) from .auxiliary_data import ( get_auxiliary_data_hash_and_supplement, @@ -59,8 +60,11 @@ from .helpers import ( INVALID_OUTPUT, INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE, INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES, + INVALID_TOKEN_BUNDLE_MINT, INVALID_TOKEN_BUNDLE_OUTPUT, + INVALID_TX_SIGNING_REQUEST, INVALID_WITHDRAWAL, + INVALID_WITNESS_REQUEST, LOVELACE_MAX_SUPPLY, network_ids, protocol_magics, @@ -73,12 +77,13 @@ from .helpers.paths import ( CHANGE_OUTPUT_PATH_NAME, CHANGE_OUTPUT_STAKING_PATH_NAME, POOL_OWNER_STAKING_PATH_NAME, + SCHEMA_MINT, SCHEMA_PAYMENT, SCHEMA_STAKING, SCHEMA_STAKING_ANY_ACCOUNT, WITNESS_PATH_NAME, ) -from .helpers.utils import derive_public_key, to_account_path +from .helpers.utils import derive_public_key, validate_stake_credential from .layout import ( confirm_certificate, confirm_sending, @@ -87,14 +92,18 @@ from .layout import ( confirm_stake_pool_owner, confirm_stake_pool_parameters, confirm_stake_pool_registration_final, + confirm_token_minting, confirm_transaction, confirm_withdrawal, + confirm_witness_request, show_credentials, + show_transaction_signing_mode, show_warning_path, + show_warning_tx_contains_mint, show_warning_tx_network_unverifiable, show_warning_tx_output_contains_tokens, ) -from .seed import is_byron_path +from .seed import is_byron_path, is_multisig_path, is_shelley_path if False: from typing import Any, Union @@ -113,6 +122,7 @@ TX_BODY_KEY_CERTIFICATES = const(4) TX_BODY_KEY_WITHDRAWALS = const(5) TX_BODY_KEY_AUXILIARY_DATA = const(7) TX_BODY_KEY_VALIDITY_INTERVAL_START = const(8) +TX_BODY_KEY_MINT = const(9) POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT = 10 @@ -123,6 +133,8 @@ async def sign_tx( ) -> CardanoSignTxFinished: is_network_id_verifiable = await _validate_tx_signing_request(ctx, msg) + await show_transaction_signing_mode(ctx, msg.signing_mode) + # inputs, outputs and fee are mandatory fields, count the number of optional fields present tx_body_map_item_count = 3 + sum( ( @@ -131,6 +143,7 @@ async def sign_tx( msg.withdrawals_count > 0, msg.has_auxiliary_data, msg.validity_interval_start is not None, + msg.minting_asset_groups_count > 0, ) ) @@ -152,8 +165,10 @@ async def sign_tx( tx_hash, msg.witness_requests_count, msg.signing_mode, + msg.minting_asset_groups_count > 0, account_path_checker, ) + await ctx.call(response_after_witness_requests, CardanoTxHostAck) await ctx.call(CardanoTxBodyHash(tx_hash=tx_hash), CardanoTxHostAck) @@ -179,6 +194,11 @@ async def _validate_tx_signing_request( await show_warning_tx_network_unverifiable(ctx) elif msg.signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER: _validate_stake_pool_registration_tx_structure(msg) + elif msg.signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION: + if not is_network_id_verifiable: + await show_warning_tx_network_unverifiable(ctx) + else: + raise INVALID_TX_SIGNING_REQUEST return is_network_id_verifiable @@ -236,6 +256,7 @@ async def _process_transaction( keychain, withdrawals_dict, msg.withdrawals_count, + msg.signing_mode, msg.protocol_magic, msg.network_id, account_path_checker, @@ -253,13 +274,23 @@ async def _process_transaction( if msg.validity_interval_start is not None: tx_dict.add(TX_BODY_KEY_VALIDITY_INTERVAL_START, msg.validity_interval_start) + if msg.minting_asset_groups_count > 0: + minting_dict: HashBuilderDict[bytes, HashBuilderDict] = HashBuilderDict( + msg.minting_asset_groups_count + ) + with tx_dict.add(TX_BODY_KEY_MINT, minting_dict): + await _process_minting(ctx, minting_dict) + async def _confirm_transaction( ctx: wire.Context, msg: CardanoSignTxInit, is_network_id_verifiable: bool, ) -> None: - if msg.signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: + if msg.signing_mode in ( + CardanoTxSigningMode.ORDINARY_TRANSACTION, + CardanoTxSigningMode.MULTISIG_TRANSACTION, + ): await confirm_transaction( ctx, msg.fee, @@ -272,6 +303,8 @@ async def _confirm_transaction( await confirm_stake_pool_registration_final( ctx, msg.protocol_magic, msg.ttl, msg.validity_interval_start ) + else: + raise ValueError async def _process_inputs( @@ -313,7 +346,6 @@ async def _process_outputs( ctx, keychain, output, - signing_mode, protocol_magic, network_id, ) @@ -363,7 +395,6 @@ async def _process_asset_groups( asset_group: CardanoAssetGroup = await ctx.call( CardanoTxItemAck(), CardanoAssetGroup ) - asset_group.policy_id = bytes(asset_group.policy_id) _validate_asset_group(asset_group, seen_policy_ids) seen_policy_ids.add(asset_group.policy_id) @@ -391,12 +422,12 @@ async def _process_tokens( seen_asset_name_bytes: set[bytes] = set() for _ in range(tokens_count): token: CardanoToken = await ctx.call(CardanoTxItemAck(), CardanoToken) - token.asset_name_bytes = bytes(token.asset_name_bytes) _validate_token(token, seen_asset_name_bytes) seen_asset_name_bytes.add(token.asset_name_bytes) if should_show_tokens: await confirm_sending_token(ctx, policy_id, token) + assert token.amount is not None # _validate_token tokens_dict.add(token.asset_name_bytes, token.amount) @@ -445,6 +476,7 @@ async def _process_certificates( keychain, pool_owners_list, pool_parameters.owners_count, + protocol_magic, network_id, account_path_checker, ) @@ -467,6 +499,7 @@ async def _process_pool_owners( keychain: seed.Keychain, pool_owners_list: HashBuilderList[bytes], owners_count: int, + protocol_magic: int, network_id: int, account_path_checker: AccountPathChecker, ) -> None: @@ -474,7 +507,7 @@ async def _process_pool_owners( for _ in range(owners_count): owner: CardanoPoolOwner = await ctx.call(CardanoTxItemAck(), CardanoPoolOwner) validate_pool_owner(owner, account_path_checker) - await _show_pool_owner(ctx, keychain, owner, network_id) + await _show_pool_owner(ctx, keychain, owner, protocol_magic, network_id) pool_owners_list.append(cborize_pool_owner(keychain, owner)) @@ -502,6 +535,7 @@ async def _process_withdrawals( keychain: seed.Keychain, withdrawals_dict: HashBuilderDict[bytes, int], withdrawals_count: int, + signing_mode: CardanoTxSigningMode, protocol_magic: int, network_id: int, account_path_checker: AccountPathChecker, @@ -512,18 +546,26 @@ async def _process_withdrawals( # until the CIP with canonical CBOR is finalized storing the seen_withdrawals is the only way we can check for # duplicate withdrawals - seen_withdrawals: set[tuple[int, ...]] = set() + seen_withdrawals: set[tuple[int, ...] | bytes] = set() for _ in range(withdrawals_count): withdrawal: CardanoTxWithdrawal = await ctx.call( CardanoTxItemAck(), CardanoTxWithdrawal ) - _validate_withdrawal(withdrawal, seen_withdrawals, account_path_checker) + _validate_withdrawal( + withdrawal, seen_withdrawals, signing_mode, account_path_checker + ) await confirm_withdrawal(ctx, withdrawal) + reward_address_type = ( + CardanoAddressType.REWARD + if withdrawal.path + else CardanoAddressType.REWARD_SCRIPT + ) reward_address = derive_address_bytes( keychain, CardanoAddressParametersType( - address_type=CardanoAddressType.REWARD, - address_n=withdrawal.path, + address_type=reward_address_type, + address_n_staking=withdrawal.path, + script_staking_hash=withdrawal.script_hash, ), protocol_magic, network_id, @@ -566,25 +608,80 @@ async def _process_auxiliary_data( await ctx.call(auxiliary_data_supplement, CardanoTxHostAck) +async def _process_minting( + ctx: wire.Context, minting_dict: HashBuilderDict[bytes, HashBuilderDict] +) -> None: + """Read, validate and serialize the asset groups of token minting.""" + token_minting: CardanoTxMint = await ctx.call(CardanoTxItemAck(), CardanoTxMint) + + await show_warning_tx_contains_mint(ctx) + + # until the CIP with canonical CBOR is finalized storing the seen_policy_ids is the only way we can check for + # duplicate policy_ids + seen_policy_ids: set[bytes] = set() + for _ in range(token_minting.asset_groups_count): + asset_group: CardanoAssetGroup = await ctx.call( + CardanoTxItemAck(), CardanoAssetGroup + ) + _validate_asset_group(asset_group, seen_policy_ids, is_mint=True) + seen_policy_ids.add(asset_group.policy_id) + + tokens: HashBuilderDict[bytes, int] = HashBuilderDict(asset_group.tokens_count) + with minting_dict.add(asset_group.policy_id, tokens): + await _process_minting_tokens( + ctx, + tokens, + asset_group.policy_id, + asset_group.tokens_count, + ) + + +async def _process_minting_tokens( + ctx: wire.Context, + tokens: HashBuilderDict[bytes, int], + policy_id: bytes, + tokens_count: int, +) -> None: + """Read, validate, confirm and serialize the tokens of an asset group.""" + # until the CIP with canonical CBOR is finalized storing the seen_asset_name_bytes is the only way we can check for + # duplicate tokens + seen_asset_name_bytes: set[bytes] = set() + for _ in range(tokens_count): + token: CardanoToken = await ctx.call(CardanoTxItemAck(), CardanoToken) + _validate_token(token, seen_asset_name_bytes, is_mint=True) + seen_asset_name_bytes.add(token.asset_name_bytes) + await confirm_token_minting(ctx, policy_id, token) + + assert token.mint_amount is not None # _validate_token + tokens.add(token.asset_name_bytes, token.mint_amount) + + async def _process_witness_requests( ctx: wire.Context, keychain: seed.Keychain, tx_hash: bytes, witness_requests_count: int, signing_mode: CardanoTxSigningMode, + transaction_has_token_minting: bool, account_path_checker: AccountPathChecker, ) -> CardanoTxResponseType: response: CardanoTxResponseType = CardanoTxItemAck() + for _ in range(witness_requests_count): witness_request = await ctx.call(response, CardanoTxWitnessRequest) - _validate_witness_request(witness_request, signing_mode, account_path_checker) - await _show_witness(ctx, witness_request.path) - - response = ( - _get_byron_witness(keychain, witness_request.path, tx_hash) - if is_byron_path(witness_request.path) - else _get_shelley_witness(keychain, witness_request.path, tx_hash) + _validate_witness_request( + witness_request, + signing_mode, + transaction_has_token_minting, + account_path_checker, ) + path = witness_request.path + await _show_witness_request(ctx, path, signing_mode) + if is_byron_path(path): + response = _get_byron_witness(keychain, path, tx_hash) + else: + response = _get_shelley_witness(keychain, path, tx_hash) + return response @@ -633,6 +730,7 @@ def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTxInit) -> No msg.certificates_count != 1 or msg.signing_mode != CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER or msg.withdrawals_count != 0 + or msg.minting_asset_groups_count != 0 ): raise INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE @@ -648,7 +746,10 @@ def _validate_output( raise INVALID_OUTPUT if address_parameters := output.address_parameters: - validate_address_parameters(address_parameters) + if signing_mode != CardanoTxSigningMode.ORDINARY_TRANSACTION: + raise INVALID_OUTPUT + + validate_output_address_parameters(address_parameters) _fail_if_strict_and_unusual(address_parameters) elif output.address is not None: validate_output_address(output.address, protocol_magic, network_id) @@ -679,7 +780,6 @@ async def _show_output( ctx: wire.Context, keychain: seed.Keychain, output: CardanoTxOutput, - signing_mode: CardanoTxSigningMode, protocol_magic: int, network_id: int, ) -> None: @@ -710,21 +810,38 @@ async def _show_output( def _validate_asset_group( - asset_group: CardanoAssetGroup, seen_policy_ids: set[bytes] + asset_group: CardanoAssetGroup, seen_policy_ids: set[bytes], is_mint: bool = False ) -> None: + INVALID_TOKEN_BUNDLE = ( + INVALID_TOKEN_BUNDLE_MINT if is_mint else INVALID_TOKEN_BUNDLE_OUTPUT + ) + if len(asset_group.policy_id) != MINTING_POLICY_ID_LENGTH: - raise INVALID_TOKEN_BUNDLE_OUTPUT + raise INVALID_TOKEN_BUNDLE if asset_group.tokens_count == 0: - raise INVALID_TOKEN_BUNDLE_OUTPUT + raise INVALID_TOKEN_BUNDLE if asset_group.policy_id in seen_policy_ids: - raise INVALID_TOKEN_BUNDLE_OUTPUT + raise INVALID_TOKEN_BUNDLE -def _validate_token(token: CardanoToken, seen_asset_name_bytes: set[bytes]) -> None: +def _validate_token( + token: CardanoToken, seen_asset_name_bytes: set[bytes], is_mint: bool = False +) -> None: + INVALID_TOKEN_BUNDLE = ( + INVALID_TOKEN_BUNDLE_MINT if is_mint else INVALID_TOKEN_BUNDLE_OUTPUT + ) + + if is_mint: + if token.mint_amount is None or token.amount is not None: + raise INVALID_TOKEN_BUNDLE + else: + if token.amount is None or token.mint_amount is not None: + raise INVALID_TOKEN_BUNDLE + if len(token.asset_name_bytes) > MAX_ASSET_NAME_LENGTH: - raise INVALID_TOKEN_BUNDLE_OUTPUT + raise INVALID_TOKEN_BUNDLE if token.asset_name_bytes in seen_asset_name_bytes: - raise INVALID_TOKEN_BUNDLE_OUTPUT + raise INVALID_TOKEN_BUNDLE async def _show_certificate( @@ -733,30 +850,38 @@ async def _show_certificate( signing_mode: CardanoTxSigningMode, ) -> None: if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: + assert certificate.path # validate_certificate await _fail_or_warn_if_invalid_path( ctx, SCHEMA_STAKING, certificate.path, CERTIFICATE_PATH_NAME ) await confirm_certificate(ctx, certificate) + elif signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION: + assert certificate.script_hash # validate_certificate + await confirm_certificate(ctx, certificate) elif signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER: await _show_stake_pool_registration_certificate(ctx, certificate) def _validate_withdrawal( withdrawal: CardanoTxWithdrawal, - seen_withdrawals: set[tuple[int, ...]], + seen_withdrawals: set[tuple[int, ...] | bytes], + signing_mode: CardanoTxSigningMode, account_path_checker: AccountPathChecker, ) -> None: - if not SCHEMA_STAKING_ANY_ACCOUNT.match(withdrawal.path): - raise INVALID_WITHDRAWAL + validate_stake_credential( + withdrawal.path, withdrawal.script_hash, signing_mode, INVALID_WITHDRAWAL + ) if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY: raise INVALID_WITHDRAWAL - path_tuple = tuple(withdrawal.path) - if path_tuple in seen_withdrawals: + credential = tuple(withdrawal.path) if withdrawal.path else withdrawal.script_hash + assert credential # validate_stake_credential + + if credential in seen_withdrawals: raise wire.ProcessError("Duplicate withdrawals") else: - seen_withdrawals.add(path_tuple) + seen_withdrawals.add(credential) account_path_checker.add_withdrawal(withdrawal) @@ -818,17 +943,33 @@ async def _show_pool_owner( def _validate_witness_request( witness_request: CardanoTxWitnessRequest, signing_mode: CardanoTxSigningMode, + transaction_has_token_minting: bool, account_path_checker: AccountPathChecker, ) -> None: - # witness path validation happens in _show_witness + # further witness path validation happens in _show_witness_request + is_minting = SCHEMA_MINT.match(witness_request.path) - if signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER: - _ensure_no_payment_witness(witness_request) + if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: + if not ( + is_byron_path(witness_request.path) + or is_shelley_path(witness_request.path) + or is_minting + ): + raise INVALID_WITNESS_REQUEST + if is_minting and not transaction_has_token_minting: + raise INVALID_WITNESS_REQUEST + elif signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER: + _ensure_only_staking_witnesses(witness_request) + elif signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION: + if not is_multisig_path(witness_request.path) and not is_minting: + raise INVALID_WITNESS_REQUEST + if is_minting and not transaction_has_token_minting: + raise INVALID_WITNESS_REQUEST account_path_checker.add_witness_request(witness_request) -def _ensure_no_payment_witness(witness: CardanoTxWitnessRequest) -> None: +def _ensure_only_staking_witnesses(witness: CardanoTxWitnessRequest) -> None: """ 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.) @@ -845,15 +986,27 @@ def _ensure_no_payment_witness(witness: CardanoTxWitnessRequest) -> None: raise INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES -async def _show_witness( +async def _show_witness_request( ctx: wire.Context, witness_path: list[int], + signing_mode: CardanoTxSigningMode, ) -> None: - is_payment = SCHEMA_PAYMENT.match(witness_path) - is_staking = SCHEMA_STAKING.match(witness_path) - - if not is_payment and not is_staking: - await _fail_or_warn_path(ctx, witness_path, WITNESS_PATH_NAME) + if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: + # In an ordinary transaction we only allow payment, staking or minting paths. + # If the path is an unusual payment or staking path, we either fail or show the path to the user + # depending on Trezor's configuration. If it's a minting path, we always show it. + is_payment = SCHEMA_PAYMENT.match(witness_path) + is_staking = SCHEMA_STAKING.match(witness_path) + is_minting = SCHEMA_MINT.match(witness_path) + + if is_minting: + await confirm_witness_request(ctx, witness_path) + elif not is_payment and not is_staking: + await _fail_or_warn_path(ctx, witness_path, WITNESS_PATH_NAME) + elif signing_mode == CardanoTxSigningMode.MULTISIG_TRANSACTION: + await confirm_witness_request(ctx, witness_path) + elif signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER: + await confirm_witness_request(ctx, witness_path) def _is_network_id_verifiable(msg: CardanoSignTxInit) -> bool: diff --git a/core/src/trezor/enums/CardanoTxSigningMode.py b/core/src/trezor/enums/CardanoTxSigningMode.py index 64dd65898..17aa27964 100644 --- a/core/src/trezor/enums/CardanoTxSigningMode.py +++ b/core/src/trezor/enums/CardanoTxSigningMode.py @@ -4,3 +4,4 @@ ORDINARY_TRANSACTION = 0 POOL_REGISTRATION_AS_OWNER = 1 +MULTISIG_TRANSACTION = 2 diff --git a/core/src/trezor/enums/MessageType.py b/core/src/trezor/enums/MessageType.py index 9d2c51037..048f67941 100644 --- a/core/src/trezor/enums/MessageType.py +++ b/core/src/trezor/enums/MessageType.py @@ -152,6 +152,7 @@ if not utils.BITCOIN_ONLY: CardanoPoolRelayParameters = 329 CardanoGetNativeScriptHash = 330 CardanoNativeScriptHash = 331 + CardanoTxMint = 332 RippleGetAddress = 400 RippleAddress = 401 RippleSignTx = 402 diff --git a/core/src/trezor/enums/__init__.py b/core/src/trezor/enums/__init__.py index 7233d968a..e4b86fb12 100644 --- a/core/src/trezor/enums/__init__.py +++ b/core/src/trezor/enums/__init__.py @@ -157,6 +157,7 @@ if TYPE_CHECKING: CardanoPoolRelayParameters = 329 CardanoGetNativeScriptHash = 330 CardanoNativeScriptHash = 331 + CardanoTxMint = 332 RippleGetAddress = 400 RippleAddress = 401 RippleSignTx = 402 @@ -361,6 +362,7 @@ if TYPE_CHECKING: class CardanoTxSigningMode(IntEnum): ORDINARY_TRANSACTION = 0 POOL_REGISTRATION_AS_OWNER = 1 + MULTISIG_TRANSACTION = 2 class CardanoTxWitnessType(IntEnum): BYRON_WITNESS = 0 diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index 86017c6c1..951368bde 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -1222,6 +1222,7 @@ if TYPE_CHECKING: has_auxiliary_data: "bool" validity_interval_start: "int | None" witness_requests_count: "int" + minting_asset_groups_count: "int" def __init__( self, @@ -1236,6 +1237,7 @@ if TYPE_CHECKING: withdrawals_count: "int", has_auxiliary_data: "bool", witness_requests_count: "int", + minting_asset_groups_count: "int", ttl: "int | None" = None, validity_interval_start: "int | None" = None, ) -> None: @@ -1299,13 +1301,15 @@ if TYPE_CHECKING: class CardanoToken(protobuf.MessageType): asset_name_bytes: "bytes" - amount: "int" + amount: "int | None" + mint_amount: "int | None" def __init__( self, *, asset_name_bytes: "bytes", - amount: "int", + amount: "int | None" = None, + mint_amount: "int | None" = None, ) -> None: pass @@ -1404,6 +1408,7 @@ if TYPE_CHECKING: path: "list[int]" pool: "bytes | None" pool_parameters: "CardanoPoolParametersType | None" + script_hash: "bytes | None" def __init__( self, @@ -1412,6 +1417,7 @@ if TYPE_CHECKING: path: "list[int] | None" = None, pool: "bytes | None" = None, pool_parameters: "CardanoPoolParametersType | None" = None, + script_hash: "bytes | None" = None, ) -> None: pass @@ -1422,12 +1428,14 @@ if TYPE_CHECKING: class CardanoTxWithdrawal(protobuf.MessageType): path: "list[int]" amount: "int" + script_hash: "bytes | None" def __init__( self, *, amount: "int", path: "list[int] | None" = None, + script_hash: "bytes | None" = None, ) -> None: pass @@ -1471,6 +1479,20 @@ if TYPE_CHECKING: def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["CardanoTxAuxiliaryData"]: return isinstance(msg, cls) + class CardanoTxMint(protobuf.MessageType): + asset_groups_count: "int" + + def __init__( + self, + *, + asset_groups_count: "int", + ) -> None: + pass + + @classmethod + def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["CardanoTxMint"]: + return isinstance(msg, cls) + class CardanoTxItemAck(protobuf.MessageType): @classmethod diff --git a/core/tests/test_apps.cardano.certificate.py b/core/tests/test_apps.cardano.certificate.py new file mode 100644 index 000000000..3eb257f6b --- /dev/null +++ b/core/tests/test_apps.cardano.certificate.py @@ -0,0 +1,357 @@ +from common import * +from trezor import wire +from trezor.enums import CardanoCertificateType, CardanoTxSigningMode +from trezor.messages import CardanoTxCertificate, CardanoPoolParametersType + +from apps.common.paths import HARDENED + +if not utils.BITCOIN_ONLY: + from apps.cardano.certificates import validate_certificate + from apps.cardano.helpers import protocol_magics, network_ids + from apps.cardano.helpers.account_path_check import AccountPathChecker + + +@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin") +class TestCardanoCertificate(unittest.TestCase): + def test_validate_certificate(self): + valid_test_vectors = [ + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_REGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_REGISTRATION, + script_hash=unhexlify( + "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + ), + ), + CardanoTxSigningMode.MULTISIG_TRANSACTION, + ), + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DELEGATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + pool=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DELEGATION, + script_hash=unhexlify( + "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + ), + pool=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + ), + CardanoTxSigningMode.MULTISIG_TRANSACTION, + ), + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DEREGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DEREGISTRATION, + script_hash=unhexlify( + "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + ), + ), + CardanoTxSigningMode.MULTISIG_TRANSACTION, + ), + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_POOL_REGISTRATION, + pool_parameters=CardanoPoolParametersType( + pool_id=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + vrf_key_hash=unhexlify( + "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640" + ), + pledge=500000000, + cost=340000000, + margin_numerator=1, + margin_denominator=2, + reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + owners_count=1, + relays_count=1, + metadata=None, + ), + ), + CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, + ), + ] + + invalid_test_vectors = [ + # STAKE_REGISTRATION neither path or script_hash is set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_REGISTRATION, + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_REGISTRATION both path and script_hash are set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_REGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + script_hash=unhexlify( + "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + ), + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_REGISTRATION pool is set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_REGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + pool=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_REGISTRATION pool parameters are set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_REGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + pool_parameters=CardanoPoolParametersType( + pool_id=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + vrf_key_hash=unhexlify( + "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640" + ), + pledge=500000000, + cost=340000000, + margin_numerator=1, + margin_denominator=2, + reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + owners_count=1, + relays_count=1, + ), + ), + CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, + ), + # STAKE_DELEGATION neither path or script_hash is set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DELEGATION, + pool=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_DELEGATION both path and script_hash are set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DELEGATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + script_hash=unhexlify( + "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + ), + pool=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_DELEGATION pool parameters are set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DELEGATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + pool=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + pool_parameters=CardanoPoolParametersType( + pool_id=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + vrf_key_hash=unhexlify( + "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640" + ), + pledge=500000000, + cost=340000000, + margin_numerator=1, + margin_denominator=2, + reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + owners_count=1, + relays_count=1, + ), + ), + CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, + ), + # STAKE_DEREGISTRATION neither path or script_hash is set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DEREGISTRATION, + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_DEREGISTRATION both path and script_hash are set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DEREGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + script_hash=unhexlify( + "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + ), + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_DEREGISTRATION pool is set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DEREGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + pool=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_DEREGISTRATION pool parameters are set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_DEREGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + pool_parameters=CardanoPoolParametersType( + pool_id=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + vrf_key_hash=unhexlify( + "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640" + ), + pledge=500000000, + cost=340000000, + margin_numerator=1, + margin_denominator=2, + reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + owners_count=1, + relays_count=1, + ), + ), + CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, + ), + # STAKE_POOL_REGISTRATION pool parameters are not set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_POOL_REGISTRATION, + ), + CardanoTxSigningMode.ORDINARY_TRANSACTION, + ), + # STAKE_POOL_REGISTRATION path is set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_POOL_REGISTRATION, + path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 2, 0], + pool_parameters=CardanoPoolParametersType( + pool_id=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + vrf_key_hash=unhexlify( + "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640" + ), + pledge=500000000, + cost=340000000, + margin_numerator=1, + margin_denominator=2, + reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + owners_count=1, + relays_count=1, + ), + ), + CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, + ), + # STAKE_POOL_REGISTRATION script hash is set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_POOL_REGISTRATION, + script_hash=unhexlify( + "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + ), + pool_parameters=CardanoPoolParametersType( + pool_id=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + vrf_key_hash=unhexlify( + "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640" + ), + pledge=500000000, + cost=340000000, + margin_numerator=1, + margin_denominator=2, + reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + owners_count=1, + relays_count=1, + ), + ), + CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, + ), + # STAKE_POOL_REGISTRATION pool is set + ( + CardanoTxCertificate( + type=CardanoCertificateType.STAKE_POOL_REGISTRATION, + script_hash=unhexlify( + "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd" + ), + pool=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + pool_parameters=CardanoPoolParametersType( + pool_id=unhexlify( + "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973" + ), + vrf_key_hash=unhexlify( + "198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640" + ), + pledge=500000000, + cost=340000000, + margin_numerator=1, + margin_denominator=2, + reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el", + owners_count=1, + relays_count=1, + ), + ), + CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, + ), + ] + + for certificate, signing_mode in valid_test_vectors: + validate_certificate( + certificate, + signing_mode, + protocol_magics.MAINNET, + network_ids.MAINNET, + AccountPathChecker(), + ) + + for certificate, signing_mode in invalid_test_vectors: + with self.assertRaises(wire.ProcessError): + validate_certificate( + certificate, + signing_mode, + protocol_magics.MAINNET, + network_ids.MAINNET, + AccountPathChecker(), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/python/src/trezorlib/cardano.py b/python/src/trezorlib/cardano.py index e046fced0..14eb23411 100644 --- a/python/src/trezorlib/cardano.py +++ b/python/src/trezorlib/cardano.py @@ -24,6 +24,7 @@ from .tools import expect SIGNING_MODE_IDS = { "ORDINARY_TRANSACTION": messages.CardanoTxSigningMode.ORDINARY_TRANSACTION, "POOL_REGISTRATION_AS_OWNER": messages.CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, + "MULTISIG_TRANSACTION": messages.CardanoTxSigningMode.MULTISIG_TRANSACTION, } PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42} @@ -41,9 +42,7 @@ REQUIRED_FIELDS_POOL_PARAMETERS = ( "reward_account", "owners", ) -REQUIRED_FIELDS_WITHDRAWAL = ("path", "amount") REQUIRED_FIELDS_TOKEN_GROUP = ("policy_id", "tokens") -REQUIRED_FIELDS_TOKEN = ("asset_name_bytes", "amount") REQUIRED_FIELDS_CATALYST_REGISTRATION = ( "voting_public_key", "staking_path", @@ -54,6 +53,7 @@ REQUIRED_FIELDS_CATALYST_REGISTRATION = ( INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields" INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY = "The output's token_bundle entry is invalid" +INVALID_MINT_TOKEN_BUNDLE_ENTRY = "The mint token_bundle entry is invalid" InputWithPath = Tuple[messages.CardanoTxInput, List[int]] AssetGroupWithTokens = Tuple[messages.CardanoAssetGroup, List[messages.CardanoToken]] @@ -66,6 +66,9 @@ CertificateItem = Union[ messages.CardanoPoolOwner, messages.CardanoPoolRelayParameters, ] +MintItem = Union[ + messages.CardanoTxMint, messages.CardanoAssetGroup, messages.CardanoToken +] PoolOwnersAndRelays = Tuple[ List[messages.CardanoPoolOwner], List[messages.CardanoPoolRelayParameters] ] @@ -160,10 +163,12 @@ def parse_output(output) -> OutputWithAssetGroups: address = output["address"] if contains_address_type: - address_parameters = _parse_address_parameters(output) + address_parameters = _parse_address_parameters( + output, INCOMPLETE_OUTPUT_ERROR_MESSAGE + ) if "token_bundle" in output: - token_bundle = _parse_token_bundle(output["token_bundle"]) + token_bundle = _parse_token_bundle(output["token_bundle"], is_mint=False) return ( messages.CardanoTxOutput( @@ -176,19 +181,26 @@ def parse_output(output) -> OutputWithAssetGroups: ) -def _parse_token_bundle(token_bundle) -> List[AssetGroupWithTokens]: +def _parse_token_bundle(token_bundle, is_mint: bool) -> List[AssetGroupWithTokens]: + error_message: str + if is_mint: + error_message = INVALID_MINT_TOKEN_BUNDLE_ENTRY + else: + error_message = INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY + result = [] for token_group in token_bundle: if not all(k in token_group for k in REQUIRED_FIELDS_TOKEN_GROUP): - raise ValueError(INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY) + raise ValueError(error_message) - tokens = _parse_tokens(token_group["tokens"]) + tokens = _parse_tokens(token_group["tokens"], is_mint) result.append( ( messages.CardanoAssetGroup( policy_id=bytes.fromhex(token_group["policy_id"]), tokens_count=len(tokens), + is_mint=is_mint, ), tokens, ) @@ -197,16 +209,34 @@ def _parse_token_bundle(token_bundle) -> List[AssetGroupWithTokens]: return result -def _parse_tokens(tokens) -> List[messages.CardanoToken]: +def _parse_tokens(tokens, is_mint: bool) -> List[messages.CardanoToken]: + error_message: str + if is_mint: + error_message = INVALID_MINT_TOKEN_BUNDLE_ENTRY + else: + error_message = INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY + result = [] for token in tokens: - if not all(k in token for k in REQUIRED_FIELDS_TOKEN): - raise ValueError(INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY) + if "asset_name_bytes" not in token: + raise ValueError(error_message) + + mint_amount = None + amount = None + if is_mint: + if "mint_amount" not in token: + raise ValueError(error_message) + mint_amount = int(token["mint_amount"]) + else: + if "amount" not in token: + raise ValueError(error_message) + amount = int(token["amount"]) result.append( messages.CardanoToken( asset_name_bytes=bytes.fromhex(token["asset_name_bytes"]), - amount=int(token["amount"]), + amount=amount, + mint_amount=mint_amount, ) ) @@ -214,12 +244,12 @@ def _parse_tokens(tokens) -> List[messages.CardanoToken]: def _parse_address_parameters( - address_parameters, + address_parameters, error_message: str ) -> messages.CardanoAddressParametersType: if "addressType" not in address_parameters: - raise ValueError(INCOMPLETE_OUTPUT_ERROR_MESSAGE) + raise ValueError(error_message) - path = tools.parse_path(address_parameters.get("path")) + payment_path = tools.parse_path(address_parameters.get("path")) staking_path = tools.parse_path(address_parameters.get("stakingPath")) staking_key_hash_bytes = parse_optional_bytes( address_parameters.get("stakingKeyHash") @@ -233,7 +263,7 @@ def _parse_address_parameters( return create_address_parameters( int(address_parameters["addressType"]), - path, + payment_path, staking_path, staking_key_hash_bytes, address_parameters.get("blockIndex"), @@ -287,11 +317,16 @@ def parse_certificate(certificate) -> CertificateWithPoolOwnersAndRelays: if "pool" not in certificate: raise CERTIFICATE_MISSING_FIELDS_ERROR + path, script_hash = _parse_path_or_script_hash( + certificate, CERTIFICATE_MISSING_FIELDS_ERROR + ) + return ( messages.CardanoTxCertificate( type=certificate_type, - path=tools.parse_path(certificate["path"]), + path=path, pool=bytes.fromhex(certificate["pool"]), + script_hash=script_hash, ), None, ) @@ -299,12 +334,13 @@ def parse_certificate(certificate) -> CertificateWithPoolOwnersAndRelays: messages.CardanoCertificateType.STAKE_REGISTRATION, messages.CardanoCertificateType.STAKE_DEREGISTRATION, ): - if "path" not in certificate: - raise CERTIFICATE_MISSING_FIELDS_ERROR + path, script_hash = _parse_path_or_script_hash( + certificate, CERTIFICATE_MISSING_FIELDS_ERROR + ) + return ( messages.CardanoTxCertificate( - type=certificate_type, - path=tools.parse_path(certificate["path"]), + type=certificate_type, path=path, script_hash=script_hash ), None, ) @@ -356,6 +392,18 @@ def parse_certificate(certificate) -> CertificateWithPoolOwnersAndRelays: raise ValueError("Unknown certificate type") +def _parse_path_or_script_hash( + obj, error: ValueError +) -> Tuple[List[int], Optional[bytes]]: + if "path" not in obj and "script_hash" not in obj: + raise error + + path = tools.parse_path(obj.get("path")) + script_hash = parse_optional_bytes(obj.get("script_hash")) + + return path, script_hash + + def _parse_pool_owner(pool_owner) -> messages.CardanoPoolOwner: if "staking_key_path" in pool_owner: return messages.CardanoPoolOwner( @@ -404,13 +452,21 @@ def _parse_pool_relay(pool_relay) -> messages.CardanoPoolRelayParameters: def parse_withdrawal(withdrawal) -> messages.CardanoTxWithdrawal: - if not all(k in withdrawal for k in REQUIRED_FIELDS_WITHDRAWAL): - raise ValueError("Withdrawal is missing some fields") + WITHDRAWAL_MISSING_FIELDS_ERROR = ValueError( + "The withdrawal is missing some fields" + ) + + if "amount" not in withdrawal: + raise WITHDRAWAL_MISSING_FIELDS_ERROR + + path, script_hash = _parse_path_or_script_hash( + withdrawal, WITHDRAWAL_MISSING_FIELDS_ERROR + ) - path = withdrawal["path"] return messages.CardanoTxWithdrawal( - path=tools.parse_path(path), + path=path, amount=int(withdrawal["amount"]), + script_hash=script_hash, ) @@ -441,7 +497,8 @@ def parse_auxiliary_data(auxiliary_data) -> messages.CardanoTxAuxiliaryData: staking_path=tools.parse_path(catalyst_registration["staking_path"]), nonce=catalyst_registration["nonce"], reward_address_parameters=_parse_address_parameters( - catalyst_registration["reward_address_parameters"] + catalyst_registration["reward_address_parameters"], + AUXILIARY_DATA_MISSING_FIELDS_ERROR, ), ) ) @@ -455,33 +512,62 @@ def parse_auxiliary_data(auxiliary_data) -> messages.CardanoTxAuxiliaryData: ) -def _get_witness_paths( +def parse_mint(mint) -> List[AssetGroupWithTokens]: + return _parse_token_bundle(mint, is_mint=True) + + +def parse_additional_witness_request( + additional_witness_request, +) -> Path: + if "path" not in additional_witness_request: + raise ValueError("Invalid additional witness request") + + return tools.parse_path(additional_witness_request["path"]) + + +def _get_witness_requests( inputs: List[InputWithPath], certificates: List[CertificateWithPoolOwnersAndRelays], withdrawals: List[messages.CardanoTxWithdrawal], -) -> List[Path]: + additional_witness_requests: List[Path], + signing_mode: messages.CardanoTxSigningMode, +) -> List[messages.CardanoTxWitnessRequest]: paths = set() - for _, path in inputs: - if path: - paths.add(tuple(path)) - for certificate, pool_owners_and_relays in certificates: - if certificate.type in ( - messages.CardanoCertificateType.STAKE_DEREGISTRATION, - messages.CardanoCertificateType.STAKE_DELEGATION, - ): - paths.add(tuple(certificate.path)) - elif ( - certificate.type == messages.CardanoCertificateType.STAKE_POOL_REGISTRATION - and pool_owners_and_relays is not None - ): - owners, _ = pool_owners_and_relays - for pool_owner in owners: - if pool_owner.staking_key_path: - paths.add(tuple(pool_owner.staking_key_path)) - for withdrawal in withdrawals: - paths.add(tuple(withdrawal.path)) - return sorted([list(path) for path in paths]) + # don't gather paths from tx elements in MULTISIG_TRANSACTION signing mode + if signing_mode != messages.CardanoTxSigningMode.MULTISIG_TRANSACTION: + for _, path in inputs: + if path: + paths.add(tuple(path)) + for certificate, pool_owners_and_relays in certificates: + if ( + certificate.type + in ( + messages.CardanoCertificateType.STAKE_DEREGISTRATION, + messages.CardanoCertificateType.STAKE_DELEGATION, + ) + and certificate.path + ): + paths.add(tuple(certificate.path)) + elif ( + certificate.type + == messages.CardanoCertificateType.STAKE_POOL_REGISTRATION + and pool_owners_and_relays is not None + ): + owners, _ = pool_owners_and_relays + for pool_owner in owners: + if pool_owner.staking_key_path: + paths.add(tuple(pool_owner.staking_key_path)) + for withdrawal in withdrawals: + if withdrawal.path: + paths.add(tuple(withdrawal.path)) + + # add additional_witness_requests in all cases + for additional_witness_request in additional_witness_requests: + paths.add(tuple(additional_witness_request)) + + sorted_paths = sorted([list(path) for path in paths]) + return [messages.CardanoTxWitnessRequest(path=path) for path in sorted_paths] def _get_input_items(inputs: List[InputWithPath]) -> Iterator[messages.CardanoTxInput]: @@ -508,6 +594,13 @@ def _get_certificate_items( yield from relays +def _get_mint_items(mint: List[AssetGroupWithTokens]) -> Iterator[MintItem]: + yield messages.CardanoTxMint(asset_groups_count=len(mint)) + for asset_group, tokens in mint: + yield asset_group + yield from tokens + + # ====== Client functions ====== # @@ -561,10 +654,14 @@ def sign_tx( protocol_magic: int = PROTOCOL_MAGICS["mainnet"], network_id: int = NETWORK_IDS["mainnet"], auxiliary_data: messages.CardanoTxAuxiliaryData = None, + mint: List[AssetGroupWithTokens] = (), + additional_witness_requests: List[Path] = (), ) -> SignTxResponse: UNEXPECTED_RESPONSE_ERROR = exceptions.TrezorException("Unexpected response") - witness_paths = _get_witness_paths(inputs, certificates, withdrawals) + witness_requests = _get_witness_requests( + inputs, certificates, withdrawals, additional_witness_requests, signing_mode + ) response = client.call( messages.CardanoSignTxInit( @@ -579,7 +676,8 @@ def sign_tx( protocol_magic=protocol_magic, network_id=network_id, has_auxiliary_data=auxiliary_data is not None, - witness_requests_count=len(witness_paths), + minting_asset_groups_count=len(mint), + witness_requests_count=len(witness_requests), ) ) if not isinstance(response, messages.CardanoTxItemAck): @@ -615,9 +713,15 @@ def sign_tx( if not isinstance(response, messages.CardanoTxItemAck): raise UNEXPECTED_RESPONSE_ERROR + if mint: + for mint_item in _get_mint_items(mint): + response = client.call(mint_item) + if not isinstance(response, messages.CardanoTxItemAck): + raise UNEXPECTED_RESPONSE_ERROR + sign_tx_response["witnesses"] = [] - for path in witness_paths: - response = client.call(messages.CardanoTxWitnessRequest(path=path)) + for witness_request in witness_requests: + response = client.call(witness_request) if not isinstance(response, messages.CardanoTxWitnessResponse): raise UNEXPECTED_RESPONSE_ERROR sign_tx_response["witnesses"].append( diff --git a/python/src/trezorlib/cli/cardano.py b/python/src/trezorlib/cli/cardano.py index 8b40de4c7..99b81774f 100644 --- a/python/src/trezorlib/cli/cardano.py +++ b/python/src/trezorlib/cli/cardano.py @@ -66,6 +66,11 @@ def sign_tx(client, file, signing_mode, protocol_magic, network_id, testnet): for withdrawal in transaction.get("withdrawals", ()) ] auxiliary_data = cardano.parse_auxiliary_data(transaction.get("auxiliary_data")) + mint = cardano.parse_mint(transaction.get("mint", ())) + additional_witness_requests = [ + cardano.parse_additional_witness_request(p) + for p in transaction["additional_witness_requests"] + ] sign_tx_response = cardano.sign_tx( client, @@ -80,6 +85,8 @@ def sign_tx(client, file, signing_mode, protocol_magic, network_id, testnet): protocol_magic, network_id, auxiliary_data, + mint, + additional_witness_requests, ) sign_tx_response["tx_hash"] = sign_tx_response["tx_hash"].hex() diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 384ec090d..360241deb 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -178,6 +178,7 @@ class MessageType(IntEnum): CardanoPoolRelayParameters = 329 CardanoGetNativeScriptHash = 330 CardanoNativeScriptHash = 331 + CardanoTxMint = 332 RippleGetAddress = 400 RippleAddress = 401 RippleSignTx = 402 @@ -380,6 +381,7 @@ class CardanoTxAuxiliaryDataSupplementType(IntEnum): class CardanoTxSigningMode(IntEnum): ORDINARY_TRANSACTION = 0 POOL_REGISTRATION_AS_OWNER = 1 + MULTISIG_TRANSACTION = 2 class CardanoTxWitnessType(IntEnum): @@ -2072,6 +2074,7 @@ class CardanoSignTxInit(protobuf.MessageType): 10: protobuf.Field("has_auxiliary_data", "bool", repeated=False, required=True), 11: protobuf.Field("validity_interval_start", "uint64", repeated=False, required=False), 12: protobuf.Field("witness_requests_count", "uint32", repeated=False, required=True), + 13: protobuf.Field("minting_asset_groups_count", "uint32", repeated=False, required=True), } def __init__( @@ -2087,6 +2090,7 @@ class CardanoSignTxInit(protobuf.MessageType): withdrawals_count: "int", has_auxiliary_data: "bool", witness_requests_count: "int", + minting_asset_groups_count: "int", ttl: Optional["int"] = None, validity_interval_start: Optional["int"] = None, ) -> None: @@ -2100,6 +2104,7 @@ class CardanoSignTxInit(protobuf.MessageType): self.withdrawals_count = withdrawals_count self.has_auxiliary_data = has_auxiliary_data self.witness_requests_count = witness_requests_count + self.minting_asset_groups_count = minting_asset_groups_count self.ttl = ttl self.validity_interval_start = validity_interval_start @@ -2165,17 +2170,20 @@ class CardanoToken(protobuf.MessageType): MESSAGE_WIRE_TYPE = 324 FIELDS = { 1: protobuf.Field("asset_name_bytes", "bytes", repeated=False, required=True), - 2: protobuf.Field("amount", "uint64", repeated=False, required=True), + 2: protobuf.Field("amount", "uint64", repeated=False, required=False), + 3: protobuf.Field("mint_amount", "sint64", repeated=False, required=False), } def __init__( self, *, asset_name_bytes: "bytes", - amount: "int", + amount: Optional["int"] = None, + mint_amount: Optional["int"] = None, ) -> None: self.asset_name_bytes = asset_name_bytes self.amount = amount + self.mint_amount = mint_amount class CardanoPoolOwner(protobuf.MessageType): @@ -2292,6 +2300,7 @@ class CardanoTxCertificate(protobuf.MessageType): 2: protobuf.Field("path", "uint32", repeated=True, required=False), 3: protobuf.Field("pool", "bytes", repeated=False, required=False), 4: protobuf.Field("pool_parameters", "CardanoPoolParametersType", repeated=False, required=False), + 5: protobuf.Field("script_hash", "bytes", repeated=False, required=False), } def __init__( @@ -2301,11 +2310,13 @@ class CardanoTxCertificate(protobuf.MessageType): path: Optional[List["int"]] = None, pool: Optional["bytes"] = None, pool_parameters: Optional["CardanoPoolParametersType"] = None, + script_hash: Optional["bytes"] = None, ) -> None: self.path = path if path is not None else [] self.type = type self.pool = pool self.pool_parameters = pool_parameters + self.script_hash = script_hash class CardanoTxWithdrawal(protobuf.MessageType): @@ -2313,6 +2324,7 @@ class CardanoTxWithdrawal(protobuf.MessageType): FIELDS = { 1: protobuf.Field("path", "uint32", repeated=True, required=False), 2: protobuf.Field("amount", "uint64", repeated=False, required=True), + 3: protobuf.Field("script_hash", "bytes", repeated=False, required=False), } def __init__( @@ -2320,9 +2332,11 @@ class CardanoTxWithdrawal(protobuf.MessageType): *, amount: "int", path: Optional[List["int"]] = None, + script_hash: Optional["bytes"] = None, ) -> None: self.path = path if path is not None else [] self.amount = amount + self.script_hash = script_hash class CardanoCatalystRegistrationParametersType(protobuf.MessageType): @@ -2365,6 +2379,20 @@ class CardanoTxAuxiliaryData(protobuf.MessageType): self.hash = hash +class CardanoTxMint(protobuf.MessageType): + MESSAGE_WIRE_TYPE = 332 + FIELDS = { + 1: protobuf.Field("asset_groups_count", "uint32", repeated=False, required=True), + } + + def __init__( + self, + *, + asset_groups_count: "int", + ) -> None: + self.asset_groups_count = asset_groups_count + + class CardanoTxItemAck(protobuf.MessageType): MESSAGE_WIRE_TYPE = 313 diff --git a/tests/device_tests/cardano/test_sign_tx.py b/tests/device_tests/cardano/test_sign_tx.py index d2c5590f0..e73d4cde4 100644 --- a/tests/device_tests/cardano/test_sign_tx.py +++ b/tests/device_tests/cardano/test_sign_tx.py @@ -31,6 +31,7 @@ pytestmark = [ @parametrize_using_common_fixtures( "cardano/sign_tx_stake_pool_registration.json", "cardano/sign_tx.json", + "cardano/sign_tx.multisig.json", "cardano/sign_tx.slip39.json", ) def test_cardano_sign_tx(client, parameters, result): @@ -40,6 +41,11 @@ def test_cardano_sign_tx(client, parameters, result): certificates = [cardano.parse_certificate(c) for c in parameters["certificates"]] withdrawals = [cardano.parse_withdrawal(w) for w in parameters["withdrawals"]] auxiliary_data = cardano.parse_auxiliary_data(parameters["auxiliary_data"]) + mint = cardano.parse_mint(parameters["mint"]) + additional_witness_requests = [ + cardano.parse_additional_witness_request(p) + for p in parameters["additional_witness_requests"] + ] if parameters.get("security_checks") == "prompt": device.apply_settings( @@ -62,12 +68,16 @@ def test_cardano_sign_tx(client, parameters, result): protocol_magic=parameters["protocol_magic"], network_id=parameters["network_id"], auxiliary_data=auxiliary_data, + mint=mint, + additional_witness_requests=additional_witness_requests, ) assert response == _transform_expected_result(result) @parametrize_using_common_fixtures( - "cardano/sign_tx.failed.json", "cardano/sign_tx_stake_pool_registration.failed.json" + "cardano/sign_tx.failed.json", + "cardano/sign_tx.multisig.failed.json", + "cardano/sign_tx_stake_pool_registration.failed.json", ) def test_cardano_sign_tx_failed(client, parameters, result): signing_mode = messages.CardanoTxSigningMode.__members__[parameters["signing_mode"]] @@ -76,6 +86,18 @@ def test_cardano_sign_tx_failed(client, parameters, result): certificates = [cardano.parse_certificate(c) for c in parameters["certificates"]] withdrawals = [cardano.parse_withdrawal(w) for w in parameters["withdrawals"]] auxiliary_data = cardano.parse_auxiliary_data(parameters["auxiliary_data"]) + mint = cardano.parse_mint(parameters["mint"]) + additional_witness_requests = [ + cardano.parse_additional_witness_request(p) + for p in parameters["additional_witness_requests"] + ] + + if parameters.get("security_checks") == "prompt": + device.apply_settings( + client, safety_checks=messages.SafetyCheckLevel.PromptTemporarily + ) + else: + device.apply_settings(client, safety_checks=messages.SafetyCheckLevel.Strict) with client: with pytest.raises(TrezorFailure, match=result["error_message"]): @@ -92,6 +114,8 @@ def test_cardano_sign_tx_failed(client, parameters, result): protocol_magic=parameters["protocol_magic"], network_id=parameters["network_id"], auxiliary_data=auxiliary_data, + mint=mint, + additional_witness_requests=additional_witness_requests, )