parent
2c503b16f5
commit
2313293477
@ -0,0 +1,241 @@
|
||||
from trezor.crypto import hashlib
|
||||
from trezor.crypto.curve import ed25519
|
||||
from trezor.messages import CardanoAddressType
|
||||
|
||||
from apps.common import cbor
|
||||
|
||||
from ..common.seed import remove_ed25519_prefix
|
||||
from .address import derive_address_bytes, derive_human_readable_address
|
||||
from .helpers import INVALID_AUXILIARY_DATA, bech32
|
||||
from .helpers.bech32 import HRP_JORMUN_PUBLIC_KEY
|
||||
from .helpers.paths import SCHEMA_STAKING_ANY_ACCOUNT
|
||||
from .layout import confirm_catalyst_registration, show_auxiliary_data_hash
|
||||
|
||||
if False:
|
||||
from typing import Union
|
||||
from trezor import wire
|
||||
|
||||
from trezor.messages.CardanoCatalystRegistrationParametersType import (
|
||||
CardanoCatalystRegistrationParametersType,
|
||||
)
|
||||
from trezor.messages.CardanoTxAuxiliaryDataType import CardanoTxAuxiliaryDataType
|
||||
|
||||
CatalystRegistrationPayload = dict[int, Union[bytes, int]]
|
||||
CatalystRegistrationSignature = dict[int, bytes]
|
||||
CatalystRegistration = dict[
|
||||
int, Union[CatalystRegistrationPayload, CatalystRegistrationSignature]
|
||||
]
|
||||
|
||||
from . import seed
|
||||
|
||||
AUXILIARY_DATA_HASH_SIZE = 32
|
||||
CATALYST_VOTING_PUBLIC_KEY_LENGTH = 32
|
||||
CATALYST_REGISTRATION_HASH_SIZE = 32
|
||||
|
||||
METADATA_KEY_CATALYST_REGISTRATION = 61284
|
||||
METADATA_KEY_CATALYST_REGISTRATION_SIGNATURE = 61285
|
||||
|
||||
|
||||
def validate_auxiliary_data(
|
||||
keychain: seed.Keychain,
|
||||
auxiliary_data: CardanoTxAuxiliaryDataType | None,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> None:
|
||||
if not auxiliary_data:
|
||||
return
|
||||
|
||||
fields_provided = 0
|
||||
if auxiliary_data.blob:
|
||||
fields_provided += 1
|
||||
_validate_auxiliary_data_blob(auxiliary_data.blob)
|
||||
if auxiliary_data.catalyst_registration_parameters:
|
||||
fields_provided += 1
|
||||
_validate_catalyst_registration_parameters(
|
||||
keychain,
|
||||
auxiliary_data.catalyst_registration_parameters,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
)
|
||||
|
||||
if fields_provided != 1:
|
||||
raise INVALID_AUXILIARY_DATA
|
||||
|
||||
|
||||
def _validate_auxiliary_data_blob(auxiliary_data_blob: bytes) -> None:
|
||||
try:
|
||||
# validation to prevent CBOR injection and invalid CBOR
|
||||
# we don't validate data format, just that it's a valid CBOR
|
||||
cbor.decode(auxiliary_data_blob)
|
||||
except Exception:
|
||||
raise INVALID_AUXILIARY_DATA
|
||||
|
||||
|
||||
def _validate_catalyst_registration_parameters(
|
||||
keychain: seed.Keychain,
|
||||
catalyst_registration_parameters: CardanoCatalystRegistrationParametersType,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> None:
|
||||
if (
|
||||
len(catalyst_registration_parameters.voting_public_key)
|
||||
!= CATALYST_VOTING_PUBLIC_KEY_LENGTH
|
||||
):
|
||||
raise INVALID_AUXILIARY_DATA
|
||||
|
||||
if not SCHEMA_STAKING_ANY_ACCOUNT.match(
|
||||
catalyst_registration_parameters.staking_path
|
||||
):
|
||||
raise INVALID_AUXILIARY_DATA
|
||||
|
||||
address_parameters = catalyst_registration_parameters.reward_address_parameters
|
||||
if address_parameters.address_type == CardanoAddressType.BYRON:
|
||||
raise INVALID_AUXILIARY_DATA
|
||||
|
||||
# try to derive the address to validate it
|
||||
derive_address_bytes(keychain, address_parameters, protocol_magic, network_id)
|
||||
|
||||
|
||||
async def show_auxiliary_data(
|
||||
ctx: wire.Context,
|
||||
keychain: seed.Keychain,
|
||||
auxiliary_data: CardanoTxAuxiliaryDataType | None,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> None:
|
||||
if not auxiliary_data:
|
||||
return
|
||||
|
||||
if auxiliary_data.catalyst_registration_parameters:
|
||||
await _show_catalyst_registration(
|
||||
ctx,
|
||||
keychain,
|
||||
auxiliary_data.catalyst_registration_parameters,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
)
|
||||
|
||||
auxiliary_data_bytes = get_auxiliary_data_cbor(
|
||||
keychain, auxiliary_data, protocol_magic, network_id
|
||||
)
|
||||
|
||||
auxiliary_data_hash = hash_auxiliary_data(bytes(auxiliary_data_bytes))
|
||||
await show_auxiliary_data_hash(ctx, auxiliary_data_hash)
|
||||
|
||||
|
||||
async def _show_catalyst_registration(
|
||||
ctx: wire.Context,
|
||||
keychain: seed.Keychain,
|
||||
catalyst_registration_parameters: CardanoCatalystRegistrationParametersType,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> None:
|
||||
public_key = catalyst_registration_parameters.voting_public_key
|
||||
encoded_public_key = bech32.encode(HRP_JORMUN_PUBLIC_KEY, public_key)
|
||||
staking_path = catalyst_registration_parameters.staking_path
|
||||
reward_address = derive_human_readable_address(
|
||||
keychain,
|
||||
catalyst_registration_parameters.reward_address_parameters,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
)
|
||||
nonce = catalyst_registration_parameters.nonce
|
||||
|
||||
await confirm_catalyst_registration(
|
||||
ctx, encoded_public_key, staking_path, reward_address, nonce
|
||||
)
|
||||
|
||||
|
||||
def get_auxiliary_data_cbor(
|
||||
keychain: seed.Keychain,
|
||||
auxiliary_data: CardanoTxAuxiliaryDataType,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> bytes:
|
||||
if auxiliary_data.blob:
|
||||
return auxiliary_data.blob
|
||||
elif auxiliary_data.catalyst_registration_parameters:
|
||||
cborized_catalyst_registration = _cborize_catalyst_registration(
|
||||
keychain,
|
||||
auxiliary_data.catalyst_registration_parameters,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
)
|
||||
return cbor.encode(_wrap_metadata(cborized_catalyst_registration))
|
||||
else:
|
||||
raise INVALID_AUXILIARY_DATA
|
||||
|
||||
|
||||
def _cborize_catalyst_registration(
|
||||
keychain: seed.Keychain,
|
||||
catalyst_registration_parameters: CardanoCatalystRegistrationParametersType,
|
||||
protocol_magic: int,
|
||||
network_id: int,
|
||||
) -> CatalystRegistration:
|
||||
staking_node = keychain.derive(catalyst_registration_parameters.staking_path)
|
||||
staking_key = remove_ed25519_prefix(staking_node.public_key())
|
||||
|
||||
catalyst_registration_payload: CatalystRegistrationPayload = {
|
||||
1: catalyst_registration_parameters.voting_public_key,
|
||||
2: staking_key,
|
||||
3: derive_address_bytes(
|
||||
keychain,
|
||||
catalyst_registration_parameters.reward_address_parameters,
|
||||
protocol_magic,
|
||||
network_id,
|
||||
),
|
||||
4: catalyst_registration_parameters.nonce,
|
||||
}
|
||||
|
||||
catalyst_registration_payload_signature = (
|
||||
_create_catalyst_registration_payload_signature(
|
||||
keychain,
|
||||
catalyst_registration_payload,
|
||||
catalyst_registration_parameters.staking_path,
|
||||
)
|
||||
)
|
||||
catalyst_registration_signature = {1: catalyst_registration_payload_signature}
|
||||
|
||||
return {
|
||||
METADATA_KEY_CATALYST_REGISTRATION: catalyst_registration_payload,
|
||||
METADATA_KEY_CATALYST_REGISTRATION_SIGNATURE: catalyst_registration_signature,
|
||||
}
|
||||
|
||||
|
||||
def _create_catalyst_registration_payload_signature(
|
||||
keychain: seed.Keychain,
|
||||
catalyst_registration_payload: CatalystRegistrationPayload,
|
||||
path: list[int],
|
||||
) -> bytes:
|
||||
node = keychain.derive(path)
|
||||
|
||||
encoded_catalyst_registration = cbor.encode(
|
||||
{METADATA_KEY_CATALYST_REGISTRATION: catalyst_registration_payload}
|
||||
)
|
||||
|
||||
catalyst_registration_hash = hashlib.blake2b(
|
||||
data=encoded_catalyst_registration,
|
||||
outlen=CATALYST_REGISTRATION_HASH_SIZE,
|
||||
).digest()
|
||||
|
||||
return ed25519.sign_ext(
|
||||
node.private_key(), node.private_key_ext(), catalyst_registration_hash
|
||||
)
|
||||
|
||||
|
||||
def _wrap_metadata(metadata: dict) -> tuple[dict, tuple]:
|
||||
"""
|
||||
A new structure of metadata is used after Cardano Mary era. The metadata
|
||||
is wrapped in a tuple and auxiliary_scripts may follow it. Cardano
|
||||
tooling uses this new format of "wrapped" metadata even if no
|
||||
auxiliary_scripts are included. So we do the same here.
|
||||
|
||||
https://github.com/input-output-hk/cardano-ledger-specs/blob/f7deb22be14d31b535f56edc3ca542c548244c67/shelley-ma/shelley-ma-test/cddl-files/shelley-ma.cddl#L212
|
||||
"""
|
||||
return metadata, ()
|
||||
|
||||
|
||||
def hash_auxiliary_data(auxiliary_data: bytes) -> bytes:
|
||||
return hashlib.blake2b(
|
||||
data=auxiliary_data, outlen=AUXILIARY_DATA_HASH_SIZE
|
||||
).digest()
|
@ -0,0 +1,37 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
import protobuf as p
|
||||
|
||||
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List, Optional # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoCatalystRegistrationParametersType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
voting_public_key: bytes,
|
||||
reward_address_parameters: CardanoAddressParametersType,
|
||||
nonce: int,
|
||||
staking_path: Optional[List[int]] = None,
|
||||
) -> None:
|
||||
self.staking_path = staking_path if staking_path is not None else []
|
||||
self.voting_public_key = voting_public_key
|
||||
self.reward_address_parameters = reward_address_parameters
|
||||
self.nonce = nonce
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('voting_public_key', p.BytesType, p.FLAG_REQUIRED),
|
||||
2: ('staking_path', p.UVarintType, p.FLAG_REPEATED),
|
||||
3: ('reward_address_parameters', CardanoAddressParametersType, p.FLAG_REQUIRED),
|
||||
4: ('nonce', p.UVarintType, p.FLAG_REQUIRED),
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
# Automatically generated by pb2py
|
||||
# fmt: off
|
||||
from .. import protobuf as p
|
||||
|
||||
from .CardanoAddressParametersType import CardanoAddressParametersType
|
||||
|
||||
if __debug__:
|
||||
try:
|
||||
from typing import Dict, List, Optional # noqa: F401
|
||||
from typing_extensions import Literal # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class CardanoCatalystRegistrationParametersType(p.MessageType):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
voting_public_key: bytes,
|
||||
reward_address_parameters: CardanoAddressParametersType,
|
||||
nonce: int,
|
||||
staking_path: Optional[List[int]] = None,
|
||||
) -> None:
|
||||
self.staking_path = staking_path if staking_path is not None else []
|
||||
self.voting_public_key = voting_public_key
|
||||
self.reward_address_parameters = reward_address_parameters
|
||||
self.nonce = nonce
|
||||
|
||||
@classmethod
|
||||
def get_fields(cls) -> Dict:
|
||||
return {
|
||||
1: ('voting_public_key', p.BytesType, p.FLAG_REQUIRED),
|
||||
2: ('staking_path', p.UVarintType, p.FLAG_REPEATED),
|
||||
3: ('reward_address_parameters', CardanoAddressParametersType, p.FLAG_REQUIRED),
|
||||
4: ('nonce', p.UVarintType, p.FLAG_REQUIRED),
|
||||
}
|
Loading…
Reference in new issue