mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-12 16:30:56 +00:00
Add support for stakepool registration to Cardano
This commit is contained in:
parent
bf524854cd
commit
b261f789f3
@ -29,6 +29,13 @@ enum CardanoCertificateType {
|
||||
STAKE_REGISTRATION = 0;
|
||||
STAKE_DEREGISTRATION = 1;
|
||||
STAKE_DELEGATION = 2;
|
||||
STAKE_POOL_REGISTRATION = 3;
|
||||
}
|
||||
|
||||
enum CardanoPoolRelayType {
|
||||
SINGLE_HOST_IP = 0;
|
||||
SINGLE_HOST_NAME = 1;
|
||||
MULTIPLE_HOST_NAME = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,13 +145,58 @@ message CardanoSignTx {
|
||||
optional uint64 amount = 3; // amount to spend
|
||||
optional CardanoAddressParametersType address_parameters = 4; // parameters used to derive the address
|
||||
}
|
||||
|
||||
/**
|
||||
* Stake pool owner parameters
|
||||
*/
|
||||
message CardanoPoolOwnerType {
|
||||
repeated uint32 staking_key_path = 1; // BIP-32-style path to derive staking key of the owner
|
||||
optional bytes staking_key_hash = 2; // owner's staking key if it is an external owner
|
||||
}
|
||||
|
||||
/**
|
||||
* Stake pool relay parameters
|
||||
*/
|
||||
message CardanoPoolRelayParametersType {
|
||||
required CardanoPoolRelayType type = 1; // pool relay type
|
||||
optional bytes ipv4_address = 2; // ipv4 address of the relay given as 4 bytes
|
||||
optional bytes ipv6_address = 3; // ipv6 address of the relay given as 16 bytes
|
||||
optional string host_name = 4; // relay host name given as URL, at most 64 characters
|
||||
optional uint32 port = 5; // relay port number in the range 0-65535
|
||||
}
|
||||
|
||||
/**
|
||||
* Stake pool metadata parameters
|
||||
*/
|
||||
message CardanoPoolMetadataType {
|
||||
required string url = 1; // stake pool url hosting metadata, at most 64 characters
|
||||
required bytes hash = 2; // stake pool metadata hash
|
||||
}
|
||||
|
||||
/**
|
||||
* Stake pool parameters
|
||||
*/
|
||||
message CardanoPoolParametersType {
|
||||
required bytes pool_id = 1; // stake pool cold public key hash (28 bytes)
|
||||
required bytes vrf_key_hash = 2; // VRF key hash (32 bytes)
|
||||
required uint64 pledge = 3; // pledge amount in lovelace
|
||||
required uint64 cost = 4; // cost in lovelace
|
||||
required uint64 margin_numerator = 5; // pool margin numerator
|
||||
required uint64 margin_denominator = 6; // pool margin denominator
|
||||
required string reward_account = 7; // bech32 reward address where the pool receives rewards
|
||||
repeated CardanoPoolOwnerType owners = 8; // pool owners list
|
||||
repeated CardanoPoolRelayParametersType relays = 9; // pool relays list
|
||||
optional CardanoPoolMetadataType metadata = 10; // pool metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing cardano transaction certificate
|
||||
*/
|
||||
message CardanoTxCertificateType {
|
||||
optional CardanoCertificateType type = 1; // certificate type
|
||||
repeated uint32 path = 2; // BIP-32 path to derive (staking) key
|
||||
optional bytes pool = 3; // pool hash
|
||||
message CardanoTxCertificateType {
|
||||
optional CardanoCertificateType type = 1; // certificate type
|
||||
repeated uint32 path = 2; // BIP-32 path to derive (staking) key
|
||||
optional bytes pool = 3; // pool hash
|
||||
optional CardanoPoolParametersType pool_parameters = 4; // used for stake pool registration certificate
|
||||
}
|
||||
/**
|
||||
* Structure representing cardano transaction withdrawals
|
||||
|
459
common/tests/fixtures/cardano/sign_tx_stake_pool_registration.failed.json
vendored
Normal file
459
common/tests/fixtures/cardano/sign_tx_stake_pool_registration.failed.json
vendored
Normal file
@ -0,0 +1,459 @@
|
||||
{
|
||||
"setup": {
|
||||
"mnemonic": "all all all all all all all all all all all all",
|
||||
"passphrase": ""
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Missing owner with path",
|
||||
"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_hash": "3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c490711"
|
||||
}
|
||||
],
|
||||
"relays": [],
|
||||
"metadata": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "Invalid certificate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Two owners with path",
|
||||
"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_path": "m/1852'/1815'/0'/2/0"
|
||||
}
|
||||
],
|
||||
"relays": [],
|
||||
"metadata": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "Invalid certificate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Invalid pool id",
|
||||
"parameters": {
|
||||
"protocol_magic": 764824073,
|
||||
"network_id": 1,
|
||||
"fee": 42,
|
||||
"ttl": 10,
|
||||
"certificates": [
|
||||
{
|
||||
"type": 3,
|
||||
"pool_parameters": {
|
||||
"pool_id": "deadbeef",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"relays": [],
|
||||
"metadata": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "Invalid certificate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Margin higher than 1",
|
||||
"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": 2,
|
||||
"denominator": 1
|
||||
},
|
||||
"reward_account": "stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
|
||||
"owners": [
|
||||
{
|
||||
"staking_key_path": "m/1852'/1815'/0'/2/0"
|
||||
}
|
||||
],
|
||||
"relays": [],
|
||||
"metadata": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "Invalid certificate"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Contains other certificates",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"relays": [],
|
||||
"metadata": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 0,
|
||||
"path": "m/1852'/1815'/0'/2/0"
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "Stakepool registration transaction cannot contain other certificates nor withdrawals"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Contains withdrawal",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"relays": [],
|
||||
"metadata": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [
|
||||
{
|
||||
"path": "m/1852'/1815'/0'/2/0",
|
||||
"amount": "1000"
|
||||
}
|
||||
],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "Stakepool registration transaction cannot contain other certificates nor withdrawals"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "All tx inputs must be external (without path)",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"relays": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"path": "m/1852'/1815'/0'/0/0",
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "Stakepool registration transaction can contain only external inputs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Pool reward address belongs to different network than the tx",
|
||||
"parameters": {
|
||||
"protocol_magic": 42,
|
||||
"network_id": 0,
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"relays": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr_test1vr9s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcfqqtmut0e",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "ProcessError: Invalid address"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Pool reward address is a base address",
|
||||
"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": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"owners": [
|
||||
{
|
||||
"staking_key_path": "m/1852'/1815'/0'/2/0"
|
||||
}
|
||||
],
|
||||
"relays": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"inputs": [
|
||||
{
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"error_message": "ProcessError: Invalid address"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
194
common/tests/fixtures/cardano/sign_tx_stake_pool_registration.json
vendored
Normal file
194
common/tests/fixtures/cardano/sign_tx_stake_pool_registration.json
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
{
|
||||
"setup": {
|
||||
"mnemonic": "all all all all all all all all all all all all",
|
||||
"passphrase": ""
|
||||
},
|
||||
"tests": [
|
||||
{
|
||||
"description": "Sample stake pool registration certificate",
|
||||
"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": [],
|
||||
"metadata": "",
|
||||
"input_flow": [["SWIPE", "SWIPE", "SWIPE", "YES"], ["SWIPE", "YES"], ["SWIPE", "YES"], ["YES"]],
|
||||
"inputs": [
|
||||
{
|
||||
"path": null,
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"tx_hash": "e3b9a5657bf62609465a930c8359d774c73944973cfc5a104a0f0ed1e1e8db21",
|
||||
"serialized_tx": "83a500818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018182583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff0102182a030a04818a03581cf61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb49735820198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d06401a1dcd65001a1443fd00d81e820102581de13a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c49071182581c122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b4277581c3a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c4907118584001904d244c0a8000150b80d01200000a3852e8a00003473700384001904d2f650b80d01200000a3852e8a00003473700384001904d244c0a80001f683011904d26d7777772e746573742e7465737482026e7777772e74657374322e74657374827568747470733a2f2f7777772e746573742e746573745820914c57c1f12bbf4a82b12d977d4f274674856a11ed4b9b95bd70f5d41c5064a6a10081825820bc65be1b0b9d7531778a1317c2aa6de936963c3f9ac7d5ee9e9eda25e0c97c5e584006305b52f76d2d2da6925c02036a9a28456976009f8c6432513f273110d09ea26db79c696cec322b010e5cbb7d90a6b473b157e65df846a1487062569a5f5a04f6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Stake pool registration certificate with no pool metadata",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"relays": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"input_flow": [["SWIPE", "SWIPE", "SWIPE", "YES"], ["YES"], ["YES"], ["YES"]],
|
||||
"inputs": [
|
||||
{
|
||||
"path": null,
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"tx_hash": "504f9214142996e0b7e315103b25d88a4afa3d01dd5be22376921b52b01483c3",
|
||||
"serialized_tx": "83a500818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018182583901eb0baa5e570cffbe2934db29df0b6a3d7c0430ee65d4c3a7ab2fefb91bc428e4720702ebd5dab4fb175324c192dc9bb76cc5da956e3c8dff0102182a030a04818a03581cf61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb49735820198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d06401a1dcd65001a1443fd00d81e820102581de13a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c49071181581c122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b427780f6a10081825820bc65be1b0b9d7531778a1317c2aa6de936963c3f9ac7d5ee9e9eda25e0c97c5e5840aa2099208399fcc27c18d7ef0c7e873f9e22f0935b7e912cddd34b33b8cafd541a878dc01c042ce490e4c9bad3c62c2f59acaa009d336c9ff875c5f153d34900f6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Stake pool registration on testnet",
|
||||
"parameters": {
|
||||
"protocol_magic": 42,
|
||||
"network_id": 0,
|
||||
"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": "stake_test1uqa87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygfvlkaz",
|
||||
"owners": [
|
||||
{
|
||||
"staking_key_path": "m/1852'/1815'/0'/2/0"
|
||||
}
|
||||
],
|
||||
"relays": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"withdrawals": [],
|
||||
"metadata": "",
|
||||
"input_flow": [["SWIPE", "SWIPE", "SWIPE", "YES"], ["YES"], ["YES"], ["YES"]],
|
||||
"inputs": [
|
||||
{
|
||||
"path": null,
|
||||
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
|
||||
"prev_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"address": "addr_test1vr9s8py7y68e3x66sscs0wkhlg5ssfrfs65084jrlrqcfqqtmut0e",
|
||||
"amount": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"result": {
|
||||
"tx_hash": "12921b4f8e77f815e0c8ed97c541fbd5ba38a6d3323f4ff1af0cb934b8ac6b39",
|
||||
"serialized_tx": "83a500818258203b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b700018182581d60cb03849e268f989b5a843107bad7fa2908246986a8f3d643f8c184800102182a030a04818a03581cf61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb49735820198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d06401a1dcd65001a1443fd00d81e820102581de03a7f09d3df4cf66a7399c2b05bfa234d5a29560c311fc5db4c49071181581c122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b427780f6a10081825820bc65be1b0b9d7531778a1317c2aa6de936963c3f9ac7d5ee9e9eda25e0c97c5e584027cab81902d04b2491d7aa2bf57bd9db59d33c2df1502dae0412d5225c6b0b8f7b057de6a7e7eae25016ed6ea1f6e6239fb36a285216c6ee4a3cb3376287a300f6"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -4,7 +4,7 @@ from trezor.messages import CardanoAddressParametersType, CardanoAddressType
|
||||
|
||||
from apps.common.seed import remove_ed25519_prefix
|
||||
|
||||
from .byron_address import derive_byron_address, validate_output_byron_address
|
||||
from .byron_address import derive_byron_address, validate_byron_address
|
||||
from .helpers import INVALID_ADDRESS, NETWORK_MISMATCH, bech32, network_ids
|
||||
from .helpers.paths import SCHEMA_STAKING
|
||||
from .helpers.utils import variable_length_encode
|
||||
@ -32,7 +32,13 @@ MIN_ADDRESS_BYTES_LENGTH = 29
|
||||
MAX_ADDRESS_BYTES_LENGTH = 65
|
||||
|
||||
|
||||
def validate_output_address(address: str, protocol_magic: int, network_id: int) -> None:
|
||||
def _validate_address_and_get_type(
|
||||
address: str, protocol_magic: int, network_id: int
|
||||
) -> int:
|
||||
"""
|
||||
Validates Cardano address and returns its type
|
||||
for the convenience of outward-facing functions.
|
||||
"""
|
||||
if address is None or len(address) == 0:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
@ -40,12 +46,31 @@ def validate_output_address(address: str, protocol_magic: int, network_id: int)
|
||||
address_type = _get_address_type(address_bytes)
|
||||
|
||||
if address_type == CardanoAddressType.BYRON:
|
||||
validate_output_byron_address(address_bytes, protocol_magic)
|
||||
validate_byron_address(address_bytes, protocol_magic)
|
||||
elif address_type in ADDRESS_TYPES_SHELLEY:
|
||||
_validate_output_shelley_address(address, address_bytes, network_id)
|
||||
_validate_shelley_address(address, address_bytes, network_id)
|
||||
else:
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
return address_type
|
||||
|
||||
|
||||
def validate_output_address(address: str, protocol_magic: int, network_id: int) -> None:
|
||||
address_type = _validate_address_and_get_type(address, protocol_magic, network_id)
|
||||
|
||||
if address_type in (CardanoAddressType.REWARD, CardanoAddressType.REWARD_SCRIPT):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
|
||||
def validate_reward_address(address: str, protocol_magic: int, network_id: int) -> None:
|
||||
address_type = _validate_address_and_get_type(address, protocol_magic, network_id)
|
||||
|
||||
if address_type not in (
|
||||
CardanoAddressType.REWARD,
|
||||
CardanoAddressType.REWARD_SCRIPT,
|
||||
):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
|
||||
def get_address_bytes_unsafe(address: str) -> bytes:
|
||||
try:
|
||||
@ -63,19 +88,13 @@ def _get_address_type(address: bytes) -> int:
|
||||
return address[0] >> 4
|
||||
|
||||
|
||||
def _validate_output_shelley_address(
|
||||
def _validate_shelley_address(
|
||||
address_str: str, address_bytes: bytes, network_id: int
|
||||
) -> None:
|
||||
address_type = _get_address_type(address_bytes)
|
||||
# reward address cannot be an output address
|
||||
if (
|
||||
address_type == CardanoAddressType.REWARD
|
||||
or address_type == CardanoAddressType.REWARD_SCRIPT
|
||||
):
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
_validate_address_size(address_bytes, address_type)
|
||||
_validate_output_address_bech32_hrp(address_str, address_type, network_id)
|
||||
_validate_address_bech32_hrp(address_str, address_type, network_id)
|
||||
_validate_address_network_id(address_bytes, network_id)
|
||||
|
||||
|
||||
@ -86,7 +105,7 @@ def _validate_address_size(
|
||||
raise INVALID_ADDRESS
|
||||
|
||||
|
||||
def _validate_output_address_bech32_hrp(
|
||||
def _validate_address_bech32_hrp(
|
||||
address_str: str, address_type: EnumTypeCardanoAddressType, network_id: int
|
||||
) -> None:
|
||||
valid_hrp = _get_bech32_hrp_for_address(address_type, network_id)
|
||||
@ -136,14 +155,22 @@ def derive_human_readable_address(
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> str:
|
||||
address = derive_address_bytes(keychain, parameters, protocol_magic, network_id)
|
||||
address_bytes = derive_address_bytes(
|
||||
keychain, parameters, protocol_magic, network_id
|
||||
)
|
||||
|
||||
address_type = _get_address_type(address)
|
||||
return encode_human_readable_address(address_bytes)
|
||||
|
||||
|
||||
def encode_human_readable_address(address_bytes: bytes) -> str:
|
||||
address_type = _get_address_type(address_bytes)
|
||||
if address_type == CardanoAddressType.BYRON:
|
||||
return base58.encode(address)
|
||||
return base58.encode(address_bytes)
|
||||
elif address_type in ADDRESS_TYPES_SHELLEY:
|
||||
hrp = _get_bech32_hrp_for_address(_get_address_type(address), network_id)
|
||||
return bech32.encode(hrp, address)
|
||||
hrp = _get_bech32_hrp_for_address(
|
||||
address_type, _get_address_network_id(address_bytes)
|
||||
)
|
||||
return bech32.encode(hrp, address_bytes)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
@ -293,7 +320,18 @@ def _derive_reward_address(
|
||||
if not SCHEMA_STAKING.match(path):
|
||||
raise wire.DataError("Invalid path for reward address!")
|
||||
|
||||
header = _create_address_header(CardanoAddressType.REWARD, network_id)
|
||||
staking_key_hash = get_public_key_hash(keychain, path)
|
||||
|
||||
return pack_reward_address_bytes(staking_key_hash, network_id)
|
||||
|
||||
|
||||
def pack_reward_address_bytes(
|
||||
staking_key_hash: bytes,
|
||||
network_id: int,
|
||||
) -> bytes:
|
||||
"""
|
||||
Helper function to transform raw staking key hash into reward address
|
||||
"""
|
||||
header = _create_address_header(CardanoAddressType.REWARD, network_id)
|
||||
|
||||
return header + staking_key_hash
|
||||
|
@ -52,7 +52,7 @@ def get_address_attributes(protocol_magic: int) -> dict:
|
||||
return address_attributes
|
||||
|
||||
|
||||
def validate_output_byron_address(address: bytes, protocol_magic: int) -> None:
|
||||
def validate_byron_address(address: bytes, protocol_magic: int) -> None:
|
||||
address_data_encoded = _decode_address_raw(address)
|
||||
_validate_address_data_protocol_magic(address_data_encoded, protocol_magic)
|
||||
|
||||
|
258
core/src/apps/cardano/certificates.py
Normal file
258
core/src/apps/cardano/certificates.py
Normal file
@ -0,0 +1,258 @@
|
||||
from trezor.messages import CardanoCertificateType, CardanoPoolRelayType
|
||||
|
||||
from apps.common import cbor
|
||||
|
||||
from .address import (
|
||||
get_address_bytes_unsafe,
|
||||
get_public_key_hash,
|
||||
validate_reward_address,
|
||||
)
|
||||
from .helpers import INVALID_CERTIFICATE, LOVELACE_MAX_SUPPLY
|
||||
from .helpers.paths import SCHEMA_STAKING
|
||||
|
||||
if False:
|
||||
from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType
|
||||
from trezor.messages.CardanoPoolParametersType import CardanoPoolParametersType
|
||||
from trezor.messages.CardanoPoolRelayParametersType import (
|
||||
CardanoPoolRelayParametersType,
|
||||
)
|
||||
from trezor.messages.CardanoPoolOwnerType import CardanoPoolOwnerType
|
||||
from trezor.messages.CardanoPoolMetadataType import CardanoPoolMetadataType
|
||||
from typing import List, Optional, Union, Tuple, Any
|
||||
from . import seed
|
||||
|
||||
CborSequence = Union[List[Any], Tuple[Any, ...]]
|
||||
|
||||
POOL_HASH_SIZE = 28
|
||||
VRF_KEY_HASH_SIZE = 32
|
||||
POOL_METADATA_HASH_SIZE = 32
|
||||
PUBLIC_KEY_HASH_SIZE = 28
|
||||
IPV4_ADDRESS_SIZE = 4
|
||||
IPV6_ADDRESS_SIZE = 16
|
||||
|
||||
MAX_URL_LENGTH = 64
|
||||
MAX_PORT_NUMBER = 65535
|
||||
|
||||
|
||||
def validate_certificate(
|
||||
certificate: CardanoTxCertificateType, protocol_magic: int, network_id: int
|
||||
) -> None:
|
||||
if certificate.type in (
|
||||
CardanoCertificateType.STAKE_DELEGATION,
|
||||
CardanoCertificateType.STAKE_REGISTRATION,
|
||||
CardanoCertificateType.STAKE_DEREGISTRATION,
|
||||
):
|
||||
if not SCHEMA_STAKING.match(certificate.path):
|
||||
raise INVALID_CERTIFICATE
|
||||
|
||||
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
|
||||
if not certificate.pool or len(certificate.pool) != POOL_HASH_SIZE:
|
||||
raise INVALID_CERTIFICATE
|
||||
|
||||
if certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
|
||||
if certificate.pool_parameters is None:
|
||||
raise INVALID_CERTIFICATE
|
||||
_validate_pool_parameters(
|
||||
certificate.pool_parameters, protocol_magic, network_id
|
||||
)
|
||||
|
||||
|
||||
def cborize_certificate(
|
||||
keychain: seed.Keychain, certificate: CardanoTxCertificateType
|
||||
) -> CborSequence:
|
||||
if certificate.type in (
|
||||
CardanoCertificateType.STAKE_REGISTRATION,
|
||||
CardanoCertificateType.STAKE_DEREGISTRATION,
|
||||
):
|
||||
return (
|
||||
certificate.type,
|
||||
(0, get_public_key_hash(keychain, certificate.path)),
|
||||
)
|
||||
elif certificate.type == CardanoCertificateType.STAKE_DELEGATION:
|
||||
return (
|
||||
certificate.type,
|
||||
(0, get_public_key_hash(keychain, certificate.path)),
|
||||
certificate.pool,
|
||||
)
|
||||
elif certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
|
||||
pool_parameters = certificate.pool_parameters
|
||||
|
||||
assert pool_parameters is not None
|
||||
|
||||
return (
|
||||
certificate.type,
|
||||
pool_parameters.pool_id,
|
||||
pool_parameters.vrf_key_hash,
|
||||
pool_parameters.pledge,
|
||||
pool_parameters.cost,
|
||||
cbor.Tagged(
|
||||
30,
|
||||
(
|
||||
pool_parameters.margin_numerator,
|
||||
pool_parameters.margin_denominator,
|
||||
),
|
||||
),
|
||||
# this relies on pool_parameters.reward_account being validated beforehand
|
||||
# in _validate_pool_parameters
|
||||
get_address_bytes_unsafe(pool_parameters.reward_account),
|
||||
_cborize_pool_owners(keychain, pool_parameters.owners),
|
||||
_cborize_pool_relays(pool_parameters.relays),
|
||||
_cborize_pool_metadata(pool_parameters.metadata),
|
||||
)
|
||||
else:
|
||||
raise INVALID_CERTIFICATE
|
||||
|
||||
|
||||
def assert_certificate_cond(condition: bool) -> None:
|
||||
if not condition:
|
||||
raise INVALID_CERTIFICATE
|
||||
|
||||
|
||||
def _validate_pool_parameters(
|
||||
pool_parameters: CardanoPoolParametersType, protocol_magic: int, network_id: int
|
||||
) -> None:
|
||||
assert_certificate_cond(len(pool_parameters.pool_id) == POOL_HASH_SIZE)
|
||||
assert_certificate_cond(len(pool_parameters.vrf_key_hash) == VRF_KEY_HASH_SIZE)
|
||||
assert_certificate_cond(0 <= pool_parameters.pledge <= LOVELACE_MAX_SUPPLY)
|
||||
assert_certificate_cond(0 <= pool_parameters.cost <= LOVELACE_MAX_SUPPLY)
|
||||
assert_certificate_cond(pool_parameters.margin_numerator > 0)
|
||||
assert_certificate_cond(pool_parameters.margin_denominator > 0)
|
||||
assert_certificate_cond(
|
||||
pool_parameters.margin_numerator <= pool_parameters.margin_denominator
|
||||
)
|
||||
assert_certificate_cond(len(pool_parameters.owners) > 0)
|
||||
|
||||
validate_reward_address(pool_parameters.reward_account, protocol_magic, network_id)
|
||||
|
||||
for pool_relay in pool_parameters.relays:
|
||||
_validate_pool_relay(pool_relay)
|
||||
|
||||
_validate_pool_owners(pool_parameters.owners)
|
||||
|
||||
if pool_parameters.metadata:
|
||||
_validate_pool_metadata(pool_parameters.metadata)
|
||||
|
||||
|
||||
def _validate_pool_owners(owners: List[CardanoPoolOwnerType]) -> None:
|
||||
owners_as_path_count = 0
|
||||
for owner in owners:
|
||||
assert_certificate_cond(
|
||||
owner.staking_key_hash is not None or owner.staking_key_path is not None
|
||||
)
|
||||
if owner.staking_key_hash is not None:
|
||||
assert_certificate_cond(len(owner.staking_key_hash) == PUBLIC_KEY_HASH_SIZE)
|
||||
if owner.staking_key_path:
|
||||
assert_certificate_cond(SCHEMA_STAKING.match(owner.staking_key_path))
|
||||
|
||||
if owner.staking_key_path:
|
||||
owners_as_path_count += 1
|
||||
|
||||
assert_certificate_cond(owners_as_path_count == 1)
|
||||
|
||||
|
||||
def _validate_pool_relay(pool_relay: CardanoPoolRelayParametersType) -> None:
|
||||
if pool_relay.type == CardanoPoolRelayType.SINGLE_HOST_IP:
|
||||
assert_certificate_cond(
|
||||
pool_relay.ipv4_address is not None or pool_relay.ipv6_address is not None
|
||||
)
|
||||
if pool_relay.ipv4_address is not None:
|
||||
assert_certificate_cond(len(pool_relay.ipv4_address) == IPV4_ADDRESS_SIZE)
|
||||
if pool_relay.ipv6_address is not None:
|
||||
assert_certificate_cond(len(pool_relay.ipv6_address) == IPV6_ADDRESS_SIZE)
|
||||
assert_certificate_cond(
|
||||
pool_relay.port is not None and 0 <= pool_relay.port <= MAX_PORT_NUMBER
|
||||
)
|
||||
elif pool_relay.type == CardanoPoolRelayType.SINGLE_HOST_NAME:
|
||||
assert_certificate_cond(
|
||||
pool_relay.host_name is not None
|
||||
and len(pool_relay.host_name) <= MAX_URL_LENGTH
|
||||
)
|
||||
assert_certificate_cond(
|
||||
pool_relay.port is not None and 0 <= pool_relay.port <= MAX_PORT_NUMBER
|
||||
)
|
||||
elif pool_relay.type == CardanoPoolRelayType.MULTIPLE_HOST_NAME:
|
||||
assert_certificate_cond(
|
||||
pool_relay.host_name is not None
|
||||
and len(pool_relay.host_name) <= MAX_URL_LENGTH
|
||||
)
|
||||
else:
|
||||
raise INVALID_CERTIFICATE
|
||||
|
||||
|
||||
def _validate_pool_metadata(pool_metadata: CardanoPoolMetadataType) -> None:
|
||||
assert_certificate_cond(len(pool_metadata.url) <= MAX_URL_LENGTH)
|
||||
assert_certificate_cond(len(pool_metadata.hash) == POOL_METADATA_HASH_SIZE)
|
||||
assert_certificate_cond(all((32 <= ord(c) < 127) for c in pool_metadata.url))
|
||||
|
||||
|
||||
def _cborize_pool_owners(
|
||||
keychain: seed.Keychain, pool_owners: List[CardanoPoolOwnerType]
|
||||
) -> List[bytes]:
|
||||
result = []
|
||||
|
||||
for pool_owner in pool_owners:
|
||||
if pool_owner.staking_key_path:
|
||||
result.append(get_public_key_hash(keychain, pool_owner.staking_key_path))
|
||||
elif pool_owner.staking_key_hash:
|
||||
result.append(pool_owner.staking_key_hash)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _cborize_ipv6_address(ipv6_address: Optional[bytes]) -> Optional[bytes]:
|
||||
if ipv6_address is None:
|
||||
return None
|
||||
|
||||
# ipv6 addresses are serialized to CBOR as uint_32[4] little endian
|
||||
assert len(ipv6_address) == IPV6_ADDRESS_SIZE
|
||||
|
||||
result = b""
|
||||
for i in range(0, 4):
|
||||
result += bytes(reversed(ipv6_address[i * 4 : i * 4 + 4]))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _cborize_pool_relays(
|
||||
pool_relays: List[CardanoPoolRelayParametersType],
|
||||
) -> List[CborSequence]:
|
||||
result: List[CborSequence] = []
|
||||
|
||||
for pool_relay in pool_relays:
|
||||
if pool_relay.type == CardanoPoolRelayType.SINGLE_HOST_IP:
|
||||
result.append(
|
||||
(
|
||||
pool_relay.type,
|
||||
pool_relay.port,
|
||||
pool_relay.ipv4_address,
|
||||
_cborize_ipv6_address(pool_relay.ipv6_address),
|
||||
)
|
||||
)
|
||||
elif pool_relay.type == CardanoPoolRelayType.SINGLE_HOST_NAME:
|
||||
result.append(
|
||||
(
|
||||
pool_relay.type,
|
||||
pool_relay.port,
|
||||
pool_relay.host_name,
|
||||
)
|
||||
)
|
||||
elif pool_relay.type == CardanoPoolRelayType.MULTIPLE_HOST_NAME:
|
||||
result.append(
|
||||
(
|
||||
pool_relay.type,
|
||||
pool_relay.host_name,
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _cborize_pool_metadata(
|
||||
pool_metadata: Optional[CardanoPoolMetadataType],
|
||||
) -> Optional[CborSequence]:
|
||||
if not pool_metadata:
|
||||
return None
|
||||
|
||||
return (pool_metadata.url, pool_metadata.hash)
|
@ -5,3 +5,11 @@ NETWORK_MISMATCH = wire.ProcessError("Output address network mismatch!")
|
||||
INVALID_CERTIFICATE = wire.ProcessError("Invalid certificate")
|
||||
INVALID_WITHDRAWAL = wire.ProcessError("Invalid withdrawal")
|
||||
INVALID_METADATA = wire.ProcessError("Invalid metadata")
|
||||
INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE = wire.ProcessError(
|
||||
"Stakepool registration transaction cannot contain other certificates nor withdrawals"
|
||||
)
|
||||
INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS = wire.ProcessError(
|
||||
"Stakepool registration transaction can contain only external inputs"
|
||||
)
|
||||
|
||||
LOVELACE_MAX_SUPPLY = 45_000_000_000 * 1_000_000
|
||||
|
@ -8,7 +8,7 @@ from .utils import to_account_path
|
||||
if False:
|
||||
from typing import List
|
||||
from trezor.messages import CardanoAddressParametersType
|
||||
from . import seed
|
||||
from .. import seed
|
||||
|
||||
|
||||
"""
|
||||
|
@ -6,6 +6,8 @@ from trezor.messages import (
|
||||
ButtonRequestType,
|
||||
CardanoAddressType,
|
||||
CardanoCertificateType,
|
||||
CardanoPoolMetadataType,
|
||||
CardanoPoolOwnerType,
|
||||
)
|
||||
from trezor.strings import format_amount
|
||||
from trezor.ui.button import ButtonDefault
|
||||
@ -16,16 +18,23 @@ from trezor.utils import chunks
|
||||
from apps.common.confirm import confirm, require_confirm, require_hold_to_confirm
|
||||
from apps.common.layout import address_n_to_str, show_warning
|
||||
|
||||
from . import seed
|
||||
from .address import (
|
||||
encode_human_readable_address,
|
||||
get_public_key_hash,
|
||||
pack_reward_address_bytes,
|
||||
)
|
||||
from .helpers import protocol_magics
|
||||
from .helpers.utils import to_account_path
|
||||
|
||||
if False:
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from trezor import wire
|
||||
from trezor.messages import (
|
||||
CardanoBlockchainPointerType,
|
||||
CardanoTxCertificateType,
|
||||
CardanoTxWithdrawalType,
|
||||
CardanoPoolParametersType,
|
||||
)
|
||||
from trezor.messages.CardanoAddressParametersType import EnumTypeCardanoAddressType
|
||||
|
||||
@ -42,6 +51,7 @@ CERTIFICATE_TYPE_NAMES = {
|
||||
CardanoCertificateType.STAKE_REGISTRATION: "Stake key registration",
|
||||
CardanoCertificateType.STAKE_DEREGISTRATION: "Stake key deregistration",
|
||||
CardanoCertificateType.STAKE_DELEGATION: "Stake delegation",
|
||||
CardanoCertificateType.STAKE_POOL_REGISTRATION: "Stakepool registration",
|
||||
}
|
||||
|
||||
# Maximum number of characters per line in monospace font.
|
||||
@ -52,7 +62,7 @@ def format_coin_amount(amount: int) -> str:
|
||||
return "%s %s" % (format_amount(amount, 6), "ADA")
|
||||
|
||||
|
||||
async def confirm_sending(ctx: wire.Context, amount: int, to: str):
|
||||
async def confirm_sending(ctx: wire.Context, amount: int, to: str) -> None:
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Confirm sending:")
|
||||
page1.bold(format_coin_amount(amount))
|
||||
@ -68,7 +78,7 @@ async def confirm_sending(ctx: wire.Context, amount: int, to: str):
|
||||
|
||||
async def show_warning_tx_no_staking_info(
|
||||
ctx: wire.Context, address_type: EnumTypeCardanoAddressType, amount: int
|
||||
):
|
||||
) -> None:
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Change " + ADDRESS_TYPE_NAMES[address_type].lower())
|
||||
page1.normal("address has no stake")
|
||||
@ -83,7 +93,7 @@ async def show_warning_tx_pointer_address(
|
||||
ctx: wire.Context,
|
||||
pointer: CardanoBlockchainPointerType,
|
||||
amount: int,
|
||||
):
|
||||
) -> None:
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Change address has a")
|
||||
page1.normal("pointer with staking")
|
||||
@ -105,7 +115,7 @@ async def show_warning_tx_different_staking_account(
|
||||
ctx: wire.Context,
|
||||
staking_account_path: List[int],
|
||||
amount: int,
|
||||
):
|
||||
) -> None:
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Change address staking")
|
||||
page1.normal("rights do not match")
|
||||
@ -124,7 +134,7 @@ async def show_warning_tx_staking_key_hash(
|
||||
ctx: wire.Context,
|
||||
staking_key_hash: bytes,
|
||||
amount: int,
|
||||
):
|
||||
) -> None:
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Change address staking")
|
||||
page1.normal("rights do not match")
|
||||
@ -162,7 +172,11 @@ async def confirm_transaction(
|
||||
|
||||
async def confirm_certificate(
|
||||
ctx: wire.Context, certificate: CardanoTxCertificateType
|
||||
) -> bool:
|
||||
) -> None:
|
||||
# stake pool registration requires custom confirmation logic not covered
|
||||
# in this call
|
||||
assert certificate.type != CardanoCertificateType.STAKE_POOL_REGISTRATION
|
||||
|
||||
pages = []
|
||||
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
@ -181,9 +195,108 @@ async def confirm_certificate(
|
||||
await require_confirm(ctx, Paginated(pages))
|
||||
|
||||
|
||||
async def confirm_stake_pool_parameters(
|
||||
ctx: wire.Context,
|
||||
pool_parameters: CardanoPoolParametersType,
|
||||
network_id: int,
|
||||
protocol_magic: int,
|
||||
) -> None:
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Confirm:")
|
||||
page1.bold("Stake pool registration")
|
||||
page1.normal("Network:")
|
||||
page1.bold(protocol_magics.to_ui_string(protocol_magic))
|
||||
|
||||
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page2.normal("Pool id:")
|
||||
page2.bold(hexlify(pool_parameters.pool_id).decode())
|
||||
|
||||
page3 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page3.normal("Pool reward account:")
|
||||
page3.bold(pool_parameters.reward_account)
|
||||
|
||||
page4 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page4.normal("Pledge: " + format_coin_amount(pool_parameters.pledge))
|
||||
page4.normal("Cost: " + format_coin_amount(pool_parameters.cost))
|
||||
margin_percentage = (
|
||||
100.0 * pool_parameters.margin_numerator / pool_parameters.margin_denominator
|
||||
)
|
||||
percentage_formatted = ("%f" % margin_percentage).rstrip("0").rstrip(".")
|
||||
page4.normal("Margin: %s%%" % percentage_formatted)
|
||||
|
||||
await require_confirm(ctx, Paginated([page1, page2, page3, page4]))
|
||||
|
||||
|
||||
async def confirm_stake_pool_owners(
|
||||
ctx: wire.Context,
|
||||
keychain: seed.keychain,
|
||||
owners: List[CardanoPoolOwnerType],
|
||||
network_id: int,
|
||||
) -> None:
|
||||
pages = []
|
||||
for index, owner in enumerate(owners, 1):
|
||||
page = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page.normal("Pool owner #%d:" % (index))
|
||||
|
||||
if owner.staking_key_path:
|
||||
page.bold(address_n_to_str(owner.staking_key_path))
|
||||
page.normal(
|
||||
encode_human_readable_address(
|
||||
pack_reward_address_bytes(
|
||||
get_public_key_hash(keychain, owner.staking_key_path),
|
||||
network_id,
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
page.bold(
|
||||
encode_human_readable_address(
|
||||
pack_reward_address_bytes(owner.staking_key_hash, network_id)
|
||||
)
|
||||
)
|
||||
|
||||
pages.append(page)
|
||||
|
||||
await require_confirm(ctx, Paginated(pages))
|
||||
|
||||
|
||||
async def confirm_stake_pool_metadata(
|
||||
ctx: wire.Context,
|
||||
metadata: Optional[CardanoPoolMetadataType],
|
||||
) -> None:
|
||||
|
||||
if metadata is None:
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Pool has no metadata")
|
||||
page1.normal("(anonymous pool)")
|
||||
|
||||
await require_confirm(ctx, page1)
|
||||
return
|
||||
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Pool metadata url:")
|
||||
page1.bold(metadata.url)
|
||||
|
||||
page2 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page2.normal("Pool metadata hash:")
|
||||
page2.bold(hexlify(metadata.hash).decode())
|
||||
|
||||
await require_confirm(ctx, Paginated([page1, page2]))
|
||||
|
||||
|
||||
async def confirm_stake_pool_registration_final(
|
||||
ctx: wire.Context,
|
||||
) -> None:
|
||||
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Confirm signing the stake pool registration as an owner")
|
||||
|
||||
await require_hold_to_confirm(ctx, page1)
|
||||
|
||||
|
||||
async def confirm_withdrawal(
|
||||
ctx: wire.Context, withdrawal: CardanoTxWithdrawalType
|
||||
) -> bool:
|
||||
) -> None:
|
||||
page1 = Text("Confirm transaction", ui.ICON_SEND, ui.GREEN)
|
||||
page1.normal("Confirm withdrawal")
|
||||
page1.normal("for account:")
|
||||
|
@ -16,14 +16,16 @@ from .address import (
|
||||
derive_address_bytes,
|
||||
derive_human_readable_address,
|
||||
get_address_bytes_unsafe,
|
||||
get_public_key_hash,
|
||||
validate_output_address,
|
||||
)
|
||||
from .byron_address import get_address_attributes
|
||||
from .certificates import cborize_certificate, validate_certificate
|
||||
from .helpers import (
|
||||
INVALID_CERTIFICATE,
|
||||
INVALID_METADATA,
|
||||
INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE,
|
||||
INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS,
|
||||
INVALID_WITHDRAWAL,
|
||||
LOVELACE_MAX_SUPPLY,
|
||||
network_ids,
|
||||
protocol_magics,
|
||||
staking_use_cases,
|
||||
@ -33,6 +35,10 @@ from .helpers.utils import to_account_path
|
||||
from .layout import (
|
||||
confirm_certificate,
|
||||
confirm_sending,
|
||||
confirm_stake_pool_metadata,
|
||||
confirm_stake_pool_owners,
|
||||
confirm_stake_pool_parameters,
|
||||
confirm_stake_pool_registration_final,
|
||||
confirm_transaction,
|
||||
confirm_withdrawal,
|
||||
show_warning_tx_different_staking_account,
|
||||
@ -43,12 +49,12 @@ from .layout import (
|
||||
from .seed import is_byron_path, is_shelley_path
|
||||
|
||||
if False:
|
||||
from typing import Dict, List, Tuple
|
||||
from trezor.messages.CardanoSignTx import CardanoSignTx
|
||||
from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType
|
||||
from trezor.messages.CardanoTxInputType import CardanoTxInputType
|
||||
from trezor.messages.CardanoTxOutputType import CardanoTxOutputType
|
||||
from trezor.messages.CardanoTxCertificateType import CardanoTxCertificateType
|
||||
from trezor.messages.CardanoTxWithdrawalType import CardanoTxWithdrawalType
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
# the maximum allowed change address. this should be large enough for normal
|
||||
# use and still allow to quickly brute-force the correct bip32 path
|
||||
@ -56,9 +62,6 @@ MAX_CHANGE_ADDRESS_INDEX = const(1000000)
|
||||
ACCOUNT_PATH_INDEX = const(2)
|
||||
BIP_PATH_LENGTH = const(5)
|
||||
|
||||
LOVELACE_MAX_SUPPLY = 45_000_000_000 * 1_000_000
|
||||
|
||||
POOL_HASH_SIZE = 28
|
||||
METADATA_HASH_SIZE = 32
|
||||
MAX_METADATA_LENGTH = 500
|
||||
|
||||
@ -66,25 +69,34 @@ MAX_METADATA_LENGTH = 500
|
||||
@seed.with_keychain
|
||||
async def sign_tx(
|
||||
ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain
|
||||
) -> CardanoSignedTx:
|
||||
if msg.fee > LOVELACE_MAX_SUPPLY:
|
||||
raise wire.ProcessError("Fee is out of range!")
|
||||
|
||||
validate_network_info(msg.network_id, msg.protocol_magic)
|
||||
|
||||
if _has_stake_pool_registration(msg):
|
||||
return await _sign_stake_pool_registration_tx(ctx, msg, keychain)
|
||||
else:
|
||||
return await _sign_standard_tx(ctx, msg, keychain)
|
||||
|
||||
|
||||
async def _sign_standard_tx(
|
||||
ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain
|
||||
) -> CardanoSignedTx:
|
||||
try:
|
||||
if msg.fee > LOVELACE_MAX_SUPPLY:
|
||||
raise wire.ProcessError("Fee is out of range!")
|
||||
|
||||
validate_network_info(msg.network_id, msg.protocol_magic)
|
||||
|
||||
for i in msg.inputs:
|
||||
await validate_path(
|
||||
ctx, keychain, i.address_n, SCHEMA_ADDRESS.match(i.address_n)
|
||||
)
|
||||
|
||||
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
|
||||
_validate_certificates(msg.certificates)
|
||||
_validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id)
|
||||
_validate_withdrawals(msg.withdrawals)
|
||||
_validate_metadata(msg.metadata)
|
||||
|
||||
# display the transaction in UI
|
||||
await _show_tx(ctx, keychain, msg)
|
||||
await _show_standard_tx(ctx, keychain, msg)
|
||||
|
||||
# sign the transaction bundle and prepare the result
|
||||
serialized_tx, tx_hash = _serialize_tx(keychain, msg)
|
||||
@ -98,6 +110,50 @@ async def sign_tx(
|
||||
return tx
|
||||
|
||||
|
||||
async def _sign_stake_pool_registration_tx(
|
||||
ctx: wire.Context, msg: CardanoSignTx, keychain: seed.Keychain
|
||||
) -> CardanoSignedTx:
|
||||
"""
|
||||
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.)
|
||||
in the transaction are not supposed to be controlled by the HW wallet, which
|
||||
means the user is vulnerable to unknowingly supplying a witness for an UTXO
|
||||
or other tx entry they think is external, resulting in the co-signers
|
||||
gaining control over their funds (Something SLIP-0019 is dealing with for
|
||||
BTC but no similar standard is currently available for Cardano). Hence we
|
||||
completely forbid witnessing inputs and other entries of the transaction
|
||||
except the stake pool certificate itself and we provide a witness only to the
|
||||
user's staking key in the list of pool owners.
|
||||
"""
|
||||
try:
|
||||
_validate_stake_pool_registration_tx_structure(msg)
|
||||
|
||||
_ensure_no_signing_inputs(msg.inputs)
|
||||
_validate_outputs(keychain, msg.outputs, msg.protocol_magic, msg.network_id)
|
||||
_validate_certificates(msg.certificates, msg.protocol_magic, msg.network_id)
|
||||
_validate_metadata(msg.metadata)
|
||||
|
||||
await _show_stake_pool_registration_tx(ctx, keychain, msg)
|
||||
|
||||
# sign the transaction bundle and prepare the result
|
||||
serialized_tx, tx_hash = _serialize_tx(keychain, msg)
|
||||
tx = CardanoSignedTx(serialized_tx=serialized_tx, tx_hash=tx_hash)
|
||||
|
||||
except ValueError as e:
|
||||
if __debug__:
|
||||
log.exception(__name__, e)
|
||||
raise wire.ProcessError("Signing failed")
|
||||
|
||||
return tx
|
||||
|
||||
|
||||
def _has_stake_pool_registration(msg: CardanoSignTx):
|
||||
return any(
|
||||
cert.type == CardanoCertificateType.STAKE_POOL_REGISTRATION
|
||||
for cert in msg.certificates
|
||||
)
|
||||
|
||||
|
||||
def validate_network_info(network_id: int, protocol_magic: int) -> None:
|
||||
"""
|
||||
We are only concerned about checking that both network_id and protocol_magic
|
||||
@ -111,6 +167,15 @@ def validate_network_info(network_id: int, protocol_magic: int) -> None:
|
||||
raise wire.ProcessError("Invalid network id/protocol magic combination!")
|
||||
|
||||
|
||||
def _validate_stake_pool_registration_tx_structure(msg: CardanoSignTx):
|
||||
if (
|
||||
len(msg.certificates) != 1
|
||||
or not _has_stake_pool_registration(msg)
|
||||
or len(msg.withdrawals) != 0
|
||||
):
|
||||
raise INVALID_STAKE_POOL_REGISTRATION_TX_STRUCTURE
|
||||
|
||||
|
||||
def _validate_outputs(
|
||||
keychain: seed.Keychain,
|
||||
outputs: List[CardanoTxOutputType],
|
||||
@ -139,14 +204,16 @@ def _validate_outputs(
|
||||
raise wire.ProcessError("Total transaction amount is out of range!")
|
||||
|
||||
|
||||
def _validate_certificates(certificates: List[CardanoTxCertificateType]) -> None:
|
||||
for certificate in certificates:
|
||||
if not SCHEMA_STAKING.match(certificate.path):
|
||||
raise INVALID_CERTIFICATE
|
||||
def _ensure_no_signing_inputs(inputs: List[CardanoTxInputType]):
|
||||
if any(i.address_n for i in inputs):
|
||||
raise INVALID_STAKEPOOL_REGISTRATION_TX_INPUTS
|
||||
|
||||
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
|
||||
if certificate.pool is None or len(certificate.pool) != POOL_HASH_SIZE:
|
||||
raise INVALID_CERTIFICATE
|
||||
|
||||
def _validate_certificates(
|
||||
certificates: List[CardanoTxCertificateType], protocol_magic: int, network_id: int
|
||||
) -> None:
|
||||
for certificate in certificates:
|
||||
validate_certificate(certificate, protocol_magic, network_id)
|
||||
|
||||
|
||||
def _validate_withdrawals(withdrawals: List[CardanoTxWithdrawalType]) -> None:
|
||||
@ -176,10 +243,10 @@ def _validate_metadata(metadata: bytes) -> None:
|
||||
|
||||
|
||||
def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, bytes]:
|
||||
tx_body = _build_tx_body(keychain, msg)
|
||||
tx_body = _cborize_tx_body(keychain, msg)
|
||||
tx_hash = _hash_tx_body(tx_body)
|
||||
|
||||
witnesses = _build_witnesses(
|
||||
witnesses = _cborize_witnesses(
|
||||
keychain,
|
||||
msg.inputs,
|
||||
msg.certificates,
|
||||
@ -197,9 +264,9 @@ def _serialize_tx(keychain: seed.Keychain, msg: CardanoSignTx) -> Tuple[bytes, b
|
||||
return serialized_tx, tx_hash
|
||||
|
||||
|
||||
def _build_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
|
||||
inputs_for_cbor = _build_inputs(msg.inputs)
|
||||
outputs_for_cbor = _build_outputs(
|
||||
def _cborize_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
|
||||
inputs_for_cbor = _cborize_inputs(msg.inputs)
|
||||
outputs_for_cbor = _cborize_outputs(
|
||||
keychain, msg.outputs, msg.protocol_magic, msg.network_id
|
||||
)
|
||||
|
||||
@ -211,11 +278,11 @@ def _build_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
|
||||
}
|
||||
|
||||
if msg.certificates:
|
||||
certificates_for_cbor = _build_certificates(keychain, msg.certificates)
|
||||
certificates_for_cbor = _cborize_certificates(keychain, msg.certificates)
|
||||
tx_body[4] = certificates_for_cbor
|
||||
|
||||
if msg.withdrawals:
|
||||
withdrawals_for_cbor = _build_withdrawals(
|
||||
withdrawals_for_cbor = _cborize_withdrawals(
|
||||
keychain, msg.withdrawals, msg.protocol_magic, msg.network_id
|
||||
)
|
||||
tx_body[5] = withdrawals_for_cbor
|
||||
@ -228,11 +295,11 @@ def _build_tx_body(keychain: seed.Keychain, msg: CardanoSignTx) -> Dict:
|
||||
return tx_body
|
||||
|
||||
|
||||
def _build_inputs(inputs: List[CardanoTxInputType]) -> List[Tuple[bytes, int]]:
|
||||
return [(input.prev_hash, input.prev_index) for input in inputs]
|
||||
def _cborize_inputs(inputs: List[CardanoTxInputType]) -> List[Tuple[bytes, int]]:
|
||||
return [(tx_input.prev_hash, tx_input.prev_index) for tx_input in inputs]
|
||||
|
||||
|
||||
def _build_outputs(
|
||||
def _cborize_outputs(
|
||||
keychain: seed.Keychain,
|
||||
outputs: List[CardanoTxOutputType],
|
||||
protocol_magic: int,
|
||||
@ -254,29 +321,14 @@ def _build_outputs(
|
||||
return result
|
||||
|
||||
|
||||
def _build_certificates(
|
||||
keychain: seed.Keychain, certificates: List[CardanoTxCertificateType]
|
||||
def _cborize_certificates(
|
||||
keychain: seed.Keychain,
|
||||
certificates: List[CardanoTxCertificateType],
|
||||
) -> List[Tuple]:
|
||||
result = []
|
||||
for certificate in certificates:
|
||||
public_key_hash = get_public_key_hash(keychain, certificate.path)
|
||||
|
||||
stake_credential = [0, public_key_hash]
|
||||
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
|
||||
certificate_for_cbor = (
|
||||
certificate.type,
|
||||
stake_credential,
|
||||
certificate.pool,
|
||||
)
|
||||
else:
|
||||
certificate_for_cbor = (certificate.type, stake_credential)
|
||||
|
||||
result.append(certificate_for_cbor)
|
||||
|
||||
return result
|
||||
return [cborize_certificate(keychain, cert) for cert in certificates]
|
||||
|
||||
|
||||
def _build_withdrawals(
|
||||
def _cborize_withdrawals(
|
||||
keychain: seed.Keychain,
|
||||
withdrawals: List[CardanoTxWithdrawalType],
|
||||
protocol_magic: int,
|
||||
@ -308,7 +360,7 @@ def _hash_tx_body(tx_body: Dict) -> bytes:
|
||||
return hashlib.blake2b(data=tx_body_cbor, outlen=32).digest()
|
||||
|
||||
|
||||
def _build_witnesses(
|
||||
def _cborize_witnesses(
|
||||
keychain: seed.Keychain,
|
||||
inputs: List[CardanoTxInputType],
|
||||
certificates: List[CardanoTxCertificateType],
|
||||
@ -316,10 +368,10 @@ def _build_witnesses(
|
||||
tx_body_hash: bytes,
|
||||
protocol_magic: int,
|
||||
) -> Dict:
|
||||
shelley_witnesses = _build_shelley_witnesses(
|
||||
shelley_witnesses = _cborize_shelley_witnesses(
|
||||
keychain, inputs, certificates, withdrawals, tx_body_hash
|
||||
)
|
||||
byron_witnesses = _build_byron_witnesses(
|
||||
byron_witnesses = _cborize_byron_witnesses(
|
||||
keychain, inputs, tx_body_hash, protocol_magic
|
||||
)
|
||||
|
||||
@ -334,7 +386,7 @@ def _build_witnesses(
|
||||
return witnesses
|
||||
|
||||
|
||||
def _build_shelley_witnesses(
|
||||
def _cborize_shelley_witnesses(
|
||||
keychain: seed.Keychain,
|
||||
inputs: List[CardanoTxInputType],
|
||||
certificates: List[CardanoTxCertificateType],
|
||||
@ -345,25 +397,30 @@ def _build_shelley_witnesses(
|
||||
|
||||
# include only one witness for each path
|
||||
paths = set()
|
||||
for input in inputs:
|
||||
if not is_shelley_path(input.address_n):
|
||||
continue
|
||||
paths.add(tuple(input.address_n))
|
||||
for tx_input in inputs:
|
||||
if is_shelley_path(tx_input.address_n):
|
||||
paths.add(tuple(tx_input.address_n))
|
||||
for certificate in certificates:
|
||||
if not _is_certificate_witness_required(certificate.type):
|
||||
continue
|
||||
paths.add(tuple(certificate.path))
|
||||
if certificate.type in (
|
||||
CardanoCertificateType.STAKE_DEREGISTRATION,
|
||||
CardanoCertificateType.STAKE_DELEGATION,
|
||||
):
|
||||
paths.add(tuple(certificate.path))
|
||||
elif certificate.type == CardanoCertificateType.STAKE_POOL_REGISTRATION:
|
||||
for pool_owner in certificate.pool_parameters.owners:
|
||||
if pool_owner.staking_key_path:
|
||||
paths.add(tuple(pool_owner.staking_key_path))
|
||||
for withdrawal in withdrawals:
|
||||
paths.add(tuple(withdrawal.path))
|
||||
|
||||
for path in paths:
|
||||
witness = _build_shelley_witness(keychain, tx_body_hash, list(path))
|
||||
witness = _cborize_shelley_witness(keychain, tx_body_hash, list(path))
|
||||
shelley_witnesses.append(witness)
|
||||
|
||||
return shelley_witnesses
|
||||
|
||||
|
||||
def _build_shelley_witness(
|
||||
def _cborize_shelley_witness(
|
||||
keychain: seed.Keychain, tx_body_hash: bytes, path: List[int]
|
||||
) -> List[Tuple[bytes, bytes]]:
|
||||
node = keychain.derive(path)
|
||||
@ -376,11 +433,7 @@ def _build_shelley_witness(
|
||||
return public_key, signature
|
||||
|
||||
|
||||
def _is_certificate_witness_required(certificate_type: int) -> bool:
|
||||
return certificate_type != CardanoCertificateType.STAKE_REGISTRATION
|
||||
|
||||
|
||||
def _build_byron_witnesses(
|
||||
def _cborize_byron_witnesses(
|
||||
keychain: seed.Keychain,
|
||||
inputs: List[CardanoTxInputType],
|
||||
tx_body_hash: bytes,
|
||||
@ -390,10 +443,9 @@ def _build_byron_witnesses(
|
||||
|
||||
# include only one witness for each path
|
||||
paths = set()
|
||||
for input in inputs:
|
||||
if not is_byron_path(input.address_n):
|
||||
continue
|
||||
paths.add(tuple(input.address_n))
|
||||
for tx_input in inputs:
|
||||
if is_byron_path(tx_input.address_n):
|
||||
paths.add(tuple(tx_input.address_n))
|
||||
|
||||
for path in paths:
|
||||
node = keychain.derive(list(path))
|
||||
@ -410,7 +462,7 @@ def _build_byron_witnesses(
|
||||
return byron_witnesses
|
||||
|
||||
|
||||
async def _show_tx(
|
||||
async def _show_standard_tx(
|
||||
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
|
||||
) -> None:
|
||||
total_amount = await _show_outputs(ctx, keychain, msg)
|
||||
@ -427,6 +479,23 @@ async def _show_tx(
|
||||
)
|
||||
|
||||
|
||||
async def _show_stake_pool_registration_tx(
|
||||
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
|
||||
) -> None:
|
||||
stake_pool_registration_certificate = msg.certificates[0]
|
||||
pool_parameters = stake_pool_registration_certificate.pool_parameters
|
||||
|
||||
# display the transaction (certificate) in UI
|
||||
await confirm_stake_pool_parameters(
|
||||
ctx, pool_parameters, msg.network_id, msg.protocol_magic
|
||||
)
|
||||
await confirm_stake_pool_owners(
|
||||
ctx, keychain, pool_parameters.owners, msg.network_id
|
||||
)
|
||||
await confirm_stake_pool_metadata(ctx, pool_parameters.metadata)
|
||||
await confirm_stake_pool_registration_final(ctx)
|
||||
|
||||
|
||||
async def _show_outputs(
|
||||
ctx: wire.Context, keychain: seed.Keychain, msg: CardanoSignTx
|
||||
) -> int:
|
||||
@ -488,8 +557,8 @@ async def _show_change_output_staking_warnings(
|
||||
|
||||
# addresses from the same account as inputs should be hidden
|
||||
def _should_hide_output(output: List[int], inputs: List[CardanoTxInputType]) -> bool:
|
||||
for input in inputs:
|
||||
inp = input.address_n
|
||||
for tx_input in inputs:
|
||||
inp = tx_input.address_n
|
||||
if (
|
||||
len(output) != BIP_PATH_LENGTH
|
||||
or output[: (ACCOUNT_PATH_INDEX + 1)] != inp[: (ACCOUNT_PATH_INDEX + 1)]
|
||||
|
@ -6,3 +6,4 @@ if False:
|
||||
STAKE_REGISTRATION = 0 # type: Literal[0]
|
||||
STAKE_DEREGISTRATION = 1 # type: Literal[1]
|
||||
STAKE_DELEGATION = 2 # type: Literal[2]
|
||||
STAKE_POOL_REGISTRATION = 3 # type: Literal[3]
|
||||
|
29
core/src/trezor/messages/CardanoPoolMetadataType.py
Normal file
29
core/src/trezor/messages/CardanoPoolMetadataType.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoPoolMetadataType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
url: str,
|
||||
hash: bytes,
|
||||
) -> None:
|
||||
self.url = url
|
||||
self.hash = hash
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('url', p.UnicodeType, p.FLAG_REQUIRED),
|
||||
2: ('hash', p.BytesType, p.FLAG_REQUIRED),
|
||||
}
|
29
core/src/trezor/messages/CardanoPoolOwnerType.py
Normal file
29
core/src/trezor/messages/CardanoPoolOwnerType.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoPoolOwnerType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
staking_key_path: List[int] = None,
|
||||
staking_key_hash: bytes = None,
|
||||
) -> None:
|
||||
self.staking_key_path = staking_key_path if staking_key_path is not None else []
|
||||
self.staking_key_hash = staking_key_hash
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('staking_key_path', p.UVarintType, p.FLAG_REPEATED),
|
||||
2: ('staking_key_hash', p.BytesType, None),
|
||||
}
|
57
core/src/trezor/messages/CardanoPoolParametersType.py
Normal file
57
core/src/trezor/messages/CardanoPoolParametersType.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
from .CardanoPoolMetadataType import CardanoPoolMetadataType
|
||||
from .CardanoPoolOwnerType import CardanoPoolOwnerType
|
||||
from .CardanoPoolRelayParametersType import CardanoPoolRelayParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoPoolParametersType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
pool_id: bytes,
|
||||
vrf_key_hash: bytes,
|
||||
pledge: int,
|
||||
cost: int,
|
||||
margin_numerator: int,
|
||||
margin_denominator: int,
|
||||
reward_account: str,
|
||||
owners: List[CardanoPoolOwnerType] = None,
|
||||
relays: List[CardanoPoolRelayParametersType] = None,
|
||||
metadata: CardanoPoolMetadataType = None,
|
||||
) -> None:
|
||||
self.owners = owners if owners is not None else []
|
||||
self.relays = relays if relays is not None else []
|
||||
self.pool_id = pool_id
|
||||
self.vrf_key_hash = vrf_key_hash
|
||||
self.pledge = pledge
|
||||
self.cost = cost
|
||||
self.margin_numerator = margin_numerator
|
||||
self.margin_denominator = margin_denominator
|
||||
self.reward_account = reward_account
|
||||
self.metadata = metadata
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('pool_id', p.BytesType, p.FLAG_REQUIRED),
|
||||
2: ('vrf_key_hash', p.BytesType, p.FLAG_REQUIRED),
|
||||
3: ('pledge', p.UVarintType, p.FLAG_REQUIRED),
|
||||
4: ('cost', p.UVarintType, p.FLAG_REQUIRED),
|
||||
5: ('margin_numerator', p.UVarintType, p.FLAG_REQUIRED),
|
||||
6: ('margin_denominator', p.UVarintType, p.FLAG_REQUIRED),
|
||||
7: ('reward_account', p.UnicodeType, p.FLAG_REQUIRED),
|
||||
8: ('owners', CardanoPoolOwnerType, p.FLAG_REPEATED),
|
||||
9: ('relays', CardanoPoolRelayParametersType, p.FLAG_REPEATED),
|
||||
10: ('metadata', CardanoPoolMetadataType, None),
|
||||
}
|
39
core/src/trezor/messages/CardanoPoolRelayParametersType.py
Normal file
39
core/src/trezor/messages/CardanoPoolRelayParametersType.py
Normal file
@ -0,0 +1,39 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypeCardanoPoolRelayType = Literal[0, 1, 2]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoPoolRelayParametersType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
type: EnumTypeCardanoPoolRelayType,
|
||||
ipv4_address: bytes = None,
|
||||
ipv6_address: bytes = None,
|
||||
host_name: str = None,
|
||||
port: int = None,
|
||||
) -> None:
|
||||
self.type = type
|
||||
self.ipv4_address = ipv4_address
|
||||
self.ipv6_address = ipv6_address
|
||||
self.host_name = host_name
|
||||
self.port = port
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('type', p.EnumType("CardanoPoolRelayType", (0, 1, 2)), p.FLAG_REQUIRED),
|
||||
2: ('ipv4_address', p.BytesType, None),
|
||||
3: ('ipv6_address', p.BytesType, None),
|
||||
4: ('host_name', p.UnicodeType, None),
|
||||
5: ('port', p.UVarintType, None),
|
||||
}
|
8
core/src/trezor/messages/CardanoPoolRelayType.py
Normal file
8
core/src/trezor/messages/CardanoPoolRelayType.py
Normal file
@ -0,0 +1,8 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
if False:
|
||||
from typing_extensions import Literal
|
||||
|
||||
SINGLE_HOST_IP = 0 # type: Literal[0]
|
||||
SINGLE_HOST_NAME = 1 # type: Literal[1]
|
||||
MULTIPLE_HOST_NAME = 2 # type: Literal[2]
|
@ -2,11 +2,13 @@
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
from .CardanoPoolParametersType import CardanoPoolParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypeCardanoCertificateType = Literal[0, 1, 2]
|
||||
EnumTypeCardanoCertificateType = Literal[0, 1, 2, 3]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@ -19,15 +21,18 @@ class CardanoTxCertificateType(p.MessageType):
|
||||
path: List[int] = None,
|
||||
type: EnumTypeCardanoCertificateType = None,
|
||||
pool: bytes = None,
|
||||
pool_parameters: CardanoPoolParametersType = None,
|
||||
) -> None:
|
||||
self.path = path if path is not None else []
|
||||
self.type = type
|
||||
self.pool = pool
|
||||
self.pool_parameters = pool_parameters
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('type', p.EnumType("CardanoCertificateType", (0, 1, 2)), None),
|
||||
1: ('type', p.EnumType("CardanoCertificateType", (0, 1, 2, 3)), None),
|
||||
2: ('path', p.UVarintType, p.FLAG_REPEATED),
|
||||
3: ('pool', p.BytesType, None),
|
||||
4: ('pool_parameters', CardanoPoolParametersType, None),
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
# You should have received a copy of the License along with this library.
|
||||
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
|
||||
|
||||
from ipaddress import ip_address
|
||||
from typing import List
|
||||
|
||||
from . import messages, tools
|
||||
@ -23,8 +24,17 @@ PROTOCOL_MAGICS = {"mainnet": 764824073, "testnet": 42}
|
||||
NETWORK_IDS = {"mainnet": 1, "testnet": 0}
|
||||
|
||||
REQUIRED_FIELDS_TRANSACTION = ("inputs", "outputs")
|
||||
REQUIRED_FIELDS_INPUT = ("path", "prev_hash", "prev_index")
|
||||
REQUIRED_FIELDS_CERTIFICATE = ("path", "type")
|
||||
REQUIRED_FIELDS_INPUT = ("prev_hash", "prev_index")
|
||||
REQUIRED_FIELDS_CERTIFICATE = ("type",)
|
||||
REQUIRED_FIELDS_POOL_PARAMETERS = (
|
||||
"pool_id",
|
||||
"vrf_key_hash",
|
||||
"pledge",
|
||||
"cost",
|
||||
"margin",
|
||||
"reward_account",
|
||||
"owners",
|
||||
)
|
||||
REQUIRED_FIELDS_WITHDRAWAL = ("path", "amount")
|
||||
|
||||
INCOMPLETE_OUTPUT_ERROR_MESSAGE = "The output is missing some fields"
|
||||
@ -77,16 +87,14 @@ def create_certificate_pointer(
|
||||
)
|
||||
|
||||
|
||||
def create_input(input) -> messages.CardanoTxInputType:
|
||||
if not all(k in input for k in REQUIRED_FIELDS_INPUT):
|
||||
def create_input(tx_input) -> messages.CardanoTxInputType:
|
||||
if not all(k in tx_input for k in REQUIRED_FIELDS_INPUT):
|
||||
raise ValueError("The input is missing some fields")
|
||||
|
||||
path = input["path"]
|
||||
|
||||
return messages.CardanoTxInputType(
|
||||
address_n=tools.parse_path(path),
|
||||
prev_hash=bytes.fromhex(input["prev_hash"]),
|
||||
prev_index=input["prev_index"],
|
||||
address_n=tools.parse_path(tx_input.get("path")),
|
||||
prev_hash=bytes.fromhex(tx_input["prev_hash"]),
|
||||
prev_index=tx_input["prev_index"],
|
||||
)
|
||||
|
||||
|
||||
@ -131,34 +139,125 @@ def _create_change_output(output) -> messages.CardanoTxOutputType:
|
||||
|
||||
|
||||
def create_certificate(certificate) -> messages.CardanoTxCertificateType:
|
||||
if not all(k in certificate for k in REQUIRED_FIELDS_CERTIFICATE):
|
||||
raise ValueError("The certificate is missing some fields")
|
||||
CERTIFICATE_MISSING_FIELDS_ERROR = ValueError(
|
||||
"The certificate is missing some fields"
|
||||
)
|
||||
|
||||
if not all(k in certificate for k in REQUIRED_FIELDS_CERTIFICATE):
|
||||
raise CERTIFICATE_MISSING_FIELDS_ERROR
|
||||
|
||||
path = certificate["path"]
|
||||
certificate_type = certificate["type"]
|
||||
|
||||
if certificate_type == messages.CardanoCertificateType.STAKE_DELEGATION:
|
||||
if "pool" not in certificate:
|
||||
raise ValueError("The certificate is missing some fields")
|
||||
raise CERTIFICATE_MISSING_FIELDS_ERROR
|
||||
|
||||
pool = certificate["pool"]
|
||||
return messages.CardanoTxCertificateType(
|
||||
type=certificate_type,
|
||||
path=tools.parse_path(path),
|
||||
pool=bytes.fromhex(pool),
|
||||
path=tools.parse_path(certificate["path"]),
|
||||
pool=bytes.fromhex(certificate["pool"]),
|
||||
)
|
||||
elif (
|
||||
certificate_type == messages.CardanoCertificateType.STAKE_REGISTRATION
|
||||
or certificate_type == messages.CardanoCertificateType.STAKE_DEREGISTRATION
|
||||
elif certificate_type in (
|
||||
messages.CardanoCertificateType.STAKE_REGISTRATION,
|
||||
messages.CardanoCertificateType.STAKE_DEREGISTRATION,
|
||||
):
|
||||
if "path" not in certificate:
|
||||
raise CERTIFICATE_MISSING_FIELDS_ERROR
|
||||
return messages.CardanoTxCertificateType(
|
||||
type=certificate_type,
|
||||
path=tools.parse_path(path),
|
||||
path=tools.parse_path(certificate["path"]),
|
||||
)
|
||||
elif certificate_type == messages.CardanoCertificateType.STAKE_POOL_REGISTRATION:
|
||||
pool_parameters = certificate["pool_parameters"]
|
||||
|
||||
if any(
|
||||
required_param not in pool_parameters
|
||||
for required_param in REQUIRED_FIELDS_POOL_PARAMETERS
|
||||
):
|
||||
raise CERTIFICATE_MISSING_FIELDS_ERROR
|
||||
|
||||
if pool_parameters.get("metadata") is not None:
|
||||
pool_metadata = messages.CardanoPoolMetadataType(
|
||||
url=pool_parameters["metadata"]["url"],
|
||||
hash=bytes.fromhex(pool_parameters["metadata"]["hash"]),
|
||||
)
|
||||
else:
|
||||
pool_metadata = None
|
||||
|
||||
return messages.CardanoTxCertificateType(
|
||||
type=certificate_type,
|
||||
pool_parameters=messages.CardanoPoolParametersType(
|
||||
pool_id=bytes.fromhex(pool_parameters["pool_id"]),
|
||||
vrf_key_hash=bytes.fromhex(pool_parameters["vrf_key_hash"]),
|
||||
pledge=int(pool_parameters["pledge"]),
|
||||
cost=int(pool_parameters["cost"]),
|
||||
margin_numerator=int(pool_parameters["margin"]["numerator"]),
|
||||
margin_denominator=int(pool_parameters["margin"]["denominator"]),
|
||||
reward_account=pool_parameters["reward_account"],
|
||||
metadata=pool_metadata,
|
||||
owners=[
|
||||
_create_pool_owner(pool_owner)
|
||||
for pool_owner in pool_parameters.get("owners", [])
|
||||
],
|
||||
relays=[
|
||||
_create_pool_relay(pool_relay)
|
||||
for pool_relay in pool_parameters.get("relays", [])
|
||||
]
|
||||
if "relays" in pool_parameters
|
||||
else [],
|
||||
),
|
||||
)
|
||||
else:
|
||||
raise ValueError("Unknown certificate type")
|
||||
|
||||
|
||||
def _create_pool_owner(pool_owner) -> messages.CardanoPoolOwnerType:
|
||||
if "staking_key_path" in pool_owner:
|
||||
return messages.CardanoPoolOwnerType(
|
||||
staking_key_path=tools.parse_path(pool_owner["staking_key_path"])
|
||||
)
|
||||
|
||||
return messages.CardanoPoolOwnerType(
|
||||
staking_key_hash=bytes.fromhex(pool_owner["staking_key_hash"])
|
||||
)
|
||||
|
||||
|
||||
def _create_pool_relay(pool_relay) -> messages.CardanoPoolRelayParametersType:
|
||||
pool_relay_type = int(pool_relay["type"])
|
||||
|
||||
if pool_relay_type == messages.CardanoPoolRelayType.SINGLE_HOST_IP:
|
||||
ipv4_address_packed = (
|
||||
ip_address(pool_relay["ipv4_address"]).packed
|
||||
if "ipv4_address" in pool_relay
|
||||
else None
|
||||
)
|
||||
ipv6_address_packed = (
|
||||
ip_address(pool_relay["ipv6_address"]).packed
|
||||
if "ipv6_address" in pool_relay
|
||||
else None
|
||||
)
|
||||
|
||||
return messages.CardanoPoolRelayParametersType(
|
||||
type=pool_relay_type,
|
||||
port=int(pool_relay["port"]),
|
||||
ipv4_address=ipv4_address_packed,
|
||||
ipv6_address=ipv6_address_packed,
|
||||
)
|
||||
elif pool_relay_type == messages.CardanoPoolRelayType.SINGLE_HOST_NAME:
|
||||
return messages.CardanoPoolRelayParametersType(
|
||||
type=pool_relay_type,
|
||||
port=int(pool_relay["port"]),
|
||||
host_name=pool_relay["host_name"],
|
||||
)
|
||||
elif pool_relay_type == messages.CardanoPoolRelayType.MULTIPLE_HOST_NAME:
|
||||
return messages.CardanoPoolRelayParametersType(
|
||||
type=pool_relay_type,
|
||||
host_name=pool_relay["host_name"],
|
||||
)
|
||||
|
||||
raise ValueError("Unknown pool relay type")
|
||||
|
||||
|
||||
def create_withdrawal(withdrawal) -> messages.CardanoTxWithdrawalType:
|
||||
if not all(k in withdrawal for k in REQUIRED_FIELDS_WITHDRAWAL):
|
||||
raise ValueError("Withdrawal is missing some fields")
|
||||
|
@ -6,3 +6,4 @@ if False:
|
||||
STAKE_REGISTRATION = 0 # type: Literal[0]
|
||||
STAKE_DEREGISTRATION = 1 # type: Literal[1]
|
||||
STAKE_DELEGATION = 2 # type: Literal[2]
|
||||
STAKE_POOL_REGISTRATION = 3 # type: Literal[3]
|
||||
|
29
python/src/trezorlib/messages/CardanoPoolMetadataType.py
Normal file
29
python/src/trezorlib/messages/CardanoPoolMetadataType.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoPoolMetadataType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
url: str,
|
||||
hash: bytes,
|
||||
) -> None:
|
||||
self.url = url
|
||||
self.hash = hash
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('url', p.UnicodeType, p.FLAG_REQUIRED),
|
||||
2: ('hash', p.BytesType, p.FLAG_REQUIRED),
|
||||
}
|
29
python/src/trezorlib/messages/CardanoPoolOwnerType.py
Normal file
29
python/src/trezorlib/messages/CardanoPoolOwnerType.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoPoolOwnerType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
staking_key_path: List[int] = None,
|
||||
staking_key_hash: bytes = None,
|
||||
) -> None:
|
||||
self.staking_key_path = staking_key_path if staking_key_path is not None else []
|
||||
self.staking_key_hash = staking_key_hash
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('staking_key_path', p.UVarintType, p.FLAG_REPEATED),
|
||||
2: ('staking_key_hash', p.BytesType, None),
|
||||
}
|
57
python/src/trezorlib/messages/CardanoPoolParametersType.py
Normal file
57
python/src/trezorlib/messages/CardanoPoolParametersType.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
from .CardanoPoolMetadataType import CardanoPoolMetadataType
|
||||
from .CardanoPoolOwnerType import CardanoPoolOwnerType
|
||||
from .CardanoPoolRelayParametersType import CardanoPoolRelayParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoPoolParametersType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
pool_id: bytes,
|
||||
vrf_key_hash: bytes,
|
||||
pledge: int,
|
||||
cost: int,
|
||||
margin_numerator: int,
|
||||
margin_denominator: int,
|
||||
reward_account: str,
|
||||
owners: List[CardanoPoolOwnerType] = None,
|
||||
relays: List[CardanoPoolRelayParametersType] = None,
|
||||
metadata: CardanoPoolMetadataType = None,
|
||||
) -> None:
|
||||
self.owners = owners if owners is not None else []
|
||||
self.relays = relays if relays is not None else []
|
||||
self.pool_id = pool_id
|
||||
self.vrf_key_hash = vrf_key_hash
|
||||
self.pledge = pledge
|
||||
self.cost = cost
|
||||
self.margin_numerator = margin_numerator
|
||||
self.margin_denominator = margin_denominator
|
||||
self.reward_account = reward_account
|
||||
self.metadata = metadata
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('pool_id', p.BytesType, p.FLAG_REQUIRED),
|
||||
2: ('vrf_key_hash', p.BytesType, p.FLAG_REQUIRED),
|
||||
3: ('pledge', p.UVarintType, p.FLAG_REQUIRED),
|
||||
4: ('cost', p.UVarintType, p.FLAG_REQUIRED),
|
||||
5: ('margin_numerator', p.UVarintType, p.FLAG_REQUIRED),
|
||||
6: ('margin_denominator', p.UVarintType, p.FLAG_REQUIRED),
|
||||
7: ('reward_account', p.UnicodeType, p.FLAG_REQUIRED),
|
||||
8: ('owners', CardanoPoolOwnerType, p.FLAG_REPEATED),
|
||||
9: ('relays', CardanoPoolRelayParametersType, p.FLAG_REPEATED),
|
||||
10: ('metadata', CardanoPoolMetadataType, None),
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypeCardanoPoolRelayType = Literal[0, 1, 2]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoPoolRelayParametersType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
type: EnumTypeCardanoPoolRelayType,
|
||||
ipv4_address: bytes = None,
|
||||
ipv6_address: bytes = None,
|
||||
host_name: str = None,
|
||||
port: int = None,
|
||||
) -> None:
|
||||
self.type = type
|
||||
self.ipv4_address = ipv4_address
|
||||
self.ipv6_address = ipv6_address
|
||||
self.host_name = host_name
|
||||
self.port = port
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('type', p.EnumType("CardanoPoolRelayType", (0, 1, 2)), p.FLAG_REQUIRED),
|
||||
2: ('ipv4_address', p.BytesType, None),
|
||||
3: ('ipv6_address', p.BytesType, None),
|
||||
4: ('host_name', p.UnicodeType, None),
|
||||
5: ('port', p.UVarintType, None),
|
||||
}
|
8
python/src/trezorlib/messages/CardanoPoolRelayType.py
Normal file
8
python/src/trezorlib/messages/CardanoPoolRelayType.py
Normal file
@ -0,0 +1,8 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
if False:
|
||||
from typing_extensions import Literal
|
||||
|
||||
SINGLE_HOST_IP = 0 # type: Literal[0]
|
||||
SINGLE_HOST_NAME = 1 # type: Literal[1]
|
||||
MULTIPLE_HOST_NAME = 2 # type: Literal[2]
|
@ -2,11 +2,13 @@
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
from .CardanoPoolParametersType import CardanoPoolParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
EnumTypeCardanoCertificateType = Literal[0, 1, 2]
|
||||
EnumTypeCardanoCertificateType = Literal[0, 1, 2, 3]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@ -19,15 +21,18 @@ class CardanoTxCertificateType(p.MessageType):
|
||||
path: List[int] = None,
|
||||
type: EnumTypeCardanoCertificateType = None,
|
||||
pool: bytes = None,
|
||||
pool_parameters: CardanoPoolParametersType = None,
|
||||
) -> None:
|
||||
self.path = path if path is not None else []
|
||||
self.type = type
|
||||
self.pool = pool
|
||||
self.pool_parameters = pool_parameters
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('type', p.EnumType("CardanoCertificateType", (0, 1, 2)), None),
|
||||
1: ('type', p.EnumType("CardanoCertificateType", (0, 1, 2, 3)), None),
|
||||
2: ('path', p.UVarintType, p.FLAG_REPEATED),
|
||||
3: ('pool', p.BytesType, None),
|
||||
4: ('pool_parameters', CardanoPoolParametersType, None),
|
||||
}
|
||||
|
@ -27,6 +27,10 @@ from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||
from .CardanoBlockchainPointerType import CardanoBlockchainPointerType
|
||||
from .CardanoGetAddress import CardanoGetAddress
|
||||
from .CardanoGetPublicKey import CardanoGetPublicKey
|
||||
from .CardanoPoolMetadataType import CardanoPoolMetadataType
|
||||
from .CardanoPoolOwnerType import CardanoPoolOwnerType
|
||||
from .CardanoPoolParametersType import CardanoPoolParametersType
|
||||
from .CardanoPoolRelayParametersType import CardanoPoolRelayParametersType
|
||||
from .CardanoPublicKey import CardanoPublicKey
|
||||
from .CardanoSignTx import CardanoSignTx
|
||||
from .CardanoSignedTx import CardanoSignedTx
|
||||
@ -303,6 +307,7 @@ from . import ButtonRequestType
|
||||
from . import Capability
|
||||
from . import CardanoAddressType
|
||||
from . import CardanoCertificateType
|
||||
from . import CardanoPoolRelayType
|
||||
from . import DebugLinkShowTextStyle
|
||||
from . import DebugSwipeDirection
|
||||
from . import FailureType
|
||||
|
@ -29,7 +29,9 @@ pytestmark = [
|
||||
|
||||
|
||||
@parametrize_using_common_fixtures(
|
||||
"cardano/sign_tx.json", "cardano/sign_tx.slip39.json"
|
||||
"cardano/sign_tx_stake_pool_registration.json",
|
||||
"cardano/sign_tx.json",
|
||||
"cardano/sign_tx.slip39.json",
|
||||
)
|
||||
def test_cardano_sign_tx(client, parameters, result):
|
||||
inputs = [cardano.create_input(i) for i in parameters["inputs"]]
|
||||
@ -74,7 +76,9 @@ def test_cardano_sign_tx(client, parameters, result):
|
||||
assert response.serialized_tx.hex() == result["serialized_tx"]
|
||||
|
||||
|
||||
@parametrize_using_common_fixtures("cardano/sign_tx.failed.json")
|
||||
@parametrize_using_common_fixtures(
|
||||
"cardano/sign_tx.failed.json", "cardano/sign_tx_stake_pool_registration.failed.json"
|
||||
)
|
||||
def test_cardano_sign_tx_failed(client, parameters, result):
|
||||
inputs = [cardano.create_input(i) for i in parameters["inputs"]]
|
||||
outputs = [cardano.create_output(o) for o in parameters["outputs"]]
|
||||
|
@ -1,21 +1,24 @@
|
||||
{
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters0-result0]": "6aa71de5007b0faf1eea4b1cfda1da6a739f852c0d875a1e59d83c03178c2f98",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters1-result1]": "7abf2e87a9b1e50afdf3502ba9480b07a59d59ccccf24915b46fb81285ae3fa8",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters10-result10]": "9e09260bd9eb848694f6265008d9faee059d2b3b28d64688807f8bf725acd052",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters11-result11]": "c32706d1092edf9ac2504c88eddfe3e55b8a5b6c0e2d6fcd7fbf84232aabfcb8",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters12-result12]": "39c495f6c8d1a046044b8d49569f51f615b163abeae98c0be8313761de828862",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters13-result13]": "f03f064e8829a27a49296c28755493983d86a235ddeac1c926c7195dd254940f",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters14-result14]": "623341dfed3aaca40284ec5b444fc768edc5af9c706d8c4e4f7a5e1e90343652",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters15-result15]": "0f79d964628581aae91593f7a1e7bf9b4748b900d7973e1b48a78382cc8f6c4e",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters16-result16]": "4597efa8c2d34df7ab70c626a244d14b783fa7be1f88f213c2f9a39d726976e2",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters2-result2]": "539936eee440830f64536228980a78b098a8e421e8b6c819fe49bc8efcac9b18",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters3-result3]": "6aa71de5007b0faf1eea4b1cfda1da6a739f852c0d875a1e59d83c03178c2f98",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters4-result4]": "68505427a1021dd3ef7dea03956d1ea3167c8fa3016e90328151618c45d7695e",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters5-result5]": "223fc826cc19d6dd9d768a7564d19d054aa6c596557a40b9e5448c546444d36b",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters6-result6]": "0830d903c59c2d98782c0a87ba5400b656139b26ae3243ae24a1d8874b37bf7e",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters7-result7]": "18adb579bcfe99ca7f62c4c01f32003d19c32313bcd4cf20d93e9f15e7a67ec8",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters8-result8]": "a5ca1d59dedfe9052a9ccaf7fd9aaa98c80a646936dfeb74501848477a9dcb77",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters9-result9]": "d99b762b76e5edae82ec0b3f34db1f6122f83350188c8bb44a7214b4d88d2014",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters0-result0]": "f5592f2f6201cc8ac9c16d3c6b171ce824026a811a88828618cb296a34736c3e",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters1-result1]": "7aee8a0c6563e0efd8bd394c486fd639501f01be0f8f8119051e5b56059a6fab",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters10-result10]": "ca6ccfa1be6a0a6238f9df89c1988260096188bf3679582f71f645bb4748a7dd",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters11-result11]": "8432eda8c3fe635430d7c2624e7b2ad9afdc1bc02fc03116c199557d57cef499",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters12-result12]": "8adfc9bad89bb9b3e7c3e06e2a1b0b4ea4f132ff08d1408a379ab571b75b83b2",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters13-result13]": "3b6a8b864c5e0048df8d6a6d65fca5b57ea4d0aa164102e4f55fa5cb27a4f882",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters14-result14]": "cd7194bab223396175cb6b9f470b10dd04289db445d096974d51d25cb876372a",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters15-result15]": "47e0ed559b895a6e2149c4cb4842fd90382f0f3c85f1bece8e953a194acc7b0b",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters16-result16]": "db6cf62fc7b0288a849e3c5e1429bce68278f2ffddb3c06fae596d04fcfcced9",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters17-result17]": "38cbd5c3b44aa837ce09e70f650057253efc06ba89fa0769c3fde9068bd03c67",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters18-result18]": "e8764a1052f67e7c9e59baf6d684a68e5abd7c71ec94efdf7020c19215029e5d",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters19-result19]": "640f577d6434802d9975413fe49690cf5b1f023a73e2f6289bf5431b89c5c786",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters2-result2]": "88b0104aacf640351044b134f1bc91b62aef9251c936b4cdc112cbd2f623ad74",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters3-result3]": "a83d3cc90d8ef2660471d906524b9d2d044ab78c656f21f2b8d9141e6ee49429",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters4-result4]": "a413ddafc1e5dbef2e21541937529cb0ff1ff2622574e69cc10e3f09e19984cf",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters5-result5]": "195d1303aa0a4bf05b6d5c1131e9250b2249687d48f6a998bf4d03d601ae361a",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters6-result6]": "e68a1bbe79d5c986c2efb1dd629c394cac62421ad44a37407278e139778edf95",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters7-result7]": "0befa45bf43d6c3267bd81f6fd44af0c6e7abe22e4a8e3f5866b279dd1ff338a",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters8-result8]": "c91d4621064bacea6a8acf24ee938c0ed3c91901da17da81d06fe37781ce8ca9",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx[parameters9-result9]": "6e6fa6af3768075b34ea40b0936991e2a59a7f996e950df4f77a6c9f0c4e23ba",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters0-result0]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters1-result1]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters10-result10]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
@ -31,29 +34,38 @@
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters2-result2]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters20-result20]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters21-result21]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters22-result22]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters23-result23]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters24-result24]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters25-result25]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters26-result26]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters27-result27]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters28-result28]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters29-result29]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters3-result3]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters30-result30]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters4-result4]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters5-result5]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters6-result6]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters7-result7]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters8-result8]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"cardano-test_sign_tx.py::test_cardano_sign_tx_failed[parameters9-result9]": "612dad8ab8762162a186ec9279d7de0bdfc589c52b4e4f4eba0545a00f21c3f0",
|
||||
"test_autolock.py::test_apply_auto_lock_delay": "38c720e0d29b7487060f2a0f8d256a5e5b4f735511e049c43f6ea62e560603ae",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[10]": "a751228f82166c107a8e8872919e2b010ef3079763adc473066e7a3ada36f864",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[123]": "caf130bf5fa66fa5ac17432689046c1b6cd8b6a495bac3abef3c414d89b81e3f",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[3601]": "b2a9a7f3e50afb04fb174627a07b939284aa0acc8b3b53af56f75a35ff1b32c9",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[536870]": "ca2b4707227cc15089f4d6ba710706d2d5c270f19a4668c09f04f175143b202e",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[60]": "af8d06c92fc5f9aad5685bf56e59b26ec44418a6174ff73db69803f23785802a",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[7227]": "437cc6b0780d482a835c23f0935683c40177ae4b0ff31da4fc99eba603545ffe",
|
||||
"test_autolock.py::test_autolock_cancels_ui": "bb4776bfea145528544554b11bdf13ae99f63a371e8eb0885b0a9bd5b608e027",
|
||||
"test_autolock.py::test_autolock_default_value": "b9f4cd94638f5f8f4c02026b0ccaee89b42406ab63ce7fcef5c9164467de939b",
|
||||
"test_basic.py-test_device_id_different": "bc6acd0386b9d009e6550519917d6e08632b3badde0b0cf04c95abe5f773038a",
|
||||
"test_autolock.py::test_apply_auto_lock_delay": "374c0a05defdff548f7456a328241885dae1798dbfcefe6335fe72a31dddb95c",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[10]": "814accb30dc6baa977f567418943d69b1b74193e02da9cb4a0ae3199bc38bc9e",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[123]": "4371667cbade4d9b6689b040044e97ea4704e523175fa666c5afa2766817a752",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[3601]": "f68a8a86e304b4da46bf37ca6ea621199ad12699e34ca5bb5c686ed438f38f3c",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[536870]": "94b446b079c49ce1de35a235eb392e2674f84455e704ae70ce34d911cb3283b4",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[60]": "7277ef42d405c8574f6b46ea676ed54dda27d1472d4c919fe8288f3c09736c56",
|
||||
"test_autolock.py::test_apply_auto_lock_delay_valid[7227]": "0928987c48f16f0f79a5c6c992aa9282f91c3f8d7b61177ae90318fba8d41dd3",
|
||||
"test_autolock.py::test_autolock_cancels_ui": "26874b271f9e9fe04ee9154b32fd640a969dc93426b7c77eebdf823750bf436f",
|
||||
"test_autolock.py::test_autolock_default_value": "bb75c33cf21eace3f1a94e07a628d34b85691451ca5c3b62b1337afdc484d147",
|
||||
"test_basic.py-test_device_id_different": "ea1ad2312172311b7b1a2e9b23dd12f489c071656b37e82720915f0381f157a4",
|
||||
"test_basic.py-test_device_id_same": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_basic.py-test_features": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_basic.py-test_ping": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_applysettings.py-test_apply_homescreen_toif": "408bdb69368ebdf1d299c6d43c1571f86cb1a0f1f606c5badd2f05ce7731f121",
|
||||
"test_msg_applysettings.py-test_apply_settings": "8f9f6013bb8a44fda279e9c7d091328fd7ccb39222a02bee701918528355083a",
|
||||
"test_msg_applysettings.py-test_apply_settings_passphrase": "40de0143b32b5d06ece43d47be27bb91499f0c2417754ddb8e9e03ff41a7f6d4",
|
||||
"test_msg_applysettings.py-test_apply_homescreen_toif": "d8779189bbf826dfd88ccb85bb55a46cdcb954e4a371efda5272daf12db4969e",
|
||||
"test_msg_applysettings.py-test_apply_settings": "960589746d0acb5b38295af98a30dace8febe58ae35d062b0a4ea91b73fbfa7e",
|
||||
"test_msg_applysettings.py-test_apply_settings_passphrase": "b549a407b461cb9ae3b59b0ffe7407e1749df99d8c17d6297a92cdf178a7274d",
|
||||
"test_msg_applysettings.py-test_apply_settings_passphrase_on_device": "3e6527e227bdde54f51bc9c417b176d0d87fdb6c40c4761368f50eb201b4beed",
|
||||
"test_msg_applysettings.py-test_apply_settings_rotation": "6f0fa323dd2c82d01994273c023d3ed5e43d43c9c562664a10266f4a7f7ba4cc",
|
||||
"test_msg_applysettings.py-test_experimental_features": "3127d41bd8615097295b917110ece9dd364986809288c7f958ff71d52106e346",
|
||||
@ -190,64 +202,64 @@
|
||||
"test_msg_lisk_signtx.py-test_lisk_sign_tx_send_with_data": "ea969f90b6e4b840bb8728a9a99e5d07f59f478dba6b144218148d9db83f7a49",
|
||||
"test_msg_lisk_verifymessage.py-test_verify": "45df85077b20182803b5c4363386c555845e070f3a8a019add99e34dad510a07",
|
||||
"test_msg_lisk_verifymessage.py-test_verify_long": "d7d0ae3402b9ca6c7b0e61164fa483c4ba9549d306780c98ae15edd2dde51285",
|
||||
"test_msg_loaddevice.py-test_load_device_1": "1c6db0d592b1d22b3c9fce3ddab8a9fd138f11d83e5d4e64431a02bf4ffed605",
|
||||
"test_msg_loaddevice.py-test_load_device_2": "dc13c8486d8a59c5062e19139d8b3cea4ece1a3bc93592be7dc226f83ba54477",
|
||||
"test_msg_loaddevice.py-test_load_device_1": "83a92a294ddd6410a897726c67979081602d47e98206bc5f043d45532ad8d899",
|
||||
"test_msg_loaddevice.py-test_load_device_2": "1f3d44ad9cc1372b430afbf50652c8388c85b5c7ed2f829aa785a00f18ee6100",
|
||||
"test_msg_loaddevice.py-test_load_device_slip39_advanced": "1c6db0d592b1d22b3c9fce3ddab8a9fd138f11d83e5d4e64431a02bf4ffed605",
|
||||
"test_msg_loaddevice.py-test_load_device_slip39_basic": "1c6db0d592b1d22b3c9fce3ddab8a9fd138f11d83e5d4e64431a02bf4ffed605",
|
||||
"test_msg_loaddevice.py-test_load_device_utf": "ad7c162c76a13a161166aba78c461ad5525a9a5da846e8d99854248d521e6979",
|
||||
"test_msg_monero_getaddress.py-test_monero_getaddress": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_monero_getwatchkey.py-test_monero_getwatchkey": "d77fa4d4322e145c41f1ce07526ff59f8b58d8854aeffaa5266e14cd572350e7",
|
||||
"test_msg_nem_getaddress.py-test_nem_getaddress": "e726f99401a20eb74c33d755cecea2a3f69b7ae5b541302677ee05f80f5aef19",
|
||||
"test_msg_nem_signtx_mosaics_t2.py-test_nem_signtx_mosaic_creation": "b5f6dd88b31d18d648b5741bb521c9fd1961732e2ed520256657c2e8a9ec3539",
|
||||
"test_msg_nem_signtx_mosaics_t2.py-test_nem_signtx_mosaic_creation_levy": "8145638044e332510cd356d44910ccc493c8ced38b2086dd0438f56f860742ea",
|
||||
"test_msg_nem_signtx_mosaics_t2.py-test_nem_signtx_mosaic_creation_properties": "7dd1dd750dbf7b15ad20aa0a2ab99e69d1fc41cc8c4092b1030a3193e9a2186d",
|
||||
"test_msg_nem_signtx_mosaics_t2.py-test_nem_signtx_mosaic_supply_change": "aa1a4b35ee4409b8cfe2ca908eb18251c2152c634e0239d162f52e40b31db4a8",
|
||||
"test_msg_nem_signtx_multisig.py-test_nem_signtx_aggregate_modification": "b89a43ac3e5095ba09eed4d5cd1687e8aa9c788a8a04625c7957bf407ec2b8a7",
|
||||
"test_msg_nem_signtx_multisig.py-test_nem_signtx_multisig": "b079079747af3e849b186296300402b9061bb5c935864e5e40c4fbc19d225f79",
|
||||
"test_msg_nem_signtx_multisig.py-test_nem_signtx_multisig_signer": "2ea597bb8abec191fcaa08b6dc323669c514278be7428c90e1b51331d8f120d7",
|
||||
"test_msg_nem_signtx_others.py-test_nem_signtx_importance_transfer": "7bc67eccfcfbbf24b21a422855fe14457b7b46d105bbbeafd022b9a08cd2cc51",
|
||||
"test_msg_nem_signtx_others.py-test_nem_signtx_provision_namespace": "6b9ddabb24d5bd9c33769aa9c6acb7d340f802714251684faab6158369c1fb00",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_encrypted_payload": "8be92fe2b419640a3606b290d1ac7db789314b16a8ca337a65ac21bdf51a8b1e",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_known_mosaic": "495f2eab53517bdc7a6584f42c611c42502492a4d6e80777a349a93c2365a5db",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_known_mosaic_with_levy": "b2ff2a0df957e576bed19e333de05dca8e9359793c5a3e66b56d6e847b0e33fc",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_multiple_mosaics": "28f27d4e80b05c13c3991cbca3d71f2fd060caa5a9bf4e8475b3207e66ebfc40",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_simple": "d36a67610b16f835b174a053fe60104a03ea5d49fbd612d73f5d8cdb31fce421",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_unknown_mosaic": "1fd9bf33c3c481d8b76fbdddfc3e8d91df6a1a97661a8f8c4b57cd3df41e83f0",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_xem_as_mosaic": "842307e1734fea44aca9e53e2d76e0c6206348c4461f9eb1a36021bed1f681c8",
|
||||
"test_msg_loaddevice.py-test_load_device_utf": "433ecad2d6f347f7426c2b17687d62d2eead2fc8feb382c4f3e6ea37dbefd731",
|
||||
"test_msg_monero_getaddress.py-test_monero_getaddress": "020e5d945cec751dbf68e255be94e33902c5984c15ccb09628c70c6ebeaaeb35",
|
||||
"test_msg_monero_getwatchkey.py-test_monero_getwatchkey": "96f96e6d7df1d1fe2473205f5a1ad8b667304243e8633373aa6b01a9bc38eb6d",
|
||||
"test_msg_nem_getaddress.py-test_nem_getaddress": "4582fe8957b8e5206e48d467d9eb84a39e67ffba5e8a49064003f1c7294137be",
|
||||
"test_msg_nem_signtx_mosaics_t2.py-test_nem_signtx_mosaic_creation": "5d72d75fc30bd11ef6d610b449f3fad21b9f9698e8e696c7808f9d3750b89a66",
|
||||
"test_msg_nem_signtx_mosaics_t2.py-test_nem_signtx_mosaic_creation_levy": "5fe2aeef1d25267cef47f35ec597b2f7143f268787de25a0bd6ea1a8380c8ab3",
|
||||
"test_msg_nem_signtx_mosaics_t2.py-test_nem_signtx_mosaic_creation_properties": "5ff19fd208b8ab5b1cea09fa88868fe4319a98ab8e918eab1efa8bc7968cbb55",
|
||||
"test_msg_nem_signtx_mosaics_t2.py-test_nem_signtx_mosaic_supply_change": "84dfba9831a5bb4ef2fb4fa4ec6433151fb78f8a18b6aa9e3f23027c27e80d7c",
|
||||
"test_msg_nem_signtx_multisig.py-test_nem_signtx_aggregate_modification": "1b50b5c1b3d9376b89b5aaf30c16fed1eb6b5a7a88659e87a0e0629ca2623e52",
|
||||
"test_msg_nem_signtx_multisig.py-test_nem_signtx_multisig": "100df58edd0ef8dd72288594a54139aba1af3f55b5e19ee75823407a23c2613d",
|
||||
"test_msg_nem_signtx_multisig.py-test_nem_signtx_multisig_signer": "c1fe251b11daddecd5075982a35175ba11bb601171e3edef443686fc3c43bcab",
|
||||
"test_msg_nem_signtx_others.py-test_nem_signtx_importance_transfer": "a4781a279d7bf574fd86a4cfe7b03d52af4de4e7d01bd2b326a1a605bffaceb6",
|
||||
"test_msg_nem_signtx_others.py-test_nem_signtx_provision_namespace": "71d3e2e097424b11e95064fb93ae06f34113308d0ebd40fd8e2826bc3231b360",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_encrypted_payload": "54d6bf4584d23086dd4311893f42c97229d2e47af0a7f3e9f0aaa30e40700b39",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_known_mosaic": "0aab9460cabbc6d6cf49fbe7f6c6306809c7f11f003da75d65c3d66dd84cb7eb",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_known_mosaic_with_levy": "7b08657db57c245c617fc355a19a15ce05600b0fdde39a67aaee358f92f4bb14",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_multiple_mosaics": "9d233a8d198500dc57fa333396143856ab241a4827dc53aa8130586190fe857c",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_simple": "e31d77e6c850b10a587eb93b2d3ab747cd979c03b591b91a448331f2816f255c",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_unknown_mosaic": "10555d0222e2fb2bdeaa8eec2b6ae425bdcb658ef38fd261c24afc94f58dab97",
|
||||
"test_msg_nem_signtx_transfers.py-test_nem_signtx_xem_as_mosaic": "5930695897c2300c6e4750248b9961c154cfdeb7e23c7bf76f3cc81e5a26f33e",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_bad_parameters[label-test]": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_bad_parameters[language-test]": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_bad_parameters[passphrase_protection-True]": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_bad_parameters[pin_protection-True]": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_bad_parameters[u2f_counter-1]": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_dry_run": "1462a2534e0bdee573e96396316500046db0a188de7740065070d0dfb1bb0fa5",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_invalid_seed_core": "a3f0dd0d5d24e6500df0eacd3d5eb3d1670c54a01a036e5bbd546a9aac733e85",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_seed_mismatch": "85c61f5304a32e8b84a37ef80d035cfdcbf89a8631bde53409b1ec7f1013740c",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_dry_run": "c2a3a87c27919c1051b65b015d7e8da26c8c3f9460d4385680df6f00b793c802",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_invalid_seed_core": "907eb2bb211d240e3663ccb686bfcc43326a45919b6eb5922ea1a767a8484aa5",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_seed_mismatch": "fd86b449afe992bde4ca2df227779317f419fa94d92d0b32b95a906172948147",
|
||||
"test_msg_recoverydevice_bip39_dryrun.py::test_uninitialized": "14fcdd2ded299ca099a35966cc9f21204b31de8d6bab9ec91cb64537bd70440c",
|
||||
"test_msg_recoverydevice_bip39_t2.py::test_already_initialized": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_recoverydevice_bip39_t2.py::test_tt_nopin_nopassphrase": "86e52bb95d0f53193cc83e828f6e6baea59ebcaa26e06784bbb4f6873ee442ac",
|
||||
"test_msg_recoverydevice_bip39_t2.py::test_tt_pin_passphrase": "7a7b9d20cc5b2d6fcdf0e35d90cfcd46bfe536067becdea5568fd7f3d102306f",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_abort": "a54d4f29cf1fc3ce26831f52d0ae98a30a2f3e108f822cce08a9bfdd3319356e",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_extra_share_entered": "c972403fc15f00527f12b3226bdb918a5c29315ba88e496982f09a4fdac43218",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_group_threshold_reached": "137427360db303e288035972866df29ab0b272d30c8b11108bc68252f1aef748",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_noabort": "78a8cc92a79f90b45c3e14f01c1c57ba0fbef63438c9abe9cd1feb35b0e03c0a",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_same_share": "1ed63220ab59dd2feab4a42ffa565a9ff50980a72da022c35f6134869534c0fe",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f150abe2dd2b]": "c69e74416015afdeb589d257511c3a8a693c1f584717d948f93a3250d6713ef6",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0ae0458ff0c6d62": "9131fad9e499bb4cb3ee18c5535b89647d149746464334e0854052410b6a33d8",
|
||||
"test_msg_recoverydevice_slip39_advanced_dryrun.py::test_2of3_dryrun": "fdf2733eac6e1cc6f5758cf599dc6a02e3000145cd83150f0727602d98744b8d",
|
||||
"test_msg_recoverydevice_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "950a00e2a14070cb9c78658dd13064cf860cd125d604df242cf8a22ce9cf7a5e",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_1of1": "de184147e0786f76c324019964ffebd0f170474d0e1a72b0aa120daa36c624d7",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_abort": "a54d4f29cf1fc3ce26831f52d0ae98a30a2f3e108f822cce08a9bfdd3319356e",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_ask_word_number": "01b6945fab5f321da8858b58e7ea9f2fb1e7391884545cb563d1a34aab0c3e7a",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_noabort": "3db993abfb7e8d35e4a0acf1d8975d42fe51d1bee630639238f642b5c6c5f26d",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_recover_with_pin_passphrase": "45330e1d06ad7b4fc5710c0cd44fdd40afd9bbb7ce1e1c291eadb0306536719a",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_same_share": "3a5317f3bcf96931bb9b262f31fd3461d14560dce0a6c068e545283e9bf526a0",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "c3cbc4aa0243f89d421de05ee02a941b44e0794ae1f9ca064d7ecea6b3dd4176",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39bdbf2463be0878": "c7151e24b74ddb70ce6d10459f5ba318e8a7947cbc8abecc90df97d5abdb7609",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_wrong_nth_word[0]": "3164a3744b29cdd345cbae18b8963a008e89c4d4bcebe98d2c320bf714c9c299",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_wrong_nth_word[1]": "b85543b48047ebb93b1b8c509d0596205d193bf99b3cd1c6650b24d97f6bd6d4",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_wrong_nth_word[2]": "6fff99c5997b08bc18d6f6dbfe67a141eda00a848168af5927b46eff48e46770",
|
||||
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_dryrun": "d84427489f691ecc222b62f83af3e97fa09097404dcba07772a43b5eb0c689e8",
|
||||
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "55f2dd6b4958659f071c3f57e06286f872ac38af4828f446a0f4e91c657dfccc",
|
||||
"test_msg_recoverydevice_bip39_t2.py::test_tt_nopin_nopassphrase": "2908caefeacaa731e7247db9a2875f7550cacc62d4d1b62a2334f08168c3f22d",
|
||||
"test_msg_recoverydevice_bip39_t2.py::test_tt_pin_passphrase": "61ac4aaca83579969a302d4e03fc5ec8b1e7d622eac5eab2aff4e3368ee61490",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_abort": "d994be4628c6b374a8aeff99f2ef7c7be6fd1e05121a10dad123a404ba7e923a",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_extra_share_entered": "d31239d6b8c1945f3bab7a268f3cae36fa3f77183d8b3a97ea6588f663a3ce88",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_group_threshold_reached": "ee07a786398226d80cb41464229769171925beb6c4e0025960970eb125d483af",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_noabort": "0b1c1f230998cb8c650812045984c40427f180b285fcabfde30f8f2dd7560d92",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_same_share": "1500840679f5d0541b2202ba3d3375776faa4057aeb53407f41ab42bb8ac2dab",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_secret[shares0-c2d2e26ad06023c60145f150abe2dd2b]": "cd8cc5a9a90379a8557e5b43b4218758b125b533e7baa1b92acb2ea9139f5f2e",
|
||||
"test_msg_recoverydevice_slip39_advanced.py::test_secret[shares1-c41d5cf80fed71a008a3a0ae0458ff0c6d62": "ca228984335961a172bf009af0af73f8ec9edaf65ed9ecd80cceb2fead7a4ca7",
|
||||
"test_msg_recoverydevice_slip39_advanced_dryrun.py::test_2of3_dryrun": "f06aa2facf36c4b8773c966e607e376067df6113217bfb9e1fa3fa941c2cae37",
|
||||
"test_msg_recoverydevice_slip39_advanced_dryrun.py::test_2of3_invalid_seed_dryrun": "0c1567a119ca622a3b02cf8587e0736befce2d9fa4028fa392fba526a4fe9e49",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_1of1": "e75ebdfcd4e1558d3227809f7aaf245e6fa4398ac6480a520e7bfe16335d6bd4",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_abort": "d994be4628c6b374a8aeff99f2ef7c7be6fd1e05121a10dad123a404ba7e923a",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_ask_word_number": "509efcccd25f681a0d04c1760f1f213a3746f672772bcdcf5a3df547e669e45e",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_noabort": "f455bdead158468dfe880d74557d796fd5af5a87107987ef35866fdfd7f03ad6",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_recover_with_pin_passphrase": "357f9b7f2194621c5cd64e3af3376cc76375aab946bc87d03ac8ea3007265ddb",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_same_share": "df831b728982131580adf5e3e536f602f69e3969b0d288840613e4a647e41ff4",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares0-491b795b80fc21ccdf466c0fbc98c8fc]": "94b21c4a430b064b75ef10f8bf4f440a4f902f51c2aae6d6f58185d44bd87d1b",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_secret[shares1-b770e0da1363247652de97a39bdbf2463be0878": "66af54a3d24114f23e73081a2cedcaacff1b1a9ff423afa40c2017d02f312afe",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_wrong_nth_word[0]": "ea2d2dafddc64cfa1e874e6aa46f13a9b7aee185a6dc53f91da9f6a5f1b94697",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_wrong_nth_word[1]": "2a4f594f045f8a51071046ae4ea0d2ebdc51b5879ffb9c43d2b5a63910d8d031",
|
||||
"test_msg_recoverydevice_slip39_basic.py::test_wrong_nth_word[2]": "50cd0c3604d99a1b523500c108036e628543af06ced5b4cb7ea1d09f3f538816",
|
||||
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_dryrun": "db689145d77d786034a827dd386096c97fe0d8de8f4fc787db6e3fb5430c9fa1",
|
||||
"test_msg_recoverydevice_slip39_basic_dryrun.py::test_2of3_invalid_seed_dryrun": "5720bb6428bda4af3343bdad115c8dd727c48963520ead9a3987cc0d878bbc7b",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_already_initialized": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_failed_pin": "ff7fe2e2d69a8e0dda7d9ec811ff0164aa5f85f9c56fe693932749b9be92c868",
|
||||
"test_msg_resetdevice_bip39_t2.py-test_reset_device": "5f1b6cdc46e416430df1afd114bceda57fb644108d594ce1f466460ba4917b41",
|
||||
@ -324,7 +336,7 @@
|
||||
"test_msg_signtx_decred.py-test_send_decred_change": "6b44d98d39753a65e4aee69185d7dcecaafd405403f47835d0706ce52083b2ca",
|
||||
"test_msg_signtx_external.py::test_p2pkh_presigned": "075b9a41516faba90ddd8a6ed894ed4b60de1c11dd96400a57d37e64adbc73c4",
|
||||
"test_msg_signtx_external.py::test_p2pkh_with_proof": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_signtx_external.py::test_p2wpkh_in_p2sh_presigned": "f88ace4e725d81fbe79bc243d427f4d2284c478cc605b32c17336226bacb7600",
|
||||
"test_msg_signtx_external.py::test_p2wpkh_in_p2sh_presigned": "2b37805ae0e06f23e78219a4e9af90091a0b1189f9788fbfd4e1fa507e954e8a",
|
||||
"test_msg_signtx_external.py::test_p2wpkh_in_p2sh_with_proof": "5a80508a71a9ef64f94762b07636f90e464832f0f4a3102af8fa1a8c69e94586",
|
||||
"test_msg_signtx_external.py::test_p2wpkh_presigned": "af948f06299d23a6a25c8183e9541d6761cdbeafdf5b5f92aca27b832544ddc7",
|
||||
"test_msg_signtx_external.py::test_p2wpkh_with_false_proof": "180fa10c6aab6dafa764dc598ce7cc4ac216ad27051e6f414503fc000f85cae9",
|
||||
@ -439,6 +451,6 @@
|
||||
"test_reset_backup.py::test_skip_backup_msg[2-backup_flow_slip39_advanced]": "cd6c1248d9ee4d6416c57026a96190a84ac8608af04fd42c9c8c6b7275226aba",
|
||||
"test_sdcard.py::test_sd_format": "6bb7486932a5d38cdbb9b1368ee92aca3fad384115c744feadfade80c1605dd8",
|
||||
"test_sdcard.py::test_sd_no_format": "f47e897caee95cf98c1b4506732825f853c4b8afcdc2713e38e3b4055973c9ac",
|
||||
"test_sdcard.py::test_sd_protect_unlock": "52a0a4b847ceab2ef5bc9b22898e14df4e4b703227f4eda9807947702da28af8",
|
||||
"test_sdcard.py::test_sd_protect_unlock": "49687a221a97a01d822abd0a0be5da8c2c8913004cfa275bfb0c7cbe71bf4a27",
|
||||
"test_u2f_counter.py::test_u2f_counter": "7d96a4d262b9d8a2c1158ac1e5f0f7b2c3ed5f2ba9d6235a014320313f9488fe"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user