feat(cardano): add get-native-script-hash call

pull/1846/head
gabrielkerekes 3 years ago committed by matejcik
parent 54909681a8
commit dd9652cd07

@ -25,6 +25,21 @@ enum CardanoAddressType {
REWARD_SCRIPT = 15;
}
enum CardanoNativeScriptType {
PUB_KEY = 0;
ALL = 1;
ANY = 2;
N_OF_K = 3;
INVALID_BEFORE = 4;
INVALID_HEREAFTER = 5;
}
enum CardanoNativeScriptHashDisplayFormat {
HIDE = 0;
BECH32 = 1;
POLICY_ID = 2;
}
enum CardanoCertificateType {
STAKE_REGISTRATION = 0;
STAKE_DEREGISTRATION = 1;
@ -64,6 +79,39 @@ message CardanoBlockchainPointerType {
required uint32 certificate_index = 3;
}
/*
* @embed
*/
message CardanoNativeScript {
required CardanoNativeScriptType type = 1;
repeated CardanoNativeScript scripts = 2;
optional bytes key_hash = 3;
repeated uint32 key_path = 4;
optional uint32 required_signatures_count = 5;
optional uint64 invalid_before = 6;
optional uint64 invalid_hereafter = 7;
}
/**
* Request: Ask device for Cardano native script hash
* @start
* @next CardanoNativeScriptHash
* @next Failure
*/
message CardanoGetNativeScriptHash {
required CardanoNativeScript script = 1;
required CardanoNativeScriptHashDisplayFormat display_format = 2; // display hash as bech32 or policy id
}
/**
* Request: Ask device for Cardano native script hash
* @end
*/
message CardanoNativeScriptHash {
required bytes script_hash = 1;
}
/**
* Structure to represent address parameters so they can be
* reused in CardanoGetAddress and CardanoTxOutputType.

@ -263,7 +263,8 @@ enum MessageType {
MessageType_CardanoTxAuxiliaryData = 327 [(wire_in) = true];
MessageType_CardanoPoolOwner = 328 [(wire_in) = true];
MessageType_CardanoPoolRelayParameters = 329 [(wire_in) = true];
MessageType_CardanoGetNativeScriptHash = 330 [(wire_in) = true];
MessageType_CardanoNativeScriptHash = 331 [(wire_in) = true, (wire_out) = true];
// Ripple
MessageType_RippleGetAddress = 400 [(wire_in) = true];

@ -0,0 +1,321 @@
{
"setup": {
"mnemonic": "all all all all all all all all all all all all",
"passphrase": ""
},
"tests": [
{
"description": "PUB_KEY script",
"parameters": {
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
"result": {
"expected_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
},
{
"description": "PUB_KEY script containing a path",
"parameters": {
"type": 0,
"key_path": "m/1854'/1815'/0'/0/0"
},
"result": {
"expected_hash": "29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd"
}
},
{
"description": "ALL script",
"parameters": {
"type": 1,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
}
]
},
"result": {
"expected_hash": "af5c2ce476a6ede1c879f7b1909d6a0b96cb2081391712d4a355cef6"
}
},
{
"description": "ALL script containing a path",
"parameters": {
"type": 1,
"scripts": [
{
"type": 0,
"key_path": "m/1854'/1815'/0'/0/0"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
}
]
},
"result": {
"expected_hash": "af5c2ce476a6ede1c879f7b1909d6a0b96cb2081391712d4a355cef6"
}
},
{
"description": "ALL script containing a 1855 path",
"parameters": {
"type": 1,
"scripts": [
{
"type": 0,
"key_path": "m/1855'/1815'/0'"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
}
]
},
"result": {
"expected_hash": "fbf6672eb655c29b0f148fa1429be57c2174b067a7b3e3942e967fe8"
}
},
{
"description": "ANY script",
"parameters": {
"type": 2,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
}
]
},
"result": {
"expected_hash": "d6428ec36719146b7b5fb3a2d5322ce702d32762b8c7eeeb797a20db"
}
},
{
"description": "ANY script with a nested script containing a path",
"parameters": {
"type": 2,
"scripts": [
{
"type": 0,
"key_path": "m/1854'/1815'/0'/0/0"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
}
]
},
"result": {
"expected_hash": "d6428ec36719146b7b5fb3a2d5322ce702d32762b8c7eeeb797a20db"
}
},
{
"description": "N_OF_K script",
"parameters": {
"type": 3,
"required_signatures_count": 2,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
},
{
"type": 0,
"key_hash": "cecb1d427c4ae436d28cc0f8ae9bb37501a5b77bcc64cd1693e9ae20"
}
]
},
"result": {
"expected_hash": "2b2b17fd18e18acae4601d4818a1dee00a917ff72e772fa8482e36c9"
}
},
{
"description": "N_OF_K script containing a path",
"parameters": {
"type": 3,
"required_signatures_count": 2,
"scripts": [
{
"type": 0,
"key_path": "m/1854'/1815'/0'/0/0"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
},
{
"type": 0,
"key_hash": "cecb1d427c4ae436d28cc0f8ae9bb37501a5b77bcc64cd1693e9ae20"
}
]
},
"result": {
"expected_hash": "2b2b17fd18e18acae4601d4818a1dee00a917ff72e772fa8482e36c9"
}
},
{
"description": "INVALID_BEFORE script",
"parameters": {
"type": 1,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 4,
"invalid_before": 100
}
]
},
"result": {
"expected_hash": "c6262ef9bb2b1291c058d93b46dabf458e2d135f803f60713f84b0b7"
}
},
{
"description": "INVALID_HEREAFTER script",
"parameters": {
"type": 1,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 5,
"invalid_hereafter": 200
}
]
},
"result": {
"expected_hash": "b12ac304f89f4cd4d23f59a2b90d2b2697f7540b8f470d6aa05851b5"
}
},
{
"description": "Nested script",
"parameters": {
"type": 1,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 0,
"key_path": "m/1854'/1815'/0'/0/0"
},
{
"type": 2,
"scripts": [
{
"type": 0,
"key_path": "m/1854'/1815'/0'/0/0"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
}
]
},
{
"type": 3,
"required_signatures_count": 2,
"scripts": [
{
"type": 0,
"key_path": "m/1854'/1815'/0'/0/0"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
},
{
"type": 0,
"key_hash": "cecb1d427c4ae436d28cc0f8ae9bb37501a5b77bcc64cd1693e9ae20"
}
]
},
{
"type": 4,
"invalid_before": 100
},
{
"type": 5,
"invalid_hereafter": 200
}
]
},
"result": {
"expected_hash": "4a6b4288459bf34668c0b281f922691460caf0c7c09caee3a726c27a"
}
},
{
"description": "Nested script without paths and shared with Ledger",
"parameters": {
"type": 1,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 2,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
}
]
},
{
"type": 3,
"required_signatures_count": 2,
"scripts": [
{
"type": 0,
"key_hash": "c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
},
{
"type": 0,
"key_hash": "0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
},
{
"type": 0,
"key_hash": "cecb1d427c4ae436d28cc0f8ae9bb37501a5b77bcc64cd1693e9ae20"
}
]
},
{
"type": 4,
"invalid_before": 100
},
{
"type": 5,
"invalid_hereafter": 200
}
]
},
"result": {
"expected_hash": "0d63e8d2c5a00cbcffbdf9112487c443466e1ea7d8c834df5ac5c425"
}
}
]
}

@ -390,6 +390,10 @@ if not utils.BITCOIN_ONLY:
import trezor.enums.CardanoAddressType
trezor.enums.CardanoCertificateType
import trezor.enums.CardanoCertificateType
trezor.enums.CardanoNativeScriptHashDisplayFormat
import trezor.enums.CardanoNativeScriptHashDisplayFormat
trezor.enums.CardanoNativeScriptType
import trezor.enums.CardanoNativeScriptType
trezor.enums.CardanoPoolRelayType
import trezor.enums.CardanoPoolRelayType
trezor.enums.CardanoTxAuxiliaryDataSupplementType
@ -448,6 +452,8 @@ if not utils.BITCOIN_ONLY:
import apps.cardano.certificates
apps.cardano.get_address
import apps.cardano.get_address
apps.cardano.get_native_script_hash
import apps.cardano.get_native_script_hash
apps.cardano.get_public_key
import apps.cardano.get_public_key
apps.cardano.helpers
@ -470,6 +476,8 @@ if not utils.BITCOIN_ONLY:
import apps.cardano.helpers.utils
apps.cardano.layout
import apps.cardano.layout
apps.cardano.native_script
import apps.cardano.native_script
apps.cardano.seed
import apps.cardano.seed
apps.cardano.sign_tx

@ -0,0 +1,24 @@
from trezor import wire
from trezor.enums import CardanoNativeScriptHashDisplayFormat
from trezor.messages import CardanoNativeScriptHash
from . import native_script, seed
from .layout import show_native_script, show_script_hash
if False:
from trezor.messages import CardanoGetNativeScriptHash
@seed.with_keychain
async def get_native_script_hash(
ctx: wire.Context, msg: CardanoGetNativeScriptHash, keychain: seed.Keychain
) -> CardanoNativeScriptHash:
native_script.validate_native_script(msg.script)
script_hash = native_script.get_native_script_hash(keychain, msg.script)
if msg.display_format != CardanoNativeScriptHashDisplayFormat.HIDE:
await show_native_script(ctx, msg.script)
await show_script_hash(ctx, script_hash, msg.display_format)
return CardanoNativeScriptHash(script_hash=script_hash)

@ -15,6 +15,8 @@ INVALID_STAKEPOOL_REGISTRATION_TX_WITNESSES = wire.ProcessError(
"Stakepool registration transaction can only contain staking witnesses"
)
INVALID_WITNESS_REQUEST = wire.ProcessError("Invalid witness request")
INVALID_NATIVE_SCRIPT = wire.ProcessError("Invalid native script")
LOVELACE_MAX_SUPPLY = 45_000_000_000 * 1_000_000
ADDRESS_KEY_HASH_SIZE = 28
SCRIPT_HASH_SIZE = 28

@ -9,6 +9,9 @@ HRP_REWARD_ADDRESS = "stake"
HRP_TESTNET_REWARD_ADDRESS = "stake_test"
# Jormungandr public key prefix - https://github.com/input-output-hk/voting-tools-lib/blob/18dae637e80db72444476606ab264b973bcf1a9d/src/Cardano/API/Extended.hs#L226
HRP_JORMUN_PUBLIC_KEY = "ed25519_pk"
HRP_SCRIPT_HASH = "script"
HRP_KEY_HASH = "addr_vkh"
HRP_SHARED_KEY_HASH = "addr_shared_vkh"
def encode(hrp: str, data: bytes) -> str:

@ -3,7 +3,7 @@ from trezor.crypto import hashlib
from apps.cardano.helpers.paths import ACCOUNT_PATH_INDEX, unharden
from apps.common.seed import remove_ed25519_prefix
from . import bech32
from . import ADDRESS_KEY_HASH_SIZE, bech32
if False:
from .. import seed
@ -59,6 +59,20 @@ 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 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()
def derive_public_key(
keychain: seed.Keychain, path: list[int], extended: bool = False
) -> bytes:

@ -1,7 +1,14 @@
from trezor import ui
from trezor.enums import ButtonRequestType, CardanoAddressType, CardanoCertificateType
from trezor.enums import (
ButtonRequestType,
CardanoAddressType,
CardanoCertificateType,
CardanoNativeScriptHashDisplayFormat,
CardanoNativeScriptType,
)
from trezor.strings import format_amount
from trezor.ui.layouts import (
confirm_blob,
confirm_metadata,
confirm_output,
confirm_path_warning,
@ -20,7 +27,9 @@ from .helpers import protocol_magics
from .helpers.utils import (
format_account_number,
format_asset_fingerprint,
format_key_hash,
format_optional_int,
format_script_hash,
format_stake_pool_id,
to_account_path,
)
@ -29,6 +38,7 @@ if False:
from trezor import wire
from trezor.messages import (
CardanoBlockchainPointerType,
CardanoNativeScript,
CardanoTxCertificate,
CardanoTxWithdrawal,
CardanoPoolParametersType,
@ -48,6 +58,15 @@ ADDRESS_TYPE_NAMES = {
CardanoAddressType.REWARD: "Reward",
}
SCRIPT_TYPE_NAMES = {
CardanoNativeScriptType.PUB_KEY: "Key",
CardanoNativeScriptType.ALL: "All",
CardanoNativeScriptType.ANY: "Any",
CardanoNativeScriptType.N_OF_K: "N of K",
CardanoNativeScriptType.INVALID_BEFORE: "Invalid before",
CardanoNativeScriptType.INVALID_HEREAFTER: "Invalid hereafter",
}
CERTIFICATE_TYPE_NAMES = {
CardanoCertificateType.STAKE_REGISTRATION: "Stake key registration",
CardanoCertificateType.STAKE_DEREGISTRATION: "Stake key deregistration",
@ -64,6 +83,103 @@ def is_printable_ascii_bytestring(bytestr: bytes) -> bool:
return all((32 < b < 127) for b in bytestr)
async def show_native_script(
ctx: wire.Context,
script: CardanoNativeScript,
indices: list[int] = [],
) -> None:
indices_str = ".".join([str(i) for i in indices])
script_type_name_suffix = ""
if script.type == CardanoNativeScriptType.PUB_KEY:
if script.key_path:
script_type_name_suffix = "path"
elif script.key_hash:
script_type_name_suffix = "hash"
props: list[PropertyType] = [
(
"Script%s - %s %s:"
% (
(" " + indices_str if indices_str else ""),
SCRIPT_TYPE_NAMES[script.type],
script_type_name_suffix,
),
None,
)
]
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)))
elif script.key_path:
props.append((address_n_to_str(script.key_path), None))
elif script.type == CardanoNativeScriptType.N_OF_K:
assert script.required_signatures_count is not None # validate_script
props.append(
(
"Requires %s out of %s signatures."
% (script.required_signatures_count, len(script.scripts)),
None,
)
)
elif script.type == CardanoNativeScriptType.INVALID_BEFORE:
assert script.invalid_before is not None # validate_script
props.append((str(script.invalid_before), None))
elif script.type == CardanoNativeScriptType.INVALID_HEREAFTER:
assert script.invalid_hereafter is not None # validate_script
props.append((str(script.invalid_hereafter), None))
if script.type in (
CardanoNativeScriptType.ALL,
CardanoNativeScriptType.ANY,
CardanoNativeScriptType.N_OF_K,
):
assert script.scripts # validate_script
props.append(("Contains %i nested scripts." % len(script.scripts), None))
await confirm_properties(
ctx,
"verify_script",
title="Verify script",
props=props,
br_code=ButtonRequestType.Other,
)
for i, sub_script in enumerate(script.scripts):
await show_native_script(ctx, sub_script, indices + [(i + 1)])
async def show_script_hash(
ctx: wire.Context,
script_hash: bytes,
display_format: CardanoNativeScriptHashDisplayFormat,
) -> None:
assert display_format in (
CardanoNativeScriptHashDisplayFormat.BECH32,
CardanoNativeScriptHashDisplayFormat.POLICY_ID,
)
if display_format == CardanoNativeScriptHashDisplayFormat.BECH32:
await confirm_properties(
ctx,
"verify_script",
title="Verify script",
props=[("Script hash:", format_script_hash(script_hash))],
br_code=ButtonRequestType.Other,
)
elif display_format == CardanoNativeScriptHashDisplayFormat.POLICY_ID:
await confirm_blob(
ctx,
"verify_script",
title="Verify script",
data=script_hash,
description="Policy ID:",
br_code=ButtonRequestType.Other,
)
async def confirm_sending(
ctx: wire.Context,
ada_amount: int,

@ -0,0 +1,171 @@
from trezor.crypto import hashlib
from trezor.enums import CardanoAddressType, CardanoNativeScriptType
from apps.cardano.helpers import (
ADDRESS_KEY_HASH_SIZE,
INVALID_NATIVE_SCRIPT,
SCRIPT_HASH_SIZE,
)
from apps.common import cbor
from .helpers.paths import SCHEMA_MINT
from .helpers.utils import get_public_key_hash
from .seed import Keychain, is_multisig_path
if False:
from typing import Any
from trezor.messages import CardanoNativeScript
from apps.common.cbor import CborSequence
SCRIPT_ADDRESS_TYPES = (
CardanoAddressType.BASE_SCRIPT_KEY,
CardanoAddressType.BASE_KEY_SCRIPT,
CardanoAddressType.BASE_SCRIPT_SCRIPT,
CardanoAddressType.POINTER_SCRIPT,
CardanoAddressType.ENTERPRISE_SCRIPT,
CardanoAddressType.REWARD_SCRIPT,
)
def validate_native_script(script: CardanoNativeScript | None) -> None:
if not script:
raise INVALID_NATIVE_SCRIPT
_validate_native_script_structure(script)
if script.type == CardanoNativeScriptType.PUB_KEY:
if script.key_hash and script.key_path:
raise INVALID_NATIVE_SCRIPT
if script.key_hash:
if len(script.key_hash) != ADDRESS_KEY_HASH_SIZE:
raise INVALID_NATIVE_SCRIPT
elif script.key_path:
if not is_multisig_path(script.key_path) and not SCHEMA_MINT.match(
script.key_path
):
raise INVALID_NATIVE_SCRIPT
else:
raise INVALID_NATIVE_SCRIPT
elif script.type == CardanoNativeScriptType.ALL:
for sub_script in script.scripts:
validate_native_script(sub_script)
elif script.type == CardanoNativeScriptType.ANY:
for sub_script in script.scripts:
validate_native_script(sub_script)
elif script.type == CardanoNativeScriptType.N_OF_K:
if script.required_signatures_count is None:
raise INVALID_NATIVE_SCRIPT
if script.required_signatures_count > len(script.scripts):
raise INVALID_NATIVE_SCRIPT
for sub_script in script.scripts:
validate_native_script(sub_script)
elif script.type == CardanoNativeScriptType.INVALID_BEFORE:
if script.invalid_before is None:
raise INVALID_NATIVE_SCRIPT
elif script.type == CardanoNativeScriptType.INVALID_HEREAFTER:
if script.invalid_hereafter is None:
raise INVALID_NATIVE_SCRIPT
def _validate_native_script_structure(script: CardanoNativeScript) -> None:
key_hash = script.key_hash
key_path = script.key_path
scripts = script.scripts
required_signatures_count = script.required_signatures_count
invalid_before = script.invalid_before
invalid_hereafter = script.invalid_hereafter
fields_to_be_empty: dict[CardanoNativeScriptType, tuple[Any, ...]] = {
CardanoNativeScriptType.PUB_KEY: (
scripts,
required_signatures_count,
invalid_before,
invalid_hereafter,
),
CardanoNativeScriptType.ALL: (
key_hash,
key_path,
required_signatures_count,
invalid_before,
invalid_hereafter,
),
CardanoNativeScriptType.ANY: (
key_hash,
key_path,
required_signatures_count,
invalid_before,
invalid_hereafter,
),
CardanoNativeScriptType.N_OF_K: (
key_hash,
key_path,
invalid_before,
invalid_hereafter,
),
CardanoNativeScriptType.INVALID_BEFORE: (
key_hash,
key_path,
required_signatures_count,
invalid_hereafter,
),
CardanoNativeScriptType.INVALID_HEREAFTER: (
key_hash,
key_path,
required_signatures_count,
invalid_before,
),
}
if script.type not in fields_to_be_empty or any(fields_to_be_empty[script.type]):
raise INVALID_NATIVE_SCRIPT
def get_native_script_hash(keychain: Keychain, script: CardanoNativeScript) -> bytes:
script_cbor = cbor.encode(cborize_native_script(keychain, script))
prefixed_script_cbor = b"\00" + script_cbor
return hashlib.blake2b(data=prefixed_script_cbor, outlen=SCRIPT_HASH_SIZE).digest()
def cborize_native_script(
keychain: Keychain, script: CardanoNativeScript
) -> CborSequence:
script_content: CborSequence
if script.type == CardanoNativeScriptType.PUB_KEY:
if script.key_hash:
script_content = (script.key_hash,)
elif script.key_path:
script_content = (get_public_key_hash(keychain, script.key_path),)
else:
raise INVALID_NATIVE_SCRIPT
elif script.type == CardanoNativeScriptType.ALL:
script_content = (
tuple(
cborize_native_script(keychain, sub_script)
for sub_script in script.scripts
),
)
elif script.type == CardanoNativeScriptType.ANY:
script_content = (
tuple(
cborize_native_script(keychain, sub_script)
for sub_script in script.scripts
),
)
elif script.type == CardanoNativeScriptType.N_OF_K:
script_content = (
script.required_signatures_count,
tuple(
cborize_native_script(keychain, sub_script)
for sub_script in script.scripts
),
)
elif script.type == CardanoNativeScriptType.INVALID_BEFORE:
script_content = (script.invalid_before,)
elif script.type == CardanoNativeScriptType.INVALID_HEREAFTER:
script_content = (script.invalid_hereafter,)
else:
raise INVALID_NATIVE_SCRIPT
return (script.type,) + script_content

@ -152,6 +152,8 @@ def find_message_handler_module(msg_type: int) -> str:
return "apps.cardano.get_public_key"
elif msg_type == MessageType.CardanoSignTxInit:
return "apps.cardano.sign_tx"
elif msg_type == MessageType.CardanoGetNativeScriptHash:
return "apps.cardano.get_native_script_hash"
# tezos
elif msg_type == MessageType.TezosGetAddress:

@ -0,0 +1,7 @@
# Automatically generated by pb2py
# fmt: off
# isort:skip_file
HIDE = 0
BECH32 = 1
POLICY_ID = 2

@ -0,0 +1,10 @@
# Automatically generated by pb2py
# fmt: off
# isort:skip_file
PUB_KEY = 0
ALL = 1
ANY = 2
N_OF_K = 3
INVALID_BEFORE = 4
INVALID_HEREAFTER = 5

@ -150,6 +150,8 @@ if not utils.BITCOIN_ONLY:
CardanoTxAuxiliaryData = 327
CardanoPoolOwner = 328
CardanoPoolRelayParameters = 329
CardanoGetNativeScriptHash = 330
CardanoNativeScriptHash = 331
RippleGetAddress = 400
RippleAddress = 401
RippleSignTx = 402

@ -155,6 +155,8 @@ if TYPE_CHECKING:
CardanoTxAuxiliaryData = 327
CardanoPoolOwner = 328
CardanoPoolRelayParameters = 329
CardanoGetNativeScriptHash = 330
CardanoNativeScriptHash = 331
RippleGetAddress = 400
RippleAddress = 401
RippleSignTx = 402
@ -328,6 +330,19 @@ if TYPE_CHECKING:
REWARD = 14
REWARD_SCRIPT = 15
class CardanoNativeScriptType(IntEnum):
PUB_KEY = 0
ALL = 1
ANY = 2
N_OF_K = 3
INVALID_BEFORE = 4
INVALID_HEREAFTER = 5
class CardanoNativeScriptHashDisplayFormat(IntEnum):
HIDE = 0
BECH32 = 1
POLICY_ID = 2
class CardanoCertificateType(IntEnum):
STAKE_REGISTRATION = 0
STAKE_DEREGISTRATION = 1

@ -27,6 +27,8 @@ if TYPE_CHECKING:
from trezor.enums import Capability # noqa: F401
from trezor.enums import CardanoAddressType # noqa: F401
from trezor.enums import CardanoCertificateType # noqa: F401
from trezor.enums import CardanoNativeScriptHashDisplayFormat # noqa: F401
from trezor.enums import CardanoNativeScriptType # noqa: F401
from trezor.enums import CardanoPoolRelayType # noqa: F401
from trezor.enums import CardanoTxAuxiliaryDataSupplementType # noqa: F401
from trezor.enums import CardanoTxSigningMode # noqa: F401
@ -1059,6 +1061,62 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["CardanoBlockchainPointerType"]:
return isinstance(msg, cls)
class CardanoNativeScript(protobuf.MessageType):
type: "CardanoNativeScriptType"
scripts: "list[CardanoNativeScript]"
key_hash: "bytes | None"
key_path: "list[int]"
required_signatures_count: "int | None"
invalid_before: "int | None"
invalid_hereafter: "int | None"
def __init__(
self,
*,
type: "CardanoNativeScriptType",
scripts: "list[CardanoNativeScript] | None" = None,
key_path: "list[int] | None" = None,
key_hash: "bytes | None" = None,
required_signatures_count: "int | None" = None,
invalid_before: "int | None" = None,
invalid_hereafter: "int | None" = None,
) -> None:
pass
@classmethod
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["CardanoNativeScript"]:
return isinstance(msg, cls)
class CardanoGetNativeScriptHash(protobuf.MessageType):
script: "CardanoNativeScript"
display_format: "CardanoNativeScriptHashDisplayFormat"
def __init__(
self,
*,
script: "CardanoNativeScript",
display_format: "CardanoNativeScriptHashDisplayFormat",
) -> None:
pass
@classmethod
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["CardanoGetNativeScriptHash"]:
return isinstance(msg, cls)
class CardanoNativeScriptHash(protobuf.MessageType):
script_hash: "bytes"
def __init__(
self,
*,
script_hash: "bytes",
) -> None:
pass
@classmethod
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["CardanoNativeScriptHash"]:
return isinstance(msg, cls)
class CardanoAddressParametersType(protobuf.MessageType):
address_type: "CardanoAddressType"
address_n: "list[int]"

@ -0,0 +1,301 @@
from common import *
from trezor import wire
from trezor.crypto import bip32
from trezor.enums import CardanoNativeScriptType
from trezor.messages import CardanoNativeScript
if not utils.BITCOIN_ONLY:
from apps.cardano.seed import Keychain
from apps.cardano.native_script import get_native_script_hash, validate_native_script
VALID_NATIVE_SCRIPTS = [
# PUB_KEY
[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
),
),
b"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd",
],
# PUB_KEY with path
[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1854 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
b"29fb5fd4aa8cadd6705acc8263cee0fc62edca5ac38db593fec2f9fd",
],
# ALL
[
CardanoNativeScript(
type=CardanoNativeScriptType.ALL,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1854 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
),
),
],
),
b"af5c2ce476a6ede1c879f7b1909d6a0b96cb2081391712d4a355cef6",
],
# ALL with 1855 path
[
CardanoNativeScript(
type=CardanoNativeScriptType.ALL,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1855 | HARDENED, 1815 | HARDENED, 0 | HARDENED],
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
),
),
],
),
b"fbf6672eb655c29b0f148fa1429be57c2174b067a7b3e3942e967fe8",
],
# ALL scripts are empty
[
CardanoNativeScript(type=CardanoNativeScriptType.ALL, scripts=[]),
b"d441227553a0f1a965fee7d60a0f724b368dd1bddbc208730fccebcf"
],
# ANY
[
CardanoNativeScript(
type=CardanoNativeScriptType.ANY,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1854 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
),
),
],
),
b"d6428ec36719146b7b5fb3a2d5322ce702d32762b8c7eeeb797a20db",
],
# ANY scripts are empty
[
CardanoNativeScript(type=CardanoNativeScriptType.ANY, scripts=[]),
b"52dc3d43b6d2465e96109ce75ab61abe5e9c1d8a3c9ce6ff8a3af528"
],
# N OF K
[
CardanoNativeScript(
type=CardanoNativeScriptType.N_OF_K,
required_signatures_count=2,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1854 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
),
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"cecb1d427c4ae436d28cc0f8ae9bb37501a5b77bcc64cd1693e9ae20"
),
),
],
),
b"2b2b17fd18e18acae4601d4818a1dee00a917ff72e772fa8482e36c9",
],
# N_OF_K scripts are empty
[
CardanoNativeScript(type=CardanoNativeScriptType.N_OF_K, required_signatures_count=0, scripts=[]),
b"3530cc9ae7f2895111a99b7a02184dd7c0cea7424f1632d73951b1d7"
],
# INVALID BEFORE
[
CardanoNativeScript(
type=CardanoNativeScriptType.ALL,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
),
),
CardanoNativeScript(
type=CardanoNativeScriptType.INVALID_BEFORE, invalid_before=100
),
],
),
b"c6262ef9bb2b1291c058d93b46dabf458e2d135f803f60713f84b0b7",
],
# INVALID HEREAFTER
[
CardanoNativeScript(
type=CardanoNativeScriptType.ALL,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
),
),
CardanoNativeScript(
type=CardanoNativeScriptType.INVALID_HEREAFTER,
invalid_hereafter=200,
),
],
),
b"b12ac304f89f4cd4d23f59a2b90d2b2697f7540b8f470d6aa05851b5",
],
# NESTED SCRIPT
[
CardanoNativeScript(
type=CardanoNativeScriptType.ALL,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"c4b9265645fde9536c0795adbcc5291767a0c61fd62448341d7e0386"
),
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1854 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
CardanoNativeScript(
type=CardanoNativeScriptType.ANY,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1854 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
),
),
],
),
CardanoNativeScript(
type=CardanoNativeScriptType.N_OF_K,
required_signatures_count=2,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1854 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"0241f2d196f52a92fbd2183d03b370c30b6960cfdeae364ffabac889"
),
),
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"cecb1d427c4ae436d28cc0f8ae9bb37501a5b77bcc64cd1693e9ae20"
),
),
],
),
CardanoNativeScript(
type=CardanoNativeScriptType.INVALID_BEFORE, invalid_before=100
),
CardanoNativeScript(
type=CardanoNativeScriptType.INVALID_HEREAFTER,
invalid_hereafter=200,
),
],
),
b"4a6b4288459bf34668c0b281f922691460caf0c7c09caee3a726c27a",
],
]
INVALID_SCRIPTS = [
# PUB_KEY key_hash has invalid length
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify("3a55d9f68255dfbefa1efd711f82d005fae1be2e145d616c90cf0f"),
),
# PUB_KEY key_path is not multisig or mint
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1852 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0, 0],
),
# PUB_KEY mint key_path is too long
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_path=[1855 | HARDENED, 1815 | HARDENED, 0 | HARDENED, 0],
),
# N_OF_K required_signatures_count is not set
CardanoNativeScript(
type=CardanoNativeScriptType.N_OF_K,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"3a55d9f68255dfbefa1efd711f82d005fae1be2e145d616c90cf0fa9"
),
),
],
),
# N_OF_K N is larger than K
CardanoNativeScript(
type=CardanoNativeScriptType.N_OF_K,
required_signatures_count=2,
scripts=[
CardanoNativeScript(
type=CardanoNativeScriptType.PUB_KEY,
key_hash=unhexlify(
"3a55d9f68255dfbefa1efd711f82d005fae1be2e145d616c90cf0fa9"
),
),
],
),
# INVALID_BEFORE invalid_before is not set
CardanoNativeScript(type=CardanoNativeScriptType.INVALID_BEFORE),
# INVALID_HEREAFTER invalid_hereafter is not set
CardanoNativeScript(type=CardanoNativeScriptType.INVALID_HEREAFTER),
]
@unittest.skipUnless(not utils.BITCOIN_ONLY, "altcoin")
class TestCardanoNativeScript(unittest.TestCase):
def test_get_native_script_hash(self):
mnemonic = "all all all all all all all all all all all all"
passphrase = ""
node = bip32.from_mnemonic_cardano(mnemonic, passphrase)
keychain = Keychain(node)
for script, expected_hash in VALID_NATIVE_SCRIPTS:
actual_hash = get_native_script_hash(keychain, script)
self.assertEqual(hexlify(actual_hash), expected_hash)
def test_validate_native_script(self):
for script, _ in VALID_NATIVE_SCRIPTS:
validate_native_script(script)
def test_validate_native_script_invalid(self):
for script in INVALID_SCRIPTS:
with self.assertRaises(wire.ProcessError):
validate_native_script(script)
if __name__ == "__main__":
unittest.main()

@ -86,6 +86,14 @@ AuxiliaryDataSupplement = Dict[str, Union[int, bytes]]
SignTxResponse = Dict[str, Union[bytes, List[Witness], AuxiliaryDataSupplement]]
def parse_optional_bytes(value: Optional[str]) -> Optional[bytes]:
return bytes.fromhex(value) if value is not None else None
def parse_optional_int(value) -> Optional[int]:
return int(value) if value is not None else None
def create_address_parameters(
address_type: messages.CardanoAddressType,
address_n: List[int],
@ -230,6 +238,35 @@ def _parse_address_parameters(
)
def parse_native_script(native_script) -> messages.CardanoNativeScript:
if "type" not in native_script:
raise ValueError("Script is missing some fields")
type = native_script["type"]
scripts = [
parse_native_script(sub_script)
for sub_script in native_script.get("scripts", ())
]
key_hash = _parse_optional_bytes(native_script.get("key_hash"))
key_path = tools.parse_path(native_script.get("key_path"))
required_signatures_count = parse_optional_int(
native_script.get("required_signatures_count")
)
invalid_before = parse_optional_int(native_script.get("invalid_before"))
invalid_hereafter = parse_optional_int(native_script.get("invalid_hereafter"))
return messages.CardanoNativeScript(
type=type,
scripts=scripts,
key_hash=key_hash,
key_path=key_path,
required_signatures_count=required_signatures_count,
invalid_before=invalid_before,
invalid_hereafter=invalid_hereafter,
)
def parse_certificate(certificate) -> CertificateWithPoolOwnersAndRelays:
CERTIFICATE_MISSING_FIELDS_ERROR = ValueError(
"The certificate is missing some fields"
@ -380,9 +417,7 @@ def parse_auxiliary_data(auxiliary_data) -> messages.CardanoTxAuxiliaryData:
)
# include all provided fields so we can test validation in FW
hash = None
if "hash" in auxiliary_data:
hash = bytes.fromhex(auxiliary_data["hash"])
hash = parse_optional_bytes(auxiliary_data.get("hash"))
catalyst_registration_parameters = None
if "catalyst_registration_parameters" in auxiliary_data:
@ -493,6 +528,20 @@ def get_public_key(client, address_n: List[int]) -> messages.CardanoPublicKey:
return client.call(messages.CardanoGetPublicKey(address_n=address_n))
@expect(messages.CardanoNativeScriptHash)
def get_native_script_hash(
client,
native_script: messages.CardanoNativeScript,
display_format: messages.CardanoNativeScriptHashDisplayFormat = messages.CardanoNativeScriptHashDisplayFormat.HIDE,
) -> messages.CardanoNativeScriptHash:
return client.call(
messages.CardanoGetNativeScriptHash(
script=native_script,
display_format=display_format,
)
)
def sign_tx(
client,
signing_mode: messages.CardanoTxSigningMode,

@ -187,3 +187,20 @@ def get_public_key(client, address):
"""Get Cardano public key."""
address_n = tools.parse_path(address)
return cardano.get_public_key(client, address_n)
@cli.command()
@click.argument("file", type=click.File("r"))
@click.option(
"-d",
"--display-format",
type=ChoiceType({m.name: m for m in messages.CardanoNativeScriptHashDisplayFormat}),
default="HIDE",
)
@with_client
def get_native_script_hash(client, file, display_format):
"""Get Cardano native script hash."""
native_script_json = json.load(file)
native_script = cardano.parse_native_script(native_script_json)
return cardano.get_native_script_hash(client, native_script, display_format)

@ -176,6 +176,8 @@ class MessageType(IntEnum):
CardanoTxAuxiliaryData = 327
CardanoPoolOwner = 328
CardanoPoolRelayParameters = 329
CardanoGetNativeScriptHash = 330
CardanoNativeScriptHash = 331
RippleGetAddress = 400
RippleAddress = 401
RippleSignTx = 402
@ -342,6 +344,21 @@ class CardanoAddressType(IntEnum):
REWARD_SCRIPT = 15
class CardanoNativeScriptType(IntEnum):
PUB_KEY = 0
ALL = 1
ANY = 2
N_OF_K = 3
INVALID_BEFORE = 4
INVALID_HEREAFTER = 5
class CardanoNativeScriptHashDisplayFormat(IntEnum):
HIDE = 0
BECH32 = 1
POLICY_ID = 2
class CardanoCertificateType(IntEnum):
STAKE_REGISTRATION = 0
STAKE_DEREGISTRATION = 1
@ -1874,6 +1891,69 @@ class CardanoBlockchainPointerType(protobuf.MessageType):
self.certificate_index = certificate_index
class CardanoNativeScript(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None
FIELDS = {
1: protobuf.Field("type", "CardanoNativeScriptType", repeated=False, required=True),
2: protobuf.Field("scripts", "CardanoNativeScript", repeated=True, required=False),
3: protobuf.Field("key_hash", "bytes", repeated=False, required=False),
4: protobuf.Field("key_path", "uint32", repeated=True, required=False),
5: protobuf.Field("required_signatures_count", "uint32", repeated=False, required=False),
6: protobuf.Field("invalid_before", "uint64", repeated=False, required=False),
7: protobuf.Field("invalid_hereafter", "uint64", repeated=False, required=False),
}
def __init__(
self,
*,
type: "CardanoNativeScriptType",
scripts: Optional[List["CardanoNativeScript"]] = None,
key_path: Optional[List["int"]] = None,
key_hash: Optional["bytes"] = None,
required_signatures_count: Optional["int"] = None,
invalid_before: Optional["int"] = None,
invalid_hereafter: Optional["int"] = None,
) -> None:
self.scripts = scripts if scripts is not None else []
self.key_path = key_path if key_path is not None else []
self.type = type
self.key_hash = key_hash
self.required_signatures_count = required_signatures_count
self.invalid_before = invalid_before
self.invalid_hereafter = invalid_hereafter
class CardanoGetNativeScriptHash(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 330
FIELDS = {
1: protobuf.Field("script", "CardanoNativeScript", repeated=False, required=True),
2: protobuf.Field("display_format", "CardanoNativeScriptHashDisplayFormat", repeated=False, required=True),
}
def __init__(
self,
*,
script: "CardanoNativeScript",
display_format: "CardanoNativeScriptHashDisplayFormat",
) -> None:
self.script = script
self.display_format = display_format
class CardanoNativeScriptHash(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 331
FIELDS = {
1: protobuf.Field("script_hash", "bytes", repeated=False, required=True),
}
def __init__(
self,
*,
script_hash: "bytes",
) -> None:
self.script_hash = script_hash
class CardanoAddressParametersType(protobuf.MessageType):
MESSAGE_WIRE_TYPE = None
FIELDS = {

@ -0,0 +1,28 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2019 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# 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 trezorlib.cardano import get_native_script_hash, parse_native_script
from ...common import parametrize_using_common_fixtures
@parametrize_using_common_fixtures(
"cardano/get_native_script_hash.json",
)
def test_cardano_get_native_script_hash(client, parameters, result):
native_script = parse_native_script(parameters)
native_script_hash = get_native_script_hash(client, native_script).script_hash
assert native_script_hash.hex() == result["expected_hash"]
Loading…
Cancel
Save