1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-28 17:18:29 +00:00

feat(cardano): update transaction signing for multisig

This commit is contained in:
gabrielkerekes 2021-07-23 14:54:32 +02:00 committed by matejcik
parent 21281d7cf4
commit 1f33ab420d
25 changed files with 3112 additions and 243 deletions

View File

@ -61,6 +61,7 @@ enum CardanoTxAuxiliaryDataSupplementType {
enum CardanoTxSigningMode { enum CardanoTxSigningMode {
ORDINARY_TRANSACTION = 0; ORDINARY_TRANSACTION = 0;
POOL_REGISTRATION_AS_OWNER = 1; POOL_REGISTRATION_AS_OWNER = 1;
MULTISIG_TRANSACTION = 2;
} }
enum CardanoTxWitnessType { enum CardanoTxWitnessType {
@ -193,6 +194,7 @@ message CardanoSignTxInit {
required bool has_auxiliary_data = 10; required bool has_auxiliary_data = 10;
optional uint64 validity_interval_start = 11; optional uint64 validity_interval_start = 11;
required uint32 witness_requests_count = 12; required uint32 witness_requests_count = 12;
required uint32 minting_asset_groups_count = 13;
} }
/** /**
@ -230,7 +232,8 @@ message CardanoAssetGroup {
*/ */
message CardanoToken { message CardanoToken {
required bytes asset_name_bytes = 1; // asset name as bytestring (may be either ascii string or hash) 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 { message CardanoTxCertificate {
required CardanoCertificateType type = 1; // certificate type 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 bytes pool = 3; // pool hash
optional CardanoPoolParametersType pool_parameters = 4; // used for stake pool registration certificate 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 * @next CardanoTxItemAck
*/ */
message CardanoTxWithdrawal { message CardanoTxWithdrawal {
repeated uint32 path = 1; repeated uint32 path = 1; // stake credential key path
required uint64 amount = 2; required uint64 amount = 2;
optional bytes script_hash = 3; // stake credential script hash
} }
/** /**
@ -322,6 +327,14 @@ message CardanoTxAuxiliaryData {
optional bytes hash = 2; 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 * Response: Acknowledgement of the last transaction item received
* @next CardanoTxInput * @next CardanoTxInput
@ -334,6 +347,7 @@ message CardanoTxAuxiliaryData {
* @next CardanoTxWithdrawal * @next CardanoTxWithdrawal
* @next CardanoTxAuxiliaryData * @next CardanoTxAuxiliaryData
* @next CardanoTxWitnessRequest * @next CardanoTxWitnessRequest
* @next CardanoTxMint
*/ */
message CardanoTxItemAck { message CardanoTxItemAck {
} }
@ -442,7 +456,7 @@ message CardanoSignTx {
message CardanoTokenType { message CardanoTokenType {
required bytes asset_name_bytes = 1; // asset name as bytestring (may be either ascii string or hash) 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
} }
/** /**

View File

@ -264,7 +264,8 @@ enum MessageType {
MessageType_CardanoPoolOwner = 328 [(wire_in) = true]; MessageType_CardanoPoolOwner = 328 [(wire_in) = true];
MessageType_CardanoPoolRelayParameters = 329 [(wire_in) = true]; MessageType_CardanoPoolRelayParameters = 329 [(wire_in) = true];
MessageType_CardanoGetNativeScriptHash = 330 [(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 // Ripple
MessageType_RippleGetAddress = 400 [(wire_in) = true]; MessageType_RippleGetAddress = 400 [(wire_in) = true];

View File

@ -27,7 +27,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid address" "error_message": "Invalid address"
@ -56,7 +58,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid address" "error_message": "Invalid address"
@ -85,7 +89,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid address" "error_message": "Invalid address"
@ -114,7 +120,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid address" "error_message": "Invalid address"
@ -143,7 +151,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid address" "error_message": "Invalid address"
@ -172,7 +182,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Fee is out of range" "error_message": "Fee is out of range"
@ -206,7 +218,9 @@
"amount": "1000000" "amount": "1000000"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Total transaction amount is out of range!" "error_message": "Total transaction amount is out of range!"
@ -235,7 +249,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Output address network mismatch" "error_message": "Output address network mismatch"
@ -264,7 +280,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Output address network mismatch" "error_message": "Output address network mismatch"
@ -293,7 +311,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid address" "error_message": "Invalid address"
@ -322,7 +342,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid address" "error_message": "Invalid address"
@ -351,7 +373,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid network id/protocol magic combination!" "error_message": "Invalid network id/protocol magic combination!"
@ -380,7 +404,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid network id/protocol magic combination!" "error_message": "Invalid network id/protocol magic combination!"
@ -409,7 +435,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid address" "error_message": "Invalid address"
@ -441,7 +469,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid output" "error_message": "Invalid output"
@ -475,7 +505,118 @@
"amount": "3003112" "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": { "result": {
"error_message": "Invalid certificate" "error_message": "Invalid certificate"
@ -510,7 +651,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate" "error_message": "Invalid certificate"
@ -544,7 +687,81 @@
"amount": "3003112" "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": { "result": {
"error_message": "Invalid withdrawal" "error_message": "Invalid withdrawal"
@ -578,7 +795,46 @@
"amount": "3003112" "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": { "result": {
"error_message": "Invalid withdrawal" "error_message": "Invalid withdrawal"
@ -616,7 +872,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Duplicate withdrawals" "error_message": "Duplicate withdrawals"
@ -647,7 +905,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid auxiliary data" "error_message": "Invalid auxiliary data"
@ -686,7 +946,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid auxiliary data" "error_message": "Invalid auxiliary data"
@ -727,7 +989,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid auxiliary data" "error_message": "Invalid auxiliary data"
@ -762,7 +1026,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid change output path" "error_message": "Invalid change output path"
@ -797,12 +1063,82 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid change output staking path" "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", "description": "Stake deregistration account larger than 100",
"parameters": { "parameters": {
@ -831,7 +1167,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate path" "error_message": "Invalid certificate path"
@ -875,7 +1213,9 @@
] ]
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid token bundle in output" "error_message": "Invalid token bundle in output"
@ -924,12 +1264,323 @@
] ]
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid token bundle in output" "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", "description": "Input and change output account mismatch",
"parameters": { "parameters": {
@ -959,7 +1610,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid witness request" "error_message": "Invalid witness request"
@ -993,7 +1646,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid witness request" "error_message": "Invalid witness request"
@ -1027,7 +1682,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid witness request" "error_message": "Invalid witness request"
@ -1067,7 +1724,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate" "error_message": "Invalid certificate"
@ -1107,7 +1766,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid withdrawal" "error_message": "Invalid withdrawal"
@ -1146,7 +1807,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid withdrawal" "error_message": "Invalid withdrawal"
@ -1179,7 +1842,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid witness request" "error_message": "Invalid witness request"
@ -1209,7 +1874,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid witness request" "error_message": "Invalid witness request"

View File

@ -27,7 +27,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6", "tx_hash": "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
@ -69,7 +71,9 @@
"amount": "1000000" "amount": "1000000"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "81b14b7e62972127eb33c0b1198de6430540ad3a98eec621a3194f2baac43a43", "tx_hash": "81b14b7e62972127eb33c0b1198de6430540ad3a98eec621a3194f2baac43a43",
@ -112,7 +116,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "16fe72bb198be423677577e6326f1f648ec5fc11263b072006382d8125a6edda", "tx_hash": "16fe72bb198be423677577e6326f1f648ec5fc11263b072006382d8125a6edda",
@ -164,7 +170,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "7e16a0b47bdfc37abf4ddd3143f7481af07ffe7abd68f752676f5b0b2890d05b", "tx_hash": "7e16a0b47bdfc37abf4ddd3143f7481af07ffe7abd68f752676f5b0b2890d05b",
@ -225,7 +233,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "5ddbb530b8a89e2b08fc91db03950c876c4a9c1c3fb6e628c4cab638b1c97648", "tx_hash": "5ddbb530b8a89e2b08fc91db03950c876c4a9c1c3fb6e628c4cab638b1c97648",
@ -269,7 +279,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "1fc82ce2420c173a0947eaf49af76fcd6f4e400e2bfb5fa152a482ea12dde24b", "tx_hash": "1fc82ce2420c173a0947eaf49af76fcd6f4e400e2bfb5fa152a482ea12dde24b",
@ -313,7 +325,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "abd1b24ac0638251398444ee136110f952738df32a512ce35894f8453d0e8edf", "tx_hash": "abd1b24ac0638251398444ee136110f952738df32a512ce35894f8453d0e8edf",
@ -356,7 +370,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "d1610bb89bece22ed3158738bc1fbb31c6af0685053e2993361e3380f49afad9", "tx_hash": "d1610bb89bece22ed3158738bc1fbb31c6af0685053e2993361e3380f49afad9",
@ -401,7 +417,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "40535fa8f88515f1da008d3cdf544cf9dbf1675c3cb0adb13b74b9293f1b7096", "tx_hash": "40535fa8f88515f1da008d3cdf544cf9dbf1675c3cb0adb13b74b9293f1b7096",
@ -443,7 +461,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "d3570557b197604109481a80aeb66cd2cfabc57f802ad593bacc12eb658e5d72", "tx_hash": "d3570557b197604109481a80aeb66cd2cfabc57f802ad593bacc12eb658e5d72",
@ -485,7 +505,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "1a3a295908afd8b2afc368071272d6964be6ee0af062bb765aea65ca454dc0c9", "tx_hash": "1a3a295908afd8b2afc368071272d6964be6ee0af062bb765aea65ca454dc0c9",
@ -522,7 +544,9 @@
} }
], ],
"outputs": [], "outputs": [],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "03535791d04fc1b4457fada025f1c1f7778b5c2d7fa580bbac8abd53b85d3255", "tx_hash": "03535791d04fc1b4457fada025f1c1f7778b5c2d7fa580bbac8abd53b85d3255",
@ -569,7 +593,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "439764b5f7e08839881536a3191faeaf111e75d9f00f83b102c5c1c6fa9fcaf9", "tx_hash": "439764b5f7e08839881536a3191faeaf111e75d9f00f83b102c5c1c6fa9fcaf9",
@ -617,7 +643,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "3aca1784d151dc75bdbb80fae71bda3f4b26af3f5fd71bd5e9e9bbcdd2b64ad1", "tx_hash": "3aca1784d151dc75bdbb80fae71bda3f4b26af3f5fd71bd5e9e9bbcdd2b64ad1",
@ -670,7 +698,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "22c67f12e6f6aa0f2f09fd27d472b19c7208ccd7c3af4b09604fd5d462c1de2b", "tx_hash": "22c67f12e6f6aa0f2f09fd27d472b19c7208ccd7c3af4b09604fd5d462c1de2b",
@ -719,7 +749,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "cc068a25994ef6a90cdab8adfbe302d6f742de9901ba2495dd64a09f2ef951f5", "tx_hash": "cc068a25994ef6a90cdab8adfbe302d6f742de9901ba2495dd64a09f2ef951f5",
@ -764,7 +796,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "1875f1d59a53f1cb4c43949867d72bcfd857fa3b64feb88f41b78ddaa1a21cbf", "tx_hash": "1875f1d59a53f1cb4c43949867d72bcfd857fa3b64feb88f41b78ddaa1a21cbf",
@ -812,7 +846,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "839a587109358e0aa81b8fb3d5fa74665fac303425ec544a4db7f6ba4e882dff", "tx_hash": "839a587109358e0aa81b8fb3d5fa74665fac303425ec544a4db7f6ba4e882dff",
@ -863,7 +899,9 @@
"amount": "1000000" "amount": "1000000"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "47cf79f20c6c62edb4162b3b232a57afc1bd0b57c7fd8389555276408a004776", "tx_hash": "47cf79f20c6c62edb4162b3b232a57afc1bd0b57c7fd8389555276408a004776",
@ -918,7 +956,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "b7269ddc59e4094a6581c653e0d5dc1e553e3a5fb6ffae47d3d094dff1cfe87b", "tx_hash": "b7269ddc59e4094a6581c653e0d5dc1e553e3a5fb6ffae47d3d094dff1cfe87b",
@ -986,7 +1026,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "0b929def7bd9f44f5602f809bc0f9be30521f6b93d625525cf33b956993bfb22", "tx_hash": "0b929def7bd9f44f5602f809bc0f9be30521f6b93d625525cf33b956993bfb22",
@ -1024,7 +1066,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "b621e22f7cb9aac1a70a3362fde88bdfd31fc100e20f3f3c24a7b853536b4f50", "tx_hash": "b621e22f7cb9aac1a70a3362fde88bdfd31fc100e20f3f3c24a7b853536b4f50",
@ -1071,7 +1115,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "00d393f7fc9a8c17b3efccb44dad9d7e15fdaf2d942a3a455b52b5be016066dd", "tx_hash": "00d393f7fc9a8c17b3efccb44dad9d7e15fdaf2d942a3a455b52b5be016066dd",
@ -1120,7 +1166,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "f4b7315ec080d05024d1f7bf6795dd234c6624970d8e272a245702de539feaa2", "tx_hash": "f4b7315ec080d05024d1f7bf6795dd234c6624970d8e272a245702de539feaa2",
@ -1164,7 +1212,9 @@
"amount": "7120787" "amount": "7120787"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "cabc87a76ad8944e8a97a7cbf9c893a77ed7d1bd963c428c3786d663adb7f0dd", "tx_hash": "cabc87a76ad8944e8a97a7cbf9c893a77ed7d1bd963c428c3786d663adb7f0dd",
@ -1299,7 +1349,9 @@
"nonce": 22634813 "nonce": 22634813
} }
}, },
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "ee0dfef8b97857ebe7aa8935af50e9f8f608ff4054c0c034600750d722d90631", "tx_hash": "ee0dfef8b97857ebe7aa8935af50e9f8f608ff4054c0c034600750d722d90631",
@ -1335,6 +1387,107 @@
"catalyst_signature": "74f27d877bbb4a5fc4f7c56869905c11f70bad0af3de24b23afaa1d024e750930f434ecc4b73e5d1723c2cb8548e8bf6098ac876487b3a6ed0891cb76994d409" "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
}
]
}
} }
] ]
} }

View File

@ -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"
}
}
]
}

View File

@ -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
}
]
}
}
]
}

View File

@ -31,7 +31,9 @@
"amount": "3003112" "amount": "3003112"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6", "tx_hash": "73e09bdebf98a9e0f17f86a2d11e0f14f4f8dae77cdf26ff1678e821f20c8db6",
@ -73,7 +75,9 @@
"amount": "1000000" "amount": "1000000"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "4c43ce4c72f145b145ae7add414722735e250d048f61c4585a5becafcbffa6ae", "tx_hash": "4c43ce4c72f145b145ae7add414722735e250d048f61c4585a5becafcbffa6ae",
@ -115,7 +119,9 @@
"amount": "1000000" "amount": "1000000"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "93a2c3cfb67ef1e4bae167b0f443c3370664bdb9171bc9cd41bad98e5cc049b2", "tx_hash": "93a2c3cfb67ef1e4bae167b0f443c3370664bdb9171bc9cd41bad98e5cc049b2",

View File

@ -48,7 +48,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate" "error_message": "Invalid certificate"
@ -101,7 +103,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate" "error_message": "Invalid certificate"
@ -151,7 +155,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate" "error_message": "Invalid certificate"
@ -201,7 +207,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate" "error_message": "Invalid certificate"
@ -272,10 +280,12 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "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" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate" "error_message": "Invalid certificate"
@ -360,10 +372,12 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "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" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Stakepool registration transaction can only contain staking witnesses" "error_message": "Stakepool registration transaction can only contain staking witnesses"
@ -459,7 +475,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "ProcessError: Invalid address" "error_message": "ProcessError: Invalid address"
@ -508,7 +526,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "ProcessError: Invalid address" "error_message": "ProcessError: Invalid address"
@ -591,7 +611,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid pool owner staking path" "error_message": "Invalid pool owner staking path"
@ -674,11 +696,394 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "ORDINARY_TRANSACTION" "mint": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": []
}, },
"result": { "result": {
"error_message": "Invalid certificate" "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"
}
} }
] ]
} }

View File

@ -81,7 +81,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "e3b9a5657bf62609465a930c8359d774c73944973cfc5a104a0f0ed1e1e8db21", "tx_hash": "e3b9a5657bf62609465a930c8359d774c73944973cfc5a104a0f0ed1e1e8db21",
@ -172,7 +174,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "c0d944db15446cf05e8db014685414c928d4d9a3e96aea229234be56eeae34c5", "tx_hash": "c0d944db15446cf05e8db014685414c928d4d9a3e96aea229234be56eeae34c5",
@ -264,7 +268,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "f3d62758ff2f520e7256e65be9d8165da60c7979a97202c19d625709412411fd", "tx_hash": "f3d62758ff2f520e7256e65be9d8165da60c7979a97202c19d625709412411fd",
@ -322,7 +328,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "504f9214142996e0b7e315103b25d88a4afa3d01dd5be22376921b52b01483c3", "tx_hash": "504f9214142996e0b7e315103b25d88a4afa3d01dd5be22376921b52b01483c3",
@ -380,7 +388,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "12921b4f8e77f815e0c8ed97c541fbd5ba38a6d3323f4ff1af0cb934b8ac6b39", "tx_hash": "12921b4f8e77f815e0c8ed97c541fbd5ba38a6d3323f4ff1af0cb934b8ac6b39",
@ -473,7 +483,9 @@
"amount": "1" "amount": "1"
} }
], ],
"signing_mode": "POOL_REGISTRATION_AS_OWNER" "mint": [],
"signing_mode": "POOL_REGISTRATION_AS_OWNER",
"additional_witness_requests": []
}, },
"result": { "result": {
"tx_hash": "880fafab19a36407e9af300c2905e2f6bc8a8debd8b625005f56994d242ba460", "tx_hash": "880fafab19a36407e9af300c2905e2f6bc8a8debd8b625005f56994d242ba460",

View File

@ -209,6 +209,24 @@ def _validate_script_hash(script_hash: bytes | None) -> None:
raise INVALID_ADDRESS_PARAMETERS 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( def _validate_address_and_get_type(
address: str, protocol_magic: int, network_id: int address: str, protocol_magic: int, network_id: int
) -> int: ) -> int:

View File

@ -13,8 +13,11 @@ from .address import (
) )
from .helpers import ADDRESS_KEY_HASH_SIZE, INVALID_CERTIFICATE, LOVELACE_MAX_SUPPLY from .helpers import ADDRESS_KEY_HASH_SIZE, INVALID_CERTIFICATE, LOVELACE_MAX_SUPPLY
from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT
from .helpers.utils import validate_stake_credential
if False: if False:
from typing import Any
from trezor.messages import ( from trezor.messages import (
CardanoPoolMetadataType, CardanoPoolMetadataType,
CardanoPoolOwner, CardanoPoolOwner,
@ -56,13 +59,16 @@ def validate_certificate(
): ):
raise INVALID_CERTIFICATE raise INVALID_CERTIFICATE
_validate_certificate_structure(certificate)
if certificate.type in ( if certificate.type in (
CardanoCertificateType.STAKE_DELEGATION, CardanoCertificateType.STAKE_DELEGATION,
CardanoCertificateType.STAKE_REGISTRATION, CardanoCertificateType.STAKE_REGISTRATION,
CardanoCertificateType.STAKE_DEREGISTRATION, CardanoCertificateType.STAKE_DEREGISTRATION,
): ):
if not SCHEMA_STAKING_ANY_ACCOUNT.match(certificate.path): validate_stake_credential(
raise INVALID_CERTIFICATE certificate.path, certificate.script_hash, signing_mode, INVALID_CERTIFICATE
)
if certificate.type == CardanoCertificateType.STAKE_DELEGATION: if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
if not certificate.pool or len(certificate.pool) != POOL_HASH_SIZE: if not certificate.pool or len(certificate.pool) != POOL_HASH_SIZE:
@ -78,6 +84,25 @@ def validate_certificate(
account_path_checker.add_certificate(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( def cborize_certificate(
keychain: seed.Keychain, certificate: CardanoTxCertificate keychain: seed.Keychain, certificate: CardanoTxCertificate
) -> CborSequence: ) -> CborSequence:
@ -87,18 +112,35 @@ def cborize_certificate(
): ):
return ( return (
certificate.type, 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: elif certificate.type == CardanoCertificateType.STAKE_DELEGATION:
return ( return (
certificate.type, certificate.type,
(0, get_public_key_hash(keychain, certificate.path)), cborize_certificate_stake_credential(
keychain, certificate.path, certificate.script_hash
),
certificate.pool, certificate.pool,
) )
else: else:
raise INVALID_CERTIFICATE 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( def cborize_initial_pool_registration_certificate_fields(
certificate: CardanoTxCertificate, certificate: CardanoTxCertificate,
) -> CborSequence: ) -> CborSequence:

View File

@ -3,19 +3,21 @@ from trezor import wire
INVALID_ADDRESS = wire.ProcessError("Invalid address") INVALID_ADDRESS = wire.ProcessError("Invalid address")
INVALID_ADDRESS_PARAMETERS = wire.ProcessError("Invalid address parameters") INVALID_ADDRESS_PARAMETERS = wire.ProcessError("Invalid address parameters")
NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch") 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_OUTPUT = wire.ProcessError("Invalid output")
INVALID_CERTIFICATE = wire.ProcessError("Invalid certificate") INVALID_CERTIFICATE = wire.ProcessError("Invalid certificate")
INVALID_WITHDRAWAL = wire.ProcessError("Invalid withdrawal") INVALID_WITHDRAWAL = wire.ProcessError("Invalid withdrawal")
INVALID_TOKEN_BUNDLE_OUTPUT = wire.ProcessError("Invalid token bundle in output") INVALID_TOKEN_BUNDLE_OUTPUT = wire.ProcessError("Invalid token bundle in output")
INVALID_AUXILIARY_DATA = wire.ProcessError("Invalid auxiliary data") INVALID_AUXILIARY_DATA = wire.ProcessError("Invalid auxiliary data")
INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE = wire.ProcessError( 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( INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES = wire.ProcessError(
"Stakepool registration transaction can only contain staking witnesses" "Stakepool registration transaction can only contain staking witnesses"
) )
INVALID_WITNESS_REQUEST = wire.ProcessError("Invalid witness request") INVALID_WITNESS_REQUEST = wire.ProcessError("Invalid witness request")
INVALID_NATIVE_SCRIPT = wire.ProcessError("Invalid native script") 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 LOVELACE_MAX_SUPPLY = 45_000_000_000 * 1_000_000
ADDRESS_KEY_HASH_SIZE = 28 ADDRESS_KEY_HASH_SIZE = 28

View File

@ -1,5 +1,5 @@
from ...common.paths import HARDENED 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 ( from . import (
INVALID_CERTIFICATE, INVALID_CERTIFICATE,
INVALID_OUTPUT, INVALID_OUTPUT,
@ -33,6 +33,10 @@ class AccountPathChecker:
self.account_path: object | list[int] = self.UNDEFINED self.account_path: object | list[int] = self.UNDEFINED
def _add(self, path: list[int], error: wire.ProcessError) -> None: 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) account_path = to_account_path(path)
if self.account_path is self.UNDEFINED: if self.account_path is self.UNDEFINED:
self.account_path = account_path self.account_path = account_path

View File

@ -1,11 +1,17 @@
from trezor.crypto import hashlib 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 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: if False:
from trezor import wire
from .. import seed from .. import seed
@ -79,3 +85,26 @@ def derive_public_key(
node = keychain.derive(path) node = keychain.derive(path)
public_key = remove_ed25519_prefix(node.public_key()) public_key = remove_ed25519_prefix(node.public_key())
return public_key if not extended else public_key + node.chain_code() 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

View File

@ -5,6 +5,7 @@ from trezor.enums import (
CardanoCertificateType, CardanoCertificateType,
CardanoNativeScriptHashDisplayFormat, CardanoNativeScriptHashDisplayFormat,
CardanoNativeScriptType, CardanoNativeScriptType,
CardanoTxSigningMode,
) )
from trezor.messages import CardanoAddressParametersType from trezor.messages import CardanoAddressParametersType
from trezor.strings import format_amount from trezor.strings import format_amount
@ -14,6 +15,7 @@ from trezor.ui.layouts import (
confirm_output, confirm_output,
confirm_path_warning, confirm_path_warning,
confirm_properties, confirm_properties,
confirm_text,
show_address, show_address,
) )
@ -31,6 +33,7 @@ from .helpers.utils import (
format_stake_pool_id, format_stake_pool_id,
to_account_path, to_account_path,
) )
from .seed import is_minting_path, is_multisig_path
if False: if False:
from trezor import wire 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( async def confirm_sending(
ctx: wire.Context, ctx: wire.Context,
ada_amount: int, ada_amount: int,
@ -208,6 +225,8 @@ async def confirm_sending(
async def confirm_sending_token( async def confirm_sending_token(
ctx: wire.Context, policy_id: bytes, token: CardanoToken ctx: wire.Context, policy_id: bytes, token: CardanoToken
) -> None: ) -> None:
assert token.amount is not None # _validate_token
await confirm_properties( await confirm_properties(
ctx, ctx,
"confirm_token", "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( async def confirm_transaction(
ctx: wire.Context, ctx: wire.Context,
fee: int, fee: int,
@ -354,13 +394,21 @@ async def confirm_certificate(
# in this call # in this call
assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION
props = [ props: list[PropertyType] = [
("Confirm:", CERTIFICATE_TYPE_NAMES[certificate.type]), ("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: if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
assert certificate.pool is not None # validate_certificate assert certificate.pool is not None # validate_certificate
props.append(("to pool:", format_stake_pool_id(certificate.pool))) 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( async def confirm_withdrawal(
ctx: wire.Context, withdrawal: CardanoTxWithdrawal ctx: wire.Context, withdrawal: CardanoTxWithdrawal
) -> None: ) -> 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( await confirm_properties(
ctx, ctx,
"confirm_withdrawal", "confirm_withdrawal",
title="Confirm transaction", title="Confirm transaction",
props=[ props=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)),
],
br_code=ButtonRequestType.Other, br_code=ButtonRequestType.Other,
) )
@ -557,43 +615,40 @@ async def show_auxiliary_data_hash(
) )
async def show_warning_address_foreign_staking_key( async def show_warning_tx_contains_mint(ctx: wire.Context) -> None:
ctx: wire.Context, await confirm_metadata(
account_path: list[int], ctx,
staking_account_path: list[int], "confirm_tokens",
staking_key_hash: bytes | None, title="Confirm transaction",
) -> None: content="The transaction contains\nminting or burning of\ntokens.",
# TODO: confirm_properties not appropriate here larger_vspace=True,
# instead, presumably, this should be a flow: br_code=ButtonRequestType.Other,
# 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),
)
]
if staking_account_path:
props.append( async def confirm_token_minting(
( ctx: wire.Context, policy_id: bytes, token: CardanoToken
"Stake account %s:" % format_account_number(staking_account_path), ) -> None:
address_n_to_str(staking_account_path), assert token.mint_amount is not None # _validate_token
) is_minting = token.mint_amount >= 0
)
else:
assert staking_key_hash is not None # _validate_base_address_staking_info
props.append(("Staking key:", staking_key_hash))
props.append(("Continue?", None))
await confirm_properties( await confirm_properties(
ctx, ctx,
"warning_foreign_stakingkey", "confirm_mint",
title="Warning", title="Confirm transaction",
props=props, props=[
icon=ui.ICON_WRONG, (
icon_color=ui.RED, "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, br_code=ButtonRequestType.Other,
) )

View File

@ -23,6 +23,7 @@ from trezor.messages import (
CardanoTxHostAck, CardanoTxHostAck,
CardanoTxInput, CardanoTxInput,
CardanoTxItemAck, CardanoTxItemAck,
CardanoTxMint,
CardanoTxOutput, CardanoTxOutput,
CardanoTxWithdrawal, CardanoTxWithdrawal,
CardanoTxWitnessRequest, CardanoTxWitnessRequest,
@ -36,8 +37,8 @@ from .address import (
derive_address_bytes, derive_address_bytes,
derive_human_readable_address, derive_human_readable_address,
get_address_bytes_unsafe, get_address_bytes_unsafe,
validate_address_parameters,
validate_output_address, validate_output_address,
validate_output_address_parameters,
) )
from .auxiliary_data import ( from .auxiliary_data import (
get_auxiliary_data_hash_and_supplement, get_auxiliary_data_hash_and_supplement,
@ -59,8 +60,11 @@ from .helpers import (
INVALID_OUTPUT, INVALID_OUTPUT,
INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE, INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE,
INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES, INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES,
INVALID_TOKEN_BUNDLE_MINT,
INVALID_TOKEN_BUNDLE_OUTPUT, INVALID_TOKEN_BUNDLE_OUTPUT,
INVALID_TX_SIGNING_REQUEST,
INVALID_WITHDRAWAL, INVALID_WITHDRAWAL,
INVALID_WITNESS_REQUEST,
LOVELACE_MAX_SUPPLY, LOVELACE_MAX_SUPPLY,
network_ids, network_ids,
protocol_magics, protocol_magics,
@ -73,12 +77,13 @@ from .helpers.paths import (
CHANGE_OUTPUT_PATH_NAME, CHANGE_OUTPUT_PATH_NAME,
CHANGE_OUTPUT_STAKING_PATH_NAME, CHANGE_OUTPUT_STAKING_PATH_NAME,
POOL_OWNER_STAKING_PATH_NAME, POOL_OWNER_STAKING_PATH_NAME,
SCHEMA_MINT,
SCHEMA_PAYMENT, SCHEMA_PAYMENT,
SCHEMA_STAKING, SCHEMA_STAKING,
SCHEMA_STAKING_ANY_ACCOUNT, SCHEMA_STAKING_ANY_ACCOUNT,
WITNESS_PATH_NAME, 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 ( from .layout import (
confirm_certificate, confirm_certificate,
confirm_sending, confirm_sending,
@ -87,14 +92,18 @@ from .layout import (
confirm_stake_pool_owner, confirm_stake_pool_owner,
confirm_stake_pool_parameters, confirm_stake_pool_parameters,
confirm_stake_pool_registration_final, confirm_stake_pool_registration_final,
confirm_token_minting,
confirm_transaction, confirm_transaction,
confirm_withdrawal, confirm_withdrawal,
confirm_witness_request,
show_credentials, show_credentials,
show_transaction_signing_mode,
show_warning_path, show_warning_path,
show_warning_tx_contains_mint,
show_warning_tx_network_unverifiable, show_warning_tx_network_unverifiable,
show_warning_tx_output_contains_tokens, 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: if False:
from typing import Any, Union from typing import Any, Union
@ -113,6 +122,7 @@ TX_BODY_KEY_CERTIFICATES = const(4)
TX_BODY_KEY_WITHDRAWALS = const(5) TX_BODY_KEY_WITHDRAWALS = const(5)
TX_BODY_KEY_AUXILIARY_DATA = const(7) TX_BODY_KEY_AUXILIARY_DATA = const(7)
TX_BODY_KEY_VALIDITY_INTERVAL_START = const(8) TX_BODY_KEY_VALIDITY_INTERVAL_START = const(8)
TX_BODY_KEY_MINT = const(9)
POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT = 10 POOL_REGISTRATION_CERTIFICATE_ITEMS_COUNT = 10
@ -123,6 +133,8 @@ async def sign_tx(
) -> CardanoSignTxFinished: ) -> CardanoSignTxFinished:
is_network_id_verifiable = await _validate_tx_signing_request(ctx, msg) 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 # inputs, outputs and fee are mandatory fields, count the number of optional fields present
tx_body_map_item_count = 3 + sum( tx_body_map_item_count = 3 + sum(
( (
@ -131,6 +143,7 @@ async def sign_tx(
msg.withdrawals_count > 0, msg.withdrawals_count > 0,
msg.has_auxiliary_data, msg.has_auxiliary_data,
msg.validity_interval_start is not None, msg.validity_interval_start is not None,
msg.minting_asset_groups_count > 0,
) )
) )
@ -152,8 +165,10 @@ async def sign_tx(
tx_hash, tx_hash,
msg.witness_requests_count, msg.witness_requests_count,
msg.signing_mode, msg.signing_mode,
msg.minting_asset_groups_count > 0,
account_path_checker, account_path_checker,
) )
await ctx.call(response_after_witness_requests, CardanoTxHostAck) await ctx.call(response_after_witness_requests, CardanoTxHostAck)
await ctx.call(CardanoTxBodyHash(tx_hash=tx_hash), 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) await show_warning_tx_network_unverifiable(ctx)
elif msg.signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER: elif msg.signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER:
_validate_stake_pool_registration_tx_structure(msg) _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 return is_network_id_verifiable
@ -236,6 +256,7 @@ async def _process_transaction(
keychain, keychain,
withdrawals_dict, withdrawals_dict,
msg.withdrawals_count, msg.withdrawals_count,
msg.signing_mode,
msg.protocol_magic, msg.protocol_magic,
msg.network_id, msg.network_id,
account_path_checker, account_path_checker,
@ -253,13 +274,23 @@ async def _process_transaction(
if msg.validity_interval_start is not None: if msg.validity_interval_start is not None:
tx_dict.add(TX_BODY_KEY_VALIDITY_INTERVAL_START, msg.validity_interval_start) 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( async def _confirm_transaction(
ctx: wire.Context, ctx: wire.Context,
msg: CardanoSignTxInit, msg: CardanoSignTxInit,
is_network_id_verifiable: bool, is_network_id_verifiable: bool,
) -> None: ) -> None:
if msg.signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: if msg.signing_mode in (
CardanoTxSigningMode.ORDINARY_TRANSACTION,
CardanoTxSigningMode.MULTISIG_TRANSACTION,
):
await confirm_transaction( await confirm_transaction(
ctx, ctx,
msg.fee, msg.fee,
@ -272,6 +303,8 @@ async def _confirm_transaction(
await confirm_stake_pool_registration_final( await confirm_stake_pool_registration_final(
ctx, msg.protocol_magic, msg.ttl, msg.validity_interval_start ctx, msg.protocol_magic, msg.ttl, msg.validity_interval_start
) )
else:
raise ValueError
async def _process_inputs( async def _process_inputs(
@ -313,7 +346,6 @@ async def _process_outputs(
ctx, ctx,
keychain, keychain,
output, output,
signing_mode,
protocol_magic, protocol_magic,
network_id, network_id,
) )
@ -363,7 +395,6 @@ async def _process_asset_groups(
asset_group: CardanoAssetGroup = await ctx.call( asset_group: CardanoAssetGroup = await ctx.call(
CardanoTxItemAck(), CardanoAssetGroup CardanoTxItemAck(), CardanoAssetGroup
) )
asset_group.policy_id = bytes(asset_group.policy_id)
_validate_asset_group(asset_group, seen_policy_ids) _validate_asset_group(asset_group, seen_policy_ids)
seen_policy_ids.add(asset_group.policy_id) seen_policy_ids.add(asset_group.policy_id)
@ -391,12 +422,12 @@ async def _process_tokens(
seen_asset_name_bytes: set[bytes] = set() seen_asset_name_bytes: set[bytes] = set()
for _ in range(tokens_count): for _ in range(tokens_count):
token: CardanoToken = await ctx.call(CardanoTxItemAck(), CardanoToken) token: CardanoToken = await ctx.call(CardanoTxItemAck(), CardanoToken)
token.asset_name_bytes = bytes(token.asset_name_bytes)
_validate_token(token, seen_asset_name_bytes) _validate_token(token, seen_asset_name_bytes)
seen_asset_name_bytes.add(token.asset_name_bytes) seen_asset_name_bytes.add(token.asset_name_bytes)
if should_show_tokens: if should_show_tokens:
await confirm_sending_token(ctx, policy_id, token) 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) tokens_dict.add(token.asset_name_bytes, token.amount)
@ -445,6 +476,7 @@ async def _process_certificates(
keychain, keychain,
pool_owners_list, pool_owners_list,
pool_parameters.owners_count, pool_parameters.owners_count,
protocol_magic,
network_id, network_id,
account_path_checker, account_path_checker,
) )
@ -467,6 +499,7 @@ async def _process_pool_owners(
keychain: seed.Keychain, keychain: seed.Keychain,
pool_owners_list: HashBuilderList[bytes], pool_owners_list: HashBuilderList[bytes],
owners_count: int, owners_count: int,
protocol_magic: int,
network_id: int, network_id: int,
account_path_checker: AccountPathChecker, account_path_checker: AccountPathChecker,
) -> None: ) -> None:
@ -474,7 +507,7 @@ async def _process_pool_owners(
for _ in range(owners_count): for _ in range(owners_count):
owner: CardanoPoolOwner = await ctx.call(CardanoTxItemAck(), CardanoPoolOwner) owner: CardanoPoolOwner = await ctx.call(CardanoTxItemAck(), CardanoPoolOwner)
validate_pool_owner(owner, account_path_checker) 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)) pool_owners_list.append(cborize_pool_owner(keychain, owner))
@ -502,6 +535,7 @@ async def _process_withdrawals(
keychain: seed.Keychain, keychain: seed.Keychain,
withdrawals_dict: HashBuilderDict[bytes, int], withdrawals_dict: HashBuilderDict[bytes, int],
withdrawals_count: int, withdrawals_count: int,
signing_mode: CardanoTxSigningMode,
protocol_magic: int, protocol_magic: int,
network_id: int, network_id: int,
account_path_checker: AccountPathChecker, 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 # until the CIP with canonical CBOR is finalized storing the seen_withdrawals is the only way we can check for
# duplicate withdrawals # duplicate withdrawals
seen_withdrawals: set[tuple[int, ...]] = set() seen_withdrawals: set[tuple[int, ...] | bytes] = set()
for _ in range(withdrawals_count): for _ in range(withdrawals_count):
withdrawal: CardanoTxWithdrawal = await ctx.call( withdrawal: CardanoTxWithdrawal = await ctx.call(
CardanoTxItemAck(), CardanoTxWithdrawal 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) await confirm_withdrawal(ctx, withdrawal)
reward_address_type = (
CardanoAddressType.REWARD
if withdrawal.path
else CardanoAddressType.REWARD_SCRIPT
)
reward_address = derive_address_bytes( reward_address = derive_address_bytes(
keychain, keychain,
CardanoAddressParametersType( CardanoAddressParametersType(
address_type=CardanoAddressType.REWARD, address_type=reward_address_type,
address_n=withdrawal.path, address_n_staking=withdrawal.path,
script_staking_hash=withdrawal.script_hash,
), ),
protocol_magic, protocol_magic,
network_id, network_id,
@ -566,25 +608,80 @@ async def _process_auxiliary_data(
await ctx.call(auxiliary_data_supplement, CardanoTxHostAck) 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( async def _process_witness_requests(
ctx: wire.Context, ctx: wire.Context,
keychain: seed.Keychain, keychain: seed.Keychain,
tx_hash: bytes, tx_hash: bytes,
witness_requests_count: int, witness_requests_count: int,
signing_mode: CardanoTxSigningMode, signing_mode: CardanoTxSigningMode,
transaction_has_token_minting: bool,
account_path_checker: AccountPathChecker, account_path_checker: AccountPathChecker,
) -> CardanoTxResponseType: ) -> CardanoTxResponseType:
response: CardanoTxResponseType = CardanoTxItemAck() response: CardanoTxResponseType = CardanoTxItemAck()
for _ in range(witness_requests_count): for _ in range(witness_requests_count):
witness_request = await ctx.call(response, CardanoTxWitnessRequest) witness_request = await ctx.call(response, CardanoTxWitnessRequest)
_validate_witness_request(witness_request, signing_mode, account_path_checker) _validate_witness_request(
await _show_witness(ctx, witness_request.path) witness_request,
signing_mode,
response = ( transaction_has_token_minting,
_get_byron_witness(keychain, witness_request.path, tx_hash) account_path_checker,
if is_byron_path(witness_request.path)
else _get_shelley_witness(keychain, witness_request.path, tx_hash)
) )
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 return response
@ -633,6 +730,7 @@ def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTxInit) -> No
msg.certificates_count != 1 msg.certificates_count != 1
or msg.signing_mode != CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER or msg.signing_mode != CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER
or msg.withdrawals_count != 0 or msg.withdrawals_count != 0
or msg.minting_asset_groups_count != 0
): ):
raise INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE raise INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE
@ -648,7 +746,10 @@ def _validate_output(
raise INVALID_OUTPUT raise INVALID_OUTPUT
if address_parameters := output.address_parameters: 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) _fail_if_strict_and_unusual(address_parameters)
elif output.address is not None: elif output.address is not None:
validate_output_address(output.address, protocol_magic, network_id) validate_output_address(output.address, protocol_magic, network_id)
@ -679,7 +780,6 @@ async def _show_output(
ctx: wire.Context, ctx: wire.Context,
keychain: seed.Keychain, keychain: seed.Keychain,
output: CardanoTxOutput, output: CardanoTxOutput,
signing_mode: CardanoTxSigningMode,
protocol_magic: int, protocol_magic: int,
network_id: int, network_id: int,
) -> None: ) -> None:
@ -710,21 +810,38 @@ async def _show_output(
def _validate_asset_group( 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: ) -> 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: 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: if asset_group.tokens_count == 0:
raise INVALID_TOKEN_BUNDLE_OUTPUT raise INVALID_TOKEN_BUNDLE
if asset_group.policy_id in seen_policy_ids: 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: 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: if token.asset_name_bytes in seen_asset_name_bytes:
raise INVALID_TOKEN_BUNDLE_OUTPUT raise INVALID_TOKEN_BUNDLE
async def _show_certificate( async def _show_certificate(
@ -733,30 +850,38 @@ async def _show_certificate(
signing_mode: CardanoTxSigningMode, signing_mode: CardanoTxSigningMode,
) -> None: ) -> None:
if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION: if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION:
assert certificate.path # validate_certificate
await _fail_or_warn_if_invalid_path( await _fail_or_warn_if_invalid_path(
ctx, SCHEMA_STAKING, certificate.path, CERTIFICATE_PATH_NAME ctx, SCHEMA_STAKING, certificate.path, CERTIFICATE_PATH_NAME
) )
await confirm_certificate(ctx, certificate) 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: elif signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER:
await _show_stake_pool_registration_certificate(ctx, certificate) await _show_stake_pool_registration_certificate(ctx, certificate)
def _validate_withdrawal( def _validate_withdrawal(
withdrawal: CardanoTxWithdrawal, withdrawal: CardanoTxWithdrawal,
seen_withdrawals: set[tuple[int, ...]], seen_withdrawals: set[tuple[int, ...] | bytes],
signing_mode: CardanoTxSigningMode,
account_path_checker: AccountPathChecker, account_path_checker: AccountPathChecker,
) -> None: ) -> None:
if not SCHEMA_STAKING_ANY_ACCOUNT.match(withdrawal.path): validate_stake_credential(
raise INVALID_WITHDRAWAL withdrawal.path, withdrawal.script_hash, signing_mode, INVALID_WITHDRAWAL
)
if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY: if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY:
raise INVALID_WITHDRAWAL raise INVALID_WITHDRAWAL
path_tuple = tuple(withdrawal.path) credential = tuple(withdrawal.path) if withdrawal.path else withdrawal.script_hash
if path_tuple in seen_withdrawals: assert credential # validate_stake_credential
if credential in seen_withdrawals:
raise wire.ProcessError("Duplicate withdrawals") raise wire.ProcessError("Duplicate withdrawals")
else: else:
seen_withdrawals.add(path_tuple) seen_withdrawals.add(credential)
account_path_checker.add_withdrawal(withdrawal) account_path_checker.add_withdrawal(withdrawal)
@ -818,17 +943,33 @@ async def _show_pool_owner(
def _validate_witness_request( def _validate_witness_request(
witness_request: CardanoTxWitnessRequest, witness_request: CardanoTxWitnessRequest,
signing_mode: CardanoTxSigningMode, signing_mode: CardanoTxSigningMode,
transaction_has_token_minting: bool,
account_path_checker: AccountPathChecker, account_path_checker: AccountPathChecker,
) -> None: ) -> 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: if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION:
_ensure_no_payment_witness(witness_request) 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) 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 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.) 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 raise INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES
async def _show_witness( async def _show_witness_request(
ctx: wire.Context, ctx: wire.Context,
witness_path: list[int], witness_path: list[int],
signing_mode: CardanoTxSigningMode,
) -> None: ) -> None:
is_payment = SCHEMA_PAYMENT.match(witness_path) if signing_mode == CardanoTxSigningMode.ORDINARY_TRANSACTION:
is_staking = SCHEMA_STAKING.match(witness_path) # 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 not is_payment and not is_staking: if is_minting:
await _fail_or_warn_path(ctx, witness_path, WITNESS_PATH_NAME) 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: def _is_network_id_verifiable(msg: CardanoSignTxInit) -> bool:

View File

@ -4,3 +4,4 @@
ORDINARY_TRANSACTION = 0 ORDINARY_TRANSACTION = 0
POOL_REGISTRATION_AS_OWNER = 1 POOL_REGISTRATION_AS_OWNER = 1
MULTISIG_TRANSACTION = 2

View File

@ -152,6 +152,7 @@ if not utils.BITCOIN_ONLY:
CardanoPoolRelayParameters = 329 CardanoPoolRelayParameters = 329
CardanoGetNativeScriptHash = 330 CardanoGetNativeScriptHash = 330
CardanoNativeScriptHash = 331 CardanoNativeScriptHash = 331
CardanoTxMint = 332
RippleGetAddress = 400 RippleGetAddress = 400
RippleAddress = 401 RippleAddress = 401
RippleSignTx = 402 RippleSignTx = 402

View File

@ -157,6 +157,7 @@ if TYPE_CHECKING:
CardanoPoolRelayParameters = 329 CardanoPoolRelayParameters = 329
CardanoGetNativeScriptHash = 330 CardanoGetNativeScriptHash = 330
CardanoNativeScriptHash = 331 CardanoNativeScriptHash = 331
CardanoTxMint = 332
RippleGetAddress = 400 RippleGetAddress = 400
RippleAddress = 401 RippleAddress = 401
RippleSignTx = 402 RippleSignTx = 402
@ -361,6 +362,7 @@ if TYPE_CHECKING:
class CardanoTxSigningMode(IntEnum): class CardanoTxSigningMode(IntEnum):
ORDINARY_TRANSACTION = 0 ORDINARY_TRANSACTION = 0
POOL_REGISTRATION_AS_OWNER = 1 POOL_REGISTRATION_AS_OWNER = 1
MULTISIG_TRANSACTION = 2
class CardanoTxWitnessType(IntEnum): class CardanoTxWitnessType(IntEnum):
BYRON_WITNESS = 0 BYRON_WITNESS = 0

View File

@ -1222,6 +1222,7 @@ if TYPE_CHECKING:
has_auxiliary_data: "bool" has_auxiliary_data: "bool"
validity_interval_start: "int | None" validity_interval_start: "int | None"
witness_requests_count: "int" witness_requests_count: "int"
minting_asset_groups_count: "int"
def __init__( def __init__(
self, self,
@ -1236,6 +1237,7 @@ if TYPE_CHECKING:
withdrawals_count: "int", withdrawals_count: "int",
has_auxiliary_data: "bool", has_auxiliary_data: "bool",
witness_requests_count: "int", witness_requests_count: "int",
minting_asset_groups_count: "int",
ttl: "int | None" = None, ttl: "int | None" = None,
validity_interval_start: "int | None" = None, validity_interval_start: "int | None" = None,
) -> None: ) -> None:
@ -1299,13 +1301,15 @@ if TYPE_CHECKING:
class CardanoToken(protobuf.MessageType): class CardanoToken(protobuf.MessageType):
asset_name_bytes: "bytes" asset_name_bytes: "bytes"
amount: "int" amount: "int | None"
mint_amount: "int | None"
def __init__( def __init__(
self, self,
*, *,
asset_name_bytes: "bytes", asset_name_bytes: "bytes",
amount: "int", amount: "int | None" = None,
mint_amount: "int | None" = None,
) -> None: ) -> None:
pass pass
@ -1404,6 +1408,7 @@ if TYPE_CHECKING:
path: "list[int]" path: "list[int]"
pool: "bytes | None" pool: "bytes | None"
pool_parameters: "CardanoPoolParametersType | None" pool_parameters: "CardanoPoolParametersType | None"
script_hash: "bytes | None"
def __init__( def __init__(
self, self,
@ -1412,6 +1417,7 @@ if TYPE_CHECKING:
path: "list[int] | None" = None, path: "list[int] | None" = None,
pool: "bytes | None" = None, pool: "bytes | None" = None,
pool_parameters: "CardanoPoolParametersType | None" = None, pool_parameters: "CardanoPoolParametersType | None" = None,
script_hash: "bytes | None" = None,
) -> None: ) -> None:
pass pass
@ -1422,12 +1428,14 @@ if TYPE_CHECKING:
class CardanoTxWithdrawal(protobuf.MessageType): class CardanoTxWithdrawal(protobuf.MessageType):
path: "list[int]" path: "list[int]"
amount: "int" amount: "int"
script_hash: "bytes | None"
def __init__( def __init__(
self, self,
*, *,
amount: "int", amount: "int",
path: "list[int] | None" = None, path: "list[int] | None" = None,
script_hash: "bytes | None" = None,
) -> None: ) -> None:
pass pass
@ -1471,6 +1479,20 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["CardanoTxAuxiliaryData"]: def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["CardanoTxAuxiliaryData"]:
return isinstance(msg, cls) 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): class CardanoTxItemAck(protobuf.MessageType):
@classmethod @classmethod

View File

@ -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()

View File

@ -24,6 +24,7 @@ from .tools import expect
SIGNING_MODE_IDS = { SIGNING_MODE_IDS = {
"ORDINARY_TRANSACTION": messages.CardanoTxSigningMode.ORDINARY_TRANSACTION, "ORDINARY_TRANSACTION": messages.CardanoTxSigningMode.ORDINARY_TRANSACTION,
"POOL_REGISTRATION_AS_OWNER": messages.CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER, "POOL_REGISTRATION_AS_OWNER": messages.CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
"MULTISIG_TRANSACTION": messages.CardanoTxSigningMode.MULTISIG_TRANSACTION,
} }
PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42} PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42}
@ -41,9 +42,7 @@ REQUIRED_FIELDS_POOL_PARAMETERS = (
"reward_account", "reward_account",
"owners", "owners",
) )
REQUIRED_FIELDS_WITHDRAWAL = ("path", "amount")
REQUIRED_FIELDS_TOKEN_GROUP = ("policy_id", "tokens") REQUIRED_FIELDS_TOKEN_GROUP = ("policy_id", "tokens")
REQUIRED_FIELDS_TOKEN = ("asset_name_bytes", "amount")
REQUIRED_FIELDS_CATALYST_REGISTRATION = ( REQUIRED_FIELDS_CATALYST_REGISTRATION = (
"voting_public_key", "voting_public_key",
"staking_path", "staking_path",
@ -54,6 +53,7 @@ REQUIRED_FIELDS_CATALYST_REGISTRATION = (
INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields" INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields"
INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY = "The output's token_bundle entry is invalid" 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]] InputWithPath = Tuple[messages.CardanoTxInput, List[int]]
AssetGroupWithTokens = Tuple[messages.CardanoAssetGroup, List[messages.CardanoToken]] AssetGroupWithTokens = Tuple[messages.CardanoAssetGroup, List[messages.CardanoToken]]
@ -66,6 +66,9 @@ CertificateItem = Union[
messages.CardanoPoolOwner, messages.CardanoPoolOwner,
messages.CardanoPoolRelayParameters, messages.CardanoPoolRelayParameters,
] ]
MintItem = Union[
messages.CardanoTxMint, messages.CardanoAssetGroup, messages.CardanoToken
]
PoolOwnersAndRelays = Tuple[ PoolOwnersAndRelays = Tuple[
List[messages.CardanoPoolOwner], List[messages.CardanoPoolRelayParameters] List[messages.CardanoPoolOwner], List[messages.CardanoPoolRelayParameters]
] ]
@ -160,10 +163,12 @@ def parse_output(output) -> OutputWithAssetGroups:
address = output["address"] address = output["address"]
if contains_address_type: 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: 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 ( return (
messages.CardanoTxOutput( 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 = [] result = []
for token_group in token_bundle: for token_group in token_bundle:
if not all(k in token_group for k in REQUIRED_FIELDS_TOKEN_GROUP): 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( result.append(
( (
messages.CardanoAssetGroup( messages.CardanoAssetGroup(
policy_id=bytes.fromhex(token_group["policy_id"]), policy_id=bytes.fromhex(token_group["policy_id"]),
tokens_count=len(tokens), tokens_count=len(tokens),
is_mint=is_mint,
), ),
tokens, tokens,
) )
@ -197,16 +209,34 @@ def _parse_token_bundle(token_bundle) -> List[AssetGroupWithTokens]:
return result 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 = [] result = []
for token in tokens: for token in tokens:
if not all(k in token for k in REQUIRED_FIELDS_TOKEN): if "asset_name_bytes" not in token:
raise ValueError(INVALID_OUTPUT_TOKEN_BUNDLE_ENTRY) 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( result.append(
messages.CardanoToken( messages.CardanoToken(
asset_name_bytes=bytes.fromhex(token["asset_name_bytes"]), 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( def _parse_address_parameters(
address_parameters, address_parameters, error_message: str
) -> messages.CardanoAddressParametersType: ) -> messages.CardanoAddressParametersType:
if "addressType" not in address_parameters: 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_path = tools.parse_path(address_parameters.get("stakingPath"))
staking_key_hash_bytes = parse_optional_bytes( staking_key_hash_bytes = parse_optional_bytes(
address_parameters.get("stakingKeyHash") address_parameters.get("stakingKeyHash")
@ -233,7 +263,7 @@ def _parse_address_parameters(
return create_address_parameters( return create_address_parameters(
int(address_parameters["addressType"]), int(address_parameters["addressType"]),
path, payment_path,
staking_path, staking_path,
staking_key_hash_bytes, staking_key_hash_bytes,
address_parameters.get("blockIndex"), address_parameters.get("blockIndex"),
@ -287,11 +317,16 @@ def parse_certificate(certificate) -> CertificateWithPoolOwnersAndRelays:
if "pool" not in certificate: if "pool" not in certificate:
raise CERTIFICATE_MISSING_FIELDS_ERROR raise CERTIFICATE_MISSING_FIELDS_ERROR
path, script_hash = _parse_path_or_script_hash(
certificate, CERTIFICATE_MISSING_FIELDS_ERROR
)
return ( return (
messages.CardanoTxCertificate( messages.CardanoTxCertificate(
type=certificate_type, type=certificate_type,
path=tools.parse_path(certificate["path"]), path=path,
pool=bytes.fromhex(certificate["pool"]), pool=bytes.fromhex(certificate["pool"]),
script_hash=script_hash,
), ),
None, None,
) )
@ -299,12 +334,13 @@ def parse_certificate(certificate) -> CertificateWithPoolOwnersAndRelays:
messages.CardanoCertificateType.STAKE_REGISTRATION, messages.CardanoCertificateType.STAKE_REGISTRATION,
messages.CardanoCertificateType.STAKE_DEREGISTRATION, messages.CardanoCertificateType.STAKE_DEREGISTRATION,
): ):
if "path" not in certificate: path, script_hash = _parse_path_or_script_hash(
raise CERTIFICATE_MISSING_FIELDS_ERROR certificate, CERTIFICATE_MISSING_FIELDS_ERROR
)
return ( return (
messages.CardanoTxCertificate( messages.CardanoTxCertificate(
type=certificate_type, type=certificate_type, path=path, script_hash=script_hash
path=tools.parse_path(certificate["path"]),
), ),
None, None,
) )
@ -356,6 +392,18 @@ def parse_certificate(certificate) -> CertificateWithPoolOwnersAndRelays:
raise ValueError("Unknown certificate type") 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: def _parse_pool_owner(pool_owner) -> messages.CardanoPoolOwner:
if "staking_key_path" in pool_owner: if "staking_key_path" in pool_owner:
return messages.CardanoPoolOwner( return messages.CardanoPoolOwner(
@ -404,13 +452,21 @@ def _parse_pool_relay(pool_relay) -> messages.CardanoPoolRelayParameters:
def parse_withdrawal(withdrawal) -> messages.CardanoTxWithdrawal: def parse_withdrawal(withdrawal) -> messages.CardanoTxWithdrawal:
if not all(k in withdrawal for k in REQUIRED_FIELDS_WITHDRAWAL): WITHDRAWAL_MISSING_FIELDS_ERROR = ValueError(
raise ValueError("Withdrawal is missing some fields") "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( return messages.CardanoTxWithdrawal(
path=tools.parse_path(path), path=path,
amount=int(withdrawal["amount"]), 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"]), staking_path=tools.parse_path(catalyst_registration["staking_path"]),
nonce=catalyst_registration["nonce"], nonce=catalyst_registration["nonce"],
reward_address_parameters=_parse_address_parameters( 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], inputs: List[InputWithPath],
certificates: List[CertificateWithPoolOwnersAndRelays], certificates: List[CertificateWithPoolOwnersAndRelays],
withdrawals: List[messages.CardanoTxWithdrawal], withdrawals: List[messages.CardanoTxWithdrawal],
) -> List[Path]: additional_witness_requests: List[Path],
signing_mode: messages.CardanoTxSigningMode,
) -> List[messages.CardanoTxWitnessRequest]:
paths = set() 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]: def _get_input_items(inputs: List[InputWithPath]) -> Iterator[messages.CardanoTxInput]:
@ -508,6 +594,13 @@ def _get_certificate_items(
yield from relays 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 ====== # # ====== Client functions ====== #
@ -561,10 +654,14 @@ def sign_tx(
protocol_magic: int = PROTOCOL_MAGICS["mainnet"], protocol_magic: int = PROTOCOL_MAGICS["mainnet"],
network_id: int = NETWORK_IDS["mainnet"], network_id: int = NETWORK_IDS["mainnet"],
auxiliary_data: messages.CardanoTxAuxiliaryData = None, auxiliary_data: messages.CardanoTxAuxiliaryData = None,
mint: List[AssetGroupWithTokens] = (),
additional_witness_requests: List[Path] = (),
) -> SignTxResponse: ) -> SignTxResponse:
UNEXPECTED_RESPONSE_ERROR = exceptions.TrezorException("Unexpected response") 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( response = client.call(
messages.CardanoSignTxInit( messages.CardanoSignTxInit(
@ -579,7 +676,8 @@ def sign_tx(
protocol_magic=protocol_magic, protocol_magic=protocol_magic,
network_id=network_id, network_id=network_id,
has_auxiliary_data=auxiliary_data is not None, 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): if not isinstance(response, messages.CardanoTxItemAck):
@ -615,9 +713,15 @@ def sign_tx(
if not isinstance(response, messages.CardanoTxItemAck): if not isinstance(response, messages.CardanoTxItemAck):
raise UNEXPECTED_RESPONSE_ERROR 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"] = [] sign_tx_response["witnesses"] = []
for path in witness_paths: for witness_request in witness_requests:
response = client.call(messages.CardanoTxWitnessRequest(path=path)) response = client.call(witness_request)
if not isinstance(response, messages.CardanoTxWitnessResponse): if not isinstance(response, messages.CardanoTxWitnessResponse):
raise UNEXPECTED_RESPONSE_ERROR raise UNEXPECTED_RESPONSE_ERROR
sign_tx_response["witnesses"].append( sign_tx_response["witnesses"].append(

View File

@ -66,6 +66,11 @@ def sign_tx(client, file, signing_mode, protocol_magic, network_id, testnet):
for withdrawal in transaction.get("withdrawals", ()) for withdrawal in transaction.get("withdrawals", ())
] ]
auxiliary_data = cardano.parse_auxiliary_data(transaction.get("auxiliary_data")) 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( sign_tx_response = cardano.sign_tx(
client, client,
@ -80,6 +85,8 @@ def sign_tx(client, file, signing_mode, protocol_magic, network_id, testnet):
protocol_magic, protocol_magic,
network_id, network_id,
auxiliary_data, auxiliary_data,
mint,
additional_witness_requests,
) )
sign_tx_response["tx_hash"] = sign_tx_response["tx_hash"].hex() sign_tx_response["tx_hash"] = sign_tx_response["tx_hash"].hex()

View File

@ -178,6 +178,7 @@ class MessageType(IntEnum):
CardanoPoolRelayParameters = 329 CardanoPoolRelayParameters = 329
CardanoGetNativeScriptHash = 330 CardanoGetNativeScriptHash = 330
CardanoNativeScriptHash = 331 CardanoNativeScriptHash = 331
CardanoTxMint = 332
RippleGetAddress = 400 RippleGetAddress = 400
RippleAddress = 401 RippleAddress = 401
RippleSignTx = 402 RippleSignTx = 402
@ -380,6 +381,7 @@ class CardanoTxAuxiliaryDataSupplementType(IntEnum):
class CardanoTxSigningMode(IntEnum): class CardanoTxSigningMode(IntEnum):
ORDINARY_TRANSACTION = 0 ORDINARY_TRANSACTION = 0
POOL_REGISTRATION_AS_OWNER = 1 POOL_REGISTRATION_AS_OWNER = 1
MULTISIG_TRANSACTION = 2
class CardanoTxWitnessType(IntEnum): class CardanoTxWitnessType(IntEnum):
@ -2072,6 +2074,7 @@ class CardanoSignTxInit(protobuf.MessageType):
10: protobuf.Field("has_auxiliary_data", "bool", repeated=False, required=True), 10: protobuf.Field("has_auxiliary_data", "bool", repeated=False, required=True),
11: protobuf.Field("validity_interval_start", "uint64", repeated=False, required=False), 11: protobuf.Field("validity_interval_start", "uint64", repeated=False, required=False),
12: protobuf.Field("witness_requests_count", "uint32", repeated=False, required=True), 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__( def __init__(
@ -2087,6 +2090,7 @@ class CardanoSignTxInit(protobuf.MessageType):
withdrawals_count: "int", withdrawals_count: "int",
has_auxiliary_data: "bool", has_auxiliary_data: "bool",
witness_requests_count: "int", witness_requests_count: "int",
minting_asset_groups_count: "int",
ttl: Optional["int"] = None, ttl: Optional["int"] = None,
validity_interval_start: Optional["int"] = None, validity_interval_start: Optional["int"] = None,
) -> None: ) -> None:
@ -2100,6 +2104,7 @@ class CardanoSignTxInit(protobuf.MessageType):
self.withdrawals_count = withdrawals_count self.withdrawals_count = withdrawals_count
self.has_auxiliary_data = has_auxiliary_data self.has_auxiliary_data = has_auxiliary_data
self.witness_requests_count = witness_requests_count self.witness_requests_count = witness_requests_count
self.minting_asset_groups_count = minting_asset_groups_count
self.ttl = ttl self.ttl = ttl
self.validity_interval_start = validity_interval_start self.validity_interval_start = validity_interval_start
@ -2165,17 +2170,20 @@ class CardanoToken(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 324 MESSAGE_WIRE_TYPE = 324
FIELDS = { FIELDS = {
1: protobuf.Field("asset_name_bytes", "bytes", repeated=False, required=True), 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__( def __init__(
self, self,
*, *,
asset_name_bytes: "bytes", asset_name_bytes: "bytes",
amount: "int", amount: Optional["int"] = None,
mint_amount: Optional["int"] = None,
) -> None: ) -> None:
self.asset_name_bytes = asset_name_bytes self.asset_name_bytes = asset_name_bytes
self.amount = amount self.amount = amount
self.mint_amount = mint_amount
class CardanoPoolOwner(protobuf.MessageType): class CardanoPoolOwner(protobuf.MessageType):
@ -2292,6 +2300,7 @@ class CardanoTxCertificate(protobuf.MessageType):
2: protobuf.Field("path", "uint32", repeated=True, required=False), 2: protobuf.Field("path", "uint32", repeated=True, required=False),
3: protobuf.Field("pool", "bytes", repeated=False, required=False), 3: protobuf.Field("pool", "bytes", repeated=False, required=False),
4: protobuf.Field("pool_parameters", "CardanoPoolParametersType", 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__( def __init__(
@ -2301,11 +2310,13 @@ class CardanoTxCertificate(protobuf.MessageType):
path: Optional[List["int"]] = None, path: Optional[List["int"]] = None,
pool: Optional["bytes"] = None, pool: Optional["bytes"] = None,
pool_parameters: Optional["CardanoPoolParametersType"] = None, pool_parameters: Optional["CardanoPoolParametersType"] = None,
script_hash: Optional["bytes"] = None,
) -> None: ) -> None:
self.path = path if path is not None else [] self.path = path if path is not None else []
self.type = type self.type = type
self.pool = pool self.pool = pool
self.pool_parameters = pool_parameters self.pool_parameters = pool_parameters
self.script_hash = script_hash
class CardanoTxWithdrawal(protobuf.MessageType): class CardanoTxWithdrawal(protobuf.MessageType):
@ -2313,6 +2324,7 @@ class CardanoTxWithdrawal(protobuf.MessageType):
FIELDS = { FIELDS = {
1: protobuf.Field("path", "uint32", repeated=True, required=False), 1: protobuf.Field("path", "uint32", repeated=True, required=False),
2: protobuf.Field("amount", "uint64", repeated=False, required=True), 2: protobuf.Field("amount", "uint64", repeated=False, required=True),
3: protobuf.Field("script_hash", "bytes", repeated=False, required=False),
} }
def __init__( def __init__(
@ -2320,9 +2332,11 @@ class CardanoTxWithdrawal(protobuf.MessageType):
*, *,
amount: "int", amount: "int",
path: Optional[List["int"]] = None, path: Optional[List["int"]] = None,
script_hash: Optional["bytes"] = None,
) -> None: ) -> None:
self.path = path if path is not None else [] self.path = path if path is not None else []
self.amount = amount self.amount = amount
self.script_hash = script_hash
class CardanoCatalystRegistrationParametersType(protobuf.MessageType): class CardanoCatalystRegistrationParametersType(protobuf.MessageType):
@ -2365,6 +2379,20 @@ class CardanoTxAuxiliaryData(protobuf.MessageType):
self.hash = hash 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): class CardanoTxItemAck(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 313 MESSAGE_WIRE_TYPE = 313

View File

@ -31,6 +31,7 @@ pytestmark = [
@parametrize_using_common_fixtures( @parametrize_using_common_fixtures(
"cardano/sign_tx_stake_pool_registration.json", "cardano/sign_tx_stake_pool_registration.json",
"cardano/sign_tx.json", "cardano/sign_tx.json",
"cardano/sign_tx.multisig.json",
"cardano/sign_tx.slip39.json", "cardano/sign_tx.slip39.json",
) )
def test_cardano_sign_tx(client, parameters, result): 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"]] certificates = [cardano.parse_certificate(c) for c in parameters["certificates"]]
withdrawals = [cardano.parse_withdrawal(w) for w in parameters["withdrawals"]] withdrawals = [cardano.parse_withdrawal(w) for w in parameters["withdrawals"]]
auxiliary_data = cardano.parse_auxiliary_data(parameters["auxiliary_data"]) 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": if parameters.get("security_checks") == "prompt":
device.apply_settings( device.apply_settings(
@ -62,12 +68,16 @@ def test_cardano_sign_tx(client, parameters, result):
protocol_magic=parameters["protocol_magic"], protocol_magic=parameters["protocol_magic"],
network_id=parameters["network_id"], network_id=parameters["network_id"],
auxiliary_data=auxiliary_data, auxiliary_data=auxiliary_data,
mint=mint,
additional_witness_requests=additional_witness_requests,
) )
assert response == _transform_expected_result(result) assert response == _transform_expected_result(result)
@parametrize_using_common_fixtures( @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): def test_cardano_sign_tx_failed(client, parameters, result):
signing_mode = messages.CardanoTxSigningMode.__members__[parameters["signing_mode"]] 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"]] certificates = [cardano.parse_certificate(c) for c in parameters["certificates"]]
withdrawals = [cardano.parse_withdrawal(w) for w in parameters["withdrawals"]] withdrawals = [cardano.parse_withdrawal(w) for w in parameters["withdrawals"]]
auxiliary_data = cardano.parse_auxiliary_data(parameters["auxiliary_data"]) 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 client:
with pytest.raises(TrezorFailure, match=result["error_message"]): 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"], protocol_magic=parameters["protocol_magic"],
network_id=parameters["network_id"], network_id=parameters["network_id"],
auxiliary_data=auxiliary_data, auxiliary_data=auxiliary_data,
mint=mint,
additional_witness_requests=additional_witness_requests,
) )