feat(cardano): add key hash stake credentials

vdovhanych/arm-emu-deploy
David Misiak 3 years ago committed by matejcik
parent 2262602967
commit 9f9535abb3

@ -311,6 +311,7 @@ message CardanoPoolParametersType {
optional bytes pool = 3; // pool hash
optional CardanoPoolParametersType pool_parameters = 4; // used for stake pool registration certificate
optional bytes script_hash = 5; // stake credential script hash
optional bytes key_hash = 6; // stake credential key hash
}
/**
@ -321,6 +322,7 @@ message CardanoTxWithdrawal {
repeated uint32 path = 1; // stake credential key path
required uint64 amount = 2;
optional bytes script_hash = 3; // stake credential script hash
optional bytes key_hash = 4; // stake credential key hash
}
/**

@ -692,6 +692,46 @@
"error_message": "Invalid certificate"
}
},
{
"description": "Certificate has key hash",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [
{
"type": 0,
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
],
"withdrawals": [],
"auxiliary_data": null,
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": [],
"include_network_id": false
},
"result": {
"error_message": "Invalid certificate"
}
},
{
"description": "Certificate has both path and script_hash",
"parameters": {
@ -733,6 +773,47 @@
"error_message": "Invalid certificate"
}
},
{
"description": "Certificate has both path and key_hash",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [
{
"type": 0,
"path": "m/1852'/1815'/0'/0/0",
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
],
"withdrawals": [],
"auxiliary_data": null,
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": [],
"include_network_id": false
},
"result": {
"error_message": "Invalid certificate"
}
},
{
"description": "Certificate has invalid pool size",
"parameters": {
@ -894,6 +975,46 @@
"error_message": "Invalid withdrawal"
}
},
{
"description": "Withdrawal has key hash",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [],
"withdrawals": [
{
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd",
"amount": "1000"
}
],
"auxiliary_data": null,
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": [],
"include_network_id": false
},
"result": {
"error_message": "Invalid withdrawal"
}
},
{
"description": "Withdrawal amount is too large",
"parameters": {
@ -975,6 +1096,47 @@
"error_message": "Invalid withdrawal"
}
},
{
"description": "Withdrawal contains both path and key_hash",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [],
"withdrawals": [
{
"path": "m/1852'/1815'/0'/2/0",
"amount": "1000",
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
],
"auxiliary_data": null,
"inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "Ae2tdPwUPEZCanmBz5g2GEwFqKTKpNJcGYPKfDxoNeKZ8bRHr8366kseiK2",
"amount": "3003112"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "ORDINARY_TRANSACTION",
"additional_witness_requests": [],
"include_network_id": false
},
"result": {
"error_message": "Invalid withdrawal"
}
},
{
"description": "Auxiliary data hash has incorrect length",
"parameters": {

@ -14,7 +14,50 @@
"certificates": [
{
"type": 0,
"path": "m/1852'/1815'/0'/0/0"
"path": "m/1852'/1815'/0'/2/0"
}
],
"withdrawals": [],
"auxiliary_data": null,
"inputs": [
{
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
"amount": "1"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "MULTISIG_TRANSACTION",
"additional_witness_requests": [
{
"path": "m/1854'/1815'/0'/0/0"
}
],
"include_network_id": false
},
"result": {
"error_message": "Invalid certificate"
}
},
{
"description": "Multisig transaction with stake registration certificate containing a key hash",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [
{
"type": 0,
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
],
"withdrawals": [],
@ -57,7 +100,53 @@
"certificates": [
{
"type": 1,
"path": "m/1852'/1815'/0'/0/0"
"path": "m/1852'/1815'/0'/2/0"
}
],
"withdrawals": [],
"auxiliary_data": null,
"inputs": [
{
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
"amount": "1"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "MULTISIG_TRANSACTION",
"additional_witness_requests": [
{
"path": "m/1854'/1815'/0'/0/0"
},
{
"path": "m/1854'/1815'/2'/0/0"
}
],
"include_network_id": false
},
"result": {
"error_message": "Invalid certificate"
}
},
{
"description": "Multisig transaction with stake deregistration certificate containing a key hash",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [
{
"type": 1,
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
],
"withdrawals": [],
@ -103,7 +192,51 @@
"certificates": [
{
"type": 2,
"path": "m/1852'/1815'/0'/0/0",
"path": "m/1852'/1815'/0'/2/0",
"pool": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
}
],
"withdrawals": [],
"auxiliary_data": null,
"inputs": [
{
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
"amount": "1"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "MULTISIG_TRANSACTION",
"additional_witness_requests": [
{
"path": "m/1854'/1815'/0'/0/0"
}
],
"include_network_id": false
},
"result": {
"error_message": "Invalid certificate"
}
},
{
"description": "Multisig transaction with stake delegation certificate containing a key hash",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [
{
"type": 2,
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd",
"pool": "f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
}
],
@ -137,6 +270,92 @@
"error_message": "Invalid certificate"
}
},
{
"description": "Multisig transaction with withdrawal containing a path",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [],
"withdrawals": [
{
"path": "m/1852'/1815'/0'/2/0",
"amount": "1000"
}
],
"auxiliary_data": null,
"inputs": [
{
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1w9rhu54nz94k9l5v6d9rzfs47h7dv7xffcwkekuxcx3evnqpvuxu0",
"amount": "1"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "MULTISIG_TRANSACTION",
"additional_witness_requests": [
{
"path": "m/1854'/1815'/0'/0/0"
}
],
"include_network_id": false
},
"result": {
"error_message": "Invalid withdrawal"
}
},
{
"description": "Multisig transaction with withdrawal containing a key hash",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [],
"withdrawals": [
{
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd",
"amount": "1000"
}
],
"auxiliary_data": null,
"inputs": [
{
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1w9rhu54nz94k9l5v6d9rzfs47h7dv7xffcwkekuxcx3evnqpvuxu0",
"amount": "1"
}
],
"mint": [],
"script_data_hash": null,
"collateral_inputs": [],
"required_signers": [],
"signing_mode": "MULTISIG_TRANSACTION",
"additional_witness_requests": [
{
"path": "m/1854'/1815'/0'/0/0"
}
],
"include_network_id": false
},
"result": {
"error_message": "Invalid withdrawal"
}
},
{
"description": "Multisig transaction with repeated withdrawal",
"parameters": {

@ -731,6 +731,166 @@
]
}
},
{
"description": "Plutus transaction with stake credentials given as key paths",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [
{
"type": 1,
"path": "m/1852'/1815'/0'/2/0"
}
],
"withdrawals": [
{
"amount": "1000",
"path": "m/1852'/1815'/0'/2/0"
}
],
"auxiliary_data": null,
"inputs": [
{
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
"amount": "1"
}
],
"mint": [],
"script_data_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02",
"collateral_inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0
}
],
"required_signers": [
{
"key_path": "m/1852'/1815'/0'/0/1"
},
{
"key_path": "m/1854'/1815'/0'/2/0"
}
],
"signing_mode": "PLUTUS_TRANSACTION",
"additional_witness_requests": [],
"include_network_id": false
},
"result": {
"tx_hash": "717e9b63b08fda304bf7625d5df4149200b28b740db9b66082961a1d2f938ccd",
"witnesses": [
{
"type": 1,
"pub_key": "5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1",
"signature": "f92cc207742fd8303112edaffbac243dfc433778a6711d90f8eaad22b207253c0268226c4895311df975354804d3351403cbc06a01886e954903e71af3d15d06",
"chain_code": null
},
{
"type": 1,
"pub_key": "36a8ef21d5b98fdf23a27325cf643deaac35e912c835e35037f23d1061ae5b16",
"signature": "f606a56f775ed61b67d89be664d2111841251f141cfdc4995567dd6f355d79d77f5160f1053ba74b541d52f12360ae1747b4991c34d1228f47cdef3e72384a05",
"chain_code": null
},
{
"type": 1,
"pub_key": "bc65be1b0b9d7531778a1317c2aa6de936963c3f9ac7d5ee9e9eda25e0c97c5e",
"signature": "c776b7ec00819b9e37501013309b41ecb10e0235db064e0f7b22d8c230d56cfc14a48330b1ee60675578c30cb79466fa4ade86d049670601fc9dd5f7e310df07",
"chain_code": null
},
{
"type": 1,
"pub_key": "f2ef4ecd21ad28a8d270ca7be7e96c87f60dc821e13c0d0c5870344e9693637c",
"signature": "1697e336ca218344e9f9f82c19ddbdba6009eebee76b9602ecd43a7d79824c7030d80cb31a363c39d0e520888dc4135bd9b1d647ebef76ba3a816a0e1b45ad07",
"chain_code": null
}
]
}
},
{
"description": "Plutus transaction with stake credentials given as key hashes",
"parameters": {
"protocol_magic": 764824073,
"network_id": 1,
"fee": 42,
"ttl": 10,
"certificates": [
{
"type": 1,
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
],
"withdrawals": [
{
"amount": "1000",
"key_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
],
"auxiliary_data": null,
"inputs": [
{
"prev_hash": "3b40265111d8bb3c3c608d95b3a0bf83461ace32d79336579a1939b3aad1c0b7",
"prev_index": 0
}
],
"outputs": [
{
"address": "addr1q84sh2j72ux0l03fxndjnhctdg7hcppsaejafsa84vh7lwgmcs5wgus8qt4atk45lvt4xfxpjtwfhdmvchdf2m3u3hlsd5tq5r",
"amount": "1"
}
],
"mint": [],
"script_data_hash": "d593fd793c377ac50a3169bb8378ffc257c944da31aa8f355dfa5a4f6ff89e02",
"collateral_inputs": [
{
"path": "m/1852'/1815'/0'/0/0",
"prev_hash": "1af8fa0b754ff99253d983894e63a2b09cbb56c833ba18c3384210163f63dcfc",
"prev_index": 0
}
],
"required_signers": [
{
"key_path": "m/1852'/1815'/0'/0/1"
},
{
"key_path": "m/1854'/1815'/0'/2/0"
}
],
"signing_mode": "PLUTUS_TRANSACTION",
"additional_witness_requests": [],
"include_network_id": false
},
"result": {
"tx_hash": "baabb4e6dced60de330a089590ea38b7bbe505bbf9c785ef88078242f0ea9860",
"witnesses": [
{
"type": 1,
"pub_key": "5d010cf16fdeff40955633d6c565f3844a288a24967cf6b76acbeb271b4f13c1",
"signature": "38b39e71cf45c0bfa0754ab4fa24443fc09cd936f0180a37b4bca9ef72d5a431680d50e02afd93518caa868428d784bbf30b01bfcb55fc0d51e7d2616006510b",
"chain_code": null
},
{
"type": 1,
"pub_key": "36a8ef21d5b98fdf23a27325cf643deaac35e912c835e35037f23d1061ae5b16",
"signature": "c0f3c3c3c8034041e9273bd856f33f2523c6b347b996da58ef7732a0a780a4696846fc6de08662d36581027a0a72d9981c41f0e7cf69afd52990991b69468e07",
"chain_code": null
},
{
"type": 1,
"pub_key": "f2ef4ecd21ad28a8d270ca7be7e96c87f60dc821e13c0d0c5870344e9693637c",
"signature": "14251ec24c089371cecafd2a54e430b560a0bc029cdb8579368730d1376f911a653722d850e6ce3fb4ae820d02b8771d905d13aea8b5ea23b5045d3b2685f507",
"chain_code": null
}
]
}
},
{
"description": "Plutus transaction with output datum hash",
"parameters": {

@ -65,7 +65,11 @@ def validate_certificate(
CardanoCertificateType.STAKE_DEREGISTRATION,
):
validate_stake_credential(
certificate.path, certificate.script_hash, signing_mode, INVALID_CERTIFICATE
certificate.path,
certificate.script_hash,
certificate.key_hash,
signing_mode,
INVALID_CERTIFICATE,
)
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
@ -83,8 +87,6 @@ def validate_certificate(
def _validate_certificate_structure(certificate: CardanoTxCertificate) -> None:
path = certificate.path
script_hash = certificate.script_hash
pool = certificate.pool
pool_parameters = certificate.pool_parameters
@ -92,7 +94,12 @@ def _validate_certificate_structure(certificate: CardanoTxCertificate) -> None:
CardanoCertificateType.STAKE_REGISTRATION: (pool, pool_parameters),
CardanoCertificateType.STAKE_DELEGATION: (pool_parameters,),
CardanoCertificateType.STAKE_DEREGISTRATION: (pool, pool_parameters),
CardanoCertificateType.STAKE_POOL_REGISTRATION: (path, script_hash, pool),
CardanoCertificateType.STAKE_POOL_REGISTRATION: (
certificate.path,
certificate.script_hash,
certificate.key_hash,
pool,
),
}
if certificate.type not in fields_to_be_empty or any(
@ -111,14 +118,20 @@ def cborize_certificate(
return (
certificate.type,
cborize_certificate_stake_credential(
keychain, certificate.path, certificate.script_hash
keychain,
certificate.path,
certificate.script_hash,
certificate.key_hash,
),
)
elif certificate.type == CardanoCertificateType.STAKE_DELEGATION:
return (
certificate.type,
cborize_certificate_stake_credential(
keychain, certificate.path, certificate.script_hash
keychain,
certificate.path,
certificate.script_hash,
certificate.key_hash,
),
certificate.pool,
)
@ -127,10 +140,13 @@ def cborize_certificate(
def cborize_certificate_stake_credential(
keychain: seed.Keychain, path: list[int], script_hash: bytes | None
keychain: seed.Keychain,
path: list[int],
script_hash: bytes | None,
key_hash: bytes | None,
) -> tuple[int, bytes]:
if path:
return 0, get_public_key_hash(keychain, path)
if key_hash or path:
return 0, key_hash or get_public_key_hash(keychain, path)
if script_hash:
return 1, script_hash

@ -12,6 +12,8 @@ HRP_JORMUN_PUBLIC_KEY = "ed25519_pk"
HRP_SCRIPT_HASH = "script"
HRP_KEY_HASH = "addr_vkh"
HRP_SHARED_KEY_HASH = "addr_shared_vkh"
HRP_STAKE_KEY_HASH = "stake_vkh"
HRP_STAKE_SHARED_KEY_HASH = "stake_shared_vkh"
HRP_REQUIRED_SIGNER_KEY_HASH = "req_signer_vkh"
HRP_OUTPUT_DATUM_HASH = "datum"
HRP_SCRIPT_DATA_HASH = "script_data"

@ -4,7 +4,7 @@ from trezor.enums import CardanoAddressType
from ...common.paths import address_n_to_str
from .paths import CHAIN_STAKING_KEY, SCHEMA_PAYMENT, SCHEMA_STAKING
from .utils import format_key_hash, format_script_hash, to_account_path
from .utils import bech32, to_account_path
if TYPE_CHECKING:
from trezor.messages import (
@ -13,6 +13,9 @@ if TYPE_CHECKING:
)
from trezor.ui.layouts import PropertyType
CREDENTIAL_TYPE_PAYMENT: str = "payment"
CREDENTIAL_TYPE_STAKE: str = "stake"
class Credential:
"""
@ -57,12 +60,12 @@ class Credential:
) -> "Credential":
address_type = address_params.address_type
credential = cls(
"payment",
address_type,
address_params.address_n,
None,
address_params.script_payment_hash,
None,
type_name=CREDENTIAL_TYPE_PAYMENT,
address_type=address_type,
path=address_params.address_n,
key_hash=None,
script_hash=address_params.script_payment_hash,
pointer=None,
)
if address_type in (
@ -100,12 +103,12 @@ class Credential:
) -> "Credential":
address_type = address_params.address_type
credential = cls(
"stake",
address_type,
address_params.address_n_staking,
address_params.staking_key_hash,
address_params.script_staking_hash,
address_params.certificate_pointer,
type_name=CREDENTIAL_TYPE_STAKE,
address_type=address_type,
path=address_params.address_n_staking,
key_hash=address_params.staking_key_hash,
script_hash=address_params.script_staking_hash,
pointer=address_params.certificate_pointer,
)
if address_type == CardanoAddressType.BASE:
@ -185,9 +188,14 @@ class Credential:
if self.path:
return [(None, address_n_to_str(self.path))]
elif self.key_hash:
return [(None, format_key_hash(self.key_hash, False))]
hrp = (
bech32.HRP_KEY_HASH
if self.type_name == CREDENTIAL_TYPE_PAYMENT
else bech32.HRP_STAKE_KEY_HASH
)
return [(None, bech32.encode(hrp, self.key_hash))]
elif self.script_hash:
return [(None, format_script_hash(self.script_hash))]
return [(None, bech32.encode(bech32.HRP_SCRIPT_HASH, self.script_hash))]
elif self.pointer:
return [
(f"Block: {self.pointer.block_index}", None),

@ -67,27 +67,6 @@ def format_asset_fingerprint(policy_id: bytes, asset_name_bytes: bytes) -> str:
return bech32.encode("asset", fingerprint)
def format_script_hash(script_hash: bytes) -> str:
return bech32.encode(bech32.HRP_SCRIPT_HASH, script_hash)
def format_key_hash(key_hash: bytes, is_shared_key: bool) -> str:
hrp = bech32.HRP_SHARED_KEY_HASH if is_shared_key else bech32.HRP_KEY_HASH
return bech32.encode(hrp, key_hash)
def format_required_signer_key_hash(required_signer_key_hash: bytes) -> str:
return bech32.encode(bech32.HRP_REQUIRED_SIGNER_KEY_HASH, required_signer_key_hash)
def format_output_datum_hash(output_datum_hash: bytes) -> str:
return bech32.encode(bech32.HRP_OUTPUT_DATUM_HASH, output_datum_hash)
def format_script_data_hash(script_data_hash: bytes) -> str:
return bech32.encode(bech32.HRP_SCRIPT_DATA_HASH, script_data_hash)
def get_public_key_hash(keychain: seed.Keychain, path: list[int]) -> bytes:
public_key = derive_public_key(keychain, path)
return hashlib.blake2b(data=public_key, outlen=ADDRESS_KEY_HASH_SIZE).digest()
@ -104,14 +83,18 @@ def derive_public_key(
def validate_stake_credential(
path: list[int],
script_hash: bytes | None,
key_hash: bytes | None,
signing_mode: CardanoTxSigningMode,
error: wire.ProcessError,
) -> None:
if path and script_hash:
if sum(bool(k) for k in (path, script_hash, key_hash)) != 1:
raise error
if path:
if signing_mode != CardanoTxSigningMode.ORDINARY_TRANSACTION:
if signing_mode not in (
CardanoTxSigningMode.ORDINARY_TRANSACTION,
CardanoTxSigningMode.PLUTUS_TRANSACTION,
):
raise error
if not SCHEMA_STAKING_ANY_ACCOUNT.match(path):
raise error
@ -123,5 +106,10 @@ def validate_stake_credential(
raise error
if len(script_hash) != SCRIPT_HASH_SIZE:
raise error
elif key_hash:
if signing_mode != CardanoTxSigningMode.PLUTUS_TRANSACTION:
raise error
if len(key_hash) != ADDRESS_KEY_HASH_SIZE:
raise error
else:
raise error

@ -25,16 +25,11 @@ from apps.common.paths import address_n_to_str
from . import seed
from .address import derive_human_readable_address
from .helpers import protocol_magics
from .helpers import bech32, protocol_magics
from .helpers.utils import (
format_account_number,
format_asset_fingerprint,
format_key_hash,
format_optional_int,
format_output_datum_hash,
format_required_signer_key_hash,
format_script_data_hash,
format_script_hash,
format_stake_pool_id,
to_account_path,
)
@ -126,7 +121,9 @@ async def show_native_script(
if script.type == CardanoNativeScriptType.PUB_KEY:
assert script.key_hash is not None or script.key_path # validate_script
if script.key_hash:
props.append((None, format_key_hash(script.key_hash, True)))
props.append(
(None, bech32.encode(bech32.HRP_SHARED_KEY_HASH, script.key_hash))
)
elif script.key_path:
props.append((address_n_to_str(script.key_path), None))
elif script.type == CardanoNativeScriptType.N_OF_K:
@ -179,7 +176,9 @@ async def show_script_hash(
ctx,
"verify_script",
title="Verify script",
props=[("Script hash:", format_script_hash(script_hash))],
props=[
("Script hash:", bech32.encode(bech32.HRP_SCRIPT_HASH, script_hash))
],
br_code=ButtonRequestType.Other,
)
elif display_format == CardanoNativeScriptHashDisplayFormat.POLICY_ID:
@ -380,7 +379,7 @@ async def show_warning_tx_output_contains_datum_hash(
props=[
(
"The following transaction output contains datum hash:",
format_output_datum_hash(datum_hash),
bech32.encode(bech32.HRP_OUTPUT_DATUM_HASH, datum_hash),
),
("\nContinue?", None),
],
@ -476,19 +475,11 @@ async def confirm_certificate(
props: list[PropertyType] = [
("Confirm:", CERTIFICATE_TYPE_NAMES[certificate.type]),
_format_stake_credential(
certificate.path, certificate.script_hash, certificate.key_hash
),
]
if certificate.path:
props.append(
(
f"for account {format_account_number(certificate.path)}:",
address_n_to_str(to_account_path(certificate.path)),
),
)
else:
assert certificate.script_hash is not None # validate_certificate
props.append(("for script:", format_script_hash(certificate.script_hash)))
if certificate.type == CardanoCertificateType.STAKE_DELEGATION:
assert certificate.pool is not None # validate_certificate
props.append(("to pool:", format_stake_pool_id(certificate.pool)))
@ -632,21 +623,12 @@ async def confirm_withdrawal(
) -> None:
props: list[PropertyType] = [
("Confirm withdrawal", None),
_format_stake_credential(
withdrawal.path, withdrawal.script_hash, withdrawal.key_hash
),
("Amount:", format_coin_amount(withdrawal.amount)),
]
if withdrawal.path:
props.append(
(
f"for account {format_account_number(withdrawal.path)}:",
address_n_to_str(to_account_path(withdrawal.path)),
)
)
else:
assert withdrawal.script_hash is not None # validate_withdrawal
props.append(("for script:", format_script_hash(withdrawal.script_hash)))
props.append(("Amount:", format_coin_amount(withdrawal.amount)))
await confirm_properties(
ctx,
"confirm_withdrawal",
@ -656,6 +638,23 @@ async def confirm_withdrawal(
)
def _format_stake_credential(
path: list[int], script_hash: bytes | None, key_hash: bytes | None
) -> tuple[str, str]:
if path:
return (
f"for account {format_account_number(path)}:",
address_n_to_str(to_account_path(path)),
)
elif key_hash:
return ("for key hash:", bech32.encode(bech32.HRP_STAKE_KEY_HASH, key_hash))
elif script_hash:
return ("for script:", bech32.encode(bech32.HRP_SCRIPT_HASH, script_hash))
else:
# should be unreachable unless there's a bug in validation
raise ValueError
async def confirm_catalyst_registration(
ctx: wire.Context,
public_key: str,
@ -734,7 +733,12 @@ async def confirm_script_data_hash(ctx: wire.Context, script_data_hash: bytes) -
ctx,
"confirm_script_data_hash",
title="Confirm transaction",
props=[("Script data hash:", format_script_data_hash(script_data_hash))],
props=[
(
"Script data hash:",
bech32.encode(bech32.HRP_SCRIPT_DATA_HASH, script_data_hash),
)
],
br_code=ButtonRequestType.Other,
)
@ -761,7 +765,7 @@ async def confirm_required_signer(
required_signer.key_hash is not None or required_signer.key_path
) # _validate_required_signer
formatted_signer = (
format_required_signer_key_hash(required_signer.key_hash)
bech32.encode(bech32.HRP_REQUIRED_SIGNER_KEY_HASH, required_signer.key_hash)
if required_signer.key_hash is not None
else address_n_to_str(required_signer.key_path)
)

@ -524,9 +524,6 @@ async def _process_certificates(
account_path_checker: AccountPathChecker,
) -> None:
"""Read, validate, confirm and serialize the certificates."""
if certificates_count == 0:
return
for _ in range(certificates_count):
certificate: CardanoTxCertificate = await ctx.call(
CardanoTxItemAck(), CardanoTxCertificate
@ -1021,7 +1018,6 @@ async def _show_certificate(
CardanoTxSigningMode.MULTISIG_TRANSACTION,
CardanoTxSigningMode.PLUTUS_TRANSACTION,
):
assert certificate.script_hash # validate_certificate
await confirm_certificate(ctx, certificate)
elif signing_mode == CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER:
await _show_stake_pool_registration_certificate(ctx, certificate)
@ -1039,15 +1035,16 @@ def _validate_withdrawal(
previous_reward_address: bytes,
) -> None:
validate_stake_credential(
withdrawal.path, withdrawal.script_hash, signing_mode, INVALID_WITHDRAWAL
withdrawal.path,
withdrawal.script_hash,
withdrawal.key_hash,
signing_mode,
INVALID_WITHDRAWAL,
)
if not 0 <= withdrawal.amount < LOVELACE_MAX_SUPPLY:
raise INVALID_WITHDRAWAL
credential = tuple(withdrawal.path) if withdrawal.path else withdrawal.script_hash
assert credential # validate_stake_credential
reward_address = _derive_withdrawal_reward_address_bytes(
keychain, withdrawal, protocol_magic, network_id
)
@ -1093,7 +1090,7 @@ def _derive_withdrawal_reward_address_bytes(
) -> bytes:
reward_address_type = (
CardanoAddressType.REWARD
if withdrawal.path
if withdrawal.path or withdrawal.key_hash
else CardanoAddressType.REWARD_SCRIPT
)
return derive_address_bytes(
@ -1101,6 +1098,7 @@ def _derive_withdrawal_reward_address_bytes(
CardanoAddressParametersType(
address_type=reward_address_type,
address_n_staking=withdrawal.path,
staking_key_hash=withdrawal.key_hash,
script_staking_hash=withdrawal.script_hash,
),
protocol_magic,

@ -1528,6 +1528,7 @@ if TYPE_CHECKING:
pool: "bytes | None"
pool_parameters: "CardanoPoolParametersType | None"
script_hash: "bytes | None"
key_hash: "bytes | None"
def __init__(
self,
@ -1537,6 +1538,7 @@ if TYPE_CHECKING:
pool: "bytes | None" = None,
pool_parameters: "CardanoPoolParametersType | None" = None,
script_hash: "bytes | None" = None,
key_hash: "bytes | None" = None,
) -> None:
pass
@ -1548,6 +1550,7 @@ if TYPE_CHECKING:
path: "list[int]"
amount: "int"
script_hash: "bytes | None"
key_hash: "bytes | None"
def __init__(
self,
@ -1555,6 +1558,7 @@ if TYPE_CHECKING:
amount: "int",
path: "list[int] | None" = None,
script_hash: "bytes | None" = None,
key_hash: "bytes | None" = None,
) -> None:
pass

@ -40,6 +40,15 @@ class TestCardanoCertificate(unittest.TestCase):
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
@ -74,6 +83,18 @@ class TestCardanoCertificate(unittest.TestCase):
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
@ -99,6 +120,15 @@ class TestCardanoCertificate(unittest.TestCase):
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
@ -142,6 +172,19 @@ class TestCardanoCertificate(unittest.TestCase):
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_REGISTRATION both script_hash and key_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_REGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
# STAKE_REGISTRATION pool is set
(
CardanoTxCertificate(
@ -200,6 +243,22 @@ class TestCardanoCertificate(unittest.TestCase):
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_DELEGATION both script_hash and key_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DELEGATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
# STAKE_DELEGATION pool parameters are set
(
CardanoTxCertificate(
@ -244,6 +303,19 @@ class TestCardanoCertificate(unittest.TestCase):
),
CardanoTxSigningMode.ORDINARY_TRANSACTION,
),
# STAKE_DEREGISTRATION both script_hash and key_hash are set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_DEREGISTRATION,
script_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
),
CardanoTxSigningMode.PLUTUS_TRANSACTION,
),
# STAKE_DEREGISTRATION pool is set
(
CardanoTxCertificate(
@ -333,6 +405,31 @@ class TestCardanoCertificate(unittest.TestCase):
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
# STAKE_POOL_REGISTRATION key hash is set
(
CardanoTxCertificate(
type=CardanoCertificateType.STAKE_POOL_REGISTRATION,
key_hash=unhexlify(
"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
),
pool_parameters=CardanoPoolParametersType(
pool_id=unhexlify(
"f61c42cbf7c8c53af3f520508212ad3e72f674f957fe23ff0acb4973"
),
vrf_key_hash=unhexlify(
"198890ad6c92e80fbdab554dda02da9fb49d001bbd96181f3e07f7a6ab0d0640"
),
pledge=500000000,
cost=340000000,
margin_numerator=1,
margin_denominator=2,
reward_account="stake1uya87zwnmax0v6nnn8ptqkl6ydx4522kpsc3l3wmf3yswygwx45el",
owners_count=1,
relays_count=1,
),
),
CardanoTxSigningMode.POOL_REGISTRATION_AS_OWNER,
),
# STAKE_POOL_REGISTRATION pool is set
(
CardanoTxCertificate(

@ -330,7 +330,7 @@ def parse_certificate(certificate: dict) -> CertificateWithPoolOwnersAndRelays:
if "pool" not in certificate:
raise CERTIFICATE_MISSING_FIELDS_ERROR
path, script_hash = _parse_path_or_script_hash(
path, script_hash, key_hash = _parse_credential(
certificate, CERTIFICATE_MISSING_FIELDS_ERROR
)
@ -340,6 +340,7 @@ def parse_certificate(certificate: dict) -> CertificateWithPoolOwnersAndRelays:
path=path,
pool=bytes.fromhex(certificate["pool"]),
script_hash=script_hash,
key_hash=key_hash,
),
None,
)
@ -347,13 +348,16 @@ def parse_certificate(certificate: dict) -> CertificateWithPoolOwnersAndRelays:
messages.CardanoCertificateType.STAKE_REGISTRATION,
messages.CardanoCertificateType.STAKE_DEREGISTRATION,
):
path, script_hash = _parse_path_or_script_hash(
path, script_hash, key_hash = _parse_credential(
certificate, CERTIFICATE_MISSING_FIELDS_ERROR
)
return (
messages.CardanoTxCertificate(
type=certificate_type, path=path, script_hash=script_hash
type=certificate_type,
path=path,
script_hash=script_hash,
key_hash=key_hash,
),
None,
)
@ -406,16 +410,17 @@ def parse_certificate(certificate: dict) -> CertificateWithPoolOwnersAndRelays:
raise ValueError("Unknown certificate type")
def _parse_path_or_script_hash(
def _parse_credential(
obj: dict, error: ValueError
) -> Tuple[List[int], Optional[bytes]]:
if "path" not in obj and "script_hash" not in obj:
) -> Tuple[List[int], Optional[bytes], Optional[bytes]]:
if not any(k in obj for k in ("path", "script_hash", "key_hash")):
raise error
path = tools.parse_path(obj.get("path", ""))
script_hash = parse_optional_bytes(obj.get("script_hash"))
key_hash = parse_optional_bytes(obj.get("key_hash"))
return path, script_hash
return path, script_hash, key_hash
def _parse_pool_owner(pool_owner: dict) -> messages.CardanoPoolOwner:
@ -473,7 +478,7 @@ def parse_withdrawal(withdrawal: dict) -> messages.CardanoTxWithdrawal:
if "amount" not in withdrawal:
raise WITHDRAWAL_MISSING_FIELDS_ERROR
path, script_hash = _parse_path_or_script_hash(
path, script_hash, key_hash = _parse_credential(
withdrawal, WITHDRAWAL_MISSING_FIELDS_ERROR
)
@ -481,6 +486,7 @@ def parse_withdrawal(withdrawal: dict) -> messages.CardanoTxWithdrawal:
path=path,
amount=int(withdrawal["amount"]),
script_hash=script_hash,
key_hash=key_hash,
)

@ -2490,6 +2490,7 @@ class CardanoTxCertificate(protobuf.MessageType):
3: protobuf.Field("pool", "bytes", repeated=False, required=False),
4: protobuf.Field("pool_parameters", "CardanoPoolParametersType", repeated=False, required=False),
5: protobuf.Field("script_hash", "bytes", repeated=False, required=False),
6: protobuf.Field("key_hash", "bytes", repeated=False, required=False),
}
def __init__(
@ -2500,12 +2501,14 @@ class CardanoTxCertificate(protobuf.MessageType):
pool: Optional["bytes"] = None,
pool_parameters: Optional["CardanoPoolParametersType"] = None,
script_hash: Optional["bytes"] = None,
key_hash: Optional["bytes"] = None,
) -> None:
self.path: Sequence["int"] = path if path is not None else []
self.type = type
self.pool = pool
self.pool_parameters = pool_parameters
self.script_hash = script_hash
self.key_hash = key_hash
class CardanoTxWithdrawal(protobuf.MessageType):
@ -2514,6 +2517,7 @@ class CardanoTxWithdrawal(protobuf.MessageType):
1: protobuf.Field("path", "uint32", repeated=True, required=False),
2: protobuf.Field("amount", "uint64", repeated=False, required=True),
3: protobuf.Field("script_hash", "bytes", repeated=False, required=False),
4: protobuf.Field("key_hash", "bytes", repeated=False, required=False),
}
def __init__(
@ -2522,10 +2526,12 @@ class CardanoTxWithdrawal(protobuf.MessageType):
amount: "int",
path: Optional[Sequence["int"]] = None,
script_hash: Optional["bytes"] = None,
key_hash: Optional["bytes"] = None,
) -> None:
self.path: Sequence["int"] = path if path is not None else []
self.amount = amount
self.script_hash = script_hash
self.key_hash = key_hash
class CardanoCatalystRegistrationParametersType(protobuf.MessageType):

Loading…
Cancel
Save