mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-04-14 06:15:57 +00:00
feat(cardano): add get-native-script-hash call
This commit is contained in:
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];
|
||||
|
321
common/tests/fixtures/cardano/get_native_script_hash.json
vendored
Normal file
321
common/tests/fixtures/cardano/get_native_script_hash.json
vendored
Normal file
@ -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
|
||||
|
24
core/src/apps/cardano/get_native_script_hash.py
Normal file
24
core/src/apps/cardano/get_native_script_hash.py
Normal file
@ -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,
|
||||
|
171
core/src/apps/cardano/native_script.py
Normal file
171
core/src/apps/cardano/native_script.py
Normal file
@ -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
|
10
core/src/trezor/enums/CardanoNativeScriptType.py
Normal file
10
core/src/trezor/enums/CardanoNativeScriptType.py
Normal file
@ -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]"
|
||||
|
301
core/tests/test_apps.cardano.native_script.py
Normal file
301
core/tests/test_apps.cardano.native_script.py
Normal file
@ -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 = {
|
||||
|
28
tests/device_tests/cardano/test_get_native_script_hash.py
Normal file
28
tests/device_tests/cardano/test_get_native_script_hash.py
Normal file
@ -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…
Reference in New Issue
Block a user